From cc8187ad1b93b7196a42576f171719951852d04e Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Wed, 26 Aug 2026 01:13:42 +0800 Subject: [PATCH 1/3] [skip ci] UPGRADING: fix several typos and grammar bugs I read through this file and found and fixed some typos and grammar issues in this commit. --- UPGRADING | 110 +++++++++++++++++++++++++++--------------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/UPGRADING b/UPGRADING index f3b4ed8b99fa..aa6e15aaea6a 100644 --- a/UPGRADING +++ b/UPGRADING @@ -24,9 +24,9 @@ PHP 8.6 UPGRADE NOTES has materialized the property by writing into the property table. The freshly-written value is returned directly. isset() is unaffected. -- COM - . It is no longer possible to clone variant objects, this is because - the cloning behavior was ill defined. +- COM: + . It is no longer possible to clone variant objects because the cloning + behavior was ill-defined. - Curl: . The callback registered with CURLOPT_READFUNCTION now throws a ValueError @@ -35,10 +35,10 @@ PHP 8.6 UPGRADE NOTES - DOM: . Properties previously documented as @readonly (e.g. DOMNode::$nodeType, - DOMDocument::$xmlEncoding, DOMEntity::$actualEncoding, ::$encoding, - ::$version) are now declared with asymmetric visibility - (public private(set)). Attempts to write to them from outside the - class now raise "Cannot modify private(set) property ::$ + DOMDocument::$xmlEncoding, DOMEntity::$actualEncoding, + DOMEntity::$encoding, DOMEntity::$version) are now declared with asymmetric + visibility (public private(set)). Attempts to write to them from outside + the class now raise "Cannot modify private(set) property ::$ from global scope" instead of the prior readonly modification error. ReflectionProperty::isWritable() also reports these properties accurately. @@ -49,8 +49,8 @@ PHP 8.6 UPGRADE NOTES smaller index. - GD: - . imagesetstyle(), imagefilter() and imagecrop() filter their array arguments - types / values and raise a TypeError / ValueError accordingly. + . imagesetstyle(), imagefilter() and imagecrop() filter the types / values of + their array arguments and raise a TypeError / ValueError accordingly. . imageaffinematrixget() now enforces the documented array|float type for the $options parameter, including the corresponding weak and strict typing behavior. @@ -92,7 +92,7 @@ PHP 8.6 UPGRADE NOTES - PCNTL: . pcntl_alarm() now raises a ValueError if the seconds argument is - lower than zero or greater than platform's UINT_MAX. + lower than zero or greater than the platform's UINT_MAX. . pcntl_exec() now raises a ValueError if the $args argument is not a list array. @@ -180,8 +180,8 @@ PHP 8.6 UPGRADE NOTES - SimpleXML: . SimpleXMLElement::__construct() now raises a ValueError when the $data argument contains NUL bytes, matching simplexml_load_file(). With - $dataIsURL set it previously truncated the path at the first NUL byte. - Without it the string went to libxml, which at default options rejects a + $dataIsURL set, it previously truncated the path at the first NUL byte. + Without it, the string went to libxml, which with default options rejects a NUL on current versions but accepts the truncated document on older ones and under LIBXML_RECOVER. @@ -220,7 +220,7 @@ PHP 8.6 UPGRADE NOTES . SplFileObject::next() past EOF no longer increments key() without bound. SplFileObject::seek() past EOF now produces the same key() value as SplTempFileObject; the two previously returned different values. - . DirectoryIterator::key() now returns int|string, + . DirectoryIterator::key() now returns int|string, and DirectoryIterator::current() returns string|SplFileInfo|static. - Standard: @@ -232,14 +232,14 @@ PHP 8.6 UPGRADE NOTES for empty arrays or converting values, so an invalid later argument can suppress conversion side effects from earlier arrays. Values are not converted if any input array is empty. - . Form feed (\f) is now added in the default trimmed characters of trim(), + . Form feed (\f) is now added to the default trimmed characters of trim(), rtrim() and ltrim(). RFC: https://wiki.php.net/rfc/trim_form_feed . array_filter() now raises a ValueError when an invalid $mode argument value is passed. . array_change_key_case() now raises a ValueError when an invalid $case argument value is passed. - . getenv() and putenv() now raises a ValueError when the first argument + . getenv() and putenv() now raise a ValueError when the first argument contains NUL bytes. . dl() now raises a ValueError when the $extension_filename argument contains NUL bytes. @@ -267,7 +267,7 @@ PHP 8.6 UPGRADE NOTES UINT_MAX instead of allowing the value to overflow. . proc_open() now raises a ValueError when the $cwd argument contains NUL bytes. - . base_convert(), bindex(), hexdec() and octdec() now raise a notice when + . base_convert(), bindec(), hexdec() and octdec() now raise a notice when they cannot precisely convert the given number. . The following functions now raise a ValueError when the $filename argument contains NUL bytes: @@ -323,7 +323,7 @@ PHP 8.6 UPGRADE NOTES - Core: . Readonly properties may now declare default values. RFC: https://wiki.php.net/rfc/readonly_property_defaults - . It is now possible to use reference assign on WeakMap without the key + . It is now possible to use reference assignment on WeakMap without the key needing to be present beforehand. . It is now possible to define the __debugInfo() magic method on enums. RFC: https://wiki.php.net/rfc/debugable-enums @@ -371,8 +371,8 @@ PHP 8.6 UPGRADE NOTES IntlDatePatternGenerator::getBaseSkeleton() to generate the unique skeleton and base skeleton for a date/time pattern. . Added Locale::getDisplayKeyword() and Locale::getDisplayKeywordValue(), - with the alias of locale_get_display_keyword() and - locale_get_display_keyword_value() respectively. + with the aliases locale_get_display_keyword() and + locale_get_display_keyword_value(), respectively. RFC: https://wiki.php.net/rfc/getdisplaykeyword_and_getdisplaykeywordvalue . Added IntlNumberRangeFormatter class to format an interval of two numbers with a given skeleton, locale, IntlNumberRangeFormatter::COLLAPSE_AUTO, @@ -383,13 +383,13 @@ PHP 8.6 UPGRADE NOTES IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE, IntlNumberRangeFormatter::IDENTITY_FALLBACK_APPROXIMATELY and IntlNumberRangeFormatter::IDENTITY_FALLBACK_RANGE identity fallbacks. - It is supported from icu 63. + It is supported as of ICU 63. . Added SpoofChecker::areBidiConfusable() to check whether two strings are confusable for a given text direction, along with the SpoofChecker::LTR and SpoofChecker::RTL direction constants. - It is supported from icu 74. + It is supported as of ICU 74. . Added SpoofChecker::getBidiSkeleton() to generate a confusable skeleton for - a given text direction. It is supported from icu 74. + a given text direction. It is supported as of ICU 74. . Added SpoofChecker::getSkeleton() to generate a confusable skeleton for a given string. @@ -423,10 +423,10 @@ PHP 8.6 UPGRADE NOTES This makes it possible to override the timestamp and names of files. - SNMP: - . It is now possible to use the AES192, AES192C, AES256, and AES256C as + . It is now possible to use AES192, AES192C, AES256, and AES256C as SNMPv3 security protocols if the underlying library supports them. RFC: https://wiki.php.net/rfc/snmp_improvements_2026#increase_the_number_of_snmpv3_security_protocols_supported - . It is now possible to reset the MIB tree using the new snmp_read_mib() + . It is now possible to reset the MIB tree using the new snmp_init_mib() function. RFC: https://wiki.php.net/rfc/snmp_improvements_2026#allow_the_snmp_mib_to_be_reset . Additional MIB parsing and output control functionality has been exposed @@ -465,11 +465,11 @@ PHP 8.6 UPGRADE NOTES sockets. A positive value enables lingering for that many seconds, zero or a negative value disables it. Values above 65535 are clamped as the linger time is limited to an unsigned short on some platforms. - . Allowed casting filtered streams as file descriptor for select. - . Added the "write_seek_mode stream" filter parameter for the bz2, iconv, + . Allowed casting filtered streams as file descriptors for select. + . Added the "write_seek_mode" filter parameter for the bz2, iconv, zlib, and string stream filters. This parameter must be set via an - associative array where the key is "write_seek_mode stream" and the - value is one of the following strings "preserve", "reset", or "strict". + associative array where the key is "write_seek_mode" and the + value is one of the following strings: "preserve", "reset", or "strict". - URI: . Added Uri\Rfc3986\Uri::getUriType() and Uri\WhatWg\Url::isSpecialScheme(). @@ -486,7 +486,7 @@ PHP 8.6 UPGRADE NOTES ======================================== - CLI: - . The built-in development server now accepts requests using the QUERY HTTP + . The built-in development server now accepts requests using the HTTP QUERY method instead of returning 501 Not Implemented. ======================================== @@ -509,12 +509,12 @@ PHP 8.6 UPGRADE NOTES . Passing a 3rd argument to define() is now deprecated. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_define_with_case_insensitive_being_specified -- BZ2 +- BZ2: . Passing an object for the Bzip2 {de}compression stream filter is now deprecated. Use get_object_vars() on the object instead. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#passing_objects_as_parameters_to_the_bzip2decompress_and_bzip2compress_stream_filters -- GMP +- GMP: . The shift (<<, >>) and exponentiation (**) operators on GMP objects now emit a deprecation warning when converting a float right operand to int loses precision. @@ -523,14 +523,15 @@ PHP 8.6 UPGRADE NOTES . Mbregex has been deprecated, because the underlying Oniguruma library is no longer maintained. RFC: https://wiki.php.net/rfc/eol-oniguruma - . Passing objects to mb_convert_variables() is now deprecated + . Passing objects to mb_convert_variables() is now deprecated. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#passing_objects_for_vars_parameter_of_mb_convert_variables - MySQLi: . The mysqli_get_charset() function and mysqli::get_charset() method are now deprecated. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_mysqli_get_charset - . The mysqli_stmt_init() function, mysqli::stmt_init() method, and calling mysqli_stmt constructor - without providing the $query parameter are now deprecated. + . The mysqli_stmt_init() function, mysqli::stmt_init() method, and calling the + mysqli_stmt constructor without providing the $query parameter are now + deprecated. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_mysqlistmt_init - Reflection: @@ -555,10 +556,10 @@ PHP 8.6 UPGRADE NOTES RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_passing_a_sessionhandler_object_to_session_set_save_handler_which_does_not_contain_the_create_sid_and_validateid - SPL: - . The spl_classes() function is now deprecated, use + . The spl_classes() function is now deprecated. Use ReflectionExtension::getClassNames() instead. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_spl_classes - . The spl_object_hash() function is now deprecated, use spl_object_id() + . The spl_object_hash() function is now deprecated. Use spl_object_id() instead. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_spl_object_hash . The following ArrayIterator methods are now deprecated: @@ -573,7 +574,7 @@ PHP 8.6 UPGRADE NOTES * ArrayIterator::unserialize() * ArrayIterator::serialize() RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_arrayiterator_methods_that_inherit_arrayobject_implementation - . The following SplFileObject methods are now deprecated + . The following SplFileObject methods are now deprecated: * SplFileObject::fgetcsv() * SplFileObject::fputcsv() * SplFileObject::setCsvControl() @@ -591,29 +592,28 @@ PHP 8.6 UPGRADE NOTES . Passing an object to array_walk{_recursive} is now deprecated. Use get_object_vars() on the object instead. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#passing_objects_for_array_parameter_of_array_walk_and_array_walk_recursive - . The is_double() function is now deprecated, use is_float() instead. + . The is_double() function is now deprecated. Use is_float() instead. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_is_double - . The is_long() and is_integer() functions are now deprecated, use is_int() + . The is_long() and is_integer() functions are now deprecated. Use is_int() instead. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_is_integer RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_is_long - . The doubleval() function is now deprecated, use floatval() instead. + . The doubleval() function is now deprecated. Use floatval() instead. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_doubleval - . The strcoll() function is now deprecated, use Collator::compare() instead. + . The strcoll() function is now deprecated. Use Collator::compare() instead. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_strcoll . The SORT_LOCALE_STRING constant for the family of sort functions is now - deprecated, use one of the following functions instead: + deprecated. Use one of the following functions instead: * Collator::sort() * Collator::asort() - * Collator::sort() * Collator::sortWithSortKeys() RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_sort_locale_string_flag_for_sort_functions -- Zlib +- Zlib: . Passing an object for the zlib deflate and inflate stream filter is now deprecated. Use get_object_vars() on the object instead. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#passing_objects_as_parameters_to_the_zlibinflate_and_zlibdeflate_stream_filters - . Passing an object to the $option argument to deflate_init and inflate_init + . Passing an object as the $option argument to deflate_init and inflate_init is now deprecated. Use get_object_vars() on the object instead. RFC: https://wiki.php.net/rfc/deprecations_php_8_6#passing_objects_for_options_parameter_of_deflate_init_and_inflate_init @@ -622,7 +622,7 @@ PHP 8.6 UPGRADE NOTES ======================================== - GMP: - . gmp_fact() now throws a ValueError() if $num does not fit into a unsigned + . gmp_fact() now throws a ValueError if $num does not fit into an unsigned long. . gmp_pow(), gmp_binomial(), gmp_root() and gmp_rootrem() now throw a ValueError if their second argument does not fit into an unsigned long. @@ -659,8 +659,8 @@ PHP 8.6 UPGRADE NOTES ZPP before the function body is reached. - Sockets: - . socket_addrinfo_lookup() now has an additional optional argument $error - when not null, and on failure, gives the error code (one of the EAI_* + . socket_addrinfo_lookup() now has an additional optional argument $error_code + that, when not null, receives the error code on failure (one of the EAI_* constants). . socket_cmsg_space() return type has been narrowed from ?int to int. Every failure path has thrown a ValueError since PHP 8.0, so null was never @@ -668,7 +668,7 @@ PHP 8.6 UPGRADE NOTES - Standard: . header_register_callback() now declares true as its return type. It has not - been able to return false since PHP 8.0.0, where passing an invalid + been able to return false since PHP 8.0.0, when passing an invalid callback started throwing a TypeError instead. . register_tick_function() now declares true as its return type. It has always returned true on success; an invalid callback throws a TypeError @@ -722,7 +722,7 @@ PHP 8.6 UPGRADE NOTES . snmp_set_string_output_format() - Standard: - . clamp() returns the given value if in range, else return the nearest + . clamp() returns the given value if in range, else returns the nearest bound. RFC: https://wiki.php.net/rfc/clamp_v2 . stream_last_errors() and stream_clear_errors() @@ -809,7 +809,7 @@ PHP 8.6 UPGRADE NOTES - MySQLi: . Added new constant MYSQLI_OPT_COMPRESS. -- Opcache +- Opcache: . JIT is now supported for ZTS builds on Apple Silicon. ======================================== @@ -853,7 +853,7 @@ PHP 8.6 UPGRADE NOTES . EAI_INTR. . EAI_IDN_ENCODE. -- Standard +- Standard: . ARRAY_FILTER_USE_VALUE. . STREAM_CRYPTO_STATUS_NONE. . STREAM_CRYPTO_STATUS_WANT_READ. @@ -872,7 +872,7 @@ PHP 8.6 UPGRADE NOTES - MySQLi: . mysqli.default_port now checks the validity of the value which should be - between 0 and 65535 included. + between 0 and 65535 inclusive. - Opcache: . opcache.jit_debug accepts a new flag: ZEND_JIT_DEBUG_TRACE_EXIT_INFO_SRC. @@ -923,7 +923,7 @@ PHP 8.6 UPGRADE NOTES JIT. . The performance of the TAILCALL VM has been improved. . The TAILCALL VM is now enabled on Windows when compiling with Clang >= 19 - x86_64. + on x86_64. . The performance of ZTS builds has been improved. . Added stateless closure cache. RFC: https://wiki.php.net/rfc/closure-optimizations#stateless_closure_caching @@ -960,7 +960,7 @@ PHP 8.6 UPGRADE NOTES - URI: . Improved performance of Uri\WhatWg\Url::parse() when collecting validation errors by pre-allocating the error array. - . Reduced allocations when reading IPv6/IPFuture hosts and paths with + . Reduced allocations when reading IPv6/IPvFuture hosts and paths with Uri\Rfc3986\Uri. . Improved performance and memory consumption when using normalizing (non-raw) getters on already-normalized URIs with Uri\Rfc3986\Uri. From b8c43c35d11a1d71851d19db3514b6d902ce9903 Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Tue, 25 Aug 2026 19:51:29 +0100 Subject: [PATCH 2/3] sapi/cli: support Expect 100-continue in PHP dev server When posting large payloads, curl checks whether the server is ready for the body. It sends an `Expect: 100-continue` header and expects `HTTP/1.1 100 Continue` as the response before sending the body. The PHP development server did not support this, causing a timeout in curl. This made such requests take one second longer. HTTP/1.0 does not support this, so the response is only sent when the request is HTTP/1.1. - https://everything.curl.dev/http/post/expect100.html - https://github.com/php/php-src/issues/23242 Fix GH-23242 Close GH-23245 --- NEWS | 4 + sapi/cli/php_cli_server.c | 26 ++++++ sapi/cli/tests/php_cli_server.inc | 3 +- ...p_cli_server_expect_100_continue_curl.phpt | 35 ++++++++ ...hp_cli_server_expect_100_continue_iua.phpt | 33 +++++++ ...cli_server_expect_100_continue_socket.phpt | 86 +++++++++++++++++++ 6 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 sapi/cli/tests/php_cli_server_expect_100_continue_curl.phpt create mode 100644 sapi/cli/tests/php_cli_server_expect_100_continue_iua.phpt create mode 100644 sapi/cli/tests/php_cli_server_expect_100_continue_socket.phpt diff --git a/NEWS b/NEWS index 0817b78f6598..5276069926e5 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.6.0beta3 +- CLI: + . Fixed bug GH-23242 (PHP development server does not support Expect + 100-continue flow control). (Sjoerd Langkemper) + 27 Aug 2026, PHP 8.6.0beta2 diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c index 5df12648ca84..9f13f0647700 100644 --- a/sapi/cli/php_cli_server.c +++ b/sapi/cli/php_cli_server.c @@ -176,6 +176,7 @@ typedef struct php_cli_server_client { bool request_read; bool too_large_post; bool headers_written; + bool expect_continue; zend_string *current_header_name; zend_string *current_header_value; enum { HEADER_NONE=0, HEADER_FIELD, HEADER_VALUE } last_header_element; @@ -1794,6 +1795,13 @@ static int php_cli_server_client_read_request_on_headers_complete(php_http_parse return 2; } + zval *expect_val = zend_hash_str_find(&client->request.headers, "expect", sizeof("expect") - 1); + if (expect_val && Z_TYPE_P(expect_val) == IS_STRING + && zend_string_equals_literal_ci(Z_STR_P(expect_val), "100-continue") + && parser->http_major == 1 && parser->http_minor == 1) { + client->expect_continue = true; + } + return 0; } @@ -1901,6 +1909,23 @@ static int php_cli_server_client_read_request(php_cli_server_client *client, cha return -1; } + if (client->expect_continue && !client->request_read) { + /* Parser completed headers with Expect: 100-continue but hasn't + * finished reading the body. Send 100 Continue before the client + * sends the request body. Only supported in HTTP/1.1. */ + static const char continue_response[] = "HTTP/1.1 100 Continue\r\n\r\n"; + bool send_success = false; + client->expect_continue = false; + zend_try { + size_t sent = php_cli_server_client_send_through(client, continue_response, strlen(continue_response)); + send_success = sent == strlen(continue_response); + } zend_end_try(); + if (!send_success) { + *errstr = php_socket_strerror(php_socket_errno(), NULL, 0); + return -1; + } + } + return client->request_read ? 1: 0; } /* }}} */ @@ -1985,6 +2010,7 @@ static void php_cli_server_client_ctor(php_cli_server_client *client, php_cli_se client->request_read = false; client->too_large_post = false; client->headers_written = false; + client->expect_continue = false; client->last_header_element = HEADER_NONE; client->current_header_name = NULL; diff --git a/sapi/cli/tests/php_cli_server.inc b/sapi/cli/tests/php_cli_server.inc index feee2bbb5686..ec370753573c 100644 --- a/sapi/cli/tests/php_cli_server.inc +++ b/sapi/cli/tests/php_cli_server.inc @@ -5,6 +5,7 @@ class CliServerInfo { public function __construct( public string $docRoot, public $processHandle, + public $outputFile, ) {} } @@ -118,7 +119,7 @@ function php_cli_server_start( define("PHP_CLI_SERVER_PORT", $port); define("PHP_CLI_SERVER_ADDRESS", PHP_CLI_SERVER_HOSTNAME.":".PHP_CLI_SERVER_PORT); - return new CliServerInfo($doc_root, $handle); + return new CliServerInfo($doc_root, $handle, $output_file); } function php_cli_server_connect() { diff --git a/sapi/cli/tests/php_cli_server_expect_100_continue_curl.phpt b/sapi/cli/tests/php_cli_server_expect_100_continue_curl.phpt new file mode 100644 index 000000000000..37886fc25e1e --- /dev/null +++ b/sapi/cli/tests/php_cli_server_expect_100_continue_curl.phpt @@ -0,0 +1,35 @@ +--TEST-- +Expect 100-continue behavior in PHP development server (curl) +--SKIPIF-- + +--EXTENSIONS-- +curl +--FILE-- + +--EXPECT-- +int(0) +Did the PHP development server send a HTTP/1.1 100 Continue header? +bool(true) diff --git a/sapi/cli/tests/php_cli_server_expect_100_continue_iua.phpt b/sapi/cli/tests/php_cli_server_expect_100_continue_iua.phpt new file mode 100644 index 000000000000..c792630a9305 --- /dev/null +++ b/sapi/cli/tests/php_cli_server_expect_100_continue_iua.phpt @@ -0,0 +1,33 @@ +--TEST-- +Failure to send "100 Continue" is reported with ignore_user_abort=1 +--SKIPIF-- + +--FILE-- + 1, 'l_linger' => 0]); +fwrite($fp, "POST / HTTP/1.1\r\nExpect: 100-continue\r\nContent-Length: 4\r\n\r\n"); +fclose($fp); + +$output = ''; +for ($i = 0; $i < 100 && !str_contains($output, 'Invalid request'); $i++) { + usleep(50000); + $output = file_get_contents($server->outputFile); +} + +var_dump(str_contains($output, 'Invalid request'), str_contains($output, 'Unexpected EOF')); +?> +--CLEAN-- + +--EXPECT-- +bool(true) +bool(false) diff --git a/sapi/cli/tests/php_cli_server_expect_100_continue_socket.phpt b/sapi/cli/tests/php_cli_server_expect_100_continue_socket.phpt new file mode 100644 index 000000000000..0d386155721d --- /dev/null +++ b/sapi/cli/tests/php_cli_server_expect_100_continue_socket.phpt @@ -0,0 +1,86 @@ +--TEST-- +Expect 100-continue behavior in PHP development server (sockets) +--SKIPIF-- + +--FILE-- + 1, + 'l_linger' => 0, + ] + ); +} +stream_socket_shutdown($fp, STREAM_SHUT_RD); +fwrite($fp, "POST / HTTP/1.1\r\nExpect: 100-continue\r\nContent-Length: 4\r\nConnection: close\r\n\r\n"); +fclose($fp); + +$fp = php_cli_server_connect(); +fwrite($fp, "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"); +echo fgets($fp); +fclose($fp); + +echo "# GET with Expect header (no body).\n"; +$fp = php_cli_server_connect(); +fwrite($fp, "GET / HTTP/1.1\r\nExpect: 100-continue\r\nConnection: close\r\n\r\n"); +echo fgets($fp); +fclose($fp); + +echo "# POST with empty body.\n"; +$fp = php_cli_server_connect(); +fwrite($fp, "POST / HTTP/1.1\r\nExpect: 100-continue\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"); +echo fgets($fp); +fclose($fp); + +echo "# Lower-case expect header.\n"; +$fp = php_cli_server_connect(); +fwrite($fp, "POST / HTTP/1.1\r\nexpect: 100-continue\r\nContent-Length: 4\r\nConnection: close\r\n\r\n"); +echo fgets($fp); +fclose($fp); +?> +--EXPECT-- +# Send Expect: 100-continue header, receive 100 Continue response. +HTTP/1.1 100 Continue + +HTTP/1.1 200 OK +# Send Expect: 100-continue header on HTTP/1.0. +int(0) +HTTP/1.0 200 OK +# Send Expect: 100-continue header and disconnect. +HTTP/1.1 200 OK +# GET with Expect header (no body). +HTTP/1.1 200 OK +# POST with empty body. +HTTP/1.1 200 OK +# Lower-case expect header. +HTTP/1.1 100 Continue From 045d005f80934d4a122145d1afef767f7b5039e5 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 25 Aug 2026 22:51:37 +0100 Subject: [PATCH 3/3] mysqli: move behaviour existing only for mysqli_fetch_object() into it (#23451) The shared function is effectively ignored for mysqli_fetch_object() so just move the relevant behaviour and simplify the common implementation --- ext/mysqli/mysqli.c | 64 +++++++------------------------------- ext/mysqli/mysqli_api.c | 2 +- ext/mysqli/mysqli_nonapi.c | 51 ++++++++++++++++++++++++++++-- ext/mysqli/mysqli_priv.h | 2 +- 4 files changed, 61 insertions(+), 58 deletions(-) diff --git a/ext/mysqli/mysqli.c b/ext/mysqli/mysqli.c index 2f1fa1c21c55..e6f876433571 100644 --- a/ext/mysqli/mysqli.c +++ b/ext/mysqli/mysqli.c @@ -747,72 +747,30 @@ void php_mysqli_fetch_into_hash_aux(zval *return_value, MYSQL_RES * result, zend /* TODO Split this up */ /* {{{ php_mysqli_fetch_into_hash */ -void php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAMETERS, int override_flags, int into_object) +void php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAMETERS, int override_flags) { MYSQL_RES *result; zval *mysql_result; zend_long fetchtype; - HashTable *ctor_params = NULL; - zend_class_entry *ce = NULL; - if (into_object) { - if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O|Ch", &mysql_result, mysqli_result_class_entry, &ce, &ctor_params) == FAILURE) { + if (override_flags) { + ZEND_ASSERT(override_flags >= MYSQLI_ASSOC && override_flags <= MYSQLI_BOTH); + if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O", &mysql_result, mysqli_result_class_entry) == FAILURE) { RETURN_THROWS(); } - if (ce == NULL) { - ce = zend_standard_class_def; - } - if (UNEXPECTED(ce->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS))) { - zend_throw_error(NULL, "Class %s cannot be instantiated", ZSTR_VAL(ce->name)); + fetchtype = override_flags; + } else { + fetchtype = MYSQLI_BOTH; + if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O|l", &mysql_result, mysqli_result_class_entry, &fetchtype) == FAILURE) { RETURN_THROWS(); } - fetchtype = MYSQLI_ASSOC; - } else { - if (override_flags) { - if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O", &mysql_result, mysqli_result_class_entry) == FAILURE) { - RETURN_THROWS(); - } - fetchtype = override_flags; - } else { - fetchtype = MYSQLI_BOTH; - if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O|l", &mysql_result, mysqli_result_class_entry, &fetchtype) == FAILURE) { - RETURN_THROWS(); - } + if (fetchtype < MYSQLI_ASSOC || fetchtype > MYSQLI_BOTH) { + zend_argument_value_error(ERROR_ARG_POS(2), "must be one of MYSQLI_NUM, MYSQLI_ASSOC, or MYSQLI_BOTH"); + RETURN_THROWS(); } } MYSQLI_FETCH_RESOURCE(result, MYSQL_RES *, mysql_result, MYSQLI_STATUS_VALID); - if (fetchtype < MYSQLI_ASSOC || fetchtype > MYSQLI_BOTH) { - zend_argument_value_error(ERROR_ARG_POS(2), "must be one of MYSQLI_NUM, MYSQLI_ASSOC, or MYSQLI_BOTH"); - RETURN_THROWS(); - } - php_mysqli_fetch_into_hash_aux(return_value, result, fetchtype); - - if (into_object && Z_TYPE_P(return_value) == IS_ARRAY) { - zval dataset; - - ZVAL_COPY_VALUE(&dataset, return_value); - - object_init_ex(return_value, ce); - HashTable *prop_table = zend_symtable_to_proptable(Z_ARR(dataset)); - zval_ptr_dtor(&dataset); - if (!ce->default_properties_count && !ce->__set) { - Z_OBJ_P(return_value)->properties = prop_table; - } else { - zend_merge_properties(return_value, prop_table); - zend_array_release(prop_table); - } - - if (ce->constructor) { - zend_call_known_function(ce->constructor, Z_OBJ_P(return_value), Z_OBJCE_P(return_value), - /* retval */ NULL, /* argc */ 0, /* params */ NULL, ctor_params); - } else if (ctor_params && zend_hash_num_elements(ctor_params) > 0) { - zend_argument_value_error(ERROR_ARG_POS(3), - "must be empty when the specified class (%s) does not have a constructor", - ZSTR_VAL(ce->name) - ); - } - } } /* }}} */ diff --git a/ext/mysqli/mysqli_api.c b/ext/mysqli/mysqli_api.c index 526fd10b2623..e55083d940cf 100644 --- a/ext/mysqli/mysqli_api.c +++ b/ext/mysqli/mysqli_api.c @@ -780,7 +780,7 @@ PHP_FUNCTION(mysqli_fetch_lengths) /* {{{ Get a result row as an enumerated array */ PHP_FUNCTION(mysqli_fetch_row) { - php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, MYSQLI_NUM, 0); + php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, MYSQLI_NUM); } /* }}} */ diff --git a/ext/mysqli/mysqli_nonapi.c b/ext/mysqli/mysqli_nonapi.c index 1e46aeedd93b..1b973f405497 100644 --- a/ext/mysqli/mysqli_nonapi.c +++ b/ext/mysqli/mysqli_nonapi.c @@ -363,14 +363,14 @@ PHP_FUNCTION(mysqli_connect_error) /* {{{ Fetch a result row as an associative array, a numeric array, or both */ PHP_FUNCTION(mysqli_fetch_array) { - php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 0); + php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ /* {{{ Fetch a result row as an associative array */ PHP_FUNCTION(mysqli_fetch_assoc) { - php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, MYSQLI_ASSOC, 0); + php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, MYSQLI_ASSOC); } /* }}} */ @@ -525,7 +525,52 @@ PHP_FUNCTION(mysqli_stmt_error_list) /* {{{ Fetch a result row as an object */ PHP_FUNCTION(mysqli_fetch_object) { - php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, MYSQLI_ASSOC, 1); + zval *mysql_result; + zend_class_entry *ce = NULL; + HashTable *ctor_params = NULL; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O|Ch", &mysql_result, mysqli_result_class_entry, &ce, &ctor_params) == FAILURE) { + RETURN_THROWS(); + } + if (ce == NULL) { + ce = zend_standard_class_def; + } + if (UNEXPECTED(ce->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS))) { + zend_throw_error(NULL, "Class %s cannot be instantiated", ZSTR_VAL(ce->name)); + RETURN_THROWS(); + } + if (!ce->constructor && ctor_params && zend_hash_num_elements(ctor_params) > 0) { + zend_argument_value_error(ERROR_ARG_POS(3), + "must be empty when the specified class (%s) does not have a constructor", + ZSTR_VAL(ce->name) + ); + RETURN_THROWS(); + } + + MYSQL_RES *result; + MYSQLI_FETCH_RESOURCE(result, MYSQL_RES *, mysql_result, MYSQLI_STATUS_VALID); + + zval dataset; + php_mysqli_fetch_into_hash_aux(&dataset, result, MYSQLI_ASSOC); + + if (Z_TYPE(dataset) == IS_ARRAY) { + object_init_ex(return_value, ce); + HashTable *prop_table = zend_symtable_to_proptable(Z_ARR(dataset)); + zval_ptr_dtor(&dataset); + if (!ce->default_properties_count && !ce->__set) { + Z_OBJ_P(return_value)->properties = prop_table; + } else { + zend_merge_properties(return_value, prop_table); + zend_array_release(prop_table); + } + + if (ce->constructor) { + zend_call_known_function(ce->constructor, Z_OBJ_P(return_value), Z_OBJCE_P(return_value), + /* retval */ NULL, /* argc */ 0, /* params */ NULL, ctor_params); + } + } else { + RETURN_COPY_VALUE(&dataset); + } } /* }}} */ diff --git a/ext/mysqli/mysqli_priv.h b/ext/mysqli/mysqli_priv.h index 97d9300a2b79..32602ef94989 100644 --- a/ext/mysqli/mysqli_priv.h +++ b/ext/mysqli/mysqli_priv.h @@ -47,7 +47,7 @@ extern void php_mysqli_dtor_p_elements(void *data); extern void php_mysqli_close(MY_MYSQL * mysql, int close_type, int resource_status); -extern void php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAMETERS, int override_flag, int into_object); +extern void php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAMETERS, int override_flag); extern void php_clear_stmt_bind(MY_STMT *stmt); extern void php_clear_mysql(MY_MYSQL *); extern MYSQLI_WARNING *php_get_warnings(MYSQLND_CONN_DATA * mysql);