Skip to content
Merged
1 change: 1 addition & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion ext/filter/logical_filters.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions ext/opcache/tests/opcache_enable_noop_001.phpt
Original file line number Diff line number Diff line change
@@ -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--
<?php

echo "Should not warn:", PHP_EOL;
ini_set('opcache.enable', 1);
echo "Disabling:", PHP_EOL;
ini_set('opcache.enable', 0);
echo "Should warn:", PHP_EOL;
ini_set('opcache.enable', 1);

?>
--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
18 changes: 18 additions & 0 deletions ext/opcache/tests/opcache_enable_noop_002.phpt
Original file line number Diff line number Diff line change
@@ -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--
<?php

echo "Should warn, since the INI was initialized to 0:", PHP_EOL;
ini_set('opcache.enable', 1);

?>
--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
17 changes: 15 additions & 2 deletions ext/opcache/zend_accelerator_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion ext/openssl/xp_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions ext/soap/php_http.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down
3 changes: 1 addition & 2 deletions ext/standard/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions ext/standard/ftp_fopen_wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion ext/standard/http_fopen_wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
26 changes: 13 additions & 13 deletions ext/uri/php_uri.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion ext/uri/php_uri.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
22 changes: 10 additions & 12 deletions ext/uri/php_uri_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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().
Expand Down Expand Up @@ -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 {
Expand Down
21 changes: 21 additions & 0 deletions ext/uri/tests/057.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Test assigning errors by reference fails
--EXTENSIONS--
uri
--FILE--
<?php

class Foo {
public string $x = '';
}

$f = new Foo();
try {
Uri\WhatWg\Url::parse(" https://example.org ", errors: $f->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
2 changes: 1 addition & 1 deletion ext/uri/uri_parser_rfc3986.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion ext/zend_test/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion main/streams/php_stream_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion main/streams/streams.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading