1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

uri: Fix normalization memory management for uri_parser_php_parse_url.c (#19600)

There were two issues with the previous implementation of normalization:

- `php_raw_url_decode_ex()` would be used to modify a string with RC >1.
- The return value of `php_raw_url_decode_ex()` was not used, resulting in
  incorrect string lengths when percent-encoded characters are decoded.

Additionally there was a bogus assertion that verified that strings returned
from the read handlers are RC =2, which was not the case for the
`parse_url`-based parser when repeatedly retrieving a component even without
normalization happening. Remove that assertion, since its usefulness is
questionable. Any obvious data type issues with read handlers should be
detectable when testing during development.
This commit is contained in:
Tim Düsterhus
2025-08-26 23:44:36 +02:00
committed by GitHub
parent 423960aad3
commit e99f1b4fcc
6 changed files with 149 additions and 29 deletions

View File

@@ -137,11 +137,7 @@ ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_get_property(const uri_interna
return FAILURE;
}
zend_result result = property_handler->read_func(internal_uri, read_mode, zv);
ZEND_ASSERT(result == FAILURE || (Z_TYPE_P(zv) == IS_STRING && GC_REFCOUNT(Z_STR_P(zv)) == 2) || Z_TYPE_P(zv) == IS_NULL || Z_TYPE_P(zv) == IS_LONG);
return result;
return property_handler->read_func(internal_uri, read_mode, zv);
}
ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_uri_get_scheme(const uri_internal_t *internal_uri, uri_component_read_mode_t read_mode, zval *zv)

52
ext/uri/tests/100.phpt Normal file
View File

@@ -0,0 +1,52 @@
--TEST--
Test the parse_url-based URI parser
--EXTENSIONS--
uri
zend_test
--FILE--
<?php
var_dump(zend_test_uri_parser("https://%65xample:%65xample@%65xample.com:123/%65xample.html?%65xample=%65xample#%65xample", "parse_url"));
?>
--EXPECT--
array(2) {
["normalized"]=>
array(8) {
["scheme"]=>
string(5) "https"
["username"]=>
string(7) "example"
["password"]=>
string(7) "example"
["host"]=>
string(11) "example.com"
["port"]=>
int(123)
["path"]=>
string(13) "/example.html"
["query"]=>
string(15) "example=example"
["fragment"]=>
string(7) "example"
}
["raw"]=>
array(8) {
["scheme"]=>
string(5) "https"
["username"]=>
string(9) "%65xample"
["password"]=>
string(9) "%65xample"
["host"]=>
string(13) "%65xample.com"
["port"]=>
int(123)
["path"]=>
string(15) "/%65xample.html"
["query"]=>
string(19) "%65xample=%65xample"
["fragment"]=>
string(9) "%65xample"
}
}

View File

@@ -21,17 +21,21 @@
#include "Zend/zend_exceptions.h"
#include "ext/standard/url.h"
static void decode_component(zval *zv, uri_component_read_mode_t read_mode)
static zend_string *decode_component(zend_string *in, uri_component_read_mode_t read_mode)
{
if (Z_TYPE_P(zv) != IS_STRING) {
return;
}
switch (read_mode) {
case URI_COMPONENT_READ_RAW:
return zend_string_copy(in);
case URI_COMPONENT_READ_NORMALIZED_ASCII:
case URI_COMPONENT_READ_NORMALIZED_UNICODE: {
zend_string *out = zend_string_alloc(ZSTR_LEN(in), false);
if (read_mode == URI_COMPONENT_READ_RAW) {
return;
}
ZSTR_LEN(out) = php_raw_url_decode_ex(ZSTR_VAL(out), ZSTR_VAL(in), ZSTR_LEN(in));
php_raw_url_decode(Z_STRVAL_P(zv), Z_STRLEN_P(zv));
return out;
}
EMPTY_SWITCH_DEFAULT_CASE();
}
}
static zend_result uri_parser_php_parse_url_scheme_read(const uri_internal_t *internal_uri, uri_component_read_mode_t read_mode, zval *retval)
@@ -39,8 +43,7 @@ static zend_result uri_parser_php_parse_url_scheme_read(const uri_internal_t *in
const php_url *parse_url_uri = internal_uri->uri;
if (parse_url_uri->scheme) {
ZVAL_STR_COPY(retval, parse_url_uri->scheme);
decode_component(retval, read_mode);
ZVAL_STR(retval, decode_component(parse_url_uri->scheme, read_mode));
} else {
ZVAL_NULL(retval);
}
@@ -53,8 +56,7 @@ static zend_result uri_parser_php_parse_url_username_read(const uri_internal_t *
const php_url *parse_url_uri = internal_uri->uri;
if (parse_url_uri->user) {
ZVAL_STR_COPY(retval, parse_url_uri->user);
decode_component(retval, read_mode);
ZVAL_STR(retval, decode_component(parse_url_uri->user, read_mode));
} else {
ZVAL_NULL(retval);
}
@@ -67,8 +69,7 @@ static zend_result uri_parser_php_parse_url_password_read(const uri_internal_t *
const php_url *parse_url_uri = internal_uri->uri;
if (parse_url_uri->pass) {
ZVAL_STR_COPY(retval, parse_url_uri->pass);
decode_component(retval, read_mode);
ZVAL_STR(retval, decode_component(parse_url_uri->pass, read_mode));
} else {
ZVAL_NULL(retval);
}
@@ -81,8 +82,7 @@ static zend_result uri_parser_php_parse_url_host_read(const uri_internal_t *inte
const php_url *parse_url_uri = internal_uri->uri;
if (parse_url_uri->host) {
ZVAL_STR_COPY(retval, parse_url_uri->host);
decode_component(retval, read_mode);
ZVAL_STR(retval, decode_component(parse_url_uri->host, read_mode));
} else {
ZVAL_NULL(retval);
}
@@ -96,7 +96,6 @@ static zend_result uri_parser_php_parse_url_port_read(const uri_internal_t *inte
if (parse_url_uri->port) {
ZVAL_LONG(retval, parse_url_uri->port);
decode_component(retval, read_mode);
} else {
ZVAL_NULL(retval);
}
@@ -109,8 +108,7 @@ static zend_result uri_parser_php_parse_url_path_read(const uri_internal_t *inte
const php_url *parse_url_uri = internal_uri->uri;
if (parse_url_uri->path) {
ZVAL_STR_COPY(retval, parse_url_uri->path);
decode_component(retval, read_mode);
ZVAL_STR(retval, decode_component(parse_url_uri->path, read_mode));
} else {
ZVAL_NULL(retval);
}
@@ -123,8 +121,7 @@ static zend_result uri_parser_php_parse_url_query_read(const uri_internal_t *int
const php_url *parse_url_uri = internal_uri->uri;
if (parse_url_uri->query) {
ZVAL_STR_COPY(retval, parse_url_uri->query);
decode_component(retval, read_mode);
ZVAL_STR(retval, decode_component(parse_url_uri->query, read_mode));
} else {
ZVAL_NULL(retval);
}
@@ -137,8 +134,7 @@ static zend_result uri_parser_php_parse_url_fragment_read(const uri_internal_t *
const php_url *parse_url_uri = internal_uri->uri;
if (parse_url_uri->fragment) {
ZVAL_STR_COPY(retval, parse_url_uri->fragment);
decode_component(retval, read_mode);
ZVAL_STR(retval, decode_component(parse_url_uri->fragment, read_mode));
} else {
ZVAL_NULL(retval);
}

View File

@@ -40,6 +40,7 @@
#include "zend_call_stack.h"
#include "zend_exceptions.h"
#include "zend_mm_custom_handlers.h"
#include "ext/uri/php_uri.h"
#if defined(HAVE_LIBXML) && !defined(PHP_WIN32)
# include <libxml/globals.h>
@@ -724,6 +725,72 @@ static ZEND_FUNCTION(zend_test_crash)
php_printf("%s", invalid);
}
static ZEND_FUNCTION(zend_test_uri_parser)
{
zend_string *uri_string;
zend_string *parser_name;
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_STR(uri_string)
Z_PARAM_STR(parser_name)
ZEND_PARSE_PARAMETERS_END();
uri_parser_t *parser = php_uri_get_parser(parser_name);
if (parser == NULL) {
zend_argument_value_error(1, "Unknown parser");
RETURN_THROWS();
}
uri_internal_t *uri = php_uri_parse(parser, ZSTR_VAL(uri_string), ZSTR_LEN(uri_string), false);
if (uri == NULL) {
RETURN_THROWS();
}
zval value;
array_init(return_value);
zval normalized;
array_init(&normalized);
php_uri_get_scheme(uri, URI_COMPONENT_READ_NORMALIZED_ASCII, &value);
zend_hash_add(Z_ARR(normalized), ZSTR_KNOWN(ZEND_STR_SCHEME), &value);
php_uri_get_username(uri, URI_COMPONENT_READ_NORMALIZED_ASCII, &value);
zend_hash_add(Z_ARR(normalized), ZSTR_KNOWN(ZEND_STR_USERNAME), &value);
php_uri_get_password(uri, URI_COMPONENT_READ_NORMALIZED_ASCII, &value);
zend_hash_add(Z_ARR(normalized), ZSTR_KNOWN(ZEND_STR_PASSWORD), &value);
php_uri_get_host(uri, URI_COMPONENT_READ_NORMALIZED_ASCII, &value);
zend_hash_add(Z_ARR(normalized), ZSTR_KNOWN(ZEND_STR_HOST), &value);
php_uri_get_port(uri, URI_COMPONENT_READ_NORMALIZED_ASCII, &value);
zend_hash_add(Z_ARR(normalized), ZSTR_KNOWN(ZEND_STR_PORT), &value);
php_uri_get_path(uri, URI_COMPONENT_READ_NORMALIZED_ASCII, &value);
zend_hash_add(Z_ARR(normalized), ZSTR_KNOWN(ZEND_STR_PATH), &value);
php_uri_get_query(uri, URI_COMPONENT_READ_NORMALIZED_ASCII, &value);
zend_hash_add(Z_ARR(normalized), ZSTR_KNOWN(ZEND_STR_QUERY), &value);
php_uri_get_fragment(uri, URI_COMPONENT_READ_NORMALIZED_ASCII, &value);
zend_hash_add(Z_ARR(normalized), ZSTR_KNOWN(ZEND_STR_FRAGMENT), &value);
zend_hash_str_add(Z_ARR_P(return_value), "normalized", strlen("normalized"), &normalized);
zval raw;
array_init(&raw);
php_uri_get_scheme(uri, URI_COMPONENT_READ_RAW, &value);
zend_hash_add(Z_ARR(raw), ZSTR_KNOWN(ZEND_STR_SCHEME), &value);
php_uri_get_username(uri, URI_COMPONENT_READ_RAW, &value);
zend_hash_add(Z_ARR(raw), ZSTR_KNOWN(ZEND_STR_USERNAME), &value);
php_uri_get_password(uri, URI_COMPONENT_READ_RAW, &value);
zend_hash_add(Z_ARR(raw), ZSTR_KNOWN(ZEND_STR_PASSWORD), &value);
php_uri_get_host(uri, URI_COMPONENT_READ_RAW, &value);
zend_hash_add(Z_ARR(raw), ZSTR_KNOWN(ZEND_STR_HOST), &value);
php_uri_get_port(uri, URI_COMPONENT_READ_RAW, &value);
zend_hash_add(Z_ARR(raw), ZSTR_KNOWN(ZEND_STR_PORT), &value);
php_uri_get_path(uri, URI_COMPONENT_READ_RAW, &value);
zend_hash_add(Z_ARR(raw), ZSTR_KNOWN(ZEND_STR_PATH), &value);
php_uri_get_query(uri, URI_COMPONENT_READ_RAW, &value);
zend_hash_add(Z_ARR(raw), ZSTR_KNOWN(ZEND_STR_QUERY), &value);
php_uri_get_fragment(uri, URI_COMPONENT_READ_RAW, &value);
zend_hash_add(Z_ARR(raw), ZSTR_KNOWN(ZEND_STR_FRAGMENT), &value);
zend_hash_str_add(Z_ARR_P(return_value), "raw", strlen("raw"), &raw);
php_uri_free(uri);
}
static bool has_opline(zend_execute_data *execute_data)
{
return execute_data

View File

@@ -342,6 +342,8 @@ function zend_test_override_libxml_global_state(): void {}
function zend_test_gh18756(): void {}
function zend_test_opcache_preloading(): bool {}
function zend_test_uri_parser(string $uri, string $parser): array { }
}
namespace ZendTestNS {

View File

@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: b767745e4e7be7cb4ba294e238a1b0f63da8479e */
* Stub hash: eb624df6b39083abc81b8636e965370cea9e093f */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, IS_NEVER, 0)
ZEND_END_ARG_INFO()
@@ -190,6 +190,11 @@ ZEND_END_ARG_INFO()
#define arginfo_zend_test_opcache_preloading arginfo_zend_test_is_pcre_bundled
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_uri_parser, 0, 2, IS_ARRAY, 0)
ZEND_ARG_TYPE_INFO(0, uri, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, parser, IS_STRING, 0)
ZEND_END_ARG_INFO()
#define arginfo_ZendTestNS2_namespaced_func arginfo_zend_test_is_pcre_bundled
#define arginfo_ZendTestNS2_namespaced_deprecated_func arginfo_zend_test_void_return
@@ -332,6 +337,7 @@ static ZEND_FUNCTION(zend_test_log_err_debug);
static ZEND_FUNCTION(zend_test_compile_to_ast);
static ZEND_FUNCTION(zend_test_gh18756);
static ZEND_FUNCTION(zend_test_opcache_preloading);
static ZEND_FUNCTION(zend_test_uri_parser);
static ZEND_FUNCTION(ZendTestNS2_namespaced_func);
static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func);
static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func);
@@ -461,6 +467,7 @@ static const zend_function_entry ext_functions[] = {
ZEND_FE(zend_test_compile_to_ast, arginfo_zend_test_compile_to_ast)
ZEND_FE(zend_test_gh18756, arginfo_zend_test_gh18756)
ZEND_FE(zend_test_opcache_preloading, arginfo_zend_test_opcache_preloading)
ZEND_FE(zend_test_uri_parser, arginfo_zend_test_uri_parser)
#if (PHP_VERSION_ID >= 80400)
ZEND_RAW_FENTRY(ZEND_NS_NAME("ZendTestNS2", "namespaced_func"), zif_ZendTestNS2_namespaced_func, arginfo_ZendTestNS2_namespaced_func, 0, NULL, NULL)
#else