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

uri: Improve safety of URI object initialization (#19648)

* uri: Inline implementation of `php_uri_implementation_set_object_handlers()`

There is no one time fits all solution to initialization of the object
handlers. A follow-up commit will use distinct `create_object` handlers for
each parser class.

Explicitly spelling out the handlers is a well-established pattern in php-src
and I don't see a reason to diverge from that with an intransparent helper
method.

* uri: Initialize the `.internal` field of `uri_object_t` immediately upon creation

This makes the objects much safer to use, since the `.parser` will always be
available and matching the object.

* uri: Remove `uri_parser_name` parameter of `uri_unserialize()`

The parser for a given object is already known from the object itself and
particularly must never change. Reassigning the value in `uri_unserialize()` is
just unsafe, especially since the existing `->uri` is freed with the destructor
of the reassigned parser.

Just rely on the `->parser` field being set to the correct value.

* uri: Remove the `uri_parser` parameter from `php_uri_instantiate_uri()`

Similarly to the previous change to `uri_unserialize()`, the `->parser` must
always match the object for the freeing to be safe.

Given that we expect to successfully parse URIs, we can eagerly initialize the
resulting URI object when using the `::parse()` methods and destruct it again
when parsing fails and `null` is returned instead. Calling the destructor is
safe, since `uri` will be `NULL`, which will result in a noop.

The `base_url_object` must also match the object that is currently being
constructed. Verify this using assertions matching the `->ce` and the
`->parser`.

* uri: Export the individual object handlers
This commit is contained in:
Tim Düsterhus
2025-09-08 08:55:56 +02:00
committed by GitHub
parent 4e8058e776
commit 4432083f38
3 changed files with 79 additions and 69 deletions

View File

@@ -330,36 +330,9 @@ static zend_result pass_errors_by_ref_and_free(zval *errors_zv, zval *errors)
}
ZEND_ATTRIBUTE_NONNULL_ARGS(1, 2) PHPAPI void php_uri_instantiate_uri(
INTERNAL_FUNCTION_PARAMETERS, const uri_parser_t *uri_parser, const zend_string *uri_str, const zend_object *base_url_object,
INTERNAL_FUNCTION_PARAMETERS, const zend_string *uri_str, const zend_object *base_url_object,
bool should_throw, bool should_update_this_object, zval *errors_zv
) {
zval errors;
ZVAL_UNDEF(&errors);
void *base_url = NULL;
if (base_url_object != NULL) {
uri_internal_t *internal_base_url = uri_internal_from_obj(base_url_object);
URI_ASSERT_INITIALIZATION(internal_base_url);
base_url = internal_base_url->uri;
}
void *uri = uri_parser->parse_uri(ZSTR_VAL(uri_str), ZSTR_LEN(uri_str), base_url, should_throw || errors_zv != NULL ? &errors : NULL, !should_throw);
if (UNEXPECTED(uri == NULL)) {
if (should_throw) {
zval_ptr_dtor(&errors);
RETURN_THROWS();
} else {
if (pass_errors_by_ref_and_free(errors_zv, &errors) == FAILURE) {
RETURN_THROWS();
}
RETURN_NULL();
}
}
if (pass_errors_by_ref_and_free(errors_zv, &errors) == FAILURE) {
uri_parser->free_uri(uri);
RETURN_THROWS();
}
uri_object_t *uri_object;
if (should_update_this_object) {
@@ -373,7 +346,39 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(1, 2) PHPAPI void php_uri_instantiate_uri(
uri_object = Z_URI_OBJECT_P(return_value);
}
uri_object->internal.parser = uri_parser;
const uri_parser_t *uri_parser = uri_object->internal.parser;
zval errors;
ZVAL_UNDEF(&errors);
void *base_url = NULL;
if (base_url_object != NULL) {
ZEND_ASSERT(base_url_object->ce == uri_object->std.ce);
uri_internal_t *internal_base_url = uri_internal_from_obj(base_url_object);
URI_ASSERT_INITIALIZATION(internal_base_url);
ZEND_ASSERT(internal_base_url->parser == uri_parser);
base_url = internal_base_url->uri;
}
void *uri = uri_parser->parse_uri(ZSTR_VAL(uri_str), ZSTR_LEN(uri_str), base_url, should_throw || errors_zv != NULL ? &errors : NULL, !should_throw);
if (UNEXPECTED(uri == NULL)) {
if (should_throw) {
zval_ptr_dtor(&errors);
RETURN_THROWS();
} else {
if (pass_errors_by_ref_and_free(errors_zv, &errors) == FAILURE) {
RETURN_THROWS();
}
zval_ptr_dtor(return_value);
RETURN_NULL();
}
}
if (pass_errors_by_ref_and_free(errors_zv, &errors) == FAILURE) {
uri_parser->free_uri(uri);
RETURN_THROWS();
}
uri_object->internal.uri = uri;
}
@@ -388,7 +393,7 @@ static void create_rfc3986_uri(INTERNAL_FUNCTION_PARAMETERS, bool is_constructor
Z_PARAM_OBJ_OF_CLASS_OR_NULL(base_url_object, uri_rfc3986_uri_ce)
ZEND_PARSE_PARAMETERS_END();
php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, &php_uri_parser_rfc3986, uri_str, base_url_object, is_constructor, is_constructor, NULL);
php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, uri_str, base_url_object, is_constructor, is_constructor, NULL);
}
PHP_METHOD(Uri_Rfc3986_Uri, parse)
@@ -475,7 +480,7 @@ static void create_whatwg_uri(INTERNAL_FUNCTION_PARAMETERS, bool is_constructor)
Z_PARAM_ZVAL(errors)
ZEND_PARSE_PARAMETERS_END();
php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, &php_uri_parser_whatwg, uri_str, base_url_object, is_constructor, is_constructor, errors);
php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, uri_str, base_url_object, is_constructor, is_constructor, errors);
}
PHP_METHOD(Uri_WhatWg_Url, parse)
@@ -756,11 +761,7 @@ PHP_METHOD(Uri_Rfc3986_Uri, resolve)
Z_PARAM_PATH_STR(uri_str)
ZEND_PARSE_PARAMETERS_END();
zend_object *this_object = Z_OBJ_P(ZEND_THIS);
uri_internal_t *internal_uri = uri_internal_from_obj(this_object);
URI_ASSERT_INITIALIZATION(internal_uri);
php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, internal_uri->parser, uri_str, this_object, true, false, NULL);
php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, uri_str, Z_OBJ_P(ZEND_THIS), true, false, NULL);
}
PHP_METHOD(Uri_Rfc3986_Uri, __serialize)
@@ -793,7 +794,7 @@ PHP_METHOD(Uri_Rfc3986_Uri, __serialize)
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &arr);
}
static void uri_unserialize(INTERNAL_FUNCTION_PARAMETERS, const char *uri_parser_name)
static void uri_unserialize(INTERNAL_FUNCTION_PARAMETERS)
{
HashTable *data;
@@ -829,7 +830,6 @@ static void uri_unserialize(INTERNAL_FUNCTION_PARAMETERS, const char *uri_parser
}
uri_internal_t *internal_uri = uri_internal_from_obj(object);
internal_uri->parser = uri_parser_by_name(uri_parser_name, strlen(uri_parser_name));
if (internal_uri->uri != NULL) {
internal_uri->parser->free_uri(internal_uri->uri);
}
@@ -855,7 +855,7 @@ static void uri_unserialize(INTERNAL_FUNCTION_PARAMETERS, const char *uri_parser
PHP_METHOD(Uri_Rfc3986_Uri, __unserialize)
{
uri_unserialize(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PARSER_RFC3986);
uri_unserialize(INTERNAL_FUNCTION_PARAM_PASSTHRU);
}
PHP_METHOD(Uri_Rfc3986_Uri, __debugInfo)
@@ -949,11 +949,7 @@ PHP_METHOD(Uri_WhatWg_Url, resolve)
Z_PARAM_ZVAL(errors)
ZEND_PARSE_PARAMETERS_END();
zend_object *this_object = Z_OBJ_P(ZEND_THIS);
uri_internal_t *internal_uri = uri_internal_from_obj(this_object);
URI_ASSERT_INITIALIZATION(internal_uri);
php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, internal_uri->parser, uri_str, this_object, true, false, errors);
php_uri_instantiate_uri(INTERNAL_FUNCTION_PARAM_PASSTHRU, uri_str, Z_OBJ_P(ZEND_THIS), true, false, errors);
}
PHP_METHOD(Uri_WhatWg_Url, __serialize)
@@ -988,7 +984,7 @@ PHP_METHOD(Uri_WhatWg_Url, __serialize)
PHP_METHOD(Uri_WhatWg_Url, __unserialize)
{
uri_unserialize(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_URI_PARSER_WHATWG);
uri_unserialize(INTERNAL_FUNCTION_PARAM_PASSTHRU);
}
PHP_METHOD(Uri_WhatWg_Url, __debugInfo)
@@ -1000,17 +996,32 @@ PHP_METHOD(Uri_WhatWg_Url, __debugInfo)
RETURN_ARR(uri_get_debug_properties(object));
}
static zend_object *uri_create_object_handler(zend_class_entry *class_type)
PHPAPI uri_object_t *php_uri_object_create(zend_class_entry *class_type, const uri_parser_t *parser)
{
uri_object_t *uri_object = zend_object_alloc(sizeof(*uri_object), class_type);
zend_object_std_init(&uri_object->std, class_type);
object_properties_init(&uri_object->std, class_type);
return &uri_object->std;
uri_object->internal = (uri_internal_t){
.parser = parser,
.uri = NULL,
};
return uri_object;
}
static void uri_free_obj_handler(zend_object *object)
static zend_object *php_uri_object_create_rfc3986(zend_class_entry *ce)
{
return &php_uri_object_create(ce, &php_uri_parser_rfc3986)->std;
}
static zend_object *php_uri_object_create_whatwg(zend_class_entry *ce)
{
return &php_uri_object_create(ce, &php_uri_parser_whatwg)->std;
}
PHPAPI void php_uri_object_handler_free(zend_object *object)
{
uri_object_t *uri_object = uri_object_from_obj(object);
@@ -1023,18 +1034,15 @@ static void uri_free_obj_handler(zend_object *object)
zend_object_std_dtor(&uri_object->std);
}
static zend_object *uri_clone_obj_handler(zend_object *object)
PHPAPI zend_object *php_uri_object_handler_clone(zend_object *object)
{
uri_object_t *uri_object = uri_object_from_obj(object);
uri_internal_t *internal_uri = uri_internal_from_obj(object);
URI_ASSERT_INITIALIZATION(internal_uri);
zend_object *new_object = uri_create_object_handler(object->ce);
ZEND_ASSERT(new_object != NULL);
uri_object_t *new_uri_object = uri_object_from_obj(new_object);
new_uri_object->internal.parser = internal_uri->parser;
uri_object_t *new_uri_object = uri_object_from_obj(object->ce->create_object(object->ce));
ZEND_ASSERT(new_uri_object->internal.parser == internal_uri->parser);
void *uri = internal_uri->parser->clone_uri(internal_uri->uri);
ZEND_ASSERT(uri != NULL);
@@ -1046,16 +1054,6 @@ static zend_object *uri_clone_obj_handler(zend_object *object)
return &new_uri_object->std;
}
ZEND_ATTRIBUTE_NONNULL PHPAPI void php_uri_implementation_set_object_handlers(zend_class_entry *ce, zend_object_handlers *object_handlers)
{
ce->create_object = uri_create_object_handler;
ce->default_object_handlers = object_handlers;
memcpy(object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
object_handlers->offset = XtOffsetOf(uri_object_t, std);
object_handlers->free_obj = uri_free_obj_handler;
object_handlers->clone_obj = uri_clone_obj_handler;
}
PHPAPI zend_result php_uri_parser_register(const uri_parser_t *uri_parser)
{
zend_string *key = zend_string_init_interned(uri_parser->name, strlen(uri_parser->name), true);
@@ -1076,10 +1074,20 @@ PHPAPI zend_result php_uri_parser_register(const uri_parser_t *uri_parser)
static PHP_MINIT_FUNCTION(uri)
{
uri_rfc3986_uri_ce = register_class_Uri_Rfc3986_Uri();
php_uri_implementation_set_object_handlers(uri_rfc3986_uri_ce, &uri_rfc3986_uri_object_handlers);
uri_rfc3986_uri_ce->create_object = php_uri_object_create_rfc3986;
uri_rfc3986_uri_ce->default_object_handlers = &uri_rfc3986_uri_object_handlers;
memcpy(&uri_rfc3986_uri_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
uri_rfc3986_uri_object_handlers.offset = XtOffsetOf(uri_object_t, std);
uri_rfc3986_uri_object_handlers.free_obj = php_uri_object_handler_free;
uri_rfc3986_uri_object_handlers.clone_obj = php_uri_object_handler_clone;
uri_whatwg_url_ce = register_class_Uri_WhatWg_Url();
php_uri_implementation_set_object_handlers(uri_whatwg_url_ce, &uri_whatwg_uri_object_handlers);
uri_whatwg_url_ce->create_object = php_uri_object_create_whatwg;
uri_whatwg_url_ce->default_object_handlers = &uri_whatwg_uri_object_handlers;
memcpy(&uri_whatwg_uri_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
uri_whatwg_uri_object_handlers.offset = XtOffsetOf(uri_object_t, std);
uri_whatwg_uri_object_handlers.free_obj = php_uri_object_handler_free;
uri_whatwg_uri_object_handlers.clone_obj = php_uri_object_handler_clone;
uri_comparison_mode_ce = register_class_Uri_UriComparisonMode();
uri_exception_ce = register_class_Uri_UriException(zend_ce_exception);

View File

@@ -205,10 +205,8 @@ ZEND_ATTRIBUTE_NONNULL PHPAPI php_uri *php_uri_parse_to_struct(
ZEND_ATTRIBUTE_NONNULL PHPAPI void php_uri_struct_free(php_uri *uri);
ZEND_ATTRIBUTE_NONNULL_ARGS(1, 2) PHPAPI void php_uri_instantiate_uri(
INTERNAL_FUNCTION_PARAMETERS, const uri_parser_t *uri_parser, const zend_string *uri_str, const zend_object *base_url_object,
INTERNAL_FUNCTION_PARAMETERS, const zend_string *uri_str, const zend_object *base_url_object,
bool should_throw, bool should_update_this_object, zval *errors_zv
);
ZEND_ATTRIBUTE_NONNULL PHPAPI void php_uri_implementation_set_object_handlers(zend_class_entry *ce, zend_object_handlers *object_handlers);
#endif

View File

@@ -159,6 +159,10 @@ static inline uri_internal_t *uri_internal_from_obj(const zend_object *object) {
#define Z_URI_OBJECT_P(zv) uri_object_from_obj(Z_OBJ_P((zv)))
#define Z_URI_INTERNAL_P(zv) uri_internal_from_obj(Z_OBJ_P((zv)))
PHPAPI uri_object_t *php_uri_object_create(zend_class_entry *class_type, const uri_parser_t *parser);
PHPAPI void php_uri_object_handler_free(zend_object *object);
PHPAPI zend_object *php_uri_object_handler_clone(zend_object *object);
#define PHP_URI_PARSER_RFC3986 "Uri\\Rfc3986\\Uri"
#define PHP_URI_PARSER_WHATWG "Uri\\WhatWg\\Url"
#define PHP_URI_PARSER_PHP_PARSE_URL "parse_url"