diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h index 902a3269c9d38..16971fc178ea7 100644 --- a/Zend/Optimizer/zend_func_infos.h +++ b/Zend/Optimizer/zend_func_infos.h @@ -599,6 +599,7 @@ static const func_info_t func_infos[] = { F1("fsockopen", MAY_BE_RESOURCE|MAY_BE_FALSE), FN("pfsockopen", MAY_BE_RESOURCE|MAY_BE_FALSE), F1("http_build_query", MAY_BE_STRING), + F1("request_parse_body", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_ARRAY), F1("image_type_to_mime_type", MAY_BE_STRING), F1("image_type_to_extension", MAY_BE_STRING|MAY_BE_FALSE), F1("getimagesize", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_FALSE), diff --git a/Zend/tests/request_parse_body/invalid_boundary.phpt b/Zend/tests/request_parse_body/invalid_boundary.phpt new file mode 100644 index 0000000000000..980b44e94899e --- /dev/null +++ b/Zend/tests/request_parse_body/invalid_boundary.phpt @@ -0,0 +1,25 @@ +--TEST-- +request_parse_body() with multipart and invalid boundary +--ENV-- +REQUEST_METHOD=PUT +--POST_RAW-- +Content-Type: multipart/form-data; boundary="foobar +empty +--FILE-- +getMessage(), "\n"; +} + +var_dump($_POST, $_FILES); + +?> +--EXPECT-- +RequestParseBodyException: Invalid boundary in multipart/form-data POST data +array(0) { +} +array(0) { +} diff --git a/Zend/tests/request_parse_body/multipart.phpt b/Zend/tests/request_parse_body/multipart.phpt new file mode 100644 index 0000000000000..50f4a7c09d1b4 --- /dev/null +++ b/Zend/tests/request_parse_body/multipart.phpt @@ -0,0 +1,56 @@ +--TEST-- +request_parse_body() with multipart +--ENV-- +REQUEST_METHOD=PUT +--POST_RAW-- +Content-Type: multipart/form-data; boundary=---------------------------84000087610663814162942123332 +-----------------------------84000087610663814162942123332 +Content-Disposition: form-data; name="post_field_name" + +post field data +-----------------------------84000087610663814162942123332 +Content-Disposition: form-data; name="file_name"; filename="original_file_name.txt" +Content-Type: text/plain + +file data +-----------------------------84000087610663814162942123332-- +--FILE-- + +--CLEAN-- + +--EXPECTF-- +array(1) { + ["post_field_name"]=> + string(15) "post field data" +} +array(1) { + ["file_name"]=> + array(6) { + ["name"]=> + string(22) "original_file_name.txt" + ["full_path"]=> + string(22) "original_file_name.txt" + ["type"]=> + string(10) "text/plain" + ["tmp_name"]=> + string(%d) "%s" + ["error"]=> + int(0) + ["size"]=> + int(9) + } +} +string(9) "file data" diff --git a/Zend/tests/request_parse_body/multipart_garbled.phpt b/Zend/tests/request_parse_body/multipart_garbled.phpt new file mode 100644 index 0000000000000..521de0805bf45 --- /dev/null +++ b/Zend/tests/request_parse_body/multipart_garbled.phpt @@ -0,0 +1,32 @@ +--TEST-- +request_parse_body() with multipart and garbled field +--INI-- +max_file_uploads=1 +--ENV-- +REQUEST_METHOD=PUT +--POST_RAW-- +Content-Type: multipart/form-data; boundary=---------------------------84000087610663814162942123332 +-----------------------------84000087610663814162942123332 +Content-Disposition: form-data; +Content-Type: text/plain + +post field data +-----------------------------84000087610663814162942123332-- +--FILE-- +getMessage(), "\n"; +} + +var_dump($_POST, $_FILES); + +?> +--EXPECT-- +RequestParseBodyException: File Upload Mime headers garbled +array(0) { +} +array(0) { +} diff --git a/Zend/tests/request_parse_body/multipart_max_files.phpt b/Zend/tests/request_parse_body/multipart_max_files.phpt new file mode 100644 index 0000000000000..9c4fe6c3cd7bd --- /dev/null +++ b/Zend/tests/request_parse_body/multipart_max_files.phpt @@ -0,0 +1,37 @@ +--TEST-- +request_parse_body() with multipart and exceeding max files +--INI-- +max_file_uploads=1 +--ENV-- +REQUEST_METHOD=PUT +--POST_RAW-- +Content-Type: multipart/form-data; boundary=---------------------------84000087610663814162942123332 +-----------------------------84000087610663814162942123332 +Content-Disposition: form-data; name="file1"; filename="file1.txt" +Content-Type: text/plain + +file data +-----------------------------84000087610663814162942123332 +Content-Disposition: form-data; name="file2"; filename="file2.txt" +Content-Type: text/plain + +file data +-----------------------------84000087610663814162942123332-- +--FILE-- +getMessage(), "\n"; +} + +var_dump($_POST, $_FILES); + +?> +--EXPECT-- +RequestParseBodyException: Maximum number of allowable file uploads has been exceeded +array(0) { +} +array(0) { +} diff --git a/Zend/tests/request_parse_body/multipart_max_input_vars.phpt b/Zend/tests/request_parse_body/multipart_max_input_vars.phpt new file mode 100644 index 0000000000000..384344e6c1764 --- /dev/null +++ b/Zend/tests/request_parse_body/multipart_max_input_vars.phpt @@ -0,0 +1,35 @@ +--TEST-- +request_parse_body() with multipart and exceeding max input vars +--INI-- +max_input_vars=1 +--ENV-- +REQUEST_METHOD=PUT +--POST_RAW-- +Content-Type: multipart/form-data; boundary=---------------------------84000087610663814162942123332 +-----------------------------84000087610663814162942123332 +Content-Disposition: form-data; name="field1" + +post field data +-----------------------------84000087610663814162942123332 +Content-Disposition: form-data; name="field2" + +post field data +-----------------------------84000087610663814162942123332-- +--FILE-- +getMessage(), "\n"; +} + +var_dump($_POST, $_FILES); + +?> +--EXPECT-- +RequestParseBodyException: Input variables exceeded 1. To increase the limit change max_input_vars in php.ini. +array(0) { +} +array(0) { +} diff --git a/Zend/tests/request_parse_body/multipart_max_parts.phpt b/Zend/tests/request_parse_body/multipart_max_parts.phpt new file mode 100644 index 0000000000000..7767410030379 --- /dev/null +++ b/Zend/tests/request_parse_body/multipart_max_parts.phpt @@ -0,0 +1,36 @@ +--TEST-- +request_parse_body() with multipart and exceeding max parts +--INI-- +max_multipart_body_parts=1 +--ENV-- +REQUEST_METHOD=PUT +--POST_RAW-- +Content-Type: multipart/form-data; boundary=---------------------------84000087610663814162942123332 +-----------------------------84000087610663814162942123332 +Content-Disposition: form-data; name="post_field_name" + +post field data +-----------------------------84000087610663814162942123332 +Content-Disposition: form-data; name="file_name"; filename="original_file_name.txt" +Content-Type: text/plain + +file data +-----------------------------84000087610663814162942123332-- +--FILE-- +getMessage(), "\n"; +} + +var_dump($_POST, $_FILES); + +?> +--EXPECT-- +RequestParseBodyException: Multipart body parts limit exceeded 1. To increase the limit change max_multipart_body_parts in php.ini. +array(0) { +} +array(0) { +} diff --git a/Zend/tests/request_parse_body/multipart_missing_boundary.phpt b/Zend/tests/request_parse_body/multipart_missing_boundary.phpt new file mode 100644 index 0000000000000..9656071c765bf --- /dev/null +++ b/Zend/tests/request_parse_body/multipart_missing_boundary.phpt @@ -0,0 +1,27 @@ +--TEST-- +request_parse_body() with multipart and missing boundary +--ENV-- +REQUEST_METHOD=PUT +--POST_RAW-- +Content-Type: multipart/form-data +empty +--FILE-- +getMessage(), "\n"; +} + +var_dump($_POST, $_FILES); + +?> +--EXPECT-- +RequestParseBodyException: Missing boundary in multipart/form-data POST data +array(0) { +} +array(0) { +} diff --git a/Zend/tests/request_parse_body/multipart_options_invalid_key.phpt b/Zend/tests/request_parse_body/multipart_options_invalid_key.phpt new file mode 100644 index 0000000000000..acad79c9e3382 --- /dev/null +++ b/Zend/tests/request_parse_body/multipart_options_invalid_key.phpt @@ -0,0 +1,21 @@ +--TEST-- +request_parse_body() invalid key +--FILE-- + 1]); +} catch (Error $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + request_parse_body(options: [42 => 1]); +} catch (Error $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +ValueError: Invalid key "foo" in $options argument +ValueError: Invalid integer key in $options argument diff --git a/Zend/tests/request_parse_body/multipart_options_invalid_quantity.phpt b/Zend/tests/request_parse_body/multipart_options_invalid_quantity.phpt new file mode 100644 index 0000000000000..b1efa0dbc91af --- /dev/null +++ b/Zend/tests/request_parse_body/multipart_options_invalid_quantity.phpt @@ -0,0 +1,17 @@ +--TEST-- +request_parse_body() invalid quantity +--FILE-- + '1GB', + ]); +} catch (Throwable $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +Warning: Invalid quantity "1GB": unknown multiplier "B", interpreting as "1" for backwards compatibility in %s on line %d +RequestParseBodyException: Request does not provide a content type diff --git a/Zend/tests/request_parse_body/multipart_options_invalid_value_type.phpt b/Zend/tests/request_parse_body/multipart_options_invalid_value_type.phpt new file mode 100644 index 0000000000000..b61416cdf13dc --- /dev/null +++ b/Zend/tests/request_parse_body/multipart_options_invalid_value_type.phpt @@ -0,0 +1,16 @@ +--TEST-- +request_parse_body() invalid value type +--FILE-- + [], + ]); +} catch (Error $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +ValueError: Invalid array value in $options argument diff --git a/Zend/tests/request_parse_body/multipart_options_max_files.phpt b/Zend/tests/request_parse_body/multipart_options_max_files.phpt new file mode 100644 index 0000000000000..ad0c2a906237e --- /dev/null +++ b/Zend/tests/request_parse_body/multipart_options_max_files.phpt @@ -0,0 +1,39 @@ +--TEST-- +request_parse_body() max_file_uploads option +--INI-- +max_file_uploads=10 +--ENV-- +REQUEST_METHOD=PUT +--POST_RAW-- +Content-Type: multipart/form-data; boundary=---------------------------84000087610663814162942123332 +-----------------------------84000087610663814162942123332 +Content-Disposition: form-data; name="file1"; filename="file1.txt" +Content-Type: text/plain + +file data +-----------------------------84000087610663814162942123332 +Content-Disposition: form-data; name="file2"; filename="file2.txt" +Content-Type: text/plain + +file data +-----------------------------84000087610663814162942123332-- +--FILE-- + 1, + ]); +} catch (Throwable $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +var_dump($_POST, $_FILES); + +?> +--EXPECT-- +RequestParseBodyException: Maximum number of allowable file uploads has been exceeded +array(0) { +} +array(0) { +} diff --git a/Zend/tests/request_parse_body/multipart_options_max_input_vars.phpt b/Zend/tests/request_parse_body/multipart_options_max_input_vars.phpt new file mode 100644 index 0000000000000..990102abd8531 --- /dev/null +++ b/Zend/tests/request_parse_body/multipart_options_max_input_vars.phpt @@ -0,0 +1,37 @@ +--TEST-- +request_parse_body() max_input_vars option +--INI-- +max_input_vars=10 +--ENV-- +REQUEST_METHOD=PUT +--POST_RAW-- +Content-Type: multipart/form-data; boundary=---------------------------84000087610663814162942123332 +-----------------------------84000087610663814162942123332 +Content-Disposition: form-data; name="field1" + +post field data +-----------------------------84000087610663814162942123332 +Content-Disposition: form-data; name="field2" + +post field data +-----------------------------84000087610663814162942123332-- +--FILE-- + 1, + ]); +} catch (Throwable $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +var_dump($_POST, $_FILES); + +?> +--EXPECT-- +RequestParseBodyException: Input variables exceeded 1. To increase the limit change max_input_vars in php.ini. +array(0) { +} +array(0) { +} diff --git a/Zend/tests/request_parse_body/multipart_options_max_parts.phpt b/Zend/tests/request_parse_body/multipart_options_max_parts.phpt new file mode 100644 index 0000000000000..e9d8d51b24282 --- /dev/null +++ b/Zend/tests/request_parse_body/multipart_options_max_parts.phpt @@ -0,0 +1,38 @@ +--TEST-- +request_parse_body() max_multipart_body_parts option +--INI-- +max_multipart_body_parts=10 +--ENV-- +REQUEST_METHOD=PUT +--POST_RAW-- +Content-Type: multipart/form-data; boundary=---------------------------84000087610663814162942123332 +-----------------------------84000087610663814162942123332 +Content-Disposition: form-data; name="post_field_name" + +post field data +-----------------------------84000087610663814162942123332 +Content-Disposition: form-data; name="file_name"; filename="original_file_name.txt" +Content-Type: text/plain + +file data +-----------------------------84000087610663814162942123332-- +--FILE-- + 1, + ]); +} catch (Throwable $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +var_dump($_POST, $_FILES); + +?> +--EXPECT-- +RequestParseBodyException: Multipart body parts limit exceeded 1. To increase the limit change max_multipart_body_parts in php.ini. +array(0) { +} +array(0) { +} diff --git a/Zend/tests/request_parse_body/multipart_options_post_max_size.phpt b/Zend/tests/request_parse_body/multipart_options_post_max_size.phpt new file mode 100644 index 0000000000000..7a66faa7dba40 --- /dev/null +++ b/Zend/tests/request_parse_body/multipart_options_post_max_size.phpt @@ -0,0 +1,37 @@ +--TEST-- +request_parse_body() post_max_size option +--INI-- +post_max_size=1M +--ENV-- +REQUEST_METHOD=PUT +--POST_RAW-- +Content-Type: multipart/form-data; boundary=---------------------------84000087610663814162942123332 +-----------------------------84000087610663814162942123332 +Content-Disposition: form-data; name="field1" + +post field data +-----------------------------84000087610663814162942123332 +Content-Disposition: form-data; name="field2" + +post file data +-----------------------------84000087610663814162942123332-- +--FILE-- + '302', + ]); +} catch (Throwable $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +var_dump($_POST, $_FILES); + +?> +--EXPECT-- +RequestParseBodyException: POST Content-Length of 303 bytes exceeds the limit of 302 bytes +array(0) { +} +array(0) { +} diff --git a/Zend/tests/request_parse_body/multipart_options_upload_max_filesize.phpt b/Zend/tests/request_parse_body/multipart_options_upload_max_filesize.phpt new file mode 100644 index 0000000000000..981a1d6799d18 --- /dev/null +++ b/Zend/tests/request_parse_body/multipart_options_upload_max_filesize.phpt @@ -0,0 +1,47 @@ +--TEST-- +request_parse_body() upload_max_filesize option +--INI-- +upload_max_filesize=1M +--ENV-- +REQUEST_METHOD=PUT +--POST_RAW-- +Content-Type: multipart/form-data; boundary=---------------------------84000087610663814162942123332 +-----------------------------84000087610663814162942123332 +Content-Disposition: name="file1"; filename="file1.txt" + +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +-----------------------------84000087610663814162942123332-- +--FILE-- + '128', + ]); +} catch (Throwable $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +var_dump($_POST, $_FILES); + +?> +--EXPECT-- +array(0) { +} +array(1) { + ["file1"]=> + array(6) { + ["name"]=> + string(9) "file1.txt" + ["full_path"]=> + string(9) "file1.txt" + ["type"]=> + string(0) "" + ["tmp_name"]=> + string(0) "" + ["error"]=> + int(1) + ["size"]=> + int(0) + } +} diff --git a/Zend/tests/request_parse_body/unsupported_content_type.phpt b/Zend/tests/request_parse_body/unsupported_content_type.phpt new file mode 100644 index 0000000000000..bd4e3f4ff2afa --- /dev/null +++ b/Zend/tests/request_parse_body/unsupported_content_type.phpt @@ -0,0 +1,27 @@ +--TEST-- +request_parse_body() with multipart and unsupported Content-Type +--INI-- +max_input_vars=1 +--ENV-- +REQUEST_METHOD=PUT +--POST_RAW-- +Content-Type: application/json +{"hello": "world"} +--FILE-- +getMessage(), "\n"; +} + +var_dump($_POST, $_FILES); + +?> +--EXPECT-- +InvalidArgumentException: Content-Type "application/json" is not supported +array(0) { +} +array(0) { +} diff --git a/Zend/tests/request_parse_body/urlencoded.phpt b/Zend/tests/request_parse_body/urlencoded.phpt new file mode 100644 index 0000000000000..b2400c8ae3f0a --- /dev/null +++ b/Zend/tests/request_parse_body/urlencoded.phpt @@ -0,0 +1,34 @@ +--TEST-- +request_parse_body() with urlencoded +--ENV-- +REQUEST_METHOD=PUT +--POST_RAW-- +Content-Type: application/x-www-form-urlencoded +foo=foo&bar[]=1&bar[]=2 +--FILE-- + +--CLEAN-- + +--EXPECT-- +array(2) { + ["foo"]=> + string(3) "foo" + ["bar"]=> + array(2) { + [0]=> + string(1) "1" + [1]=> + string(1) "2" + } +} +array(0) { +} diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index da2ed2c12d4d9..2c6a432096d7c 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -42,6 +42,7 @@ ZEND_API zend_class_entry *zend_ce_value_error; ZEND_API zend_class_entry *zend_ce_arithmetic_error; ZEND_API zend_class_entry *zend_ce_division_by_zero_error; ZEND_API zend_class_entry *zend_ce_unhandled_match_error; +ZEND_API zend_class_entry *zend_ce_request_parse_body_exception; /* Internal pseudo-exception that is not exposed to userland. Throwing this exception *does not* execute finally blocks. */ static zend_class_entry zend_ce_unwind_exit; @@ -788,6 +789,9 @@ void zend_register_default_exception(void) /* {{{ */ zend_ce_unhandled_match_error = register_class_UnhandledMatchError(zend_ce_error); zend_init_exception_class_entry(zend_ce_unhandled_match_error); + zend_ce_request_parse_body_exception = register_class_RequestParseBodyException(zend_ce_exception); + zend_init_exception_class_entry(zend_ce_request_parse_body_exception); + INIT_CLASS_ENTRY(zend_ce_unwind_exit, "UnwindExit", NULL); INIT_CLASS_ENTRY(zend_ce_graceful_exit, "GracefulExit", NULL); diff --git a/Zend/zend_exceptions.h b/Zend/zend_exceptions.h index f61b5ecb304e2..242b71a8be05a 100644 --- a/Zend/zend_exceptions.h +++ b/Zend/zend_exceptions.h @@ -36,6 +36,7 @@ extern ZEND_API zend_class_entry *zend_ce_value_error; extern ZEND_API zend_class_entry *zend_ce_arithmetic_error; extern ZEND_API zend_class_entry *zend_ce_division_by_zero_error; extern ZEND_API zend_class_entry *zend_ce_unhandled_match_error; +extern ZEND_API zend_class_entry *zend_ce_request_parse_body_exception; ZEND_API void zend_exception_set_previous(zend_object *exception, zend_object *add_previous); ZEND_API void zend_exception_save(void); diff --git a/Zend/zend_exceptions.stub.php b/Zend/zend_exceptions.stub.php index 84109b021a746..86f2838ee9123 100644 --- a/Zend/zend_exceptions.stub.php +++ b/Zend/zend_exceptions.stub.php @@ -170,3 +170,7 @@ class DivisionByZeroError extends ArithmeticError class UnhandledMatchError extends Error { } + +class RequestParseBodyException extends Exception +{ +} diff --git a/Zend/zend_exceptions_arginfo.h b/Zend/zend_exceptions_arginfo.h index c0e274d64ef70..0971b5ca3e954 100644 --- a/Zend/zend_exceptions_arginfo.h +++ b/Zend/zend_exceptions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 4cf2c620393f468968a219b5bd12a2b5f6b03ecc */ + * Stub hash: ba1562ca8fe2fe48c40bc52d10545aa989afd86c */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Throwable_getMessage, 0, 0, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -187,6 +187,11 @@ static const zend_function_entry class_UnhandledMatchError_methods[] = { ZEND_FE_END }; + +static const zend_function_entry class_RequestParseBodyException_methods[] = { + ZEND_FE_END +}; + static zend_class_entry *register_class_Throwable(zend_class_entry *class_entry_Stringable) { zend_class_entry ce, *class_entry; @@ -401,3 +406,13 @@ static zend_class_entry *register_class_UnhandledMatchError(zend_class_entry *cl return class_entry; } + +static zend_class_entry *register_class_RequestParseBodyException(zend_class_entry *class_entry_Exception) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "RequestParseBodyException", class_RequestParseBodyException_methods); + class_entry = zend_register_internal_class_ex(&ce, class_entry_Exception); + + return class_entry; +} diff --git a/ext/mbstring/mb_gpc.c b/ext/mbstring/mb_gpc.c index 33fbd32edc663..8be70f921fd49 100644 --- a/ext/mbstring/mb_gpc.c +++ b/ext/mbstring/mb_gpc.c @@ -221,8 +221,9 @@ const mbfl_encoding *_php_mb_encoding_handler_ex(const php_mb_encoding_handler_i var = php_strtok_r(NULL, info->separator, &strtok_buf); } - if (ZEND_SIZE_T_GT_ZEND_LONG(n, (PG(max_input_vars) * 2))) { - php_error_docref(NULL, E_WARNING, "Input variables exceeded " ZEND_LONG_FMT ". To increase the limit change max_input_vars in php.ini.", PG(max_input_vars)); + zend_long max_input_vars = REQUEST_PARSE_BODY_OPTION_GET(max_input_vars, PG(max_input_vars)); + if (ZEND_SIZE_T_GT_ZEND_LONG(n, max_input_vars * 2)) { + php_error_docref(NULL, E_WARNING, "Input variables exceeded " ZEND_LONG_FMT ". To increase the limit change max_input_vars in php.ini.", max_input_vars); goto out; } diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index 02396691e5b94..ed73d16d6d0b9 100755 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -3027,6 +3027,13 @@ function pfsockopen(string $hostname, int $port = -1, &$error_code = null, &$err /** @refcount 1 */ function http_build_query(array|object $data, string $numeric_prefix = "", ?string $arg_separator = null, int $encoding_type = PHP_QUERY_RFC1738): string {} +/** + * @param array|null $options + * @return array + * @refcount 1 + */ +function request_parse_body(?array $options = null): array {} + /* image.c */ /** diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index 40ce2e70d911d..4efdcb54546b6 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 7584d1aec417e84718a7a60e244cb00df2dc039f */ + * Stub hash: 0bd0ac5d23881670cac81cda3e274cbee1e9a8dc */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0) @@ -1506,6 +1506,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_http_build_query, 0, 1, IS_STRIN ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, encoding_type, IS_LONG, 0, "PHP_QUERY_RFC1738") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_request_parse_body, 0, 0, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_image_type_to_mime_type, 0, 1, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, image_type, IS_LONG, 0) ZEND_END_ARG_INFO() @@ -2715,6 +2719,7 @@ ZEND_FUNCTION(vfprintf); ZEND_FUNCTION(fsockopen); ZEND_FUNCTION(pfsockopen); ZEND_FUNCTION(http_build_query); +ZEND_FUNCTION(request_parse_body); ZEND_FUNCTION(image_type_to_mime_type); ZEND_FUNCTION(image_type_to_extension); ZEND_FUNCTION(getimagesize); @@ -3354,6 +3359,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(fsockopen, arginfo_fsockopen) ZEND_FE(pfsockopen, arginfo_pfsockopen) ZEND_FE(http_build_query, arginfo_http_build_query) + ZEND_FE(request_parse_body, arginfo_request_parse_body) ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(image_type_to_mime_type, arginfo_image_type_to_mime_type) ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(image_type_to_extension, arginfo_image_type_to_extension) ZEND_FE(getimagesize, arginfo_getimagesize) diff --git a/ext/standard/http.c b/ext/standard/http.c index 1145329a794ff..da1fdeb392865 100644 --- a/ext/standard/http.c +++ b/ext/standard/http.c @@ -17,6 +17,9 @@ #include "php_http.h" #include "php_ini.h" #include "url.h" +#include "SAPI.h" +#include "zend_exceptions.h" +#include "ext/spl/spl_exceptions.h" static void php_url_encode_scalar(zval *scalar, smart_str *form_str, int encoding_type, zend_ulong index_int, @@ -235,3 +238,125 @@ PHP_FUNCTION(http_build_query) RETURN_STR(smart_str_extract(&formstr)); } /* }}} */ + +static zend_result cache_request_parse_body_option(HashTable *options, zval *option, int cache_offset) +{ + if (option) { + zend_long result; + if (Z_TYPE_P(option) == IS_STRING) { + zend_string *errstr; + result = zend_ini_parse_quantity(Z_STR_P(option), &errstr); + if (errstr) { + zend_error(E_WARNING, "%s", ZSTR_VAL(errstr)); + zend_string_release(errstr); + } + } else if (Z_TYPE_P(option) == IS_LONG) { + result = Z_LVAL_P(option); + } else { + zend_value_error("Invalid %s value in $options argument", zend_zval_value_name(option)); + return FAILURE; + } + SG(request_parse_body_context).options_cache[cache_offset].set = true; + SG(request_parse_body_context).options_cache[cache_offset].value = result; + } else { + SG(request_parse_body_context).options_cache[cache_offset].set = false; + } + + return SUCCESS; +} + +static zend_result cache_request_parse_body_options(HashTable *options) +{ + zend_string *key; + zval *value; + ZEND_HASH_FOREACH_STR_KEY_VAL(options, key, value) { + if (!key) { + zend_value_error("Invalid integer key in $options argument"); + return FAILURE; + } + if (ZSTR_LEN(key) == 0) { + zend_value_error("Invalid empty string key in $options argument"); + return FAILURE; + } + +#define CHECK_OPTION(name) \ + if (zend_string_equals_literal_ci(key, #name)) { \ + if (cache_request_parse_body_option(options, value, REQUEST_PARSE_BODY_OPTION_ ## name) == FAILURE) { \ + return FAILURE; \ + } \ + continue; \ + } + + switch (ZSTR_VAL(key)[0]) { + case 'm': + case 'M': + CHECK_OPTION(max_file_uploads); + CHECK_OPTION(max_input_vars); + CHECK_OPTION(max_multipart_body_parts); + break; + case 'p': + case 'P': + CHECK_OPTION(post_max_size); + break; + case 'u': + case 'U': + CHECK_OPTION(upload_max_filesize); + break; + } + + zend_value_error("Invalid key \"%s\" in $options argument", ZSTR_VAL(key)); + return FAILURE; + } ZEND_HASH_FOREACH_END(); + +#undef CACHE_OPTION + + return SUCCESS; +} + +PHP_FUNCTION(request_parse_body) +{ + HashTable *options = NULL; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_HT_OR_NULL(options) + ZEND_PARSE_PARAMETERS_END(); + + SG(request_parse_body_context).throw_exceptions = true; + if (options) { + if (cache_request_parse_body_options(options) == FAILURE) { + goto exit; + } + } + + if (!SG(request_info).content_type) { + zend_throw_error(zend_ce_request_parse_body_exception, "Request does not provide a content type"); + goto exit; + } + + sapi_read_post_data(); + if (!SG(request_info).post_entry) { + zend_throw_error(spl_ce_InvalidArgumentException, "Content-Type \"%s\" is not supported", SG(request_info).content_type); + goto exit; + } + + zval post, files, old_post, old_files; + zval *global_post = &PG(http_globals)[TRACK_VARS_POST]; + zval *global_files = &PG(http_globals)[TRACK_VARS_FILES]; + + ZVAL_COPY_VALUE(&old_post, global_post); + ZVAL_COPY_VALUE(&old_files, global_files); + array_init(global_post); + array_init(global_files); + sapi_handle_post(global_post); + ZVAL_COPY_VALUE(&post, global_post); + ZVAL_COPY_VALUE(&files, global_files); + ZVAL_COPY_VALUE(global_post, &old_post); + ZVAL_COPY_VALUE(global_files, &old_files); + + RETVAL_ARR(zend_new_pair(&post, &files)); + +exit: + SG(request_parse_body_context).throw_exceptions = false; + memset(&SG(request_parse_body_context).options_cache, 0, sizeof(SG(request_parse_body_context).options_cache)); +} diff --git a/main/SAPI.c b/main/SAPI.c index e405702063789..e30a04144db49 100644 --- a/main/SAPI.c +++ b/main/SAPI.c @@ -169,7 +169,7 @@ SAPI_API void sapi_handle_post(void *arg) } } -static void sapi_read_post_data(void) +SAPI_API void sapi_read_post_data(void) { sapi_post_entry *post_entry; uint32_t content_type_length = (uint32_t)strlen(SG(request_info).content_type); @@ -255,9 +255,11 @@ SAPI_API size_t sapi_read_post_block(char *buffer, size_t buflen) SAPI_API SAPI_POST_READER_FUNC(sapi_read_standard_form_data) { - if ((SG(post_max_size) > 0) && (SG(request_info).content_length > SG(post_max_size))) { + zend_long post_max_size = REQUEST_PARSE_BODY_OPTION_GET(post_max_size, SG(post_max_size)); + + if (post_max_size > 0 && SG(request_info).content_length > post_max_size) { php_error_docref(NULL, E_WARNING, "POST Content-Length of " ZEND_LONG_FMT " bytes exceeds the limit of " ZEND_LONG_FMT " bytes", - SG(request_info).content_length, SG(post_max_size)); + SG(request_info).content_length, post_max_size); return; } @@ -281,8 +283,8 @@ SAPI_API SAPI_POST_READER_FUNC(sapi_read_standard_form_data) } } - if ((SG(post_max_size) > 0) && (SG(read_post_bytes) > SG(post_max_size))) { - php_error_docref(NULL, E_WARNING, "Actual POST length does not match Content-Length, and exceeds " ZEND_LONG_FMT " bytes", SG(post_max_size)); + if (post_max_size > 0 && SG(read_post_bytes) > post_max_size) { + php_error_docref(NULL, E_WARNING, "Actual POST length does not match Content-Length, and exceeds " ZEND_LONG_FMT " bytes", post_max_size); break; } @@ -455,6 +457,8 @@ SAPI_API void sapi_activate(void) SG(request_info).headers_only = 0; } SG(rfc1867_uploaded_files) = NULL; + SG(request_parse_body_context).throw_exceptions = false; + memset(&SG(request_parse_body_context).options_cache, 0, sizeof(SG(request_parse_body_context).options_cache)); /* Handle request method */ if (SG(server_context)) { @@ -1018,7 +1022,7 @@ SAPI_API zend_stat_t *sapi_get_stat(void) SAPI_API char *sapi_getenv(const char *name, size_t name_len) { char *value, *tmp; - + if (!sapi_module.getenv) { return NULL; } diff --git a/main/SAPI.h b/main/SAPI.h index 33c0e280d7390..284f4cb96f1fa 100644 --- a/main/SAPI.h +++ b/main/SAPI.h @@ -108,6 +108,26 @@ typedef struct { int proto_num; } sapi_request_info; +typedef struct { + bool throw_exceptions; + struct { + bool set; + zend_long value; + } options_cache[5]; +} sapi_request_parse_body_context; + +typedef enum { + REQUEST_PARSE_BODY_OPTION_max_file_uploads = 0, + REQUEST_PARSE_BODY_OPTION_max_input_vars, + REQUEST_PARSE_BODY_OPTION_max_multipart_body_parts, + REQUEST_PARSE_BODY_OPTION_post_max_size, + REQUEST_PARSE_BODY_OPTION_upload_max_filesize, +} request_parse_body_option; + +#define REQUEST_PARSE_BODY_OPTION_GET(name, fallback) \ + (SG(request_parse_body_context).options_cache[REQUEST_PARSE_BODY_OPTION_ ## name].set \ + ? SG(request_parse_body_context).options_cache[REQUEST_PARSE_BODY_OPTION_ ## name].value \ + : (fallback)) typedef struct _sapi_globals_struct { void *server_context; @@ -127,6 +147,7 @@ typedef struct _sapi_globals_struct { HashTable known_post_content_types; zval callback_func; zend_fcall_info_cache fci_cache; + sapi_request_parse_body_context request_parse_body_context; } sapi_globals_struct; @@ -186,6 +207,7 @@ SAPI_API int sapi_add_header_ex(const char *header_line, size_t header_line_len, SAPI_API int sapi_send_headers(void); SAPI_API void sapi_free_header(sapi_header_struct *sapi_header); SAPI_API void sapi_handle_post(void *arg); +SAPI_API void sapi_read_post_data(void); SAPI_API size_t sapi_read_post_block(char *buffer, size_t buflen); SAPI_API int sapi_register_post_entries(const sapi_post_entry *post_entry); SAPI_API int sapi_register_post_entry(const sapi_post_entry *post_entry); diff --git a/main/php_variables.c b/main/php_variables.c index 22d8ff73fe3a8..db8094b0952e2 100644 --- a/main/php_variables.c +++ b/main/php_variables.c @@ -25,6 +25,7 @@ #include "php_content_types.h" #include "SAPI.h" #include "zend_globals.h" +#include "zend_exceptions.h" /* for systems that need to override reading of environment variables */ void _php_import_environment_variables(zval *array_ptr); @@ -378,7 +379,7 @@ static bool add_post_var(zval *arr, post_var_data_t *var, bool eof) static inline int add_post_vars(zval *arr, post_var_data_t *vars, bool eof) { - uint64_t max_vars = PG(max_input_vars); + uint64_t max_vars = REQUEST_PARSE_BODY_OPTION_GET(max_input_vars, PG(max_input_vars)); vars->ptr = ZSTR_VAL(vars->str.s); vars->end = ZSTR_VAL(vars->str.s) + ZSTR_LEN(vars->str.s); @@ -538,8 +539,9 @@ SAPI_API SAPI_TREAT_DATA_FUNC(php_default_treat_data) } } - if (++count > PG(max_input_vars)) { - php_error_docref(NULL, E_WARNING, "Input variables exceeded " ZEND_LONG_FMT ". To increase the limit change max_input_vars in php.ini.", PG(max_input_vars)); + zend_long max_input_vars = REQUEST_PARSE_BODY_OPTION_GET(max_input_vars, PG(max_input_vars)); + if (++count > max_input_vars) { + php_error_docref(NULL, E_WARNING, "Input variables exceeded " ZEND_LONG_FMT ". To increase the limit change max_input_vars in php.ini.", max_input_vars); break; } diff --git a/main/rfc1867.c b/main/rfc1867.c index f16af170b6aa8..4292f5065bc08 100644 --- a/main/rfc1867.c +++ b/main/rfc1867.c @@ -29,6 +29,7 @@ #include "php_variables.h" #include "rfc1867.h" #include "zend_smart_string.h" +#include "zend_exceptions.h" #ifndef DEBUG_FILE_UPLOAD # define DEBUG_FILE_UPLOAD 0 @@ -646,7 +647,7 @@ static char *multipart_buffer_read_body(multipart_buffer *self, size_t *len) * */ -SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ +SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) { char *boundary, *s = NULL, *boundary_end = NULL, *start_arr = NULL, *array_index = NULL; char *lbuf = NULL, *abuf = NULL; @@ -658,18 +659,30 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ HashTable *uploaded_files = NULL; multipart_buffer *mbuff; zval *array_ptr = (zval *) arg; + bool throw_exceptions = SG(request_parse_body_context).throw_exceptions; int fd = -1; zend_llist header; void *event_extra_data = NULL; unsigned int llen = 0; - int upload_cnt = INI_INT("max_file_uploads"); - int body_parts_cnt = INI_INT("max_multipart_body_parts"); + zend_long upload_cnt = REQUEST_PARSE_BODY_OPTION_GET(max_file_uploads, INI_INT("max_file_uploads")); + zend_long body_parts_cnt = REQUEST_PARSE_BODY_OPTION_GET(max_multipart_body_parts, INI_INT("max_multipart_body_parts")); + zend_long post_max_size = REQUEST_PARSE_BODY_OPTION_GET(post_max_size, SG(post_max_size)); + zend_long max_input_vars = REQUEST_PARSE_BODY_OPTION_GET(max_input_vars, PG(max_input_vars)); + zend_long upload_max_filesize = REQUEST_PARSE_BODY_OPTION_GET(upload_max_filesize, PG(upload_max_filesize)); const zend_encoding *internal_encoding = zend_multibyte_get_internal_encoding(); php_rfc1867_getword_t getword; php_rfc1867_getword_conf_t getword_conf; php_rfc1867_basename_t _basename; zend_long count = 0; +#define EMIT_WARNING_OR_ERROR(...) do { \ + if (throw_exceptions) { \ + zend_throw_exception_ex(zend_ce_request_parse_body_exception, 0, __VA_ARGS__); \ + } else { \ + php_error_docref(NULL, E_WARNING, __VA_ARGS__); \ + } \ + } while (0) + if (php_rfc1867_encoding_translation() && internal_encoding) { getword = php_rfc1867_getword; getword_conf = php_rfc1867_getword_conf; @@ -680,13 +693,13 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ _basename = php_ap_basename; } - if (SG(post_max_size) > 0 && SG(request_info).content_length > SG(post_max_size)) { - sapi_module.sapi_error(E_WARNING, "POST Content-Length of " ZEND_LONG_FMT " bytes exceeds the limit of " ZEND_LONG_FMT " bytes", SG(request_info).content_length, SG(post_max_size)); + if (post_max_size > 0 && SG(request_info).content_length > post_max_size) { + EMIT_WARNING_OR_ERROR("POST Content-Length of " ZEND_LONG_FMT " bytes exceeds the limit of " ZEND_LONG_FMT " bytes", SG(request_info).content_length, post_max_size); return; } if (body_parts_cnt < 0) { - body_parts_cnt = PG(max_input_vars) + upload_cnt; + body_parts_cnt = max_input_vars + upload_cnt; } int body_parts_limit = body_parts_cnt; @@ -705,7 +718,7 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ } if (!boundary || !(boundary = strchr(boundary, '='))) { - sapi_module.sapi_error(E_WARNING, "Missing boundary in multipart/form-data POST data"); + EMIT_WARNING_OR_ERROR("Missing boundary in multipart/form-data POST data"); return; } @@ -716,7 +729,7 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ boundary++; boundary_end = strchr(boundary, '"'); if (!boundary_end) { - sapi_module.sapi_error(E_WARNING, "Invalid boundary in multipart/form-data POST data"); + EMIT_WARNING_OR_ERROR("Invalid boundary in multipart/form-data POST data"); return; } } else { @@ -729,10 +742,7 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ } /* Initialize the buffer */ - if (!(mbuff = multipart_buffer_new(boundary, boundary_len))) { - sapi_module.sapi_error(E_WARNING, "Unable to initialize the input buffer"); - return; - } + mbuff = multipart_buffer_new(boundary, boundary_len); /* Initialize $_FILES[] */ zend_hash_init(&PG(rfc1867_protected_variables), 8, NULL, NULL, 0); @@ -775,7 +785,7 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ int end = 0; if (--body_parts_cnt < 0) { - php_error_docref(NULL, E_WARNING, "Multipart body parts limit exceeded %d. To increase the limit change max_multipart_body_parts in php.ini.", body_parts_limit); + EMIT_WARNING_OR_ERROR("Multipart body parts limit exceeded %d. To increase the limit change max_multipart_body_parts in php.ini.", body_parts_limit); goto fileupload_done; } @@ -849,7 +859,7 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ } } - if (++count <= PG(max_input_vars) && sapi_module.input_filter(PARSE_POST, param, &value, value_len, &new_val_len)) { + if (++count <= max_input_vars && sapi_module.input_filter(PARSE_POST, param, &value, value_len, &new_val_len)) { if (php_rfc1867_callback != NULL) { multipart_event_formdata event_formdata; size_t newlength = new_val_len; @@ -868,8 +878,8 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ } safe_php_register_variable(param, value, new_val_len, array_ptr, 0); } else { - if (count == PG(max_input_vars) + 1) { - php_error_docref(NULL, E_WARNING, "Input variables exceeded " ZEND_LONG_FMT ". To increase the limit change max_input_vars in php.ini.", PG(max_input_vars)); + if (count == max_input_vars + 1) { + EMIT_WARNING_OR_ERROR("Input variables exceeded " ZEND_LONG_FMT ". To increase the limit change max_input_vars in php.ini.", max_input_vars); } if (php_rfc1867_callback != NULL) { @@ -900,13 +910,13 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ skip_upload = 1; if (upload_cnt == 0) { --upload_cnt; - sapi_module.sapi_error(E_WARNING, "Maximum number of allowable file uploads has been exceeded"); + EMIT_WARNING_OR_ERROR("Maximum number of allowable file uploads has been exceeded"); } } /* Return with an error if the posted data is garbled */ if (!param && !filename) { - sapi_module.sapi_error(E_WARNING, "File Upload Mime headers garbled"); + EMIT_WARNING_OR_ERROR("File Upload Mime headers garbled"); goto fileupload_done; } @@ -988,7 +998,7 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ fd = php_open_temporary_fd_ex(PG(upload_tmp_dir), "php", &temp_filename, PHP_TMP_FILE_OPEN_BASEDIR_CHECK_ON_FALLBACK); upload_cnt--; if (fd == -1) { - sapi_module.sapi_error(E_WARNING, "File upload error - unable to create a temporary file"); + EMIT_WARNING_OR_ERROR("File upload error - unable to create a temporary file"); cancel_upload = PHP_UPLOAD_ERROR_E; } } @@ -1010,9 +1020,9 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ } } - if (PG(upload_max_filesize) > 0 && (zend_long)(total_bytes+blen) > PG(upload_max_filesize)) { + if (upload_max_filesize > 0 && (zend_long)(total_bytes+blen) > upload_max_filesize) { #if DEBUG_FILE_UPLOAD - sapi_module.sapi_error(E_NOTICE, "upload_max_filesize of " ZEND_LONG_FMT " bytes exceeded - file [%s=%s] not saved", PG(upload_max_filesize), param, filename); + sapi_module.sapi_error(E_NOTICE, "upload_max_filesize of " ZEND_LONG_FMT " bytes exceeded - file [%s=%s] not saved", upload_max_filesize, param, filename); #endif cancel_upload = PHP_UPLOAD_ERROR_A; } else if (max_file_size && ((zend_long)(total_bytes+blen) > max_file_size)) { @@ -1060,7 +1070,7 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ } #if DEBUG_FILE_UPLOAD if (filename[0] != '\0' && total_bytes == 0 && !cancel_upload) { - sapi_module.sapi_error(E_WARNING, "Uploaded file size 0 - file [%s=%s] not saved", param, filename); + EMIT_WARNING_OR_ERROR("Uploaded file size 0 - file [%s=%s] not saved", param, filename); cancel_upload = 5; } #endif @@ -1134,7 +1144,7 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ register_http_post_files_variable(lbuf, s, &PG(http_globals)[TRACK_VARS_FILES], 0); s = NULL; - /* Add full path of supplied file for folder uploads via + /* Add full path of supplied file for folder uploads via * */ /* Add $foo[full_path] */ @@ -1263,6 +1273,8 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ if (mbuff->boundary) efree(mbuff->boundary); if (mbuff->buffer) efree(mbuff->buffer); if (mbuff) efree(mbuff); + +#undef EMIT_WARNING_OR_ERROR } /* }}} */ diff --git a/run-tests.php b/run-tests.php index be1bb86f729a6..5837a1a85000c 100755 --- a/run-tests.php +++ b/run-tests.php @@ -2324,7 +2324,9 @@ function run_test(string $php, $file, array $env): string } $env['CONTENT_LENGTH'] = strlen($request); - $env['REQUEST_METHOD'] = 'POST'; + if (empty($env['REQUEST_METHOD'])) { + $env['REQUEST_METHOD'] = 'POST'; + } if (empty($request)) { $junit->markTestAs('BORK', $shortname, $tested, null, 'empty $request'); diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c index 4382e8647c313..e84defb3da469 100644 --- a/sapi/cli/php_cli_server.c +++ b/sapi/cli/php_cli_server.c @@ -2222,6 +2222,8 @@ static void php_cli_server_request_shutdown(php_cli_server *server, php_cli_serv destroy_request_info(&SG(request_info)); SG(server_context) = NULL; SG(rfc1867_uploaded_files) = NULL; + SG(request_parse_body_context).throw_exceptions = false; + memset(&SG(request_parse_body_context).options_cache, 0, sizeof(SG(request_parse_body_context).options_cache)); } /* }}} */ @@ -2315,6 +2317,8 @@ static zend_result php_cli_server_dispatch(php_cli_server *server, php_cli_serve sapi_module.send_headers = send_header_func; SG(sapi_headers).send_default_content_type = 1; SG(rfc1867_uploaded_files) = NULL; + SG(request_parse_body_context).throw_exceptions = false; + memset(&SG(request_parse_body_context).options_cache, 0, sizeof(SG(request_parse_body_context).options_cache)); } if (FAILURE == php_cli_server_begin_send_static(server, client)) { php_cli_server_close_connection(server, client); diff --git a/sapi/fpm/tests/ghsa-54hq-v5wp-fqgv-max-file-uploads.phpt b/sapi/fpm/tests/ghsa-54hq-v5wp-fqgv-max-file-uploads.phpt index 8c7ec54c799ba..1c199dcb6ebe4 100644 --- a/sapi/fpm/tests/ghsa-54hq-v5wp-fqgv-max-file-uploads.phpt +++ b/sapi/fpm/tests/ghsa-54hq-v5wp-fqgv-max-file-uploads.phpt @@ -45,7 +45,7 @@ $tester->close(); ?> --EXPECT-- -Warning: Maximum number of allowable file uploads has been exceeded in Unknown on line 0 +Warning: PHP Request Startup: Maximum number of allowable file uploads has been exceeded in Unknown on line 0 int(5) --CLEAN-- +--FILE-- + $post, + 'files' => $files, + 'file_content' => $file_content, + 'post_global' => $_POST, + 'files_global' => $_FILES, +], JSON_PRETTY_PRINT); +EOT; + +$tester = new FPM\Tester($cfg, $code); +$tester->start(); +$tester->expectLogStartNotices(); +echo $tester + ->request(method: 'PUT', stdin: [ + 'parts' => [ + [ + "disposition" => "form-data", + "param" => "name", + "name" => "get_parameter", + "value" => "foo", + ], + [ + "disposition" => "form-data", + "param" => "filename", + "name" => "uploaded_file", + "value" => "bar", + ], + ], + ]) + ->getBody(); +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +?> +--EXPECTF-- +{ + "post": { + "get_parameter": "foo" + }, + "files": [ + { + "name": "uploaded_file", + "full_path": "uploaded_file", + "type": "", + "tmp_name": "%s", + "error": 0, + "size": 3 + } + ], + "file_content": "bar", + "post_global": [ + "post_global" + ], + "files_global": [ + "files_global" + ] +} +--CLEAN-- + diff --git a/sapi/fpm/tests/request_parse_body_urlencoded.phpt b/sapi/fpm/tests/request_parse_body_urlencoded.phpt new file mode 100644 index 0000000000000..c81d02415af84 --- /dev/null +++ b/sapi/fpm/tests/request_parse_body_urlencoded.phpt @@ -0,0 +1,75 @@ +--TEST-- +PUT x-www-form-urlencoded +--EXTENSIONS-- +zend_test +--SKIPIF-- + +--FILE-- + $post, + 'files' => $files, + 'post_global' => $_POST, + 'files_global' => $_FILES, +], JSON_PRETTY_PRINT); +EOT; + +$tester = new FPM\Tester($cfg, $code); +$tester->start(); +$tester->expectLogStartNotices(); +echo $tester + ->request( + method: 'PUT', + headers: ['CONTENT_TYPE' => 'application/x-www-form-urlencoded'], + stdin: 'foo=foo&bar[]=1&bar[]=2' + ) + ->getBody(); +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +?> +--CLEAN-- + +--EXPECT-- +{ + "post": { + "foo": "foo", + "bar": [ + "1", + "2" + ] + }, + "files": [], + "post_global": [ + "post_global" + ], + "files_global": [ + "files_global" + ] +} diff --git a/sapi/fpm/tests/tester.inc b/sapi/fpm/tests/tester.inc index 05a33737ebb9e..a196320353b0e 100644 --- a/sapi/fpm/tests/tester.inc +++ b/sapi/fpm/tests/tester.inc @@ -697,7 +697,8 @@ class Tester string $uri = null, string $scriptFilename = null, string $scriptName = null, - ?string $stdin = null + ?string $stdin = null, + ?string $method = null, ): array { if (is_null($scriptFilename)) { $scriptFilename = $this->makeSourceFile(); @@ -712,7 +713,7 @@ class Tester $params = array_merge( [ 'GATEWAY_INTERFACE' => 'FastCGI/1.0', - 'REQUEST_METHOD' => is_null($stdin) ? 'GET' : 'POST', + 'REQUEST_METHOD' => $method ?? (is_null($stdin) ? 'GET' : 'POST'), 'SCRIPT_FILENAME' => $scriptFilename === '' ? null : $scriptFilename, 'SCRIPT_NAME' => $scriptName, 'QUERY_STRING' => $query, @@ -836,6 +837,7 @@ class Tester bool $expectError = false, int $readLimit = -1, int $writeDelay = 0, + ?string $method = null, ): Response { if ($this->hasError()) { return $this->createResponse(expectInvalid: true); @@ -845,7 +847,7 @@ class Tester $stdin = $this->parseStdin($stdin, $headers); } - $params = $this->getRequestParams($query, $headers, $uri, $scriptFilename, $scriptName, $stdin); + $params = $this->getRequestParams($query, $headers, $uri, $scriptFilename, $scriptName, $stdin, $method); $this->trace('Request params', $params); try { diff --git a/tests/basic/rfc1867_garbled_mime_headers.phpt b/tests/basic/rfc1867_garbled_mime_headers.phpt index a6c0425335f1e..9a8c7e7ec5b02 100644 --- a/tests/basic/rfc1867_garbled_mime_headers.phpt +++ b/tests/basic/rfc1867_garbled_mime_headers.phpt @@ -16,7 +16,7 @@ var_dump($_FILES); var_dump($_POST); ?> --EXPECTF-- -Warning: File Upload Mime headers garbled in %s +Warning: PHP Request Startup: File Upload Mime headers garbled in %s array(0) { } array(0) { diff --git a/tests/basic/rfc1867_invalid_boundary.phpt b/tests/basic/rfc1867_invalid_boundary.phpt index 3b63b2722206b..d8557840bd01b 100644 --- a/tests/basic/rfc1867_invalid_boundary.phpt +++ b/tests/basic/rfc1867_invalid_boundary.phpt @@ -15,7 +15,7 @@ var_dump($_FILES); var_dump($_POST); ?> --EXPECTF-- -Warning: Invalid boundary in multipart/form-data POST data in %s +Warning: PHP Request Startup: Invalid boundary in multipart/form-data POST data in %s array(0) { } array(0) { diff --git a/tests/basic/rfc1867_missing_boundary.phpt b/tests/basic/rfc1867_missing_boundary.phpt index aadd1a8aed036..32dd8b149e22c 100644 --- a/tests/basic/rfc1867_missing_boundary.phpt +++ b/tests/basic/rfc1867_missing_boundary.phpt @@ -15,7 +15,7 @@ var_dump($_FILES); var_dump($_POST); ?> --EXPECTF-- -Warning: Missing boundary in multipart/form-data POST data in %s +Warning: PHP Request Startup: Missing boundary in multipart/form-data POST data in %s array(0) { } array(0) { diff --git a/tests/basic/rfc1867_post_max_size.phpt b/tests/basic/rfc1867_post_max_size.phpt index eba547c7d9a09..8176953485932 100644 --- a/tests/basic/rfc1867_post_max_size.phpt +++ b/tests/basic/rfc1867_post_max_size.phpt @@ -15,7 +15,7 @@ var_dump($_FILES); var_dump($_POST); ?> --EXPECTF-- -Warning: POST Content-Length of %d bytes exceeds the limit of 1 bytes in %s +Warning: PHP Request Startup: POST Content-Length of 168 bytes exceeds the limit of 1 bytes in %s array(0) { } array(0) {