From 8dafaf5da2f5fc1c332c1fd3c1ccb0f322d21c51 Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Sun, 30 Aug 2026 12:49:03 +0100 Subject: [PATCH 01/13] Fix GH-23447: segfault when the SoapServer class fails to initialize SoapServer::handle() ignored the return value of object_init_ex(), so when the class given to setClass() could not be instantiated the code carried on with a NULL zval and crashed on Z_OBJCE_P(soap_obj). That happens for instance when a property default references an undefined constant, since evaluating it throws an Error and object creation fails. Now the failure is reported as a SOAP fault, the same way a throwing constructor already is. Close GH-23448 --- NEWS | 4 ++++ ext/soap/soap.c | 6 +++++- ext/soap/tests/gh23447.phpt | 28 ++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 ext/soap/tests/gh23447.phpt diff --git a/NEWS b/NEWS index 519b0ccaf053..04adab59625e 100644 --- a/NEWS +++ b/NEWS @@ -62,6 +62,10 @@ PHP NEWS . Fixed bug GH-23477 (Memory leak on duplicate native Phar manifest entries). (Weilin Du) +- SOAP: + . Fixed bug GH-23447 (Segfault when a class passed to SoapServer::setClass() + fails to initialize). (Lazizbek Ergashev) + - Standard: . Fixed a segfault when a stream filter callback unsets StreamBucket::$data before re-attaching the bucket. (iliaal) diff --git a/ext/soap/soap.c b/ext/soap/soap.c index 3d5536ef8628..4703c7779305 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -1478,7 +1478,11 @@ PHP_METHOD(SoapServer, handle) /* If new session or something weird happned */ if (soap_obj == NULL) { - object_init_ex(&tmp_soap, service->soap_class.ce); + if (UNEXPECTED(object_init_ex(&tmp_soap, service->soap_class.ce) != SUCCESS)) { + php_output_discard(); + _soap_server_exception(service, function, ZEND_THIS); + goto fail; + } /* Call constructor */ if (service->soap_class.ce->constructor) { diff --git a/ext/soap/tests/gh23447.phpt b/ext/soap/tests/gh23447.phpt new file mode 100644 index 000000000000..16ecc4568ebd --- /dev/null +++ b/ext/soap/tests/gh23447.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-23447 (Segfault when a class passed to SoapServer::setClass() fails to initialize) +--EXTENSIONS-- +soap +--CREDITS-- +Lu Maltsis (@lmaltsis) +--FILE-- + 'http://testuri.org']); +$server->setClass('foo'); + +$server->handle(<<<'XML' + + + + +XML); + +echo "ok\n"; +?> +--EXPECT-- + +SOAP-ENV:ServerUndefined constant "undefinedConstant" +ok From 1273587b05ad44e917990ef8740e5fb8b610b6ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Sun, 30 Aug 2026 14:25:46 +0200 Subject: [PATCH 02/13] Implement "Followup improvements for ext/uri" RFC - WHATWG URL percent-encoding (#22820) RFC: https://wiki.php.net/rfc/uri_followup#percent-encoding_support --- UPGRADING | 6 +- ext/uri/php_uri.c | 69 ++++++++++++++-- ext/uri/php_uri.stub.php | 16 ++++ ext/uri/php_uri_arginfo.h | 40 ++++++++- ext/uri/php_uri_common.h | 1 + ext/uri/php_uri_decl.h | 21 ++++- .../form_query_success_control.phpt | 10 +++ .../form_query_success_percent.phpt | 10 +++ .../form_query_success_space.phpt | 10 +++ .../form_query_success_special_in_set.phpt | 10 +++ ...form_query_success_special_not_in_set.phpt | 10 +++ .../form_query_success_unicode.phpt | 10 +++ .../fragment_success_control.phpt | 10 +++ .../fragment_success_percent.phpt | 10 +++ .../fragment_success_special_in_set.phpt | 10 +++ .../fragment_success_special_not_in_set.phpt | 10 +++ .../fragment_success_unicode.phpt | 10 +++ .../opaque_host_success_control.phpt | 10 +++ .../opaque_host_success_percent.phpt | 10 +++ ...paque_host_success_special_not_in_set.phpt | 10 +++ .../opaque_host_success_unicode.phpt | 10 +++ .../opaque_path_success_control.phpt | 10 +++ .../opaque_path_success_percent.phpt | 10 +++ ...paque_path_success_special_not_in_set.phpt | 10 +++ .../opaque_path_success_unicode.phpt | 10 +++ .../password_success_control.phpt | 10 +++ .../password_success_percent.phpt | 10 +++ .../password_success_special_in_set.phpt | 10 +++ .../password_success_special_not_in_set.phpt | 10 +++ .../password_success_unicode.phpt | 10 +++ .../path_segment_success_control.phpt | 10 +++ .../path_segment_success_percent.phpt | 10 +++ .../path_segment_success_special_in_set.phpt | 10 +++ ...th_segment_success_special_not_in_set.phpt | 10 +++ .../path_segment_success_unicode.phpt | 10 +++ .../path_success_control.phpt | 10 +++ .../path_success_percent.phpt | 10 +++ .../path_success_special_in_set.phpt | 10 +++ .../path_success_special_not_in_set.phpt | 10 +++ .../path_success_unicode.phpt | 10 +++ .../query_success_control.phpt | 10 +++ .../query_success_percent.phpt | 10 +++ .../percent_encoding/query_success_space.phpt | 10 +++ .../query_success_special_in_set.phpt | 10 +++ .../query_success_special_not_in_set.phpt | 10 +++ .../query_success_unicode.phpt | 10 +++ .../special_query_success_control.phpt | 10 +++ .../special_query_success_percent.phpt | 10 +++ .../special_query_success_space.phpt | 10 +++ .../special_query_success_special_in_set.phpt | 10 +++ ...cial_query_success_special_not_in_set.phpt | 10 +++ .../special_query_success_unicode.phpt | 10 +++ .../username_success_control.phpt | 10 +++ .../username_success_percent.phpt | 10 +++ .../username_success_special_in_set.phpt | 10 +++ .../username_success_special_not_in_set.phpt | 10 +++ .../username_success_unicode.phpt | 10 +++ ext/uri/uri_parser_whatwg.c | 81 ++++++++++++++++++- ext/uri/uri_parser_whatwg.h | 14 +++- 59 files changed, 739 insertions(+), 19 deletions(-) create mode 100644 ext/uri/tests/whatwg/percent_encoding/form_query_success_control.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/form_query_success_percent.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/form_query_success_space.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/form_query_success_special_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/form_query_success_special_not_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/form_query_success_unicode.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/fragment_success_control.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/fragment_success_percent.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/fragment_success_special_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/fragment_success_special_not_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/fragment_success_unicode.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/opaque_host_success_control.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/opaque_host_success_percent.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/opaque_host_success_special_not_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/opaque_host_success_unicode.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/opaque_path_success_control.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/opaque_path_success_percent.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/opaque_path_success_special_not_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/opaque_path_success_unicode.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/password_success_control.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/password_success_percent.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/password_success_special_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/password_success_special_not_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/password_success_unicode.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/path_segment_success_control.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/path_segment_success_percent.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/path_segment_success_special_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/path_segment_success_special_not_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/path_segment_success_unicode.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/path_success_control.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/path_success_percent.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/path_success_special_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/path_success_special_not_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/path_success_unicode.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/query_success_control.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/query_success_percent.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/query_success_space.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/query_success_special_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/query_success_special_not_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/query_success_unicode.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/special_query_success_control.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/special_query_success_percent.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/special_query_success_space.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/special_query_success_special_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/special_query_success_special_not_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/special_query_success_unicode.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/username_success_control.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/username_success_percent.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/username_success_special_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/username_success_special_not_in_set.phpt create mode 100644 ext/uri/tests/whatwg/percent_encoding/username_success_unicode.phpt diff --git a/UPGRADING b/UPGRADING index 74ecf90408aa..148cad212238 100644 --- a/UPGRADING +++ b/UPGRADING @@ -484,10 +484,10 @@ PHP 8.6 UPGRADE NOTES RFC: https://wiki.php.net/rfc/uri_followup#uri_type_detection . Added Uri\Rfc3986\Uri::getHostType() and Uri\WhatWg\Url::getHostType(). RFC: https://wiki.php.net/rfc/uri_followup#host_type_detection - . Added Uri\Rfc3986\UriBuilder. - RFC: https://wiki.php.net/rfc/uri_followup#uri_building - . Added Uri\WhatWg\UrlBuilder. + . Added Uri\Rfc3986\UriBuilder and Uri\WhatWg\UrlBuilder. RFC: https://wiki.php.net/rfc/uri_followup#uri_building + . Added Uri\url_percent_encode(). + RFC: https://wiki.php.net/rfc/uri_followup#percent-encoding_support ======================================== 3. Changes in SAPI modules diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c index 4caeca9f1465..bb1d8c8bb13c 100644 --- a/ext/uri/php_uri.c +++ b/ext/uri/php_uri.c @@ -36,6 +36,7 @@ zend_class_entry *php_uri_ce_rfc3986_uri_type; zend_class_entry *php_uri_ce_rfc3986_uri_host_type; zend_class_entry *php_uri_ce_whatwg_url_builder; zend_class_entry *php_uri_ce_whatwg_url; +zend_class_entry *php_uri_ce_whatwg_url_percent_encoding_mode; zend_class_entry *php_uri_ce_comparison_mode; zend_class_entry *php_uri_ce_exception; zend_class_entry *php_uri_ce_error; @@ -1073,6 +1074,60 @@ PHP_METHOD(Uri_WhatWg_Url, __debugInfo) RETURN_ARR(uri_get_debug_properties(uri_object)); } +PHP_FUNCTION(Uri_WhatWg_url_percent_encode) +{ + zend_string *input; + zend_enum_Uri_WhatWg_UrlPercentEncodingMode mode; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(input) + Z_PARAM_ENUM(mode, php_uri_ce_whatwg_url_percent_encoding_mode) + ZEND_PARSE_PARAMETERS_END(); + + zend_string *str; + + switch (mode) { + case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Username: + ZEND_FALLTHROUGH; + case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Password: + str = php_uri_parser_whatwg_percent_encode_userinfo_component(ZSTR_VAL(input), ZSTR_LEN(input)); + break; + case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_OpaqueHost: + str = php_uri_parser_whatwg_percent_encode_opaque_host_component(ZSTR_VAL(input), ZSTR_LEN(input)); + break; + case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Path: + str = php_uri_parser_whatwg_percent_encode_path_component(ZSTR_VAL(input), ZSTR_LEN(input)); + break; + case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_OpaquePath: + str = php_uri_parser_whatwg_percent_encode_opaque_path_component(ZSTR_VAL(input), ZSTR_LEN(input)); + break; + case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_PathSegment: + str = php_uri_parser_whatwg_percent_encode_path_segment_component(ZSTR_VAL(input), ZSTR_LEN(input)); + break; + case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Query: + str = php_uri_parser_whatwg_percent_encode_query_component(ZSTR_VAL(input), ZSTR_LEN(input)); + break; + case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_SpecialQuery: + str = php_uri_parser_whatwg_percent_encode_special_query_component(ZSTR_VAL(input), ZSTR_LEN(input)); + break; + case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_FormQuery: + str = php_uri_parser_whatwg_percent_encode_form_query_component(ZSTR_VAL(input), ZSTR_LEN(input)); + break; + case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Fragment: + str = php_uri_parser_whatwg_percent_encode_fragment_component(ZSTR_VAL(input), ZSTR_LEN(input)); + break; + default: ZEND_UNREACHABLE(); + } + + /* This should be unreachable in practice, as str is null only due to memory errors. */ + if (str == NULL) { + zend_throw_exception(php_uri_ce_error, "Cannot percent-encode input", 0); + RETURN_THROWS(); + } + + RETURN_NEW_STR(str); +} + PHP_METHOD(Uri_Rfc3986_UriBuilder, reset) { ZEND_PARSE_PARAMETERS_NONE(); @@ -1481,6 +1536,8 @@ static PHP_MINIT_FUNCTION(uri) object_handlers_whatwg_uri.free_obj = php_uri_object_handler_free; object_handlers_whatwg_uri.clone_obj = php_uri_object_handler_clone; + php_uri_ce_whatwg_url_percent_encoding_mode = register_class_Uri_WhatWg_UrlPercentEncodingMode(); + php_uri_ce_comparison_mode = register_class_Uri_UriComparisonMode(); php_uri_ce_exception = register_class_Uri_UriException(zend_ce_exception); php_uri_ce_error = register_class_Uri_UriError(zend_ce_error); @@ -1548,14 +1605,14 @@ ZEND_MODULE_POST_ZEND_DEACTIVATE_D(uri) zend_module_entry uri_module_entry = { STANDARD_MODULE_HEADER_EX, NULL, uri_deps, - "uri", /* Extension name */ - NULL, /* zend_function_entry */ + "uri", /* Extension name */ + ext_functions, /* zend_function_entry */ PHP_MINIT(uri), /* PHP_MINIT - Module initialization */ - PHP_MSHUTDOWN(uri), /* PHP_MSHUTDOWN - Module shutdown */ + PHP_MSHUTDOWN(uri), /* PHP_MSHUTDOWN - Module shutdown */ PHP_RINIT(uri), /* PHP_RINIT - Request initialization */ - NULL, /* PHP_RSHUTDOWN - Request shutdown */ - PHP_MINFO(uri), /* PHP_MINFO - Module info */ - PHP_VERSION, /* Version */ + NULL, /* PHP_RSHUTDOWN - Request shutdown */ + PHP_MINFO(uri), /* PHP_MINFO - Module info */ + PHP_VERSION, /* Version */ NO_MODULE_GLOBALS, ZEND_MODULE_POST_ZEND_DEACTIVATE_N(uri), STANDARD_MODULE_PROPERTIES_EX diff --git a/ext/uri/php_uri.stub.php b/ext/uri/php_uri.stub.php index 98f8873d9845..ad1d2fe32dee 100644 --- a/ext/uri/php_uri.stub.php +++ b/ext/uri/php_uri.stub.php @@ -317,4 +317,20 @@ public function __unserialize(array $data): void {} public function __debugInfo(): array {} } + + enum UrlPercentEncodingMode + { + case Username; + case Password; + case OpaqueHost; + case Path; + case OpaquePath; + case PathSegment; + case Query; + case SpecialQuery; + case FormQuery; + case Fragment; + } + + function url_percent_encode(string $input, \Uri\WhatWg\UrlPercentEncodingMode $mode): string {} } diff --git a/ext/uri/php_uri_arginfo.h b/ext/uri/php_uri_arginfo.h index 93cc3ee45a2b..c77d0485d052 100644 --- a/ext/uri/php_uri_arginfo.h +++ b/ext/uri/php_uri_arginfo.h @@ -1,7 +1,12 @@ /* This is a generated file, edit php_uri.stub.php instead. - * Stub hash: 54e953b1da0d08c64509666b9278c59483d1e171 + * Stub hash: 9e087e3aefdab5662892e7fad9de87857aa63057 * Has decl header: yes */ +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_Uri_WhatWg_url_percent_encode, 0, 2, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, input, IS_STRING, 0) + ZEND_ARG_OBJ_INFO(0, mode, Uri\\WhatWg\\\125rlPercentEncodingMode, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_reset, 0, 0, IS_STATIC, 0) ZEND_END_ARG_INFO() @@ -242,6 +247,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_Uri_WhatWg_Url___debugInfo arginfo_class_Uri_Rfc3986_Uri___serialize +ZEND_FUNCTION(Uri_WhatWg_url_percent_encode); ZEND_METHOD(Uri_Rfc3986_UriBuilder, reset); ZEND_METHOD(Uri_Rfc3986_UriBuilder, setScheme); ZEND_METHOD(Uri_Rfc3986_UriBuilder, setUserInfo); @@ -316,6 +322,11 @@ ZEND_METHOD(Uri_WhatWg_Url, __serialize); ZEND_METHOD(Uri_WhatWg_Url, __unserialize); ZEND_METHOD(Uri_WhatWg_Url, __debugInfo); +static const zend_function_entry ext_functions[] = { + ZEND_RAW_FENTRY(ZEND_NS_NAME("Uri\\WhatWg", "url_percent_encode"), zif_Uri_WhatWg_url_percent_encode, arginfo_Uri_WhatWg_url_percent_encode, 0, NULL, NULL) + ZEND_FE_END +}; + static const zend_function_entry class_Uri_Rfc3986_UriBuilder_methods[] = { ZEND_ME(Uri_Rfc3986_UriBuilder, reset, arginfo_class_Uri_Rfc3986_UriBuilder_reset, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_UriBuilder, setScheme, arginfo_class_Uri_Rfc3986_UriBuilder_setScheme, ZEND_ACC_PUBLIC) @@ -733,3 +744,30 @@ static zend_class_entry *register_class_Uri_WhatWg_Url(void) return class_entry; } + +static zend_class_entry *register_class_Uri_WhatWg_UrlPercentEncodingMode(void) +{ + zend_class_entry *class_entry = zend_register_internal_enum("Uri\\WhatWg\\UrlPercentEncodingMode", IS_UNDEF, NULL); + + zend_enum_add_case_cstr(class_entry, "Username", NULL); + + zend_enum_add_case_cstr(class_entry, "Password", NULL); + + zend_enum_add_case_cstr(class_entry, "OpaqueHost", NULL); + + zend_enum_add_case_cstr(class_entry, "Path", NULL); + + zend_enum_add_case_cstr(class_entry, "OpaquePath", NULL); + + zend_enum_add_case_cstr(class_entry, "PathSegment", NULL); + + zend_enum_add_case_cstr(class_entry, "Query", NULL); + + zend_enum_add_case_cstr(class_entry, "SpecialQuery", NULL); + + zend_enum_add_case_cstr(class_entry, "FormQuery", NULL); + + zend_enum_add_case_cstr(class_entry, "Fragment", NULL); + + return class_entry; +} diff --git a/ext/uri/php_uri_common.h b/ext/uri/php_uri_common.h index 31ef1dd2130c..a1d9d852f3b7 100644 --- a/ext/uri/php_uri_common.h +++ b/ext/uri/php_uri_common.h @@ -23,6 +23,7 @@ extern zend_class_entry *php_uri_ce_rfc3986_uri_type; extern zend_class_entry *php_uri_ce_rfc3986_uri_host_type; extern zend_class_entry *php_uri_ce_whatwg_url_builder; extern zend_class_entry *php_uri_ce_whatwg_url; +extern zend_class_entry *php_uri_ce_whatwg_url_percent_encoding_mode; extern zend_class_entry *php_uri_ce_comparison_mode; extern zend_class_entry *php_uri_ce_exception; extern zend_class_entry *php_uri_ce_error; diff --git a/ext/uri/php_uri_decl.h b/ext/uri/php_uri_decl.h index 71f748b71070..a55b44a95205 100644 --- a/ext/uri/php_uri_decl.h +++ b/ext/uri/php_uri_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit php_uri.stub.php instead. - * Stub hash: 54e953b1da0d08c64509666b9278c59483d1e171 */ + * Stub hash: 9e087e3aefdab5662892e7fad9de87857aa63057 */ -#ifndef ZEND_PHP_URI_DECL_54e953b1da0d08c64509666b9278c59483d1e171_H -#define ZEND_PHP_URI_DECL_54e953b1da0d08c64509666b9278c59483d1e171_H +#ifndef ZEND_PHP_URI_DECL_9e087e3aefdab5662892e7fad9de87857aa63057_H +#define ZEND_PHP_URI_DECL_9e087e3aefdab5662892e7fad9de87857aa63057_H typedef enum zend_enum_Uri_UriComparisonMode { ZEND_ENUM_Uri_UriComparisonMode_IncludeFragment = 1, @@ -63,4 +63,17 @@ typedef enum zend_enum_Uri_WhatWg_UrlHostType { ZEND_ENUM_Uri_WhatWg_UrlHostType_Empty = 5, } zend_enum_Uri_WhatWg_UrlHostType; -#endif /* ZEND_PHP_URI_DECL_54e953b1da0d08c64509666b9278c59483d1e171_H */ +typedef enum zend_enum_Uri_WhatWg_UrlPercentEncodingMode { + ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Username = 1, + ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Password = 2, + ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_OpaqueHost = 3, + ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Path = 4, + ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_OpaquePath = 5, + ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_PathSegment = 6, + ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Query = 7, + ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_SpecialQuery = 8, + ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_FormQuery = 9, + ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Fragment = 10, +} zend_enum_Uri_WhatWg_UrlPercentEncodingMode; + +#endif /* ZEND_PHP_URI_DECL_9e087e3aefdab5662892e7fad9de87857aa63057_H */ diff --git a/ext/uri/tests/whatwg/percent_encoding/form_query_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/form_query_success_control.phpt new file mode 100644 index 000000000000..a5dd0ce69038 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/form_query_success_control.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - form query - control code points +--FILE-- + +--EXPECT-- +string(3) "%11" diff --git a/ext/uri/tests/whatwg/percent_encoding/form_query_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/form_query_success_percent.phpt new file mode 100644 index 000000000000..238f1b0b01f7 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/form_query_success_percent.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - form query - percent sign code point +--FILE-- + +--EXPECT-- +string(14) "WHATWG%2520url" diff --git a/ext/uri/tests/whatwg/percent_encoding/form_query_success_space.phpt b/ext/uri/tests/whatwg/percent_encoding/form_query_success_space.phpt new file mode 100644 index 000000000000..50d649454213 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/form_query_success_space.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - form query - space code point +--FILE-- + +--EXPECT-- +string(10) "WHATWG+url" diff --git a/ext/uri/tests/whatwg/percent_encoding/form_query_success_special_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/form_query_success_special_in_set.phpt new file mode 100644 index 000000000000..82f622575917 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/form_query_success_special_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - form query - special code points in the percent-encode set +--FILE-- +&+,', Uri\WhatWg\UrlPercentEncodingMode::FormQuery)); + +?> +--EXPECT-- +string(21) "%22%23%3C%3E%26%2B%2C" diff --git a/ext/uri/tests/whatwg/percent_encoding/form_query_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/form_query_success_special_not_in_set.phpt new file mode 100644 index 000000000000..590f6dd31023 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/form_query_success_special_not_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - form query - special code points not in the percent-encode set +--FILE-- + +--EXPECT-- +string(4) "*-._" diff --git a/ext/uri/tests/whatwg/percent_encoding/form_query_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/form_query_success_unicode.phpt new file mode 100644 index 000000000000..bcb843ca67a6 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/form_query_success_unicode.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - form query - Unicode code points +--FILE-- + +--EXPECT-- +string(13) "f%C3%B6%C5%91" diff --git a/ext/uri/tests/whatwg/percent_encoding/fragment_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/fragment_success_control.phpt new file mode 100644 index 000000000000..c0992a9e260f --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/fragment_success_control.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - fragment - control code points +--FILE-- + +--EXPECT-- +string(3) "%11" diff --git a/ext/uri/tests/whatwg/percent_encoding/fragment_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/fragment_success_percent.phpt new file mode 100644 index 000000000000..12b2bada8fae --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/fragment_success_percent.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - fragment - percent sign code point +--FILE-- + +--EXPECT-- +string(14) "WHATWG%2520url" diff --git a/ext/uri/tests/whatwg/percent_encoding/fragment_success_special_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/fragment_success_special_in_set.phpt new file mode 100644 index 000000000000..fe129d66895f --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/fragment_success_special_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - fragment - special code points in the percent-encode set +--FILE-- +`', Uri\WhatWg\UrlPercentEncodingMode::Fragment)); + +?> +--EXPECT-- +string(15) "%20%22%3C%3E%60" diff --git a/ext/uri/tests/whatwg/percent_encoding/fragment_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/fragment_success_special_not_in_set.phpt new file mode 100644 index 000000000000..255c5173ef83 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/fragment_success_special_not_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - fragment - special code points not in the percent-encode set +--FILE-- + +--EXPECT-- +string(5) "('$+)" diff --git a/ext/uri/tests/whatwg/percent_encoding/fragment_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/fragment_success_unicode.phpt new file mode 100644 index 000000000000..0a14a113f386 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/fragment_success_unicode.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - fragment - Unicode code points +--FILE-- + +--EXPECT-- +string(13) "f%C3%B6%C5%91" diff --git a/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_control.phpt new file mode 100644 index 000000000000..7adc5b963787 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_control.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - opaque host - control code points +--FILE-- + +--EXPECT-- +string(3) "%11" diff --git a/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_percent.phpt new file mode 100644 index 000000000000..f6c6d12e4c7b --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_percent.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - opaque host - percent sign code point +--FILE-- + +--EXPECT-- +string(14) "WHATWG%2520url" diff --git a/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_special_not_in_set.phpt new file mode 100644 index 000000000000..a170079625f9 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_special_not_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - opaque host - special code points not in the percent-encode set +--FILE-- +?^`{}@'$+,", Uri\WhatWg\UrlPercentEncodingMode::OpaqueHost)); + +?> +--EXPECT-- +string(15) " "#<>?^`{}@'$+," diff --git a/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_unicode.phpt new file mode 100644 index 000000000000..b51ad97b54dc --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_unicode.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - opaque host - Unicode code points +--FILE-- + +--EXPECT-- +string(13) "f%C3%B6%C5%91" diff --git a/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_control.phpt new file mode 100644 index 000000000000..adf1bee19b6c --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_control.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - opaque path - control code points +--FILE-- + +--EXPECT-- +string(3) "%11" diff --git a/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_percent.phpt new file mode 100644 index 000000000000..7fa6a7c3973f --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_percent.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - opaque path - percent sign code point +--FILE-- + +--EXPECT-- +string(14) "WHATWG%2520url" diff --git a/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_special_not_in_set.phpt new file mode 100644 index 000000000000..fa9075856a5c --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_special_not_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - opaque path - special code points not in the percent-encode set +--FILE-- +?^`{}@'$+,", Uri\WhatWg\UrlPercentEncodingMode::OpaquePath)); + +?> +--EXPECT-- +string(15) " "#<>?^`{}@'$+," diff --git a/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_unicode.phpt new file mode 100644 index 000000000000..3fbae7c2c156 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_unicode.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - opaque path - Unicode code points +--FILE-- + +--EXPECT-- +string(13) "f%C3%B6%C5%91" diff --git a/ext/uri/tests/whatwg/percent_encoding/password_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/password_success_control.phpt new file mode 100644 index 000000000000..57ff019878df --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/password_success_control.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - password - control code points +--FILE-- + +--EXPECT-- +string(3) "%11" diff --git a/ext/uri/tests/whatwg/percent_encoding/password_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/password_success_percent.phpt new file mode 100644 index 000000000000..536a2702a428 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/password_success_percent.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - password - percent sign code point +--FILE-- + +--EXPECT-- +string(14) "WHATWG%2520url" diff --git a/ext/uri/tests/whatwg/percent_encoding/password_success_special_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/password_success_special_in_set.phpt new file mode 100644 index 000000000000..f4041e059e88 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/password_success_special_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - password - special code points in the percent-encode set +--FILE-- + +--EXPECT-- +string(21) "%5B%23%3D%3F%20%60%5D" diff --git a/ext/uri/tests/whatwg/percent_encoding/password_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/password_success_special_not_in_set.phpt new file mode 100644 index 000000000000..a4a151c44487 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/password_success_special_not_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - password - special code points not in the percent-encode set +--FILE-- + +--EXPECT-- +string(5) "('$+)" diff --git a/ext/uri/tests/whatwg/percent_encoding/password_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/password_success_unicode.phpt new file mode 100644 index 000000000000..d2d0247f98f3 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/password_success_unicode.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - password - Unicode code points +--FILE-- + +--EXPECT-- +string(13) "f%C3%B6%C5%91" diff --git a/ext/uri/tests/whatwg/percent_encoding/path_segment_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_control.phpt new file mode 100644 index 000000000000..0c9bfcbb0b1e --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_control.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - path segment - control code points +--FILE-- + +--EXPECT-- +string(3) "%11" diff --git a/ext/uri/tests/whatwg/percent_encoding/path_segment_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_percent.phpt new file mode 100644 index 000000000000..258cca36a2ee --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_percent.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - path segment - percent sign code point +--FILE-- + +--EXPECT-- +string(14) "WHATWG%2520url" diff --git a/ext/uri/tests/whatwg/percent_encoding/path_segment_success_special_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_special_in_set.phpt new file mode 100644 index 000000000000..03f43a8a0405 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_special_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - path segment - special code points in the percent-encode set +--FILE-- +?^`{}/', Uri\WhatWg\UrlPercentEncodingMode::PathSegment)); + +?> +--EXPECT-- +string(33) "%20%22%23%3C%3E%3F%5E%60%7B%7D%2F" diff --git a/ext/uri/tests/whatwg/percent_encoding/path_segment_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_special_not_in_set.phpt new file mode 100644 index 000000000000..50790f66eb6c --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_special_not_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - path segment - special code points not in the percent-encode set +--FILE-- + +--EXPECT-- +string(5) "@'$+," diff --git a/ext/uri/tests/whatwg/percent_encoding/path_segment_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_unicode.phpt new file mode 100644 index 000000000000..218d120ae9ea --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_unicode.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - path segment - Unicode code points +--FILE-- + +--EXPECT-- +string(13) "f%C3%B6%C5%91" diff --git a/ext/uri/tests/whatwg/percent_encoding/path_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/path_success_control.phpt new file mode 100644 index 000000000000..fa56acc3a3c5 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/path_success_control.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - path - control code points +--FILE-- + +--EXPECT-- +string(3) "%11" diff --git a/ext/uri/tests/whatwg/percent_encoding/path_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/path_success_percent.phpt new file mode 100644 index 000000000000..013c9e770740 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/path_success_percent.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - path - percent sign code point +--FILE-- + +--EXPECT-- +string(14) "WHATWG%2520url" diff --git a/ext/uri/tests/whatwg/percent_encoding/path_success_special_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/path_success_special_in_set.phpt new file mode 100644 index 000000000000..785c82902620 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/path_success_special_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - path - special code points in the percent-encode set +--FILE-- +?^`{}', Uri\WhatWg\UrlPercentEncodingMode::Path)); + +?> +--EXPECT-- +string(30) "%20%22%23%3C%3E%3F%5E%60%7B%7D" diff --git a/ext/uri/tests/whatwg/percent_encoding/path_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/path_success_special_not_in_set.phpt new file mode 100644 index 000000000000..17f6bf915407 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/path_success_special_not_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - path - special code points not in the percent-encode set +--FILE-- + +--EXPECT-- +string(5) "@'$+," diff --git a/ext/uri/tests/whatwg/percent_encoding/path_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/path_success_unicode.phpt new file mode 100644 index 000000000000..edfa72359251 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/path_success_unicode.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - path - Unicode code points +--FILE-- + +--EXPECT-- +string(13) "f%C3%B6%C5%91" diff --git a/ext/uri/tests/whatwg/percent_encoding/query_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/query_success_control.phpt new file mode 100644 index 000000000000..2502cc7a662c --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/query_success_control.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - query - control code points +--FILE-- + +--EXPECT-- +string(3) "%11" diff --git a/ext/uri/tests/whatwg/percent_encoding/query_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/query_success_percent.phpt new file mode 100644 index 000000000000..e9a4187b963d --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/query_success_percent.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - query - percent sign code point +--FILE-- + +--EXPECT-- +string(14) "WHATWG%2520url" diff --git a/ext/uri/tests/whatwg/percent_encoding/query_success_space.phpt b/ext/uri/tests/whatwg/percent_encoding/query_success_space.phpt new file mode 100644 index 000000000000..b9a491018580 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/query_success_space.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - query - space code point +--FILE-- + +--EXPECT-- +string(12) "WHATWG%20url" diff --git a/ext/uri/tests/whatwg/percent_encoding/query_success_special_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/query_success_special_in_set.phpt new file mode 100644 index 000000000000..a5fab6b7cd08 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/query_success_special_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - query - special code points in the percent-encode set +--FILE-- +', Uri\WhatWg\UrlPercentEncodingMode::Query)); + +?> +--EXPECT-- +string(15) "%20%22%23%3C%3E" diff --git a/ext/uri/tests/whatwg/percent_encoding/query_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/query_success_special_not_in_set.phpt new file mode 100644 index 000000000000..c8e4123d93ff --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/query_success_special_not_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - query - special code points not in the percent-encode set +--FILE-- + +--EXPECT-- +string(6) "[@?&]'" diff --git a/ext/uri/tests/whatwg/percent_encoding/query_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/query_success_unicode.phpt new file mode 100644 index 000000000000..f5b3988f08fe --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/query_success_unicode.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - query - Unicode code points +--FILE-- + +--EXPECT-- +string(13) "f%C3%B6%C5%91" diff --git a/ext/uri/tests/whatwg/percent_encoding/special_query_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/special_query_success_control.phpt new file mode 100644 index 000000000000..f6d76153043f --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/special_query_success_control.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - special query - control code points +--FILE-- + +--EXPECT-- +string(3) "%11" diff --git a/ext/uri/tests/whatwg/percent_encoding/special_query_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/special_query_success_percent.phpt new file mode 100644 index 000000000000..a1683a1ef77b --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/special_query_success_percent.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - special query - percent sign code point +--FILE-- + +--EXPECT-- +string(14) "WHATWG%2520url" diff --git a/ext/uri/tests/whatwg/percent_encoding/special_query_success_space.phpt b/ext/uri/tests/whatwg/percent_encoding/special_query_success_space.phpt new file mode 100644 index 000000000000..049a5da1d4ed --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/special_query_success_space.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - special query - space code point +--FILE-- + +--EXPECT-- +string(12) "WHATWG%20url" diff --git a/ext/uri/tests/whatwg/percent_encoding/special_query_success_special_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/special_query_success_special_in_set.phpt new file mode 100644 index 000000000000..e84df8465c9f --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/special_query_success_special_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - special query - special code points in the percent-encode set +--FILE-- +", Uri\WhatWg\UrlPercentEncodingMode::SpecialQuery)); + +?> +--EXPECT-- +string(18) "%20%27%22%23%3C%3E" diff --git a/ext/uri/tests/whatwg/percent_encoding/special_query_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/special_query_success_special_not_in_set.phpt new file mode 100644 index 000000000000..0ee677b31fe1 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/special_query_success_special_not_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - special query - special code points not in the percent-encode set +--FILE-- + +--EXPECT-- +string(5) "[@?&]" diff --git a/ext/uri/tests/whatwg/percent_encoding/special_query_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/special_query_success_unicode.phpt new file mode 100644 index 000000000000..b583d8de2ecc --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/special_query_success_unicode.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - special query - Unicode code points +--FILE-- + +--EXPECT-- +string(13) "f%C3%B6%C5%91" diff --git a/ext/uri/tests/whatwg/percent_encoding/username_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/username_success_control.phpt new file mode 100644 index 000000000000..5a64393065c9 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/username_success_control.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - username - control code points +--FILE-- + +--EXPECT-- +string(3) "%11" diff --git a/ext/uri/tests/whatwg/percent_encoding/username_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/username_success_percent.phpt new file mode 100644 index 000000000000..9ea2831efa46 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/username_success_percent.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - username - percent sign code point +--FILE-- + +--EXPECT-- +string(14) "WHATWG%2520url" diff --git a/ext/uri/tests/whatwg/percent_encoding/username_success_special_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/username_success_special_in_set.phpt new file mode 100644 index 000000000000..070250ef3630 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/username_success_special_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - username - special code points in the percent-encode set +--FILE-- + +--EXPECT-- +string(21) "%5B%23%3D%3F%20%60%5D" diff --git a/ext/uri/tests/whatwg/percent_encoding/username_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/username_success_special_not_in_set.phpt new file mode 100644 index 000000000000..8626b96ab9b3 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/username_success_special_not_in_set.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - username - special code points not in the percent-encode set +--FILE-- + +--EXPECT-- +string(5) "('$+)" diff --git a/ext/uri/tests/whatwg/percent_encoding/username_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/username_success_unicode.phpt new file mode 100644 index 000000000000..d85b0712c1f0 --- /dev/null +++ b/ext/uri/tests/whatwg/percent_encoding/username_success_unicode.phpt @@ -0,0 +1,10 @@ +--TEST-- +Test Uri\WhatWg\url_percent_encode() - username - Unicode code points +--FILE-- + +--EXPECT-- +string(13) "f%C3%B6%C5%91" diff --git a/ext/uri/uri_parser_whatwg.c b/ext/uri/uri_parser_whatwg.c index b7a74fc1e231..4f308ba224bd 100644 --- a/ext/uri/uri_parser_whatwg.c +++ b/ext/uri/uri_parser_whatwg.c @@ -25,6 +25,7 @@ ZEND_TLS lexbor_mraw_t lexbor_mraw = {0}; ZEND_TLS lxb_url_parser_t lexbor_parser = {0}; ZEND_TLS lxb_unicode_idna_t lexbor_idna = {0}; +ZEND_TLS uint8_t lexbor_custom_url_map[256] = {0}; static const size_t lexbor_mraw_byte_size = 8192; @@ -549,9 +550,7 @@ static zend_result php_uri_parser_whatwg_fragment_write(void *uri, const zval *v PHP_RINIT_FUNCTION(uri_parser_whatwg) { - lxb_status_t status; - - status = lexbor_mraw_init(&lexbor_mraw, lexbor_mraw_byte_size); + lxb_status_t status = lexbor_mraw_init(&lexbor_mraw, lexbor_mraw_byte_size); if (status != LXB_STATUS_OK) { goto fail; } @@ -566,6 +565,9 @@ PHP_RINIT_FUNCTION(uri_parser_whatwg) goto fail; } + memcpy(lexbor_custom_url_map, lxb_url_get_percent_encoding_map(), sizeof(lexbor_custom_url_map)); + lexbor_custom_url_map['%'] = -1; /* % is percent-encoded */ + return SUCCESS; fail: @@ -653,6 +655,79 @@ static zend_string *php_uri_parser_whatwg_to_string(void *uri, const php_uri_rec return smart_str_extract(&uri_str); } +static zend_string *php_uri_parser_whatwg_percent_encode_component(const char *str, const size_t str_length, const lxb_url_map_type_t map, const bool space_as_plus) +{ + lexbor_str_t lexbor_str = {0}; + + const lexbor_status_t status = lxb_url_percent_encode_utf_8( + (lxb_char_t *) str, str_length, &lexbor_str, lexbor_parser.mraw, lexbor_custom_url_map, map, space_as_plus + ); + + if (status != LXB_STATUS_OK) { + lexbor_str_destroy(&lexbor_str, lexbor_parser.mraw, false); + return NULL; + } + + zend_string *result = zend_string_init((const char *) lexbor_str.data, lexbor_str.length, false); + + lexbor_str_destroy(&lexbor_str, lexbor_parser.mraw, false); + + return result; +} + +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_userinfo_component(const char *str, const size_t str_length) +{ + return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_USERINFO, false); +} + +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_opaque_host_component(const char *str, const size_t str_length) +{ + return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_C0, false); +} + +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_path_component(const char *str, const size_t str_length) +{ + return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_PATH, false); +} + +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_opaque_path_component(const char *str, const size_t str_length) +{ + return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_C0, false); +} + +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_path_segment_component(const char *str, const size_t str_length) +{ + ZEND_ASSERT((lexbor_custom_url_map['/'] & LXB_URL_MAP_PATH) == 0); + + lexbor_custom_url_map['/'] |= LXB_URL_MAP_PATH; + + zend_string *result = php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_PATH, false); + + lexbor_custom_url_map['/'] &= ~LXB_URL_MAP_PATH; + + return result; +} + +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_query_component(const char *str, const size_t str_length) +{ + return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_QUERY, false); +} + +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_special_query_component(const char *str, const size_t str_length) +{ + return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_SPECIAL_QUERY, false); +} + +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_form_query_component(const char *str, const size_t str_length) +{ + return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_X_WWW_FORM, true); +} + +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_fragment_component(const char *str, const size_t str_length) +{ + return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_FRAGMENT, false); +} + static void php_uri_parser_whatwg_destroy(void *uri) { lxb_url_t *lexbor_uri = uri; diff --git a/ext/uri/uri_parser_whatwg.h b/ext/uri/uri_parser_whatwg.h index 0a03c8e76a93..ee387a6af95d 100644 --- a/ext/uri/uri_parser_whatwg.h +++ b/ext/uri/uri_parser_whatwg.h @@ -30,14 +30,24 @@ ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_scheme(const z ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_host(const zend_string *host); ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_port(zend_long port); -PHP_RINIT_FUNCTION(uri_parser_whatwg); - ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_whatwg_build_from_zval( lxb_url_t *lexbor_base_url, const zval *scheme, const zval *username, const zval *password, const zval *host, const zval *port, const zval *path, const zval *query, const zval *fragment, zval *errors_zv ); +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_userinfo_component(const char *str, size_t str_length); +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_opaque_host_component(const char *str, size_t str_length); +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_path_component(const char *str, size_t str_length); +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_opaque_path_component(const char *str, size_t str_length); +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_path_segment_component(const char *str, size_t str_length); +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_query_component(const char *str, size_t str_length); +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_special_query_component(const char *str, size_t str_length); +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_form_query_component(const char *str, size_t str_length); +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_fragment_component(const char *str, size_t str_length); + +PHP_RINIT_FUNCTION(uri_parser_whatwg); + ZEND_MODULE_POST_ZEND_DEACTIVATE_D(uri_parser_whatwg); #endif From c98e10797b231bd716b626e511e68f25056eca38 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Fri, 14 Aug 2026 15:38:43 +0100 Subject: [PATCH 03/13] ext/sockets: CBPF reuseport CBPF filter improvements/strengthening. A zero SO_ATTACH_REUSEPORT_CBPF value detached via SO_DETACH_BPF, an alias of SO_DETACH_FILTER, leaving the reuseport program attached. SO_DETACH_REUSEPORT_BPF is used and exported now, the value is restricted to integers and the level is validated. Close GH-23278 --- NEWS | 5 +++ UPGRADING | 11 ++++++ ext/sockets/sockets.c | 15 +++++++- ext/sockets/sockets.stub.php | 7 ++++ ext/sockets/sockets_arginfo.h | 5 ++- ext/sockets/tests/socket_reuseport_cbpf.phpt | 9 ++--- .../tests/socket_reuseport_cbpf_detach.phpt | 38 +++++++++++++++++++ 7 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 ext/sockets/tests/socket_reuseport_cbpf_detach.phpt diff --git a/NEWS b/NEWS index e112bfb5351e..cc52980d890b 100644 --- a/NEWS +++ b/NEWS @@ -28,6 +28,11 @@ PHP NEWS . Fixed bug GH-23477 (Memory leak on duplicate native Phar manifest entries). (Weilin Du) +- Sockets: + . Fixed socket_set_option() with SO_ATTACH_REUSEPORT_CBPF and a zero value, + which detached the classic BPF filter instead of the reuseport program. + (David Carlier) + - Standard: . Fixed a segfault when a stream filter callback unsets StreamBucket::$data before re-attaching the bucket. (iliaal) diff --git a/UPGRADING b/UPGRADING index 148cad212238..a164599c6f15 100644 --- a/UPGRADING +++ b/UPGRADING @@ -199,6 +199,16 @@ PHP 8.6 UPGRADE NOTES rules" message. Code that compares the exact message may need to be updated. +- Sockets: + . socket_set_option() with SO_ATTACH_REUSEPORT_CBPF now requires an int + $value and a $level of SOL_SOCKET. Any other value type throws a TypeError + instead of being coerced, and any other level raises a warning and returns + false. + . socket_set_option() with SO_ATTACH_REUSEPORT_CBPF and a $value of 0 now + detaches the reuseport filter through SO_DETACH_REUSEPORT_BPF. It + previously used SO_DETACH_BPF, an alias of SO_DETACH_FILTER, which left the + reuseport program attached. + - Sodium: . The password-hashing functions sodium_crypto_pwhash(), sodium_crypto_pwhash_str(), @@ -860,6 +870,7 @@ PHP 8.6 UPGRADE NOTES . EAI_ALLDONE. . EAI_INTR. . EAI_IDN_ENCODE. + . SO_DETACH_REUSEPORT_BPF (Linux only). - Standard: . ARRAY_FILTER_USE_VALUE. diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index c4c6f224dcfa..66df65657988 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -2341,13 +2341,26 @@ PHP_FUNCTION(socket_set_option) #ifdef SO_ATTACH_REUSEPORT_CBPF case SO_ATTACH_REUSEPORT_CBPF: { + if (level != SOL_SOCKET) { + php_error_docref(NULL, E_WARNING, "Invalid level"); + RETURN_FALSE; + } + if (Z_TYPE_P(arg4) != IS_LONG) { + zend_argument_type_error(4, "must be of type int when argument #3 ($option) is SO_ATTACH_REUSEPORT_CBPF, %s given", zend_zval_value_name(arg4)); + RETURN_THROWS(); + } zend_long cbpf_val = zval_get_long(arg4); if (!cbpf_val) { +#ifdef SO_DETACH_REUSEPORT_BPF ov = 1; optlen = sizeof(ov); opt_ptr = &ov; - optname = SO_DETACH_BPF; + optname = SO_DETACH_REUSEPORT_BPF; +#else + php_error_docref(NULL, E_WARNING, "Detaching a reuseport CBPF filter is unsupported"); + RETURN_FALSE; +#endif } else { uint32_t k = (uint32_t)cbpf_val; diff --git a/ext/sockets/sockets.stub.php b/ext/sockets/sockets.stub.php index fab32628544d..681d5ba6d0ff 100644 --- a/ext/sockets/sockets.stub.php +++ b/ext/sockets/sockets.stub.php @@ -1898,6 +1898,13 @@ */ const SO_ATTACH_REUSEPORT_CBPF = UNKNOWN; #endif +#if defined(SO_DETACH_REUSEPORT_BPF) +/** + * @var int + * @cvalue SO_DETACH_REUSEPORT_BPF + */ +const SO_DETACH_REUSEPORT_BPF = UNKNOWN; +#endif #if defined(SO_DETACH_FILTER) /** * @var int diff --git a/ext/sockets/sockets_arginfo.h b/ext/sockets/sockets_arginfo.h index cfd792244084..203c010f5171 100644 --- a/ext/sockets/sockets_arginfo.h +++ b/ext/sockets/sockets_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit sockets.stub.php instead. - * Stub hash: 711d3b84051445917c4a8a1d0cdc1d0c6328be07 */ + * Stub hash: aceee39bed5332f7f26d5d768976c4d5ab96ab3c */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_socket_select, 0, 4, MAY_BE_LONG|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(1, read, IS_ARRAY, 1) @@ -990,6 +990,9 @@ static void register_sockets_symbols(int module_number) #if defined(SO_ATTACH_REUSEPORT_CBPF) REGISTER_LONG_CONSTANT("SO_ATTACH_REUSEPORT_CBPF", SO_ATTACH_REUSEPORT_CBPF, CONST_PERSISTENT); #endif +#if defined(SO_DETACH_REUSEPORT_BPF) + REGISTER_LONG_CONSTANT("SO_DETACH_REUSEPORT_BPF", SO_DETACH_REUSEPORT_BPF, CONST_PERSISTENT); +#endif #if defined(SO_DETACH_FILTER) REGISTER_LONG_CONSTANT("SO_DETACH_FILTER", SO_DETACH_FILTER, CONST_PERSISTENT); #endif diff --git a/ext/sockets/tests/socket_reuseport_cbpf.phpt b/ext/sockets/tests/socket_reuseport_cbpf.phpt index 2210c4438f00..a9b8d5c731ff 100644 --- a/ext/sockets/tests/socket_reuseport_cbpf.phpt +++ b/ext/sockets/tests/socket_reuseport_cbpf.phpt @@ -19,19 +19,18 @@ if (!$socket) { var_dump(socket_set_option( $socket, SOL_SOCKET, SO_REUSEADDR, true)); var_dump(socket_set_option( $socket, SOL_SOCKET, SO_REUSEPORT, true)); try { - socket_set_option( $socket, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, array()); + socket_set_option( $socket, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, []); } catch (\TypeError $e) { - echo $e->getMessage() . PHP_EOL; + echo $e::class, ': ', $e->getMessage(), "\n"; } var_dump(socket_set_option( $socket, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, SKF_AD_CPU)); var_dump(socket_bind($socket, '0.0.0.0')); socket_listen($socket); socket_close($socket); ?> ---EXPECTF-- +--EXPECT-- bool(true) bool(true) - -Warning: socket_set_option(): Unable to set socket option [2]: No such file or directory in %s on line %d +TypeError: socket_set_option(): Argument #4 ($value) must be of type int when argument #3 ($option) is SO_ATTACH_REUSEPORT_CBPF, array given bool(true) bool(true) diff --git a/ext/sockets/tests/socket_reuseport_cbpf_detach.phpt b/ext/sockets/tests/socket_reuseport_cbpf_detach.phpt new file mode 100644 index 000000000000..bd1608a132d5 --- /dev/null +++ b/ext/sockets/tests/socket_reuseport_cbpf_detach.phpt @@ -0,0 +1,38 @@ +--TEST-- +socket_set_option() attach/detach round trip for reuseport CBPF filters +--EXTENSIONS-- +sockets +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) + +Warning: socket_set_option(): Unable to set socket option [%d]: %s in %s on line %d +bool(false) +bool(true) +bool(true) From 473a7e04515f19612ade59e489671e5d49a0fab4 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 29 Aug 2026 20:57:55 +0100 Subject: [PATCH 04/13] ext/zip: ZipArchive::getNameIndex() index truncated to int. The index was cast to int before being handed to zip_get_name(), whose parameter is a zip_uint64_t, so any value with a non-zero upper half wrapped and selected the wrong entry: getNameIndex(1 << 32) returned the name of entry 0 instead of false. Cast to zip_uint64_t instead, letting libzip reject out of range indices. --- NEWS | 2 + ext/zip/php_zip.c | 2 +- .../tests/oo_getnameindex_large_index.phpt | 44 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 ext/zip/tests/oo_getnameindex_large_index.phpt diff --git a/NEWS b/NEWS index 04adab59625e..aa7862f63bb4 100644 --- a/NEWS +++ b/NEWS @@ -80,6 +80,8 @@ PHP NEWS garbage collected). (Weilin Du, ndossche) . Fixed ZipArchive::extractTo() and ZipArchive::getFrom*() reporting success on corrupted entries. (David Carlier) + . Fixed ZipArchive::getNameIndex() truncating the entry index to int. + (David Carlier) - SAPI: . Fixed fuzzer targets failing to build in isolation. (Mrmaxmeier) diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c index 5e640df9a102..69b81b88753d 100644 --- a/ext/zip/php_zip.c +++ b/ext/zip/php_zip.c @@ -2181,7 +2181,7 @@ PHP_METHOD(ZipArchive, getNameIndex) ZIP_FROM_OBJECT(intern, self); - name = zip_get_name(intern, (int) index, flags); + name = zip_get_name(intern, (zip_uint64_t) index, flags); if (name) { RETVAL_STRING((char *)name); diff --git a/ext/zip/tests/oo_getnameindex_large_index.phpt b/ext/zip/tests/oo_getnameindex_large_index.phpt new file mode 100644 index 000000000000..471dffc38d91 --- /dev/null +++ b/ext/zip/tests/oo_getnameindex_large_index.phpt @@ -0,0 +1,44 @@ +--TEST-- +ZipArchive::getNameIndex() with an index that does not fit in an int +--EXTENSIONS-- +zip +--SKIPIF-- + +--FILE-- +open($file, ZipArchive::CREATE)) { + exit('failed'); +} + +$zip->addFromString('entry1.txt', 'entry #1'); +$zip->close(); + +if (!$zip->open($file)) { + exit('failed'); +} + +var_dump($zip->getNameIndex(0)); +var_dump($zip->getNameIndex(1 << 32)); +var_dump($zip->getNameIndex((1 << 32) + 1)); +var_dump($zip->getNameIndex(PHP_INT_MAX)); +var_dump($zip->getNameIndex(-1)); + +$zip->close(); +?> +--EXPECT-- +string(10) "entry1.txt" +bool(false) +bool(false) +bool(false) +bool(false) +--CLEAN-- + From 614afe7b09fd37c57f71a02fecfac33191ea141a Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 29 Aug 2026 20:58:17 +0100 Subject: [PATCH 05/13] ext/zip: php_zip_ops_stat() succeeds when the archive cannot be opened. When zip_open() failed the whole stat block was skipped, yet the function still returned 0. fstat() on a zip:// stream therefore succeeded with the zeroed statbuf it was given, reporting a zero size and no file type bits, instead of failing. Return -1 on that path. Close GH-23511 --- NEWS | 2 + .../stream_fstat_unreadable_archive.phpt | 38 +++++++++++++++++++ ext/zip/zip_stream.c | 3 ++ 3 files changed, 43 insertions(+) create mode 100644 ext/zip/tests/stream_fstat_unreadable_archive.phpt diff --git a/NEWS b/NEWS index aa7862f63bb4..db1aba15a8e6 100644 --- a/NEWS +++ b/NEWS @@ -82,6 +82,8 @@ PHP NEWS on corrupted entries. (David Carlier) . Fixed ZipArchive::getNameIndex() truncating the entry index to int. (David Carlier) + . Fixed fstat() on a zip:// stream reporting success when the archive cannot + be opened. (David Carlier) - SAPI: . Fixed fuzzer targets failing to build in isolation. (Mrmaxmeier) diff --git a/ext/zip/tests/stream_fstat_unreadable_archive.phpt b/ext/zip/tests/stream_fstat_unreadable_archive.phpt new file mode 100644 index 000000000000..a81fc8fc3cfc --- /dev/null +++ b/ext/zip/tests/stream_fstat_unreadable_archive.phpt @@ -0,0 +1,38 @@ +--TEST-- +fstat() on a zip:// stream whose archive can no longer be opened +--EXTENSIONS-- +zip +--SKIPIF-- + +--FILE-- +open($file, ZipArchive::CREATE)) { + exit('failed'); +} + +$zip->addFromString('entry.txt', 'entry'); +$zip->close(); + +$fp = fopen('zip://' . $file . '#entry.txt', 'rb'); +var_dump($fp !== false); + +file_put_contents($file, 'this is not a zip archive'); + +var_dump(fstat($fp)); + +fclose($fp); +?> +--EXPECT-- +bool(true) +bool(false) +--CLEAN-- + diff --git a/ext/zip/zip_stream.c b/ext/zip/zip_stream.c index 0356863ef7ce..b70b82a415ec 100644 --- a/ext/zip/zip_stream.c +++ b/ext/zip/zip_stream.c @@ -195,6 +195,9 @@ static int php_zip_ops_stat(php_stream *stream, php_stream_statbuf *ssb) /* {{{ ssb->sb.st_blocks = -1; #endif ssb->sb.st_ino = -1; + } else { + zend_string_release_ex(file_basename, 0); + return -1; } zend_string_release_ex(file_basename, 0); return 0; From 9e5cf96095ca54543c68787092cc28fcc4bc534d Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sat, 29 Aug 2026 08:00:20 -0400 Subject: [PATCH 06/13] [mysqlnd] Fix OK packet message length buffer over-read The OK packet message-length varint is read after the last bounds check, so a length-encoded integer at the end of a packet can advance p past header.size and even past the end of the 4096-byte command buffer. The old MIN(net_len, buf_len - (p - begin)) clamp then underflows and passes an unclamped attacker-controlled length to mnd_pestrndup(), reading heap memory beyond both the packet and its allocation. Reject a message length that extends past the payload, matching php_mysqlnd_auth_response_read() from GHSA-h35g-vwh6-m678; an audit found no further readers using the vulnerable buf_len clamp. Closes GH-23497 --- ext/mysqli/tests/fake_server.inc | 13 ++++++ .../mysqlnd_ok_packet_message_over_read.phpt | 40 +++++++++++++++++++ ext/mysqlnd/mysqlnd_wireprotocol.c | 7 +++- 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 ext/mysqli/tests/mysqlnd_ok_packet_message_over_read.phpt diff --git a/ext/mysqli/tests/fake_server.inc b/ext/mysqli/tests/fake_server.inc index dad8bc52ddd1..4056b9fb78cf 100644 --- a/ext/mysqli/tests/fake_server.inc +++ b/ext/mysqli/tests/fake_server.inc @@ -721,6 +721,19 @@ function my_mysqli_test_auth_response_message_over_read(my_mysqli_fake_server_co $conn->read(); } +function my_mysqli_test_ok_packet_message_over_read(my_mysqli_fake_server_conn $conn): void +{ + $p = new my_mysqli_fake_packet(); + $p->full = "08000001" . "00" . "00" . "00" . "0200" . "0000" . "fa"; + + $conn->send_server_greetings(); + $conn->read_packets(1); + $conn->send_server_ok(); + $conn->read_packets(1); + $conn->send($p->to_bytes(), "Malicious OK Packet [message length past the packet size]"); + $conn->read(); +} + function my_mysqli_test_stmt_response_row_over_read_string(my_mysqli_fake_server_conn $conn): void { $rh = $conn->packet_generator->server_stmt_execute_items_response(); diff --git a/ext/mysqli/tests/mysqlnd_ok_packet_message_over_read.phpt b/ext/mysqli/tests/mysqlnd_ok_packet_message_over_read.phpt new file mode 100644 index 000000000000..8364251e8df4 --- /dev/null +++ b/ext/mysqli/tests/mysqlnd_ok_packet_message_over_read.phpt @@ -0,0 +1,40 @@ +--TEST-- +mysqlnd OK packet message length buffer over-read +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +try { + $conn = new mysqli( $servername, $username, $password, "", $process->getPort()); + var_dump($conn->select_db("test")); +} catch (Exception $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +$process->terminate(); + +print "done!"; +?> +--EXPECTF-- +[*] Server started on 127.0.0.1:%d +[*] Connection established +[*] Sending - Server Greeting: %s +[*] Received: %s +[*] Sending - Server OK: %s +[*] Received: %s +[*] Sending - Malicious OK Packet [message length past the packet size]: %s + +Warning: mysqli::select_db(): OK packet message length is past the packet size in %s on line %d + +Warning: mysqli::select_db(): Error while reading INIT_DB's response packet. PID=%d in %s on line %d +mysqli_sql_exception: Malformed packet +done! diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c index 64c2c7969619..80b4b37591ab 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.c +++ b/ext/mysqlnd/mysqlnd_wireprotocol.c @@ -878,7 +878,12 @@ php_mysqlnd_ok_read(MYSQLND_CONN_DATA * conn, void * _packet) /* There is a message */ if (packet->header.size > (size_t) (p - buf) && (net_len = php_mysqlnd_net_field_length(&p))) { - packet->message_len = MIN(net_len, buf_len - (p - begin)); + if ((p - buf) > packet->header.size || packet->header.size - (p - buf) < net_len) { + DBG_ERR_FMT("OK packet message length is past the packet size"); + php_error_docref(NULL, E_WARNING, "OK packet message length is past the packet size"); + DBG_RETURN(FAIL); + } + packet->message_len = net_len; packet->message = mnd_pestrndup((char *)p, packet->message_len, FALSE); } else { packet->message = NULL; From fc7a6b900704fd7710cc1027f82a3670123430e5 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sat, 29 Aug 2026 08:00:19 -0400 Subject: [PATCH 07/13] [SimpleXML] Fix creating new attributes via attributes() dimension write sxe_prop_dim_write() overwrote the element node with the first attribute node when resolving an SXE_ITER_ATTRLIST iterator, so xmlNewProp() targeted a non-element node and was skipped entirely when no attribute existed yet. Keep the element node in place and resolve only the attribute list start, so $x->attributes()["new"] = "v" creates the attribute like the symmetric $x["new"] path; property writes on the attributes() object share the fixed path while read/exists/unset handlers are unaffected by this defect. Closes GH-23500 --- NEWS | 4 +++ ext/simplexml/simplexml.c | 3 +- .../tests/attributes_dimension_write.phpt | 30 +++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 ext/simplexml/tests/attributes_dimension_write.phpt diff --git a/NEWS b/NEWS index db1aba15a8e6..546364fbc2ca 100644 --- a/NEWS +++ b/NEWS @@ -75,6 +75,10 @@ PHP NEWS an object converted to an array fails. (David Carlier) . Fixed read buffer compaction in php_stream_filter_flush(). (crystarm) +- SimpleXML: + . Fixed writing to a dimension of the object returned by attributes() not + creating the attribute. (Ilia Alshanetsky) + - Zip: . Fixed bug GH-23276 (ZipArchive subclass storing its own stream cannot be garbage collected). (Weilin Du, ndossche) diff --git a/ext/simplexml/simplexml.c b/ext/simplexml/simplexml.c index 1a346200199b..44fdef5e12d7 100644 --- a/ext/simplexml/simplexml.c +++ b/ext/simplexml/simplexml.c @@ -443,8 +443,7 @@ static zval *sxe_prop_dim_write(zend_object *object, zval *member, zval *value, if (sxe->iter.type == SXE_ITER_ATTRLIST) { attribs = 1; elements = 0; - node = php_sxe_get_first_node_non_destructive(sxe, node); - attr = (xmlAttrPtr)node; + attr = (xmlAttrPtr)php_sxe_get_first_node_non_destructive(sxe, node); test = sxe->iter.name != NULL; } else if (sxe->iter.type != SXE_ITER_CHILD) { mynode = node; diff --git a/ext/simplexml/tests/attributes_dimension_write.phpt b/ext/simplexml/tests/attributes_dimension_write.phpt new file mode 100644 index 000000000000..8721dc7dc7c2 --- /dev/null +++ b/ext/simplexml/tests/attributes_dimension_write.phpt @@ -0,0 +1,30 @@ +--TEST-- +Creating new attributes via dimension and property writes on attributes() +--FILE-- +'); +$x->attributes()['new'] = 'v'; +echo $x->asXML(); + +$a = simplexml_load_string(''); +$a->attributes()['created'] = 'yes'; +echo $a->asXML(); + +$b = simplexml_load_string(''); +$attrs = $b->attributes(); +$attrs->other = 2; +echo $b->asXML(); + +$c = simplexml_load_string(''); +$c->attributes()['a'] = '2'; +echo $c->asXML(); +?> +--EXPECT-- + + + + + + + + From 091cb333619644379ac606c38d373c45e7c2c0de Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sun, 30 Aug 2026 11:14:46 -0400 Subject: [PATCH 08/13] [intl] Size sortWithSortKeys buffers based on array size (#23504) collator_sort_with_sort_keys() ecalloc'd sortKeyBuf and sortKeyIndxBuf at DEF_SORT_KEYS_BUF_SIZE (1MiB) each on every call regardless of array size. sortKeyBuf now starts from zend_hash_num_elements() * 32 bytes, clamped to a 4KiB minimum and the previous 1MiB cap, and grows geometrically up to DEF_SORT_KEYS_BUF_INCREMENT. sortKeyIndxBuf is allocated exactly for the element count, dropping the index-buffer growth path. Sibling audit: DEF_SORT_KEYS* constants have no other users and collator_sort()/asort()/get_sort_key() already scale allocations. --- NEWS | 2 + ext/intl/collator/collator_sort.cpp | 44 ++++++++------ ...lator_sort_with_sort_keys_buffer_size.phpt | 57 +++++++++++++++++++ 3 files changed, 85 insertions(+), 18 deletions(-) create mode 100644 ext/intl/tests/collator_sort_with_sort_keys_buffer_size.phpt diff --git a/NEWS b/NEWS index bd17cf9bf8e6..ae8edaf97c9f 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,8 @@ PHP NEWS 100-continue flow control). (Sjoerd Langkemper) - Intl: + . Fixed Collator::sortWithSortKeys() allocating fixed 2MiB buffers + regardless of array size. (Ilia Alshanetsky) . Fixed a memory leak when iterating IntlBreakIterator::getPartsIterator() results. (iliaal) . Fixed a leak in Locale::getKeywords() when a keyword value cannot be diff --git a/ext/intl/collator/collator_sort.cpp b/ext/intl/collator/collator_sort.cpp index cb1f2aefc358..f2674b9c8ff8 100644 --- a/ext/intl/collator/collator_sort.cpp +++ b/ext/intl/collator/collator_sort.cpp @@ -44,9 +44,8 @@ ZEND_EXTERN_MODULE_GLOBALS( intl ) static const size_t DEF_SORT_KEYS_BUF_SIZE = 1048576; static const size_t DEF_SORT_KEYS_BUF_INCREMENT = 1048576; - -static const size_t DEF_SORT_KEYS_INDX_BUF_SIZE = 1048576; -static const size_t DEF_SORT_KEYS_INDX_BUF_INCREMENT = 1048576; +static const size_t MIN_SORT_KEYS_BUF_SIZE = 4096; +static const size_t SORT_KEY_LENGTH_ESTIMATE = 32; static const size_t DEF_UTF16_BUF_SIZE = 1024; @@ -427,17 +426,17 @@ U_CFUNC PHP_FUNCTION( collator_sort_with_sort_keys ) zval* hashData = nullptr; /* currently processed item of input hash */ char* sortKeyBuf = nullptr; /* buffer to store sort keys */ - uint32_t sortKeyBufSize = DEF_SORT_KEYS_BUF_SIZE; /* buffer size */ + uint32_t sortKeyBufSize = 0; /* buffer size */ ptrdiff_t sortKeyBufOffset = 0; /* pos in buffer to store sort key */ uint32_t sortKeyLen = 0; /* the length of currently processing key */ uint32_t bufLeft = 0; uint32_t bufIncrement = 0; collator_sort_key_index_t* sortKeyIndxBuf = nullptr; /* buffer to store 'indexes' which will be passed to 'qsort' */ - uint32_t sortKeyIndxBufSize = DEF_SORT_KEYS_INDX_BUF_SIZE; uint32_t sortKeyIndxSize = sizeof( collator_sort_key_index_t ); uint32_t sortKeyCount = 0; + uint32_t numElements = 0; uint32_t j = 0; UChar* utf16_buf = nullptr; /* tmp buffer to hold current processing string in utf-16 */ @@ -472,9 +471,20 @@ U_CFUNC PHP_FUNCTION( collator_sort_with_sort_keys ) if( !hash || zend_hash_num_elements( hash ) == 0 ) RETURN_TRUE; + numElements = zend_hash_num_elements( hash ); + + if( numElements > DEF_SORT_KEYS_BUF_SIZE / SORT_KEY_LENGTH_ESTIMATE ) { + sortKeyBufSize = DEF_SORT_KEYS_BUF_SIZE; + } else { + sortKeyBufSize = numElements * SORT_KEY_LENGTH_ESTIMATE; + } + if( sortKeyBufSize < MIN_SORT_KEYS_BUF_SIZE ) { + sortKeyBufSize = MIN_SORT_KEYS_BUF_SIZE; + } + /* Create buffers */ - sortKeyBuf = reinterpret_cast(ecalloc( sortKeyBufSize, sizeof( char ) )); - sortKeyIndxBuf = reinterpret_cast(ecalloc( sortKeyIndxBufSize, sizeof( uint8_t ) )); + sortKeyBuf = reinterpret_cast(ecalloc( sortKeyBufSize, sizeof( char ) )); + sortKeyIndxBuf = reinterpret_cast(ecalloc( numElements, sortKeyIndxSize )); utf16_buf = eumalloc( utf16_buf_size ); /* Iterate through input hash and create a sort key for each value. */ @@ -524,7 +534,15 @@ U_CFUNC PHP_FUNCTION( collator_sort_with_sort_keys ) /* check for sortKeyBuf overflow, increasing its size of the buffer if needed */ if( sortKeyLen > bufLeft ) { - bufIncrement = ( sortKeyLen > DEF_SORT_KEYS_BUF_INCREMENT ) ? sortKeyLen : DEF_SORT_KEYS_BUF_INCREMENT; + bufIncrement = sortKeyBufSize; + + if( bufIncrement > DEF_SORT_KEYS_BUF_INCREMENT ) { + bufIncrement = DEF_SORT_KEYS_BUF_INCREMENT; + } + + if( bufIncrement < sortKeyLen ) { + bufIncrement = sortKeyLen; + } sortKeyBufSize += bufIncrement; bufLeft += bufIncrement; @@ -534,16 +552,6 @@ U_CFUNC PHP_FUNCTION( collator_sort_with_sort_keys ) sortKeyLen = ucol_getSortKey( co->ucoll, utf16_buf, utf16_len, (uint8_t*)sortKeyBuf + sortKeyBufOffset, bufLeft ); } - /* check sortKeyIndxBuf overflow, increasing its size of the buffer if needed */ - if( ( sortKeyCount + 1 ) * sortKeyIndxSize > sortKeyIndxBufSize ) - { - bufIncrement = ( sortKeyIndxSize > DEF_SORT_KEYS_INDX_BUF_INCREMENT ) ? sortKeyIndxSize : DEF_SORT_KEYS_INDX_BUF_INCREMENT; - - sortKeyIndxBufSize += bufIncrement; - - sortKeyIndxBuf = reinterpret_cast(erealloc( sortKeyIndxBuf, sortKeyIndxBufSize )); - } - sortKeyIndxBuf[sortKeyCount].key = (char*)sortKeyBufOffset; /* remember just offset, cause address */ /* of 'sortKeyBuf' may be changed due to realloc. */ sortKeyIndxBuf[sortKeyCount].zstr = hashData; diff --git a/ext/intl/tests/collator_sort_with_sort_keys_buffer_size.phpt b/ext/intl/tests/collator_sort_with_sort_keys_buffer_size.phpt new file mode 100644 index 000000000000..ef1d68851e37 --- /dev/null +++ b/ext/intl/tests/collator_sort_with_sort_keys_buffer_size.phpt @@ -0,0 +1,57 @@ +--TEST-- +Collator::sortWithSortKeys() buffer allocation scales with array size +--EXTENSIONS-- +intl +--FILE-- +sort($a); + +$before = memory_get_peak_usage(); +$b = ['bb', 'aa', 'cc', 'ab', 'ca', 'bc', 'ac', 'ba']; +$c->sortWithSortKeys($b); +$peakDelta = memory_get_peak_usage() - $before; + +var_dump($a); +var_dump($b); +var_dump($peakDelta < 100000); + +$long = str_repeat('a', 10000); +$d = [$long . 'b', $long . 'a']; +$c->sortWithSortKeys($d); +echo $d[0] === $long . 'a' ? "long-a\n" : "fail-a\n"; +echo $d[1] === $long . 'b' ? "long-b\n" : "fail-b\n"; +?> +--EXPECT-- +array(4) { + [0]=> + string(2) "aa" + [1]=> + string(2) "bb" + [2]=> + string(2) "cc" + [3]=> + string(2) "dd" +} +array(8) { + [0]=> + string(2) "aa" + [1]=> + string(2) "ab" + [2]=> + string(2) "ac" + [3]=> + string(2) "ba" + [4]=> + string(2) "bb" + [5]=> + string(2) "bc" + [6]=> + string(2) "ca" + [7]=> + string(2) "cc" +} +bool(true) +long-a +long-b From 9c74d7d1bcd254b4ad64a9b21fe41f546e764cd4 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sat, 29 Aug 2026 08:00:20 -0400 Subject: [PATCH 09/13] [mysqlnd] Fix result set field metadata length buffer over-read The rset_field metadata reader trusted each length-encoded string size: a hostile server could send a length marker at the end of a field packet whose value exceeds the remaining payload, advancing p past header.size and past the command buffer before the next dereference, and recording attacker-controlled lengths on pointers outside the packet that later feed memcpy() into the field memory pool. Bound each metadata string by bailing once p leaves the payload and rejecting lengths larger than the remaining bytes, matching php_mysqlnd_auth_response_read() from GHSA-h35g-vwh6-m678; an audit found no other users of the READ_RSET_FIELD macro and the trailing default-value check never dereferences its length. Closes GH-23496 --- ext/mysqli/tests/fake_server.inc | 44 +++++++++++++++++++ .../mysqlnd_rset_field_len_over_read.phpt | 42 ++++++++++++++++++ .../mysqlnd_rset_field_len_past_packet.phpt | 40 +++++++++++++++++ ext/mysqlnd/mysqlnd_wireprotocol.c | 9 +++- 4 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 ext/mysqli/tests/mysqlnd_rset_field_len_over_read.phpt create mode 100644 ext/mysqli/tests/mysqlnd_rset_field_len_past_packet.phpt diff --git a/ext/mysqli/tests/fake_server.inc b/ext/mysqli/tests/fake_server.inc index 4056b9fb78cf..3af5c7459159 100644 --- a/ext/mysqli/tests/fake_server.inc +++ b/ext/mysqli/tests/fake_server.inc @@ -829,6 +829,50 @@ function my_mysqli_test_stmt_response_row_read_two_fields(my_mysqli_fake_server_ } } +function my_mysqli_test_rset_field_metadata_len_over_read(my_mysqli_fake_server_conn $conn): void +{ + $rh = $conn->packet_generator->server_tabular_query_response(); + + $qr2 = new my_mysqli_fake_packet(); + $qr2->packet_length = "0c0000"; + $qr2->packet_number = "02"; + $qr2->catalog_length_plus_name = "0161"; + $qr2->db_length_plus_name = "0162"; + $qr2->table_length_plus_name = "0163"; + $qr2->original_t = "0164"; + $qr2->name_length_plus_name = "0165"; + $qr2->original_n = "fcff"; + + $conn->send_server_greetings(); + $conn->read_packets(1); + $conn->send_server_ok(); + $conn->read_packets(1); + $conn->send($conn->packets_to_bytes([$rh[0], $qr2]), "Malicious Tabular Response [metadata string length past the packet size]"); + $conn->read(); +} + +function my_mysqli_test_rset_field_metadata_len_past_packet(my_mysqli_fake_server_conn $conn): void +{ + $rh = $conn->packet_generator->server_tabular_query_response(); + + $qr2 = new my_mysqli_fake_packet(); + $qr2->packet_length = "0c0000"; + $qr2->packet_number = "02"; + $qr2->catalog_length_plus_name = "0161"; + $qr2->db_length_plus_name = "0162"; + $qr2->table_length_plus_name = "0163"; + $qr2->original_t = "0164"; + $qr2->name_length_plus_name = "0165"; + $qr2->original_n = "0561"; + + $conn->send_server_greetings(); + $conn->read_packets(1); + $conn->send_server_ok(); + $conn->read_packets(1); + $conn->send($conn->packets_to_bytes([$rh[0], $qr2]), "Malicious Tabular Response [metadata string length past the packet size]"); + $conn->read(); +} + function my_mysqli_test_query_response_row_length_overflow(my_mysqli_fake_server_conn $conn): void { $rh = $conn->packet_generator->server_query_execute_data_response('strval'); diff --git a/ext/mysqli/tests/mysqlnd_rset_field_len_over_read.phpt b/ext/mysqli/tests/mysqlnd_rset_field_len_over_read.phpt new file mode 100644 index 000000000000..274468ded63d --- /dev/null +++ b/ext/mysqli/tests/mysqlnd_rset_field_len_over_read.phpt @@ -0,0 +1,42 @@ +--TEST-- +mysqlnd result set field metadata string length buffer over-read (len clamped to packet size) +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +try { + $conn = new mysqli( $servername, $username, $password, "", $process->getPort()); + var_dump($conn->query("SELECT * from users")); +} catch (Exception $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +$conn->close(); + +$process->terminate(); + +print "done!"; +?> +--EXPECTF-- +[*] Server started on 127.0.0.1:%d +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: %s +[*] Sending - Server OK: 0700000200000002000000 +[*] Received: %s +[*] Sending - Malicious Tabular Response [metadata string length past the packet size]: 01000001010c00000201610162016301640165fcff + +Warning: mysqli::query(): Premature end of data (mysqlnd_wireprotocol.c:%d) in %s on line %d + +Warning: mysqli::query(): Result set field packet %d bytes shorter than expected in %s on line %d +bool(false) +done! diff --git a/ext/mysqli/tests/mysqlnd_rset_field_len_past_packet.phpt b/ext/mysqli/tests/mysqlnd_rset_field_len_past_packet.phpt new file mode 100644 index 000000000000..020d28d95b25 --- /dev/null +++ b/ext/mysqli/tests/mysqlnd_rset_field_len_past_packet.phpt @@ -0,0 +1,40 @@ +--TEST-- +mysqlnd result set field metadata string length exceeds remaining packet bytes +--EXTENSIONS-- +mysqli +--FILE-- +wait(); + +try { + $conn = new mysqli( $servername, $username, $password, "", $process->getPort()); + var_dump($conn->query("SELECT * from users")); +} catch (Exception $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +$conn->close(); + +$process->terminate(); + +print "done!"; +?> +--EXPECTF-- +[*] Server started on 127.0.0.1:%d +[*] Connection established +[*] Sending - Server Greeting: %s +[*] Received: %s +[*] Sending - Server OK: %s +[*] Received: %s +[*] Sending - Malicious Tabular Response [metadata string length past the packet size]: %s + +Warning: mysqli::query(): Result set field metadata string length is past the packet size in %s on line %d +bool(false) +done! diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c index 80b4b37591ab..f0f95a970899 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.c +++ b/ext/mysqlnd/mysqlnd_wireprotocol.c @@ -1176,10 +1176,17 @@ void php_mysqlnd_rset_header_free_mem(void * _packet) /* }}} */ #define READ_RSET_FIELD(field_name) do { \ + BAIL_IF_NO_MORE_DATA; \ len = php_mysqlnd_net_field_length(&p); \ if (UNEXPECTED(len == MYSQLND_NULL_LENGTH)) { \ goto faulty_or_fake; \ } else if (len != 0) { \ + BAIL_IF_NO_MORE_DATA; \ + if (UNEXPECTED((p - begin) > packet->header.size || packet->header.size - (p - begin) < len)) { \ + DBG_ERR_FMT("Result set field metadata string length is past the packet size"); \ + php_error_docref(NULL, E_WARNING, "Result set field metadata string length is past the packet size"); \ + DBG_RETURN(FAIL); \ + } \ meta->field_name = (const char *)p; \ meta->field_name ## _length = len; \ p += len; \ @@ -1248,7 +1255,7 @@ php_mysqlnd_rset_field_read(MYSQLND_CONN_DATA * conn, void * _packet) READ_RSET_FIELD(name); READ_RSET_FIELD(org_name); - /* 1 byte length */ + BAIL_IF_NO_MORE_DATA; if (UNEXPECTED(12 != *p)) { DBG_ERR_FMT("Protocol error. Server sent false length. Expected 12 got %d", (int) *p); php_error_docref(NULL, E_WARNING, "Protocol error. Server sent false length. Expected 12"); From b7a85ee111eaaeadb7f7c52b05cd3d0625b843be Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sat, 29 Aug 2026 08:15:41 -0400 Subject: [PATCH 10/13] [intl] Fix leak of time zone wrapper in Calendar debug info Calendar_get_debug_info() built a temporary IntlTimeZone wrapper zval via timezone_object_construct() and never released it, leaking one wrapper object per var_dump()/debug dump of an IntlCalendar. Release the wrapper with zval_ptr_dtor() after its debug info has been copied. Sibling audit: all other timezone_object_construct() call sites write into return_value and are refcount-managed; no other intl get_debug_info handler constructs temporary wrapper objects. Closes GH-23503 --- NEWS | 1 + ext/intl/calendar/calendar_class.cpp | 2 ++ .../calendar_get_debug_info_tz_leak.phpt | 26 +++++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 ext/intl/tests/calendar_get_debug_info_tz_leak.phpt diff --git a/NEWS b/NEWS index 546364fbc2ca..921ca372b294 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,7 @@ PHP NEWS . Fixed bug GH-19320 (FPM UID and GID overflow). (Pratik Bhujel) - Intl: + . Fixed a memory leak when dumping IntlCalendar instances. (Ilia Alshanetsky) . Fixed a memory leak when iterating IntlBreakIterator::getPartsIterator() results. (iliaal) . Fixed a double-free when IntlGregorianCalendar construction fails after diff --git a/ext/intl/calendar/calendar_class.cpp b/ext/intl/calendar/calendar_class.cpp index 97b21ff8f965..bacb549bdbc6 100644 --- a/ext/intl/calendar/calendar_class.cpp +++ b/ext/intl/calendar/calendar_class.cpp @@ -171,6 +171,8 @@ static HashTable *Calendar_get_debug_info(zend_object *object, int *is_temp) FREE_HASHTABLE(debug_info_tz); zend_hash_str_update(debug_info, "timeZone", sizeof("timeZone") - 1, &ztz_debug); + + zval_ptr_dtor(&ztz); } { diff --git a/ext/intl/tests/calendar_get_debug_info_tz_leak.phpt b/ext/intl/tests/calendar_get_debug_info_tz_leak.phpt new file mode 100644 index 000000000000..32b9da4368a9 --- /dev/null +++ b/ext/intl/tests/calendar_get_debug_info_tz_leak.phpt @@ -0,0 +1,26 @@ +--TEST-- +IntlCalendar get_debug_info() must not leak the time zone wrapper object +--EXTENSIONS-- +intl +--FILE-- + +--EXPECT-- +int(0) From fbca6cb2219353420490aa746151d0660a4037bc Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sat, 29 Aug 2026 07:58:59 -0400 Subject: [PATCH 11/13] dom: invalidate node list caches on class attribute mutations Reflected attribute writes such as className and id, classList mutations, and removeAttribute()/removeAttributeNS()/removeAttributeNode() modified attributes without bumping the document cache tag, so live HTMLCollection caches like getElementsByClassName() kept serving stale lengths and items. Invalidate the node list caches at every one of these mutation points. Sibling audit: Attr:: writes, setAttribute(), setAttributeNode() and setAttributeNS() already invalidate; php_dom_ns_compat_mark_attribute() only mirrors namespace declarations during reconciliation and is not user-visible. Closes GH-23501 --- NEWS | 2 + ext/dom/element.c | 13 ++++-- ...lementsByClassName_cache_invalidation.phpt | 44 +++++++++++++++++++ ext/dom/token_list.c | 2 + 4 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 ext/dom/tests/modern/common/getElementsByClassName_cache_invalidation.phpt diff --git a/NEWS b/NEWS index ccb18766e2ae..693ff78fafd0 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,8 @@ PHP NEWS middle generator delegates again). (Lazizbek Ergashev) - DOM: + . Fixed stale getElementsByClassName() and other node list caches after + className/classList writes and attribute removals. (Ilia Alshanetsky) . Fixed a use-after-free when cloning a DOMNameSpaceNode after DOMDocument::xinclude(). (iliaal) . Fixed a crash in DOMXPath when a php:function callback receives a nodeset diff --git a/ext/dom/element.c b/ext/dom/element.c index 2320216f8244..8dd7f3756050 100644 --- a/ext/dom/element.c +++ b/ext/dom/element.c @@ -156,6 +156,7 @@ static xmlAttrPtr dom_element_reflected_attribute_write(dom_object *obj, zval *n /* Typed property, so it is a string already */ ZEND_ASSERT(Z_TYPE_P(newval) == IS_STRING); + php_libxml_invalidate_node_list_cache(obj->document); return xmlSetNsProp(nodep, NULL, (const xmlChar *) name, (const xmlChar *) Z_STRVAL_P(newval)); } @@ -544,7 +545,7 @@ static void dom_deep_ns_redef(xmlNodePtr node, xmlNsPtr ns_to_redefine) efree(worklist); } -static bool dom_remove_attribute(xmlNodePtr thisp, xmlNodePtr attrp) +static bool dom_remove_attribute(xmlNodePtr thisp, xmlNodePtr attrp, php_libxml_ref_obj *document) { ZEND_ASSERT(thisp != NULL); ZEND_ASSERT(attrp != NULL); @@ -599,6 +600,7 @@ static bool dom_remove_attribute(xmlNodePtr thisp, xmlNodePtr attrp) return false; EMPTY_SWITCH_DEFAULT_CASE(); } + php_libxml_invalidate_node_list_cache(document); return true; } @@ -624,7 +626,7 @@ PHP_METHOD(DOMElement, removeAttribute) RETURN_FALSE; } - RETURN_BOOL(dom_remove_attribute(nodep, attrp)); + RETURN_BOOL(dom_remove_attribute(nodep, attrp, intern->document)); } PHP_METHOD(Dom_Element, removeAttribute) @@ -642,7 +644,7 @@ PHP_METHOD(Dom_Element, removeAttribute) attrp = dom_get_attribute_or_nsdecl(intern, nodep, BAD_CAST name, name_len); if (attrp != NULL) { - dom_remove_attribute(nodep, attrp); + dom_remove_attribute(nodep, attrp, intern->document); } } /* }}} end dom_element_remove_attribute */ @@ -800,6 +802,7 @@ static void dom_element_remove_attribute_node(INTERNAL_FUNCTION_PARAMETERS, zend RETURN_FALSE; } + php_libxml_invalidate_node_list_cache(intern->document); xmlUnlinkNode((xmlNodePtr) attrp); DOM_RET_OBJ((xmlNodePtr) attrp, intern); @@ -1200,6 +1203,7 @@ PHP_METHOD(DOMElement, removeAttributeNS) if (nsptr != NULL) { if (xmlStrEqual(BAD_CAST uri, nsptr->href)) { dom_eliminate_ns(nodep, nsptr); + php_libxml_invalidate_node_list_cache(intern->document); } else { return; } @@ -1214,6 +1218,7 @@ PHP_METHOD(DOMElement, removeAttributeNS) } else { xmlUnlinkNode((xmlNodePtr) attrp); } + php_libxml_invalidate_node_list_cache(intern->document); } } /* }}} end dom_element_remove_attribute_ns */ @@ -1922,7 +1927,7 @@ PHP_METHOD(DOMElement, toggleAttribute) /* Step 5 */ if (force_is_null || !force) { - retval = !dom_remove_attribute(thisp, attribute); + retval = !dom_remove_attribute(thisp, attribute, intern->document); goto out; } diff --git a/ext/dom/tests/modern/common/getElementsByClassName_cache_invalidation.phpt b/ext/dom/tests/modern/common/getElementsByClassName_cache_invalidation.phpt new file mode 100644 index 000000000000..4efdad1b59b4 --- /dev/null +++ b/ext/dom/tests/modern/common/getElementsByClassName_cache_invalidation.phpt @@ -0,0 +1,44 @@ +--TEST-- +getElementsByClassName() cache must be invalidated by class attribute mutations +--EXTENSIONS-- +dom +--FILE-- +$body"); +} + +$checks = [ + 'className' => function ($doc, $span) { $span->className = 'zzz'; }, + 'classList-remove' => function ($doc, $span) { $span->classList->remove('foo'); }, + 'classList-value' => function ($doc, $span) { $span->classList->value = 'zzz'; }, + 'setAttribute' => function ($doc, $span) { $span->setAttribute('class', 'zzz'); }, + 'removeAttribute' => function ($doc, $span) { $span->removeAttribute('class'); }, + 'removeAttributeNode' => function ($doc, $span) { $span->removeAttributeNode($span->attributes['class']); }, +]; +foreach ($checks as $label => $fn) { + $doc = mk(''); + $coll = $doc->getElementsByClassName('foo'); + if ($coll->length !== 1) { + echo "$label: unexpected initial length\n"; + continue; + } + $fn($doc, $doc->querySelector('span')); + echo "$label: ", $coll->length === 0 ? "OK" : "STALE {$coll->length}", "\n"; +} + +$doc = mk(''); +$coll = $doc->getElementsByClassName('foo'); +var_dump($coll->length); +$doc->querySelector('span')->className = 'foo'; +echo $coll->length === 1 ? "growth OK" : "growth STALE", "\n"; +?> +--EXPECT-- +className: OK +classList-remove: OK +classList-value: OK +setAttribute: OK +removeAttribute: OK +removeAttributeNode: OK +int(0) +growth OK diff --git a/ext/dom/token_list.c b/ext/dom/token_list.c index 524ff699f41b..34e2aa6b3ca3 100644 --- a/ext/dom/token_list.c +++ b/ext/dom/token_list.c @@ -184,6 +184,7 @@ static void dom_token_list_update(dom_token_list_object *intern) HashTable *token_set = TOKEN_LIST_GET_SET(intern); php_libxml_invalidate_cache_tag(&intern->cache_tag); + php_libxml_invalidate_node_list_cache(intern->dom.document); /* 1. If the associated element does not have an associated attribute and token set is empty, then return. */ if (attr == NULL && zend_hash_num_elements(token_set) == 0) { @@ -432,6 +433,7 @@ zend_result dom_token_list_value_write(dom_object *obj, zval *newval) zend_value_error("Value must not contain any null bytes"); return FAILURE; } + php_libxml_invalidate_node_list_cache(intern->dom.document); xmlSetNsProp(dom_token_list_get_element(intern), NULL, BAD_CAST "class", BAD_CAST Z_STRVAL_P(newval)); /* Note: we don't update the set here, the set is always lazily updated for performance reasons. */ return SUCCESS; From f09a81ddee3e58a8c5af5b311a101b3c1b20a637 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sat, 29 Aug 2026 08:16:19 -0400 Subject: [PATCH 12/13] [SOAP] Fix WSDL cache corruption when header defines headerfaults sdl_serialize_soap_body() counted j headerfaults per header but then serialized body->headers instead of tmp->headerfaults, writing N header records where j fault records were expected by sdl_deserialize_soap_body(), misaligning the cache stream and crashing on load whenever a soap:header carries headerfaults. Iterate tmp->headerfaults instead; sibling audit of the other serialize/deserialize loops in php_sdl.c found no further hash-mismatched iteration. Bump WSDL_CACHE_VERSION so existing on-disk caches are discarded. Closes GH-23502 --- NEWS | 2 ++ ext/soap/php_sdl.c | 4 +-- ext/soap/tests/headerfault_cache.phpt | 45 +++++++++++++++++++++++++ ext/soap/tests/headerfault_cache.wsdl | 47 +++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 ext/soap/tests/headerfault_cache.phpt create mode 100644 ext/soap/tests/headerfault_cache.wsdl diff --git a/NEWS b/NEWS index 921ca372b294..208ccaef35fb 100644 --- a/NEWS +++ b/NEWS @@ -66,6 +66,8 @@ PHP NEWS - SOAP: . Fixed bug GH-23447 (Segfault when a class passed to SoapServer::setClass() fails to initialize). (Lazizbek Ergashev) + . Fixed WSDL cache corruption when a soap:header defines headerfaults. + (Ilia Alshanetsky) - Standard: . Fixed a segfault when a stream filter callback unsets StreamBucket::$data diff --git a/ext/soap/php_sdl.c b/ext/soap/php_sdl.c index a44fc16f9716..7aa9e9fae259 100644 --- a/ext/soap/php_sdl.c +++ b/ext/soap/php_sdl.c @@ -1155,7 +1155,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) return ctx.sdl; } -#define WSDL_CACHE_VERSION 0x10 +#define WSDL_CACHE_VERSION 0x11 #define WSDL_CACHE_GET(ret,type,buf) memcpy(&ret,*buf,sizeof(type)); *buf += sizeof(type); #define WSDL_CACHE_GET_INT(ret,buf) ret = ((unsigned char)(*buf)[0])|((unsigned char)(*buf)[1]<<8)|((unsigned char)(*buf)[2]<<16)|((unsigned)(*buf)[3]<<24); *buf += 4; @@ -2066,7 +2066,7 @@ static void sdl_serialize_soap_body(const sdlSoapBindingFunctionBodyPtr body, co sdlSoapBindingFunctionHeaderPtr tmp2; const zend_string *key_inner; - ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(body->headers, key_inner, tmp2) { + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(tmp->headerfaults, key_inner, tmp2) { sdl_serialize_key(key_inner, out); WSDL_CACHE_PUT_1(tmp2->use, out); if (tmp2->use == SOAP_ENCODED) { diff --git a/ext/soap/tests/headerfault_cache.phpt b/ext/soap/tests/headerfault_cache.phpt new file mode 100644 index 000000000000..6da747902d80 --- /dev/null +++ b/ext/soap/tests/headerfault_cache.phpt @@ -0,0 +1,45 @@ +--TEST-- +WSDL cache corruption when soap:header has headerfaults +--EXTENSIONS-- +soap +--INI-- +soap.wsdl_cache_enabled=1 +--FILE-- + WSDL_CACHE_DISK]; + +$c1 = new SoapClient(__DIR__ . '/headerfault_cache.wsdl', $options); +var_dump($c1->__getFunctions()); + +$c2 = new SoapClient(__DIR__ . '/headerfault_cache.wsdl', $options); +var_dump($c2->__getFunctions()); + +echo "ok\n"; +?> +--CLEAN-- + +--EXPECT-- +array(1) { + [0]=> + string(32) "string testHeader(string $param)" +} +array(1) { + [0]=> + string(32) "string testHeader(string $param)" +} +ok diff --git a/ext/soap/tests/headerfault_cache.wsdl b/ext/soap/tests/headerfault_cache.wsdl new file mode 100644 index 000000000000..8a844c0b899d --- /dev/null +++ b/ext/soap/tests/headerfault_cache.wsdl @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 027a5f8536468498a659684cf273d4f8bdd06706 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Mon, 31 Aug 2026 00:19:57 +0800 Subject: [PATCH 13/13] ext/standard: Make `str_ends_with` frameless (#23510) Since str_starts_with is frameless, str_ends_with also should be frameless. --- UPGRADING | 1 + ext/standard/basic_functions.stub.php | 5 ++++- ext/standard/basic_functions_arginfo.h | 10 ++++++++-- ext/standard/basic_functions_decl.h | 8 ++++---- ext/standard/string.c | 15 +++++++++++++++ 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/UPGRADING b/UPGRADING index a164599c6f15..5a5cafc0234f 100644 --- a/UPGRADING +++ b/UPGRADING @@ -973,6 +973,7 @@ PHP 8.6 UPGRADE NOTES . Reduced temporary allocations when iterating Phar directories. - Standard: + . Improved performance of str_ends_with(). . Improved performance of array_fill_keys(). . Improved performance of array_intersect(). . Improved performance of array_map() with multiple arrays passed. diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index 3e23934cbc78..8ae94d8c6d99 100644 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -2451,7 +2451,10 @@ function str_contains(string $haystack, string $needle): bool {} */ function str_starts_with(string $haystack, string $needle): bool {} -/** @compile-time-eval */ +/** + * @compile-time-eval + * @frameless-function {"arity": 2} + */ function str_ends_with(string $haystack, string $needle): bool {} /** diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index 442085e9d6cc..4bcf008f5fdd 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 basic_functions.stub.php instead. - * Stub hash: c645e310c00d9f4cb3856c94ee60d06071e28de0 + * Stub hash: 31018a787ba261316941b0d88f090b9cf271aa0e * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) @@ -2294,6 +2294,12 @@ static const zend_frameless_function_info frameless_function_infos_str_starts_wi { 0 }, }; +ZEND_FRAMELESS_FUNCTION(str_ends_with, 2); +static const zend_frameless_function_info frameless_function_infos_str_ends_with[] = { + { ZEND_FRAMELESS_FUNCTION_NAME(str_ends_with, 2), 2 }, + { 0 }, +}; + ZEND_FRAMELESS_FUNCTION(substr, 2); ZEND_FRAMELESS_FUNCTION(substr, 3); static const zend_frameless_function_info frameless_function_infos_substr[] = { @@ -3197,7 +3203,7 @@ static const zend_function_entry ext_functions[] = { ZEND_RAW_FENTRY("strrchr", zif_strrchr, arginfo_strrchr, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) ZEND_RAW_FENTRY("str_contains", zif_str_contains, arginfo_str_contains, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_str_contains, NULL) ZEND_RAW_FENTRY("str_starts_with", zif_str_starts_with, arginfo_str_starts_with, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_str_starts_with, NULL) - ZEND_RAW_FENTRY("str_ends_with", zif_str_ends_with, arginfo_str_ends_with, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) + ZEND_RAW_FENTRY("str_ends_with", zif_str_ends_with, arginfo_str_ends_with, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_str_ends_with, NULL) ZEND_RAW_FENTRY("chunk_split", zif_chunk_split, arginfo_chunk_split, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) ZEND_RAW_FENTRY("substr", zif_substr, arginfo_substr, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_substr, NULL) ZEND_RAW_FENTRY("substr_replace", zif_substr_replace, arginfo_substr_replace, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) diff --git a/ext/standard/basic_functions_decl.h b/ext/standard/basic_functions_decl.h index f2f234f60cc2..db81e0bfc077 100644 --- a/ext/standard/basic_functions_decl.h +++ b/ext/standard/basic_functions_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit basic_functions.stub.php instead. - * Stub hash: c645e310c00d9f4cb3856c94ee60d06071e28de0 */ + * Stub hash: 31018a787ba261316941b0d88f090b9cf271aa0e */ -#ifndef ZEND_BASIC_FUNCTIONS_DECL_c645e310c00d9f4cb3856c94ee60d06071e28de0_H -#define ZEND_BASIC_FUNCTIONS_DECL_c645e310c00d9f4cb3856c94ee60d06071e28de0_H +#ifndef ZEND_BASIC_FUNCTIONS_DECL_31018a787ba261316941b0d88f090b9cf271aa0e_H +#define ZEND_BASIC_FUNCTIONS_DECL_31018a787ba261316941b0d88f090b9cf271aa0e_H typedef enum zend_enum_SortDirection { ZEND_ENUM_SortDirection_Ascending = 1, @@ -20,4 +20,4 @@ typedef enum zend_enum_RoundingMode { ZEND_ENUM_RoundingMode_PositiveInfinity = 8, } zend_enum_RoundingMode; -#endif /* ZEND_BASIC_FUNCTIONS_DECL_c645e310c00d9f4cb3856c94ee60d06071e28de0_H */ +#endif /* ZEND_BASIC_FUNCTIONS_DECL_31018a787ba261316941b0d88f090b9cf271aa0e_H */ diff --git a/ext/standard/string.c b/ext/standard/string.c index e5307a4f2d4b..af3f6a461dcf 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -1895,6 +1895,21 @@ PHP_FUNCTION(str_ends_with) } /* }}} */ +ZEND_FRAMELESS_FUNCTION(str_ends_with, 2) +{ + zval haystack_tmp, needle_tmp; + zend_string *haystack, *needle; + + Z_FLF_PARAM_STR(1, haystack, haystack_tmp); + Z_FLF_PARAM_STR(2, needle, needle_tmp); + + RETVAL_BOOL(zend_string_ends_with(haystack, needle)); + +flf_clean: + Z_FLF_PARAM_FREE_STR(1, haystack_tmp); + Z_FLF_PARAM_FREE_STR(2, needle_tmp); +} + static zend_always_inline void _zend_strpos(zval *return_value, zend_string *haystack, zend_string *needle, zend_long offset) { const char *found = NULL;