diff --git a/.github/labeler.yml b/.github/labeler.yml index 92f9c50962349..b9f0f36e147d4 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -492,6 +492,7 @@ - 'ext/spl/spl_iterators.h' - 'ext/spl/spl_observer.h' - 'ext/standard/*.h' + - 'ext/uri/*.h' - 'ext/xml/expat_compat.h' - 'ext/xml/php_xml.h' - 'main/*.h' diff --git a/NEWS b/NEWS index 35d5c34b6fcd9..bf80ab696b090 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,8 @@ PHP NEWS . Fixed memory management of Uri\WhatWg\Url objects. (timwolla) . Fixed memory management of the internal "parse_url" URI parser. (timwolla) + . Fixed double-free when assigning to $errors fails when using + the Uri\WhatWg\Url parser. (timwolla) . Clean up naming of internal API. (timwolla) 28 Aug 2025, PHP 8.5.0beta2 diff --git a/ext/filter/logical_filters.c b/ext/filter/logical_filters.c index def5331720d43..e6fbaf86b370a 100644 --- a/ext/filter/logical_filters.c +++ b/ext/filter/logical_filters.c @@ -612,7 +612,7 @@ zend_result php_filter_validate_url(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ int parser_name_set; FETCH_STR_OPTION(parser_name, URL_OPTION_URI_PARSER_CLASS); - uri_parser_t *uri_parser = php_uri_get_parser(parser_name_set ? parser_name : NULL); + const uri_parser_t *uri_parser = php_uri_get_parser(parser_name_set ? parser_name : NULL); if (uri_parser == NULL) { zend_value_error("%s(): \"uri_parser_class\" option has invalid value", get_active_function_name()); RETURN_VALIDATION_FAILED diff --git a/ext/opcache/tests/opcache_enable_noop_001.phpt b/ext/opcache/tests/opcache_enable_noop_001.phpt new file mode 100644 index 0000000000000..0e7f5bbc98793 --- /dev/null +++ b/ext/opcache/tests/opcache_enable_noop_001.phpt @@ -0,0 +1,24 @@ +--TEST-- +Dynamically setting opcache.enable does not warn when noop +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +Should not warn: +Disabling: +Should warn: + +Warning: Zend OPcache can't be temporarily enabled (it may be only disabled until the end of request) in %s on line %d diff --git a/ext/opcache/tests/opcache_enable_noop_002.phpt b/ext/opcache/tests/opcache_enable_noop_002.phpt new file mode 100644 index 0000000000000..994f2d22f4e45 --- /dev/null +++ b/ext/opcache/tests/opcache_enable_noop_002.phpt @@ -0,0 +1,18 @@ +--TEST-- +Dynamically setting opcache.enable warns when not a noop +--INI-- +opcache.enable=0 +opcache.enable_cli=1 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +Should warn, since the INI was initialized to 0: + +Warning: Zend OPcache can't be temporarily enabled (it may be only disabled until the end of request) in %s on line %d diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index 9a168b38baaf8..6f668af9b714d 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -159,10 +159,23 @@ static ZEND_INI_MH(OnEnable) stage == ZEND_INI_STAGE_DEACTIVATE) { return OnUpdateBool(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); } else { - /* It may be only temporary disabled */ + /* It may be only temporarily disabled */ bool *p = (bool *) ZEND_INI_GET_ADDR(); if (zend_ini_parse_bool(new_value)) { - zend_error(E_WARNING, ACCELERATOR_PRODUCT_NAME " can't be temporary enabled (it may be only disabled till the end of request)"); + if (*p) { + /* Do not warn if OPcache is enabled, as the update would be a noop anyways. */ + return SUCCESS; + } + + if (stage == ZEND_INI_STAGE_ACTIVATE) { + if (strcmp(sapi_module.name, "fpm-fcgi") == 0) { + zend_accel_error(ACCEL_LOG_WARNING, ACCELERATOR_PRODUCT_NAME " can't be temporarily enabled. Are you using php_admin_value[opcache.enable]=1 in an individual pool's configuration?"); + } else { + zend_accel_error(ACCEL_LOG_WARNING, ACCELERATOR_PRODUCT_NAME " can't be temporarily enabled (it may be only disabled until the end of request)"); + } + } else { + zend_error(E_WARNING, ACCELERATOR_PRODUCT_NAME " can't be temporarily enabled (it may be only disabled until the end of request)"); + } return FAILURE; } else { *p = 0; diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 6fbdb506133c7..e76f99de6df6f 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -2634,7 +2634,7 @@ static char *php_openssl_get_url_name(const char *resourcename, return NULL; } - uri_parser_t *uri_parser = php_stream_context_get_uri_parser("ssl", context); + const uri_parser_t *uri_parser = php_stream_context_get_uri_parser("ssl", context); if (uri_parser == NULL) { zend_value_error("%s(): Provided stream context has invalid value for the \"uri_parser_class\" option", get_active_function_name()); return NULL; diff --git a/ext/soap/php_http.c b/ext/soap/php_http.c index 854db4c928cbf..71082aef7f902 100644 --- a/ext/soap/php_http.c +++ b/ext/soap/php_http.c @@ -431,7 +431,7 @@ int make_http_soap_request( } if (location != NULL && ZSTR_VAL(location)[0] != '\000') { - uri_parser_t *uri_parser = php_uri_get_parser(uri_parser_class); + const uri_parser_t *uri_parser = php_uri_get_parser(uri_parser_class); if (uri_parser == NULL) { zend_argument_value_error(6, "must be a valid URI parser name"); return FALSE; @@ -1148,7 +1148,7 @@ int make_http_soap_request( char *loc; if ((loc = get_http_header_value(ZSTR_VAL(http_headers), "Location:")) != NULL) { - uri_parser_t *uri_parser = php_uri_get_parser(uri_parser_class); + const uri_parser_t *uri_parser = php_uri_get_parser(uri_parser_class); if (uri_parser == NULL) { efree(loc); zend_argument_value_error(6, "must be a valid URI parser name"); diff --git a/ext/standard/dir.c b/ext/standard/dir.c index 2eb9d853eeb16..918b92fab456f 100644 --- a/ext/standard/dir.c +++ b/ext/standard/dir.c @@ -476,8 +476,7 @@ PHP_FUNCTION(glob) #ifdef PHP_GLOB_NOMATCH no_results: #endif - array_init(return_value); - return; + RETURN_EMPTY_ARRAY(); } array_init(return_value); diff --git a/ext/standard/ftp_fopen_wrapper.c b/ext/standard/ftp_fopen_wrapper.c index 0a3b261d39c24..f4508b4657c10 100644 --- a/ext/standard/ftp_fopen_wrapper.c +++ b/ext/standard/ftp_fopen_wrapper.c @@ -135,7 +135,7 @@ static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char char *transport; int transport_len; - uri_parser_t *uri_parser = php_stream_context_get_uri_parser("ftp", context); + const uri_parser_t *uri_parser = php_stream_context_get_uri_parser("ftp", context); if (uri_parser == NULL) { zend_value_error("%s(): Provided stream context has invalid value for the \"uri_parser_class\" option", get_active_function_name()); return NULL; @@ -956,7 +956,7 @@ static int php_stream_ftp_rename(php_stream_wrapper *wrapper, const char *url_fr int result; char tmp_line[512]; - uri_parser_t *uri_parser = php_stream_context_get_uri_parser("ftp", context); + const uri_parser_t *uri_parser = php_stream_context_get_uri_parser("ftp", context); if (uri_parser == NULL) { zend_value_error("%s(): Provided stream context has invalid value for the \"uri_parser_class\" option", get_active_function_name()); return 0; diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index 81d688a2f0458..30057b70eea09 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -393,7 +393,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, return NULL; } - uri_parser_t *uri_parser = php_stream_context_get_uri_parser("http", context); + const uri_parser_t *uri_parser = php_stream_context_get_uri_parser("http", context); if (uri_parser == NULL) { zend_value_error("%s(): Provided stream context has invalid value for the \"uri_parser_class\" option", get_active_function_name()); return NULL; diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c index abdaa3eebe958..17278c0f422e8 100644 --- a/ext/uri/php_uri.c +++ b/ext/uri/php_uri.c @@ -52,7 +52,7 @@ static const zend_module_dep uri_deps[] = { static zend_array uri_parsers; -static uri_parser_t *uri_parser_by_name(const char *uri_parser_name, size_t uri_parser_name_len) +static const uri_parser_t *uri_parser_by_name(const char *uri_parser_name, size_t uri_parser_name_len) { return zend_hash_str_find_ptr(&uri_parsers, uri_parser_name, uri_parser_name_len); } @@ -69,45 +69,45 @@ static HashTable *uri_get_debug_properties(zend_object *object) return result; } - const uri_property_handlers_t property_handlers = internal_uri->parser->property_handlers; + const uri_parser_t *parser = internal_uri->parser; zval tmp; - if (property_handlers.scheme.read_func(internal_uri, URI_COMPONENT_READ_RAW, &tmp) == SUCCESS) { + if (parser->property_handlers.scheme.read_func(internal_uri, URI_COMPONENT_READ_RAW, &tmp) == SUCCESS) { zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_SCHEME), &tmp); } - if (property_handlers.username.read_func(internal_uri, URI_COMPONENT_READ_RAW, &tmp) == SUCCESS) { + if (parser->property_handlers.username.read_func(internal_uri, URI_COMPONENT_READ_RAW, &tmp) == SUCCESS) { zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_USERNAME), &tmp); } - if (property_handlers.password.read_func(internal_uri, URI_COMPONENT_READ_RAW, &tmp) == SUCCESS) { + if (parser->property_handlers.password.read_func(internal_uri, URI_COMPONENT_READ_RAW, &tmp) == SUCCESS) { zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_PASSWORD), &tmp); } - if (property_handlers.host.read_func(internal_uri, URI_COMPONENT_READ_RAW, &tmp) == SUCCESS) { + if (parser->property_handlers.host.read_func(internal_uri, URI_COMPONENT_READ_RAW, &tmp) == SUCCESS) { zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_HOST), &tmp); } - if (property_handlers.port.read_func(internal_uri, URI_COMPONENT_READ_RAW, &tmp) == SUCCESS) { + if (parser->property_handlers.port.read_func(internal_uri, URI_COMPONENT_READ_RAW, &tmp) == SUCCESS) { zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_PORT), &tmp); } - if (property_handlers.path.read_func(internal_uri, URI_COMPONENT_READ_RAW, &tmp) == SUCCESS) { + if (parser->property_handlers.path.read_func(internal_uri, URI_COMPONENT_READ_RAW, &tmp) == SUCCESS) { zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_PATH), &tmp); } - if (property_handlers.query.read_func(internal_uri, URI_COMPONENT_READ_RAW, &tmp) == SUCCESS) { + if (parser->property_handlers.query.read_func(internal_uri, URI_COMPONENT_READ_RAW, &tmp) == SUCCESS) { zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_QUERY), &tmp); } - if (property_handlers.fragment.read_func(internal_uri, URI_COMPONENT_READ_RAW, &tmp) == SUCCESS) { + if (parser->property_handlers.fragment.read_func(internal_uri, URI_COMPONENT_READ_RAW, &tmp) == SUCCESS) { zend_hash_update(result, ZSTR_KNOWN(ZEND_STR_FRAGMENT), &tmp); } return result; } -PHPAPI uri_parser_t *php_uri_get_parser(const zend_string *uri_parser_name) +PHPAPI const uri_parser_t *php_uri_get_parser(const zend_string *uri_parser_name) { if (uri_parser_name == NULL) { return uri_parser_by_name(PHP_URI_PARSER_PHP_PARSE_URL, sizeof(PHP_URI_PARSER_PHP_PARSE_URL) - 1); @@ -325,7 +325,6 @@ static zend_result pass_errors_by_ref_and_free(zval *errors_zv, zval *errors) ZEND_TRY_ASSIGN_REF_ARR(errors_zv, Z_ARRVAL_P(errors)); if (EG(exception)) { - zval_ptr_dtor(errors); return FAILURE; } @@ -360,6 +359,7 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(1, 2) PHPAPI void php_uri_instantiate_uri( } if (pass_errors_by_ref_and_free(errors_zv, &errors) == FAILURE) { + uri_parser->free_uri(uri); RETURN_THROWS(); } @@ -555,7 +555,7 @@ PHP_METHOD(Uri_Rfc3986_Uri, getRawHost) PHP_METHOD(Uri_Rfc3986_Uri, getPort) { - uri_read_component(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_PORT, URI_COMPONENT_READ_NORMALIZED_ASCII); + uri_read_component(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_PORT, URI_COMPONENT_READ_RAW); } PHP_METHOD(Uri_Rfc3986_Uri, getPath) diff --git a/ext/uri/php_uri.h b/ext/uri/php_uri.h index 2b4394b60d49c..19ba97dc4f386 100644 --- a/ext/uri/php_uri.h +++ b/ext/uri/php_uri.h @@ -47,7 +47,7 @@ PHPAPI zend_result php_uri_parser_register(const uri_parser_t *uri_parser); * @param uri_parser_name The URI parser name * @return The URI parser */ -PHPAPI uri_parser_t *php_uri_get_parser(const zend_string *uri_parser_name); +PHPAPI const uri_parser_t *php_uri_get_parser(const zend_string *uri_parser_name); ZEND_ATTRIBUTE_NONNULL PHPAPI uri_internal_t *php_uri_parse(const uri_parser_t *uri_parser, const char *uri_str, size_t uri_str_len, bool silent); diff --git a/ext/uri/php_uri_common.h b/ext/uri/php_uri_common.h index fb465bed502ab..2c461ab3fb0a4 100644 --- a/ext/uri/php_uri_common.h +++ b/ext/uri/php_uri_common.h @@ -64,17 +64,6 @@ typedef struct uri_property_handler_t { uri_write_t write_func; } uri_property_handler_t; -typedef struct uri_property_handlers_t { - uri_property_handler_t scheme; - uri_property_handler_t username; - uri_property_handler_t password; - uri_property_handler_t host; - uri_property_handler_t port; - uri_property_handler_t path; - uri_property_handler_t query; - uri_property_handler_t fragment; -} uri_property_handlers_t; - typedef struct uri_parser_t { /** * Name (the FQCN) of the URI parser. The "" name is reserved for the handler of the legacy parse_url(). @@ -138,7 +127,16 @@ typedef struct uri_parser_t { */ void (*free_uri)(void *uri); - const uri_property_handlers_t property_handlers; + struct { + uri_property_handler_t scheme; + uri_property_handler_t username; + uri_property_handler_t password; + uri_property_handler_t host; + uri_property_handler_t port; + uri_property_handler_t path; + uri_property_handler_t query; + uri_property_handler_t fragment; + } property_handlers; } uri_parser_t; typedef struct uri_internal_t { diff --git a/ext/uri/tests/057.phpt b/ext/uri/tests/057.phpt new file mode 100644 index 0000000000000..e2a109ccdacb9 --- /dev/null +++ b/ext/uri/tests/057.phpt @@ -0,0 +1,21 @@ +--TEST-- +Test assigning errors by reference fails +--EXTENSIONS-- +uri +--FILE-- +x); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +TypeError: Cannot assign array to reference held by property Foo::$x of type string diff --git a/ext/uri/uri_parser_rfc3986.c b/ext/uri/uri_parser_rfc3986.c index c80952a80c388..cf7235b071b4c 100644 --- a/ext/uri/uri_parser_rfc3986.c +++ b/ext/uri/uri_parser_rfc3986.c @@ -103,7 +103,7 @@ ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_scheme_read(con { const UriUriA *uriparser_uri = get_uri_for_reading(internal_uri->uri, read_mode); - if (uriparser_uri->scheme.first != NULL && uriparser_uri->scheme.afterLast != NULL) { + if (has_text_range(&uriparser_uri->scheme)) { ZVAL_STRINGL(retval, uriparser_uri->scheme.first, get_text_range_length(&uriparser_uri->scheme)); } else { ZVAL_NULL(retval); diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 2f5eae7b16c18..df51fa2189891 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -735,7 +735,7 @@ static ZEND_FUNCTION(zend_test_uri_parser) Z_PARAM_STR(parser_name) ZEND_PARSE_PARAMETERS_END(); - uri_parser_t *parser = php_uri_get_parser(parser_name); + const uri_parser_t *parser = php_uri_get_parser(parser_name); if (parser == NULL) { zend_argument_value_error(1, "Unknown parser"); RETURN_THROWS(); diff --git a/main/streams/php_stream_context.h b/main/streams/php_stream_context.h index 1890b32eaea24..eb082eb1402fa 100644 --- a/main/streams/php_stream_context.h +++ b/main/streams/php_stream_context.h @@ -68,7 +68,7 @@ void php_stream_context_unset_option(php_stream_context *context, struct uri_parser_t; -PHPAPI struct uri_parser_t *php_stream_context_get_uri_parser(const char *wrappername, php_stream_context *context); +PHPAPI const struct uri_parser_t *php_stream_context_get_uri_parser(const char *wrappername, php_stream_context *context); PHPAPI php_stream_notifier *php_stream_notification_alloc(void); PHPAPI void php_stream_notification_free(php_stream_notifier *notifier); END_EXTERN_C() diff --git a/main/streams/streams.c b/main/streams/streams.c index fdebc19e28245..88d982b760cd8 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -2458,7 +2458,7 @@ void php_stream_context_unset_option(php_stream_context *context, } /* }}} */ -PHPAPI struct uri_parser_t *php_stream_context_get_uri_parser(const char *wrappername, php_stream_context *context) +PHPAPI const struct uri_parser_t *php_stream_context_get_uri_parser(const char *wrappername, php_stream_context *context) { if (context == NULL) { return php_uri_get_parser(NULL); diff --git a/sapi/fpm/tests/opcache_enable_admin_value.phpt b/sapi/fpm/tests/opcache_enable_admin_value.phpt new file mode 100644 index 0000000000000..2c1081aed9eb8 --- /dev/null +++ b/sapi/fpm/tests/opcache_enable_admin_value.phpt @@ -0,0 +1,49 @@ +--TEST-- +Setting opcache.enable via php_admin_value fails gracefully +--SKIPIF-- + +--FILE-- +start(iniEntries: [ + 'opcache.enable' => '0', + 'opcache.log_verbosity_level' => '2', +]); +$tester->expectLogStartNotices(); +$tester->expectLogPattern("/Zend OPcache can't be temporarily enabled. Are you using php_admin_value\\[opcache.enable\\]=1 in an individual pool's configuration?/"); +echo $tester + ->request() + ->getBody(); +$tester->terminate(); +$tester->close(); + +?> +--EXPECT-- +NULL +--CLEAN-- +