Add request_parse_body() function

RFC: https://wiki.php.net/rfc/rfc1867-non-post

This function allows populating the $_POST and $_FILES globals for non-post
requests. This avoids manual parsing of RFC1867 requests.

Fixes #55815
Closes GH-11472
This commit is contained in:
Ilija Tovilo 2023-06-17 22:26:21 +02:00
parent 2f894389b6
commit cd66fcc68b
No known key found for this signature in database
GPG Key ID: A4F5D403F118200A
41 changed files with 995 additions and 45 deletions

View File

@ -143,6 +143,11 @@ PHP 8.4 UPGRADE NOTES
2. New Features
========================================
- Core:
. Added request_parse_body() function that allows parsing RFC1867 (multipart)
requests in non-POST HTTP requests.
RFC: https://wiki.php.net/rfc/rfc1867-non-post
- Date:
. Added static methods
DateTime[Immutable]::createFromTimestamp(int|float $timestamp): static.

View File

@ -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),

View File

@ -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--
<?php
try {
[$_POST, $_FILES] = request_parse_body();
} catch (Throwable $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}
var_dump($_POST, $_FILES);
?>
--EXPECT--
RequestParseBodyException: Invalid boundary in multipart/form-data POST data
array(0) {
}
array(0) {
}

View File

@ -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--
<?php
[$_POST, $_FILES] = request_parse_body();
var_dump($_POST, $_FILES);
$file_path = __DIR__ . '/put_multipart_uploaded_file.txt';
move_uploaded_file($_FILES['file_name']['tmp_name'], $file_path);
var_dump(file_get_contents($file_path));
?>
--CLEAN--
<?php
$file_path = __DIR__ . '/put_multipart_uploaded_file.txt';
@unlink($file_path);
?>
--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"

View File

@ -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--
<?php
try {
[$_POST, $_FILES] = request_parse_body();
} catch (Throwable $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}
var_dump($_POST, $_FILES);
?>
--EXPECT--
RequestParseBodyException: File Upload Mime headers garbled
array(0) {
}
array(0) {
}

View File

@ -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--
<?php
try {
[$_POST, $_FILES] = request_parse_body();
} 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) {
}

View File

@ -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--
<?php
try {
[$_POST, $_FILES] = request_parse_body();
} 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) {
}

View File

@ -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--
<?php
try {
[$_POST, $_FILES] = request_parse_body();
} 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) {
}

View File

@ -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--
<?php
$stream = fopen('php://memory','r+');
try {
[$_POST, $_FILES] = request_parse_body();
} catch (Throwable $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}
var_dump($_POST, $_FILES);
?>
--EXPECT--
RequestParseBodyException: Missing boundary in multipart/form-data POST data
array(0) {
}
array(0) {
}

View File

@ -0,0 +1,21 @@
--TEST--
request_parse_body() invalid key
--FILE--
<?php
try {
request_parse_body(options: ['foo' => 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

View File

@ -0,0 +1,17 @@
--TEST--
request_parse_body() invalid quantity
--FILE--
<?php
try {
request_parse_body(options: [
'upload_max_filesize' => '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

View File

@ -0,0 +1,16 @@
--TEST--
request_parse_body() invalid value type
--FILE--
<?php
try {
request_parse_body(options: [
'max_input_vars' => [],
]);
} catch (Error $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}
?>
--EXPECT--
ValueError: Invalid array value in $options argument

View File

@ -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--
<?php
try {
[$_POST, $_FILES] = request_parse_body([
'max_file_uploads' => 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) {
}

View File

@ -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--
<?php
try {
[$_POST, $_FILES] = request_parse_body([
'max_input_vars' => 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) {
}

View File

@ -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--
<?php
try {
[$_POST, $_FILES] = request_parse_body([
'max_multipart_body_parts' => 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) {
}

View File

@ -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--
<?php
try {
[$_POST, $_FILES] = request_parse_body([
'post_max_size' => '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) {
}

View File

@ -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--
<?php
try {
[$_POST, $_FILES] = request_parse_body([
'upload_max_filesize' => '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)
}
}

View File

@ -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--
<?php
try {
[$_POST, $_FILES] = request_parse_body();
} catch (Throwable $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}
var_dump($_POST, $_FILES);
?>
--EXPECT--
InvalidArgumentException: Content-Type "application/json" is not supported
array(0) {
}
array(0) {
}

View File

@ -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--
<?php
[$_POST, $_FILES] = request_parse_body();
var_dump($_POST, $_FILES);
?>
--CLEAN--
<?php
$file_path = __DIR__ . '/put_multipart_uploaded_file.txt';
@unlink($file_path);
?>
--EXPECT--
array(2) {
["foo"]=>
string(3) "foo"
["bar"]=>
array(2) {
[0]=>
string(1) "1"
[1]=>
string(1) "2"
}
}
array(0) {
}

View File

@ -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);

View File

@ -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);

View File

@ -170,3 +170,7 @@ class DivisionByZeroError extends ArithmeticError
class UnhandledMatchError extends Error
{
}
class RequestParseBodyException extends Exception
{
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<int, array>
* @refcount 1
*/
function request_parse_body(?array $options = null): array {}
/* image.c */
/**

View File

@ -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)

View File

@ -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));
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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
* <input type="file" name="files" multiple webkitdirectory>
*/
/* Add $foo[full_path] */
@ -1263,6 +1273,8 @@ fileupload_done:
if (mbuff->boundary) efree(mbuff->boundary);
if (mbuff->buffer) efree(mbuff->buffer);
if (mbuff) efree(mbuff);
#undef EMIT_WARNING_OR_ERROR
}
/* }}} */

View File

@ -2324,7 +2324,9 @@ TEST $file
}
$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');

View File

@ -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);

View File

@ -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--
<?php

View File

@ -0,0 +1,97 @@
--TEST--
PUT multipart
--EXTENSIONS--
zend_test
--SKIPIF--
<?php include "skipif.inc"; ?>
--FILE--
<?php
require_once "tester.inc";
$cfg = <<<EOT
[global]
error_log = {{FILE:LOG}}
[unconfined]
listen = {{ADDR}}
pm = dynamic
pm.max_children = 5
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 3
EOT;
$code = <<<'EOT'
<?php
$_POST = ['post_global'];
$_FILES = ['files_global'];
[$post, $files] = request_parse_body();
$file_path = __DIR__ . '/put_multipart_uploaded_file.txt';
move_uploaded_file($files[0]['tmp_name'], $file_path);
$file_content = file_get_contents($file_path);
unlink($file_path);
echo json_encode([
'post' => $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--
<?php
require_once "tester.inc";
FPM\Tester::clean();
$file_path = __DIR__ . '/put_multipart_uploaded_file.txt';
@unlink($file_path);
?>

View File

@ -0,0 +1,75 @@
--TEST--
PUT x-www-form-urlencoded
--EXTENSIONS--
zend_test
--SKIPIF--
<?php include "skipif.inc"; ?>
--FILE--
<?php
require_once "tester.inc";
$cfg = <<<EOT
[global]
error_log = {{FILE:LOG}}
[unconfined]
listen = {{ADDR}}
pm = dynamic
pm.max_children = 5
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 3
EOT;
$code = <<<'EOT'
<?php
$_POST = ['post_global'];
$_FILES = ['files_global'];
[$post, $files] = request_parse_body();
echo json_encode([
'post' => $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--
<?php
require_once "tester.inc";
FPM\Tester::clean();
$file_path = __DIR__ . '/put_multipart_uploaded_file.txt';
@unlink($file_path);
?>
--EXPECT--
{
"post": {
"foo": "foo",
"bar": [
"1",
"2"
]
},
"files": [],
"post_global": [
"post_global"
],
"files_global": [
"files_global"
]
}

View File

@ -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 {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {