Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ PHP NEWS
- Session:
. Fix RC violation of session SID constant deprecation attribute. (ilutov)

- URI:
. Fixed memory management of Uri\WhatWg\Url objects. (timwolla)
. Fixed memory management of the internal "parse_url" URI parser.
(timwolla)
. Clean up naming of internal API. (timwolla)

28 Aug 2025, PHP 8.5.0beta2

- Core:
Expand Down
16 changes: 7 additions & 9 deletions ext/uri/php_uri.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -1089,9 +1085,9 @@ PHP_RINIT_FUNCTION(uri)
return SUCCESS;
}

PHP_RSHUTDOWN_FUNCTION(uri)
ZEND_MODULE_POST_ZEND_DEACTIVATE_D(uri)
{
if (PHP_RSHUTDOWN(uri_parser_whatwg)(INIT_FUNC_ARGS_PASSTHRU) == FAILURE) {
if (ZEND_MODULE_POST_ZEND_DEACTIVATE_N(uri_parser_whatwg)() == FAILURE) {
return FAILURE;
}

Expand All @@ -1106,8 +1102,10 @@ zend_module_entry uri_module_entry = {
PHP_MINIT(uri), /* PHP_MINIT - Module initialization */
PHP_MSHUTDOWN(uri), /* PHP_MSHUTDOWN - Module shutdown */
PHP_RINIT(uri), /* PHP_RINIT - Request initialization */
PHP_RSHUTDOWN(uri), /* PHP_RSHUTDOWN - Request shutdown */
NULL, /* PHP_RSHUTDOWN - Request shutdown */
PHP_MINFO(uri), /* PHP_MINFO - Module info */
PHP_VERSION, /* Version */
STANDARD_MODULE_PROPERTIES
NO_MODULE_GLOBALS,
ZEND_MODULE_POST_ZEND_DEACTIVATE_N(uri),
STANDARD_MODULE_PROPERTIES_EX
};
23 changes: 23 additions & 0 deletions ext/uri/tests/056.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
Test Lexbor memory management
--EXTENSIONS--
uri
--FILE--
<?php

$urls = [];

for ($i = 0; $i < 1000; $i++) {
$urls[] = Uri\WhatWg\Url::parse("https://example.com/{$i}/");
}

for ($i = 0; $i < 1000; $i++) {
if ($urls[$i]->toAsciiString() !== "https://example.com/{$i}/") {
die("FAIL");
}
}

?>
==DONE==
--EXPECT--
==DONE==
52 changes: 52 additions & 0 deletions ext/uri/tests/100.phpt
Original file line number Diff line number Diff line change
@@ -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"
}
}
42 changes: 19 additions & 23 deletions ext/uri/uri_parser_php_parse_url.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,29 @@
#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)
{
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);
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand Down
86 changes: 39 additions & 47 deletions ext/uri/uri_parser_whatwg.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
#include <arpa/inet.h>
#endif

ZEND_TLS lxb_url_parser_t lexbor_parser;
ZEND_TLS unsigned short int parsed_urls;
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};

static const unsigned short int maximum_parses_before_cleanup = 500;
static const size_t lexbor_mraw_byte_size = 8192;

static zend_always_inline void zval_string_or_null_to_lexbor_str(zval *value, lexbor_str_t *lexbor_str)
Expand Down Expand Up @@ -333,17 +333,6 @@ static zend_result php_uri_parser_whatwg_password_write(struct uri_internal_t *i
return SUCCESS;
}

static zend_result init_idna(void)
{
if (lexbor_parser.idna != NULL) {
return SUCCESS;
}

lexbor_parser.idna = lxb_unicode_idna_create();

return lxb_unicode_idna_init(lexbor_parser.idna) == LXB_STATUS_OK ? SUCCESS : FAILURE;
}

static zend_result php_uri_parser_whatwg_host_read(const struct uri_internal_t *internal_uri, uri_component_read_mode_t read_mode, zval *retval)
{
const lxb_url_t *lexbor_uri = internal_uri->uri;
Expand All @@ -368,11 +357,8 @@ static zend_result php_uri_parser_whatwg_host_read(const struct uri_internal_t *
switch (read_mode) {
case URI_COMPONENT_READ_NORMALIZED_UNICODE: {
smart_str host_str = {0};
if (init_idna() == FAILURE) {
return FAILURE;
}
lxb_url_serialize_host_unicode(lexbor_parser.idna, &lexbor_uri->host, serialize_to_smart_str_callback, &host_str);
lxb_unicode_idna_clean(lexbor_parser.idna);
lxb_url_serialize_host_unicode(&lexbor_idna, &lexbor_uri->host, serialize_to_smart_str_callback, &host_str);
lxb_unicode_idna_clean(&lexbor_idna);

ZVAL_NEW_STR(retval, smart_str_extract(&host_str));
break;
Expand Down Expand Up @@ -525,48 +511,54 @@ static zend_result php_uri_parser_whatwg_fragment_write(struct uri_internal_t *i

PHP_RINIT_FUNCTION(uri_parser_whatwg)
{
lexbor_mraw_t *mraw = lexbor_mraw_create();
lxb_status_t status = lexbor_mraw_init(mraw, lexbor_mraw_byte_size);
lxb_status_t status;

status = lexbor_mraw_init(&lexbor_mraw, lexbor_mraw_byte_size);
if (status != LXB_STATUS_OK) {
lexbor_mraw_destroy(mraw, true);
return FAILURE;
goto fail;
}

status = lxb_url_parser_init(&lexbor_parser, mraw);
status = lxb_url_parser_init(&lexbor_parser, &lexbor_mraw);
if (status != LXB_STATUS_OK) {
lxb_url_parser_destroy(&lexbor_parser, false);
lexbor_mraw_destroy(mraw, true);
return FAILURE;
goto fail;
}

parsed_urls = 0;
status = lxb_unicode_idna_init(&lexbor_idna);
if (status != LXB_STATUS_OK) {
goto fail;
}

return SUCCESS;
}

PHP_RSHUTDOWN_FUNCTION(uri_parser_whatwg)
{
lxb_url_parser_memory_destroy(&lexbor_parser);
lxb_url_parser_destroy(&lexbor_parser, false);
fail:

parsed_urls = 0;
/* Unconditionally calling the _destroy() functions is
* safe on a zeroed structure. */
lxb_unicode_idna_destroy(&lexbor_idna, false);
memset(&lexbor_idna, 0, sizeof(lexbor_idna));
lxb_url_parser_destroy(&lexbor_parser, false);
memset(&lexbor_parser, 0, sizeof(lexbor_parser));
lexbor_mraw_destroy(&lexbor_mraw, false);
memset(&lexbor_mraw, 0, sizeof(lexbor_mraw));

return SUCCESS;
return FAILURE;
}

static void reset_parser_state(void)
ZEND_MODULE_POST_ZEND_DEACTIVATE_D(uri_parser_whatwg)
{
if (++parsed_urls % maximum_parses_before_cleanup == 0) {
lexbor_mraw_clean(lexbor_parser.mraw);
parsed_urls = 0;
}
lxb_unicode_idna_destroy(&lexbor_idna, false);
memset(&lexbor_idna, 0, sizeof(lexbor_idna));
lxb_url_parser_destroy(&lexbor_parser, false);
memset(&lexbor_parser, 0, sizeof(lexbor_parser));
lexbor_mraw_destroy(&lexbor_mraw, false);
memset(&lexbor_mraw, 0, sizeof(lexbor_mraw));

lxb_url_parser_clean(&lexbor_parser);
return SUCCESS;
}

lxb_url_t *php_uri_parser_whatwg_parse_ex(const char *uri_str, size_t uri_str_len, const lxb_url_t *lexbor_base_url, zval *errors, bool silent)
{
reset_parser_state();
lxb_url_parser_clean(&lexbor_parser);

lxb_url_t *url = lxb_url_parse(&lexbor_parser, lexbor_base_url, (unsigned char *) uri_str, uri_str_len);
const char *reason = fill_errors(errors);
Expand Down Expand Up @@ -600,11 +592,8 @@ static zend_string *php_uri_parser_whatwg_to_string(void *uri, uri_recomposition
case URI_RECOMPOSITION_RAW_UNICODE:
ZEND_FALLTHROUGH;
case URI_RECOMPOSITION_NORMALIZED_UNICODE:
if (init_idna() == FAILURE) {
return NULL;
}
lxb_url_serialize_idna(lexbor_parser.idna, lexbor_uri, serialize_to_smart_str_callback, &uri_str, exclude_fragment);
lxb_unicode_idna_clean(lexbor_parser.idna);
lxb_url_serialize_idna(&lexbor_idna, lexbor_uri, serialize_to_smart_str_callback, &uri_str, exclude_fragment);
lxb_unicode_idna_clean(&lexbor_idna);
break;
case URI_RECOMPOSITION_RAW_ASCII:
ZEND_FALLTHROUGH;
Expand All @@ -619,6 +608,9 @@ static zend_string *php_uri_parser_whatwg_to_string(void *uri, uri_recomposition

static void php_uri_parser_whatwg_free(void *uri)
{
lxb_url_t *lexbor_uri = uri;

lxb_url_destroy(lexbor_uri);
}

const uri_parser_t php_uri_parser_whatwg = {
Expand Down
2 changes: 1 addition & 1 deletion ext/uri/uri_parser_whatwg.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ extern const uri_parser_t php_uri_parser_whatwg;
lxb_url_t *php_uri_parser_whatwg_parse_ex(const char *uri_str, size_t uri_str_len, const lxb_url_t *lexbor_base_url, zval *errors, bool silent);

PHP_RINIT_FUNCTION(uri_parser_whatwg);
PHP_RSHUTDOWN_FUNCTION(uri_parser_whatwg);
ZEND_MODULE_POST_ZEND_DEACTIVATE_D(uri_parser_whatwg);

#endif
Loading