From f55d5126d37855fa59d905fb1d511bddcb96fb76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Fri, 29 Aug 2025 22:59:22 +0200 Subject: [PATCH 1/8] Add support for Uri\Rfc3986\Uri withers --- ext/uri/php_uri.c | 96 ++++++++++++++------ ext/uri/php_uri.stub.php | 19 ++++ ext/uri/php_uri_arginfo.h | 82 +++++++++++------ ext/uri/tests/023.phpt | 25 ++++++ ext/uri/tests/026.phpt | 66 +++++++++++++- ext/uri/tests/026_userinfo.phpt | 40 +++++++++ ext/uri/tests/027.phpt | 43 +++++++++ ext/uri/tests/028.phpt | 43 +++++++++ ext/uri/tests/029.phpt | 37 ++++++++ ext/uri/tests/030.phpt | 33 +++++++ ext/uri/uri_parser_rfc3986.c | 154 ++++++++++++++++++++++++++++++-- ext/uri/uri_parser_rfc3986.h | 1 + 12 files changed, 578 insertions(+), 61 deletions(-) create mode 100644 ext/uri/tests/026_userinfo.phpt diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c index 8b6aa5ef05ab2..caa0fc92f2fcf 100644 --- a/ext/uri/php_uri.c +++ b/ext/uri/php_uri.c @@ -498,7 +498,12 @@ PHP_METHOD(Uri_Rfc3986_Uri, getRawScheme) uri_read_component(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_SCHEME, URI_COMPONENT_READ_RAW); } -static void read_uriparser_userinfo(INTERNAL_FUNCTION_PARAMETERS, uri_component_read_mode_t read_mode) +PHP_METHOD(Uri_Rfc3986_Uri, withScheme) +{ + uri_write_component_str_or_null(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_SCHEME); +} + +static void rfc3986_userinfo_read(INTERNAL_FUNCTION_PARAMETERS, uri_component_read_mode_t read_mode) { ZEND_PARSE_PARAMETERS_NONE(); @@ -513,12 +518,48 @@ static void read_uriparser_userinfo(INTERNAL_FUNCTION_PARAMETERS, uri_component_ PHP_METHOD(Uri_Rfc3986_Uri, getUserInfo) { - read_uriparser_userinfo(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_COMPONENT_READ_NORMALIZED_ASCII); + rfc3986_userinfo_read(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_COMPONENT_READ_NORMALIZED_ASCII); } PHP_METHOD(Uri_Rfc3986_Uri, getRawUserInfo) { - read_uriparser_userinfo(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_COMPONENT_READ_RAW); + rfc3986_userinfo_read(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_COMPONENT_READ_RAW); +} + +PHP_METHOD(Uri_Rfc3986_Uri, withUserInfo) +{ + zend_string *value; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_PATH_STR_OR_NULL(value) + ZEND_PARSE_PARAMETERS_END(); + + zval zv; + if (value == NULL) { + ZVAL_NULL(&zv); + } else { + ZVAL_STR(&zv, value); + } + + uri_internal_t *internal_uri = Z_URI_INTERNAL_P(ZEND_THIS); + URI_ASSERT_INITIALIZATION(internal_uri); + + zend_object *new_object = uri_clone_obj_handler(Z_OBJ_P(ZEND_THIS)); + ZEND_ASSERT(new_object != NULL); + + uri_internal_t *new_internal_uri = uri_internal_from_obj(new_object); + URI_ASSERT_INITIALIZATION(new_internal_uri); + + zval errors; + ZVAL_UNDEF(&errors); + if (UNEXPECTED(php_uri_parser_rfc3986_userinfo_write(new_internal_uri, &zv, NULL) == FAILURE)) { + zval_ptr_dtor(&errors); + zend_object_release(new_object); + RETURN_THROWS(); + } + + ZEND_ASSERT(Z_ISUNDEF(errors)); + RETVAL_OBJ(new_object); } PHP_METHOD(Uri_Rfc3986_Uri, getUsername) @@ -551,11 +592,21 @@ PHP_METHOD(Uri_Rfc3986_Uri, getRawHost) uri_read_component(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_HOST, URI_COMPONENT_READ_RAW); } +PHP_METHOD(Uri_Rfc3986_Uri, withHost) +{ + uri_write_component_str_or_null(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_HOST); +} + PHP_METHOD(Uri_Rfc3986_Uri, getPort) { uri_read_component(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_PORT, URI_COMPONENT_READ_RAW); } +PHP_METHOD(Uri_Rfc3986_Uri, withPort) +{ + uri_write_component_long_or_null(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_PORT); +} + PHP_METHOD(Uri_Rfc3986_Uri, getPath) { uri_read_component(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_PATH, URI_COMPONENT_READ_NORMALIZED_ASCII); @@ -566,6 +617,11 @@ PHP_METHOD(Uri_Rfc3986_Uri, getRawPath) uri_read_component(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_PATH, URI_COMPONENT_READ_RAW); } +PHP_METHOD(Uri_Rfc3986_Uri, withPath) +{ + uri_write_component_str(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_PATH); +} + PHP_METHOD(Uri_Rfc3986_Uri, getQuery) { uri_read_component(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_QUERY, URI_COMPONENT_READ_NORMALIZED_ASCII); @@ -576,6 +632,11 @@ PHP_METHOD(Uri_Rfc3986_Uri, getRawQuery) uri_read_component(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_QUERY, URI_COMPONENT_READ_RAW); } +PHP_METHOD(Uri_Rfc3986_Uri, withQuery) +{ + uri_write_component_str_or_null(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_QUERY); +} + PHP_METHOD(Uri_Rfc3986_Uri, getFragment) { uri_read_component(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_FRAGMENT, URI_COMPONENT_READ_NORMALIZED_ASCII); @@ -586,6 +647,11 @@ PHP_METHOD(Uri_Rfc3986_Uri, getRawFragment) uri_read_component(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_FRAGMENT, URI_COMPONENT_READ_RAW); } +PHP_METHOD(Uri_Rfc3986_Uri, withFragment) +{ + uri_write_component_str_or_null(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_FRAGMENT); +} + static void throw_cannot_recompose_uri_to_string(zend_object *object) { zend_throw_exception_ex(NULL, 0, "Cannot recompose %s to a string", ZSTR_VAL(object->ce->name)); @@ -831,29 +897,9 @@ PHP_METHOD(Uri_WhatWg_Url, getUnicodeHost) uri_read_component(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_HOST, URI_COMPONENT_READ_NORMALIZED_UNICODE); } -PHP_METHOD(Uri_WhatWg_Url, withHost) -{ - uri_write_component_str_or_null(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_HOST); -} - -PHP_METHOD(Uri_WhatWg_Url, withPort) -{ - uri_write_component_long_or_null(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_PORT); -} - -PHP_METHOD(Uri_WhatWg_Url, withPath) +PHP_METHOD(Uri_WhatWg_Url, getFragment) { - uri_write_component_str(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_PATH); -} - -PHP_METHOD(Uri_WhatWg_Url, withQuery) -{ - uri_write_component_str_or_null(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_QUERY); -} - -PHP_METHOD(Uri_WhatWg_Url, withFragment) -{ - uri_write_component_str_or_null(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_FRAGMENT); + uri_read_component(INTERNAL_FUNCTION_PARAM_PASSTHRU, URI_PROPERTY_NAME_FRAGMENT, URI_COMPONENT_READ_NORMALIZED_UNICODE); } PHP_METHOD(Uri_WhatWg_Url, equals) diff --git a/ext/uri/php_uri.stub.php b/ext/uri/php_uri.stub.php index 9fbd40d98e5e9..b4063bee8f596 100644 --- a/ext/uri/php_uri.stub.php +++ b/ext/uri/php_uri.stub.php @@ -32,10 +32,14 @@ public function getScheme(): ?string {} public function getRawScheme(): ?string {} + public function withScheme(?string $scheme): static {} + public function getUserInfo(): ?string {} public function getRawUserInfo(): ?string {} + public function withUserInfo(#[\SensitiveParameter] ?string $userinfo): static {} + public function getUsername(): ?string {} public function getRawUsername(): ?string {} @@ -48,20 +52,30 @@ public function getHost(): ?string {} public function getRawHost(): ?string {} + public function withHost(?string $host): static {} + public function getPort(): ?int {} + public function withPort(?int $port): static {} + public function getPath(): string {} public function getRawPath(): string {} + public function withPath(string $path): static {} + public function getQuery(): ?string {} public function getRawQuery(): ?string {} + public function withQuery(?string $query): static {} + public function getFragment(): ?string {} public function getRawFragment(): ?string {} + public function withFragment(?string $fragment): static {} + public function equals(\Uri\Rfc3986\Uri $uri, \Uri\UriComparisonMode $comparisonMode = \Uri\UriComparisonMode::ExcludeFragment): bool {} public function toString(): string {} @@ -157,26 +171,31 @@ public function getAsciiHost(): ?string {} public function getUnicodeHost(): ?string {} + /** @implementation-alias Uri\Rfc3986\Uri::withHost */ public function withHost(?string $host): static {} /** @implementation-alias Uri\Rfc3986\Uri::getPort */ public function getPort(): ?int {} + /** @implementation-alias Uri\Rfc3986\Uri::withPort */ public function withPort(?int $port): static {} /** @implementation-alias Uri\Rfc3986\Uri::getPath */ public function getPath(): string {} + /** @implementation-alias Uri\Rfc3986\Uri::withPath */ public function withPath(string $path): static {} /** @implementation-alias Uri\Rfc3986\Uri::getQuery */ public function getQuery(): ?string {} + /** @implementation-alias Uri\Rfc3986\Uri::withQuery */ public function withQuery(?string $query): static {} /** @implementation-alias Uri\Rfc3986\Uri::getFragment */ public function getFragment(): ?string {} + /** @implementation-alias Uri\Rfc3986\Uri::withFragment */ public function withFragment(?string $fragment): static {} public function equals(\Uri\WhatWg\Url $url, \Uri\UriComparisonMode $comparisonMode = \Uri\UriComparisonMode::ExcludeFragment): bool {} diff --git a/ext/uri/php_uri_arginfo.h b/ext/uri/php_uri_arginfo.h index 65630f113a3d3..12a498357ea28 100644 --- a/ext/uri/php_uri_arginfo.h +++ b/ext/uri/php_uri_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: e2c448000b1e00485bc988f073ea61dfc984e953 */ + * Stub hash: bf37e0babfcc453ab0c75d0e87e142dfa3b5e61e */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_parse, 0, 1, IS_STATIC, 1) ZEND_ARG_TYPE_INFO(0, uri, IS_STRING, 0) @@ -16,10 +16,18 @@ ZEND_END_ARG_INFO() #define arginfo_class_Uri_Rfc3986_Uri_getRawScheme arginfo_class_Uri_Rfc3986_Uri_getScheme +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withScheme, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, scheme, IS_STRING, 1) +ZEND_END_ARG_INFO() + #define arginfo_class_Uri_Rfc3986_Uri_getUserInfo arginfo_class_Uri_Rfc3986_Uri_getScheme #define arginfo_class_Uri_Rfc3986_Uri_getRawUserInfo arginfo_class_Uri_Rfc3986_Uri_getScheme +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withUserInfo, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, userinfo, IS_STRING, 1) +ZEND_END_ARG_INFO() + #define arginfo_class_Uri_Rfc3986_Uri_getUsername arginfo_class_Uri_Rfc3986_Uri_getScheme #define arginfo_class_Uri_Rfc3986_Uri_getRawUsername arginfo_class_Uri_Rfc3986_Uri_getScheme @@ -32,22 +40,42 @@ ZEND_END_ARG_INFO() #define arginfo_class_Uri_Rfc3986_Uri_getRawHost arginfo_class_Uri_Rfc3986_Uri_getScheme +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withHost, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, host, IS_STRING, 1) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_getPort, 0, 0, IS_LONG, 1) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withPort, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, port, IS_LONG, 1) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_getPath, 0, 0, IS_STRING, 0) ZEND_END_ARG_INFO() #define arginfo_class_Uri_Rfc3986_Uri_getRawPath arginfo_class_Uri_Rfc3986_Uri_getPath +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withPath, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0) +ZEND_END_ARG_INFO() + #define arginfo_class_Uri_Rfc3986_Uri_getQuery arginfo_class_Uri_Rfc3986_Uri_getScheme #define arginfo_class_Uri_Rfc3986_Uri_getRawQuery arginfo_class_Uri_Rfc3986_Uri_getScheme +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withQuery, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, query, IS_STRING, 1) +ZEND_END_ARG_INFO() + #define arginfo_class_Uri_Rfc3986_Uri_getFragment arginfo_class_Uri_Rfc3986_Uri_getScheme #define arginfo_class_Uri_Rfc3986_Uri_getRawFragment arginfo_class_Uri_Rfc3986_Uri_getScheme +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withFragment, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, fragment, IS_STRING, 1) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_equals, 0, 1, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, uri, Uri\\Rfc3986\\\125ri, 0) ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, comparisonMode, Uri\\\125riComparisonMode, 0, "Uri\\UriComparisonMode::ExcludeFragment") @@ -117,33 +145,23 @@ ZEND_END_ARG_INFO() #define arginfo_class_Uri_WhatWg_Url_getUnicodeHost arginfo_class_Uri_Rfc3986_Uri_getScheme -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_Url_withHost, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, host, IS_STRING, 1) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_WhatWg_Url_withHost arginfo_class_Uri_Rfc3986_Uri_withHost #define arginfo_class_Uri_WhatWg_Url_getPort arginfo_class_Uri_Rfc3986_Uri_getPort -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_Url_withPort, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, port, IS_LONG, 1) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_WhatWg_Url_withPort arginfo_class_Uri_Rfc3986_Uri_withPort #define arginfo_class_Uri_WhatWg_Url_getPath arginfo_class_Uri_Rfc3986_Uri_getPath -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_Url_withPath, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_WhatWg_Url_withPath arginfo_class_Uri_Rfc3986_Uri_withPath #define arginfo_class_Uri_WhatWg_Url_getQuery arginfo_class_Uri_Rfc3986_Uri_getScheme -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_Url_withQuery, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, query, IS_STRING, 1) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_WhatWg_Url_withQuery arginfo_class_Uri_Rfc3986_Uri_withQuery #define arginfo_class_Uri_WhatWg_Url_getFragment arginfo_class_Uri_Rfc3986_Uri_getScheme -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_Url_withFragment, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, fragment, IS_STRING, 1) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_WhatWg_Url_withFragment arginfo_class_Uri_Rfc3986_Uri_withFragment ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_Url_equals, 0, 1, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, url, Uri\\WhatWg\\\125rl, 0) @@ -169,21 +187,28 @@ ZEND_METHOD(Uri_Rfc3986_Uri, parse); ZEND_METHOD(Uri_Rfc3986_Uri, __construct); ZEND_METHOD(Uri_Rfc3986_Uri, getScheme); ZEND_METHOD(Uri_Rfc3986_Uri, getRawScheme); +ZEND_METHOD(Uri_Rfc3986_Uri, withScheme); ZEND_METHOD(Uri_Rfc3986_Uri, getUserInfo); ZEND_METHOD(Uri_Rfc3986_Uri, getRawUserInfo); +ZEND_METHOD(Uri_Rfc3986_Uri, withUserInfo); ZEND_METHOD(Uri_Rfc3986_Uri, getUsername); ZEND_METHOD(Uri_Rfc3986_Uri, getRawUsername); ZEND_METHOD(Uri_Rfc3986_Uri, getPassword); ZEND_METHOD(Uri_Rfc3986_Uri, getRawPassword); ZEND_METHOD(Uri_Rfc3986_Uri, getHost); ZEND_METHOD(Uri_Rfc3986_Uri, getRawHost); +ZEND_METHOD(Uri_Rfc3986_Uri, withHost); ZEND_METHOD(Uri_Rfc3986_Uri, getPort); +ZEND_METHOD(Uri_Rfc3986_Uri, withPort); ZEND_METHOD(Uri_Rfc3986_Uri, getPath); ZEND_METHOD(Uri_Rfc3986_Uri, getRawPath); +ZEND_METHOD(Uri_Rfc3986_Uri, withPath); ZEND_METHOD(Uri_Rfc3986_Uri, getQuery); ZEND_METHOD(Uri_Rfc3986_Uri, getRawQuery); +ZEND_METHOD(Uri_Rfc3986_Uri, withQuery); ZEND_METHOD(Uri_Rfc3986_Uri, getFragment); ZEND_METHOD(Uri_Rfc3986_Uri, getRawFragment); +ZEND_METHOD(Uri_Rfc3986_Uri, withFragment); ZEND_METHOD(Uri_Rfc3986_Uri, equals); ZEND_METHOD(Uri_Rfc3986_Uri, toString); ZEND_METHOD(Uri_Rfc3986_Uri, toRawString); @@ -201,11 +226,6 @@ ZEND_METHOD(Uri_WhatWg_Url, withUsername); ZEND_METHOD(Uri_WhatWg_Url, withPassword); ZEND_METHOD(Uri_WhatWg_Url, getAsciiHost); ZEND_METHOD(Uri_WhatWg_Url, getUnicodeHost); -ZEND_METHOD(Uri_WhatWg_Url, withHost); -ZEND_METHOD(Uri_WhatWg_Url, withPort); -ZEND_METHOD(Uri_WhatWg_Url, withPath); -ZEND_METHOD(Uri_WhatWg_Url, withQuery); -ZEND_METHOD(Uri_WhatWg_Url, withFragment); ZEND_METHOD(Uri_WhatWg_Url, equals); ZEND_METHOD(Uri_WhatWg_Url, toAsciiString); ZEND_METHOD(Uri_WhatWg_Url, toUnicodeString); @@ -219,21 +239,28 @@ static const zend_function_entry class_Uri_Rfc3986_Uri_methods[] = { ZEND_ME(Uri_Rfc3986_Uri, __construct, arginfo_class_Uri_Rfc3986_Uri___construct, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, getScheme, arginfo_class_Uri_Rfc3986_Uri_getScheme, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, getRawScheme, arginfo_class_Uri_Rfc3986_Uri_getRawScheme, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_Uri, withScheme, arginfo_class_Uri_Rfc3986_Uri_withScheme, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, getUserInfo, arginfo_class_Uri_Rfc3986_Uri_getUserInfo, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, getRawUserInfo, arginfo_class_Uri_Rfc3986_Uri_getRawUserInfo, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_Uri, withUserInfo, arginfo_class_Uri_Rfc3986_Uri_withUserInfo, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, getUsername, arginfo_class_Uri_Rfc3986_Uri_getUsername, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, getRawUsername, arginfo_class_Uri_Rfc3986_Uri_getRawUsername, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, getPassword, arginfo_class_Uri_Rfc3986_Uri_getPassword, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, getRawPassword, arginfo_class_Uri_Rfc3986_Uri_getRawPassword, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, getHost, arginfo_class_Uri_Rfc3986_Uri_getHost, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, getRawHost, arginfo_class_Uri_Rfc3986_Uri_getRawHost, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_Uri, withHost, arginfo_class_Uri_Rfc3986_Uri_withHost, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, getPort, arginfo_class_Uri_Rfc3986_Uri_getPort, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_Uri, withPort, arginfo_class_Uri_Rfc3986_Uri_withPort, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, getPath, arginfo_class_Uri_Rfc3986_Uri_getPath, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, getRawPath, arginfo_class_Uri_Rfc3986_Uri_getRawPath, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_Uri, withPath, arginfo_class_Uri_Rfc3986_Uri_withPath, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, getQuery, arginfo_class_Uri_Rfc3986_Uri_getQuery, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, getRawQuery, arginfo_class_Uri_Rfc3986_Uri_getRawQuery, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_Uri, withQuery, arginfo_class_Uri_Rfc3986_Uri_withQuery, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, getFragment, arginfo_class_Uri_Rfc3986_Uri_getFragment, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, getRawFragment, arginfo_class_Uri_Rfc3986_Uri_getRawFragment, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_Uri, withFragment, arginfo_class_Uri_Rfc3986_Uri_withFragment, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, equals, arginfo_class_Uri_Rfc3986_Uri_equals, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, toString, arginfo_class_Uri_Rfc3986_Uri_toString, ZEND_ACC_PUBLIC) ZEND_ME(Uri_Rfc3986_Uri, toRawString, arginfo_class_Uri_Rfc3986_Uri_toRawString, ZEND_ACC_PUBLIC) @@ -265,15 +292,15 @@ static const zend_function_entry class_Uri_WhatWg_Url_methods[] = { ZEND_ME(Uri_WhatWg_Url, withPassword, arginfo_class_Uri_WhatWg_Url_withPassword, ZEND_ACC_PUBLIC) ZEND_ME(Uri_WhatWg_Url, getAsciiHost, arginfo_class_Uri_WhatWg_Url_getAsciiHost, ZEND_ACC_PUBLIC) ZEND_ME(Uri_WhatWg_Url, getUnicodeHost, arginfo_class_Uri_WhatWg_Url_getUnicodeHost, ZEND_ACC_PUBLIC) - ZEND_ME(Uri_WhatWg_Url, withHost, arginfo_class_Uri_WhatWg_Url_withHost, ZEND_ACC_PUBLIC) + ZEND_RAW_FENTRY("withHost", zim_Uri_Rfc3986_Uri_withHost, arginfo_class_Uri_WhatWg_Url_withHost, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("getPort", zim_Uri_Rfc3986_Uri_getPort, arginfo_class_Uri_WhatWg_Url_getPort, ZEND_ACC_PUBLIC, NULL, NULL) - ZEND_ME(Uri_WhatWg_Url, withPort, arginfo_class_Uri_WhatWg_Url_withPort, ZEND_ACC_PUBLIC) + ZEND_RAW_FENTRY("withPort", zim_Uri_Rfc3986_Uri_withPort, arginfo_class_Uri_WhatWg_Url_withPort, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("getPath", zim_Uri_Rfc3986_Uri_getPath, arginfo_class_Uri_WhatWg_Url_getPath, ZEND_ACC_PUBLIC, NULL, NULL) - ZEND_ME(Uri_WhatWg_Url, withPath, arginfo_class_Uri_WhatWg_Url_withPath, ZEND_ACC_PUBLIC) + ZEND_RAW_FENTRY("withPath", zim_Uri_Rfc3986_Uri_withPath, arginfo_class_Uri_WhatWg_Url_withPath, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("getQuery", zim_Uri_Rfc3986_Uri_getQuery, arginfo_class_Uri_WhatWg_Url_getQuery, ZEND_ACC_PUBLIC, NULL, NULL) - ZEND_ME(Uri_WhatWg_Url, withQuery, arginfo_class_Uri_WhatWg_Url_withQuery, ZEND_ACC_PUBLIC) + ZEND_RAW_FENTRY("withQuery", zim_Uri_Rfc3986_Uri_withQuery, arginfo_class_Uri_WhatWg_Url_withQuery, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("getFragment", zim_Uri_Rfc3986_Uri_getFragment, arginfo_class_Uri_WhatWg_Url_getFragment, ZEND_ACC_PUBLIC, NULL, NULL) - ZEND_ME(Uri_WhatWg_Url, withFragment, arginfo_class_Uri_WhatWg_Url_withFragment, ZEND_ACC_PUBLIC) + ZEND_RAW_FENTRY("withFragment", zim_Uri_Rfc3986_Uri_withFragment, arginfo_class_Uri_WhatWg_Url_withFragment, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_ME(Uri_WhatWg_Url, equals, arginfo_class_Uri_WhatWg_Url_equals, ZEND_ACC_PUBLIC) ZEND_ME(Uri_WhatWg_Url, toAsciiString, arginfo_class_Uri_WhatWg_Url_toAsciiString, ZEND_ACC_PUBLIC) ZEND_ME(Uri_WhatWg_Url, toUnicodeString, arginfo_class_Uri_WhatWg_Url_toUnicodeString, ZEND_ACC_PUBLIC) @@ -322,6 +349,9 @@ static zend_class_entry *register_class_Uri_Rfc3986_Uri(void) INIT_NS_CLASS_ENTRY(ce, "Uri\\Rfc3986", "Uri", class_Uri_Rfc3986_Uri_methods); class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_READONLY_CLASS); + + zend_add_parameter_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "withuserinfo", sizeof("withuserinfo") - 1), 0, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); + return class_entry; } diff --git a/ext/uri/tests/023.phpt b/ext/uri/tests/023.phpt index a1ca06bd6f6e5..e3a1324685219 100644 --- a/ext/uri/tests/023.phpt +++ b/ext/uri/tests/023.phpt @@ -5,6 +5,26 @@ uri --FILE-- withScheme("http"); +$uri3 = $uri2->withScheme(null); + +var_dump($uri1->getScheme()); +var_dump($uri2->getScheme()); +var_dump($uri3->getScheme()); + +try { + $uri3->withScheme(""); +} catch (Uri\InvalidUriException $e) { + echo $e->getMessage() . "\n"; +} + +try { + $uri3->withScheme("http%73"); +} catch (Uri\InvalidUriException $e) { + echo $e->getMessage() . "\n"; +} + $url1 = Uri\WhatWg\Url::parse("https://example.com"); $url2 = $url1->withScheme("http"); @@ -27,5 +47,10 @@ try { --EXPECT-- string(5) "https" string(4) "http" +NULL +The specified scheme is malformed +The specified scheme is malformed +string(5) "https" +string(4) "http" The specified scheme is malformed The specified scheme is malformed diff --git a/ext/uri/tests/026.phpt b/ext/uri/tests/026.phpt index 47a8597fa2e7d..2d55667108cf7 100644 --- a/ext/uri/tests/026.phpt +++ b/ext/uri/tests/026.phpt @@ -5,19 +5,67 @@ uri --FILE-- withHost("test.com"); +$uri3 = $uri2->withHost("t%65st.com"); // test.com +$uri4 = $uri3->withHost(null); + +var_dump($uri1->getHost()); +var_dump($uri2->getHost()); +var_dump($uri3->getHost()); +var_dump($uri3->getRawHost()); +var_dump($uri4->getRawHost()); + +try { + $uri3->withHost("test.com:8080"); +} catch (Uri\InvalidUriException $e) { + echo $e->getMessage() . "\n"; +} + +try { + $uri3->withHost("t%3As%2Ft.com"); // t:s/t.com +} catch (Uri\InvalidUriException $e) { + echo $e->getMessage() . "\n"; +} + +try { + $uri3->withHost("t:s/t.com"); // t:s/t.com +} catch (Uri\InvalidUriException $e) { + echo $e->getMessage() . "\n"; +} + +try { + $uri2->withHost(""); +} catch (Uri\InvalidUriException $e) { + echo $e->getMessage() . "\n"; +} + +$uri1 = Uri\Rfc3986\Uri::parse("ftp://user:pass@foo.com?query=abc#foo"); +$uri2 = $uri1->withHost("test.com"); + +var_dump($uri1->getHost()); +var_dump($uri2->getHost()); + +try { + $uri1->withHost(null); +} catch (Uri\InvalidUriException $e) { + echo $e->getMessage() . "\n"; +} + $url1 = Uri\WhatWg\Url::parse("https://example.com"); $url2 = $url1->withHost("test.com"); $url3 = $url2->withHost("t%65st.com"); // test.com + +var_dump($url1->getAsciiHost()); +var_dump($url2->getAsciiHost()); +var_dump($url3->getAsciiHost()); + try { $url3->withHost("test.com:8080"); } catch (Uri\WhatWg\InvalidUrlException $e) { echo $e->getMessage() . "\n"; } -var_dump($url1->getAsciiHost()); -var_dump($url2->getAsciiHost()); -var_dump($url3->getAsciiHost()); - try { $url3->withHost("t%3As%2Ft.com"); // t:s/t.com } catch (Uri\WhatWg\InvalidUrlException $e) { @@ -44,10 +92,20 @@ var_dump($url2->getAsciiHost()); ?> --EXPECTF-- +string(11) "example.com" +string(8) "test.com" +string(8) "test.com" +string(10) "t%65st.com" +NULL +The specified host is malformed +The specified host is malformed +string(7) "foo.com" +string(8) "test.com" The specified host is malformed string(11) "example.com" string(8) "test.com" string(8) "test.com" +The specified host is malformed The specified host is malformed (DomainInvalidCodePoint) The specified host is malformed The specified host is malformed (HostMissing) diff --git a/ext/uri/tests/026_userinfo.phpt b/ext/uri/tests/026_userinfo.phpt new file mode 100644 index 0000000000000..b84523ff1d1dd --- /dev/null +++ b/ext/uri/tests/026_userinfo.phpt @@ -0,0 +1,40 @@ +--TEST-- +Test property mutation - userinfo +--EXTENSIONS-- +uri +--FILE-- +withUserInfo("user"); +$uri3 = $uri2->withUserInfo(null); +$uri4 = $uri3->withUserInfo("%75s%2Fr:pass"); // us/r:pass + +var_dump($uri1->getUserInfo()); +var_dump($uri2->getUserInfo()); +var_dump($uri3->getUserInfo()); +var_dump($uri4->getUserInfo()); +var_dump($uri4->getRawUserInfo()); + +try { + $uri4->withUserInfo("u:s/r"); +} catch (Uri\InvalidUriException $e) { + echo $e->getMessage() . "\n"; +} + +$uri5 = Uri\Rfc3986\Uri::parse("file:///foo/bar/"); +$uri6 = $uri5->withUserinfo("user:pass"); + +var_dump($uri5->getUserInfo()); +var_dump($uri6->getUserInfo()); + +?> +--EXPECT-- +NULL +string(4) "user" +NULL +string(11) "us%2Fr:pass" +string(13) "%75s%2Fr:pass" +The specified userinfo is malformed +NULL +string(9) "user:pass" diff --git a/ext/uri/tests/027.phpt b/ext/uri/tests/027.phpt index 79c121dd7f383..51ed796c403e7 100644 --- a/ext/uri/tests/027.phpt +++ b/ext/uri/tests/027.phpt @@ -5,6 +5,40 @@ uri --FILE-- withPort(22); +$uri3 = $uri2->withPort(null); + +var_dump($uri1->getPort()); +var_dump($uri2->getPort()); +var_dump($uri3->getPort()); + +$uri1 = Uri\Rfc3986\Uri::parse("ftp://foo.com:443?query=abc#foo"); +$uri2 = $uri1->withPort(8080); + +var_dump($uri1->getPort()); +var_dump($uri2->getPort()); + +$uri1 = Uri\Rfc3986\Uri::parse("file:///foo/bar"); +$uri2 = $uri1->withPort(80); + +var_dump($uri1->getPort()); +var_dump($uri2->getPort()); + +$uri1 = Uri\Rfc3986\Uri::parse("/foo"); + +try { + $uri1->withPort(null); // TODO should not throw +} catch (Uri\InvalidUriException $e) { + echo $e->getMessage() . "\n"; +} + +try { + $uri1->withPort(1); +} catch (Uri\InvalidUriException $e) { + echo $e->getMessage() . "\n"; +} + $url1 = Uri\WhatWg\Url::parse("https://example.com:8080"); $url2 = $url1->withPort(22); $url3 = $url2->withPort(null); @@ -33,4 +67,13 @@ NULL int(443) int(8080) NULL +int(80) +The specified port is malformed +The specified port is malformed +int(8080) +int(22) +NULL +int(443) +int(8080) +NULL NULL diff --git a/ext/uri/tests/028.phpt b/ext/uri/tests/028.phpt index fd565c900e02f..80f331c259379 100644 --- a/ext/uri/tests/028.phpt +++ b/ext/uri/tests/028.phpt @@ -5,6 +5,38 @@ uri --FILE-- withPath("/foo"); +$uri3 = $uri2->withPath("/t%65st"); +$uri4 = $uri3->withPath("/foo%2Fbar"); +$uri5 = $uri4->withPath(""); + +var_dump($uri1->getPath()); +var_dump($uri2->getPath()); +var_dump($uri3->getPath()); +var_dump($uri3->getRawPath()); +var_dump($uri4->getPath()); +var_dump($uri4->getRawPath()); +var_dump($uri5->getPath()); + +try { + $uri5->withPath("test"); +} catch (Uri\InvalidUriException $e) { + echo $e->getMessage() . "\n"; +} + +try { + $uri5->withPath("/#"); +} catch (Uri\InvalidUriException $e) { + echo $e->getMessage() . "\n"; +} + +$uri1 = Uri\Rfc3986\Uri::parse("/foo"); +$uri2 = $uri1->withPath("bar"); + +var_dump($uri1->getPath()); +var_dump($uri2->getPath()); + $url1 = Uri\WhatWg\Url::parse("https://example.com/foo/bar/"); $url2 = $url1->withPath("/foo"); $url3 = $url2->withPath(""); @@ -29,6 +61,17 @@ var_dump($url2->getPath()); --EXPECT-- string(9) "/foo/bar/" string(4) "/foo" +string(5) "/test" +string(7) "/t%65st" +string(10) "/foo%2Fbar" +string(10) "/foo%2Fbar" +string(0) "" +The specified path is malformed +The specified path is malformed +string(4) "/foo" +string(3) "bar" +string(9) "/foo/bar/" +string(4) "/foo" string(1) "/" string(7) "/t%65st" string(10) "/foo%2Fbar" diff --git a/ext/uri/tests/029.phpt b/ext/uri/tests/029.phpt index e23008a65ad6a..99bb15eba6454 100644 --- a/ext/uri/tests/029.phpt +++ b/ext/uri/tests/029.phpt @@ -5,6 +5,33 @@ uri --FILE-- withQuery("foo=baz"); +$uri3 = $uri2->withQuery(null); + +var_dump($uri1->getQuery()); +var_dump($uri2->getQuery()); +var_dump($uri3->getQuery()); + +$uri1 = Uri\Rfc3986\Uri::parse("https://example.com"); +$uri2 = $uri1->withQuery("?foo=bar&foo=baz"); +$uri3 = $uri1->withQuery("foo=bar&foo=baz"); +$uri4 = $uri3->withQuery("t%65st"); +$uri5 = $uri4->withQuery("foo=foo%26bar&baz=/qux%3D"); + +var_dump($uri1->getQuery()); +var_dump($uri2->getQuery()); +var_dump($uri3->getQuery()); +var_dump($uri4->getQuery()); +var_dump($uri4->getRawQuery()); +var_dump($uri5->getQuery()); + +try { + $uri5->withQuery("#"); +} catch (Uri\InvalidUriException $e) { + echo $e->getMessage() . "\n"; +} + $url1 = Uri\WhatWg\Url::parse("https://example.com?foo=bar"); $url2 = $url1->withQuery("?foo=baz"); $url3 = $url2->withQuery(null); @@ -33,6 +60,16 @@ string(7) "foo=bar" string(7) "foo=baz" NULL NULL +string(16) "?foo=bar&foo=baz" +string(15) "foo=bar&foo=baz" +string(4) "test" +string(6) "t%65st" +string(25) "foo=foo%26bar&baz=/qux%3D" +The specified query is malformed +string(7) "foo=bar" +string(7) "foo=baz" +NULL +NULL string(15) "foo=bar&foo=baz" string(15) "foo=bar&foo=baz" string(6) "t%65st" diff --git a/ext/uri/tests/030.phpt b/ext/uri/tests/030.phpt index 6bb85e6720c95..87714069b2882 100644 --- a/ext/uri/tests/030.phpt +++ b/ext/uri/tests/030.phpt @@ -5,6 +5,32 @@ uri --FILE-- withFragment("fragment2"); +$uri3 = $uri2->withFragment(null); + +var_dump($uri1->getFragment()); +var_dump($uri2->getFragment()); +var_dump($uri3->getFragment()); + +try { + $uri3->withFragment(" "); +} catch (Uri\InvalidUriException $e) { + echo $e->getMessage() . "\n"; +} + +try { + $uri1->withFragment("#fragment2"); +} catch (Uri\InvalidUriException $e) { + echo $e->getMessage() . "\n"; +} + +$uri1 = Uri\Rfc3986\Uri::parse("https://example.com?abc=def"); +$uri2 = $uri1->withFragment("fragment"); + +var_dump($uri1->getFragment()); +var_dump($uri2->getFragment()); + $url1 = Uri\WhatWg\Url::parse("https://example.com#fragment1"); $url2 = $url1->withFragment("#fragment2"); $url3 = $url2->withFragment(null); @@ -26,6 +52,13 @@ var_dump($url2->getFragment()); string(9) "fragment1" string(9) "fragment2" NULL +The specified fragment is malformed +The specified fragment is malformed +NULL +string(8) "fragment" +string(9) "fragment1" +string(9) "fragment2" +NULL string(3) "%20" NULL string(8) "fragment" diff --git a/ext/uri/uri_parser_rfc3986.c b/ext/uri/uri_parser_rfc3986.c index 70e0eaae1f0a0..3feb4db7fb02d 100644 --- a/ext/uri/uri_parser_rfc3986.c +++ b/ext/uri/uri_parser_rfc3986.c @@ -99,6 +99,13 @@ ZEND_ATTRIBUTE_NONNULL static UriUriA *get_uri_for_reading(php_uri_parser_rfc398 } } +ZEND_ATTRIBUTE_NONNULL static UriUriA *get_uri_for_writing(uri_internal_t *internal_uri) +{ + php_uri_parser_rfc3986_uris *uriparser_uris = internal_uri->uri; + + return &uriparser_uris->uri; +} + ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_scheme_read(const uri_internal_t *internal_uri, uri_component_read_mode_t read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(internal_uri->uri, read_mode); @@ -112,6 +119,25 @@ ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_scheme_read(con return SUCCESS; } +static zend_result php_uri_parser_rfc3986_scheme_write(struct uri_internal_t *internal_uri, zval *value, zval *errors) +{ + UriUriA *uriparser_uri = get_uri_for_writing(internal_uri); + int result; + + if (Z_TYPE_P(value) == IS_NULL) { + result = uriSetSchemeMmA(uriparser_uri, NULL, NULL, mm); + } else { + result = uriSetSchemeMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm); + } + + if (result != URI_SUCCESS) { + zend_throw_exception(uri_invalid_uri_exception_ce, "The specified scheme is malformed", 0); + return FAILURE; + } + + return SUCCESS; +} + ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_userinfo_read(const uri_internal_t *internal_uri, uri_component_read_mode_t read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(internal_uri->uri, read_mode); @@ -125,6 +151,26 @@ ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_userinfo_read(const ur return SUCCESS; } +zend_result php_uri_parser_rfc3986_userinfo_write(struct uri_internal_t *internal_uri, zval *value, zval *errors) +{ + UriUriA *uriparser_uri = get_uri_for_writing(internal_uri); + int result; + + if (Z_TYPE_P(value) == IS_NULL) { + result = uriSetUserInfoMmA(uriparser_uri, NULL, NULL, mm); + } else { + result = uriSetUserInfoMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm); + } + + if (result != URI_SUCCESS) { + zend_throw_exception(uri_invalid_uri_exception_ce, "The specified userinfo is malformed", 0); + return FAILURE; + } + + return SUCCESS; +} + + ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_username_read(const uri_internal_t *internal_uri, uri_component_read_mode_t read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(internal_uri->uri, read_mode); @@ -190,6 +236,25 @@ ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_host_read(const return SUCCESS; } +static zend_result php_uri_parser_rfc3986_host_write(struct uri_internal_t *internal_uri, zval *value, zval *errors) +{ + UriUriA *uriparser_uri = get_uri_for_writing(internal_uri); + int result; + + if (Z_TYPE_P(value) == IS_NULL) { + result = uriSetHostRegNameMmA(uriparser_uri, NULL, NULL, mm); + } else { + result = uriSetHostRegNameMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm); + } + + if (result != URI_SUCCESS) { + zend_throw_exception(uri_invalid_uri_exception_ce, "The specified host is malformed", 0); + return FAILURE; + } + + return SUCCESS; +} + ZEND_ATTRIBUTE_NONNULL static zend_long port_str_to_zend_long_checked(const char *str, size_t len) { if (len > MAX_LENGTH_OF_LONG) { @@ -221,6 +286,26 @@ ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_port_read(const return SUCCESS; } +static zend_result php_uri_parser_rfc3986_port_write(struct uri_internal_t *internal_uri, zval *value, zval *errors) +{ + UriUriA *uriparser_uri = get_uri_for_writing(internal_uri); + int result; + + if (Z_TYPE_P(value) == IS_NULL) { + result = uriSetPortTextMmA(uriparser_uri, NULL, NULL, mm); + } else { + ZVAL_STR(value, zend_long_to_str(Z_LVAL_P(value))); + result = uriSetPortTextMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm); + } + + if (result != URI_SUCCESS) { + zend_throw_exception(uri_invalid_uri_exception_ce, "The specified port is malformed", 0); + return FAILURE; + } + + return SUCCESS; +} + ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_path_read(const uri_internal_t *internal_uri, uri_component_read_mode_t read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(internal_uri->uri, read_mode); @@ -249,6 +334,25 @@ ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_path_read(const return SUCCESS; } +static zend_result php_uri_parser_rfc3986_path_write(struct uri_internal_t *internal_uri, zval *value, zval *errors) +{ + UriUriA *uriparser_uri = get_uri_for_writing(internal_uri); + int result; + + if (Z_STRLEN_P(value) == 0) { + result = uriSetPathMmA(uriparser_uri, NULL, NULL, mm); + } else { + result = uriSetPathMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm); + } + + if (result != URI_SUCCESS) { + zend_throw_exception(uri_invalid_uri_exception_ce, "The specified path is malformed", 0); + return FAILURE; + } + + return SUCCESS; +} + ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_query_read(const uri_internal_t *internal_uri, uri_component_read_mode_t read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(internal_uri->uri, read_mode); @@ -262,6 +366,25 @@ ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_query_read(cons return SUCCESS; } +static zend_result php_uri_parser_rfc3986_query_write(struct uri_internal_t *internal_uri, zval *value, zval *errors) +{ + UriUriA *uriparser_uri = get_uri_for_writing(internal_uri); + int result; + + if (Z_TYPE_P(value) == IS_NULL) { + result = uriSetQueryMmA(uriparser_uri, NULL, NULL, mm); + } else { + result = uriSetQueryMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm); + } + + if (result != URI_SUCCESS) { + zend_throw_exception(uri_invalid_uri_exception_ce, "The specified query is malformed", 0); + return FAILURE; + } + + return SUCCESS; +} + ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_fragment_read(const uri_internal_t *internal_uri, uri_component_read_mode_t read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(internal_uri->uri, read_mode); @@ -275,6 +398,25 @@ ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_fragment_read(c return SUCCESS; } +static zend_result php_uri_parser_rfc3986_fragment_write(struct uri_internal_t *internal_uri, zval *value, zval *errors) +{ + UriUriA *uriparser_uri = get_uri_for_writing(internal_uri); + int result; + + if (Z_TYPE_P(value) == IS_NULL) { + result = uriSetFragmentMmA(uriparser_uri, NULL, NULL, mm); + } else { + result = uriSetFragmentMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm); + } + + if (result != URI_SUCCESS) { + zend_throw_exception(uri_invalid_uri_exception_ce, "The specified fragment is malformed", 0); + return FAILURE; + } + + return SUCCESS; +} + static php_uri_parser_rfc3986_uris *uriparser_create_uris(void) { php_uri_parser_rfc3986_uris *uriparser_uris = ecalloc(1, sizeof(*uriparser_uris)); @@ -414,13 +556,13 @@ const uri_parser_t php_uri_parser_rfc3986 = { .uri_to_string = php_uri_parser_rfc3986_to_string, .free_uri = php_uri_parser_rfc3986_free, { - .scheme = {.read_func = php_uri_parser_rfc3986_scheme_read, .write_func = NULL}, + .scheme = {.read_func = php_uri_parser_rfc3986_scheme_read, .write_func = php_uri_parser_rfc3986_scheme_write}, .username = {.read_func = php_uri_parser_rfc3986_username_read, .write_func = NULL}, .password = {.read_func = php_uri_parser_rfc3986_password_read, .write_func = NULL}, - .host = {.read_func = php_uri_parser_rfc3986_host_read, .write_func = NULL}, - .port = {.read_func = php_uri_parser_rfc3986_port_read, .write_func = NULL}, - .path = {.read_func = php_uri_parser_rfc3986_path_read, .write_func = NULL}, - .query = {.read_func = php_uri_parser_rfc3986_query_read, .write_func = NULL}, - .fragment = {.read_func = php_uri_parser_rfc3986_fragment_read, .write_func = NULL}, + .host = {.read_func = php_uri_parser_rfc3986_host_read, .write_func = php_uri_parser_rfc3986_host_write}, + .port = {.read_func = php_uri_parser_rfc3986_port_read, .write_func = php_uri_parser_rfc3986_port_write}, + .path = {.read_func = php_uri_parser_rfc3986_path_read, .write_func = php_uri_parser_rfc3986_path_write}, + .query = {.read_func = php_uri_parser_rfc3986_query_read, .write_func = php_uri_parser_rfc3986_query_write}, + .fragment = {.read_func = php_uri_parser_rfc3986_fragment_read, .write_func = php_uri_parser_rfc3986_fragment_write}, } }; diff --git a/ext/uri/uri_parser_rfc3986.h b/ext/uri/uri_parser_rfc3986.h index 461136a9f06cc..7f54b194ec366 100644 --- a/ext/uri/uri_parser_rfc3986.h +++ b/ext/uri/uri_parser_rfc3986.h @@ -29,6 +29,7 @@ typedef struct php_uri_parser_rfc3986_uris { } php_uri_parser_rfc3986_uris; zend_result php_uri_parser_rfc3986_userinfo_read(const uri_internal_t *internal_uri, uri_component_read_mode_t read_mode, zval *retval); +zend_result php_uri_parser_rfc3986_userinfo_write(struct uri_internal_t *internal_uri, zval *value, zval *errors); php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_parse_ex(const char *uri_str, size_t uri_str_len, const php_uri_parser_rfc3986_uris *uriparser_base_url, bool silent); From 2259b64167cd00a0e07693ac0194be1346d51a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Fri, 29 Aug 2025 23:39:16 +0200 Subject: [PATCH 2/8] Fix normalization --- ext/uri/tests/057.phpt | 2 +- ext/uri/uri_parser_rfc3986.c | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/ext/uri/tests/057.phpt b/ext/uri/tests/057.phpt index e2a109ccdacb9..458bdb468e341 100644 --- a/ext/uri/tests/057.phpt +++ b/ext/uri/tests/057.phpt @@ -14,7 +14,7 @@ try { Uri\WhatWg\Url::parse(" https://example.org ", errors: $f->x); } catch (Throwable $e) { echo $e::class, ": ", $e->getMessage(), PHP_EOL; -} +} ?> --EXPECT-- diff --git a/ext/uri/uri_parser_rfc3986.c b/ext/uri/uri_parser_rfc3986.c index 3feb4db7fb02d..0cacef730c867 100644 --- a/ext/uri/uri_parser_rfc3986.c +++ b/ext/uri/uri_parser_rfc3986.c @@ -106,6 +106,14 @@ ZEND_ATTRIBUTE_NONNULL static UriUriA *get_uri_for_writing(uri_internal_t *inter return &uriparser_uris->uri; } +ZEND_ATTRIBUTE_NONNULL static void reset_normalized_uri_after_writing(uri_internal_t *internal_uri) +{ + php_uri_parser_rfc3986_uris *uriparser_uris = internal_uri->uri; + + uriFreeUriMembersMmA(&uriparser_uris->normalized_uri, mm); + uriparser_uris->normalized_uri_initialized = false; +} + ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_scheme_read(const uri_internal_t *internal_uri, uri_component_read_mode_t read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(internal_uri->uri, read_mode); @@ -135,6 +143,8 @@ static zend_result php_uri_parser_rfc3986_scheme_write(struct uri_internal_t *in return FAILURE; } + reset_normalized_uri_after_writing(internal_uri); + return SUCCESS; } @@ -167,10 +177,11 @@ zend_result php_uri_parser_rfc3986_userinfo_write(struct uri_internal_t *interna return FAILURE; } + reset_normalized_uri_after_writing(internal_uri); + return SUCCESS; } - ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_username_read(const uri_internal_t *internal_uri, uri_component_read_mode_t read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(internal_uri->uri, read_mode); @@ -252,6 +263,8 @@ static zend_result php_uri_parser_rfc3986_host_write(struct uri_internal_t *inte return FAILURE; } + reset_normalized_uri_after_writing(internal_uri); + return SUCCESS; } @@ -303,6 +316,8 @@ static zend_result php_uri_parser_rfc3986_port_write(struct uri_internal_t *inte return FAILURE; } + reset_normalized_uri_after_writing(internal_uri); + return SUCCESS; } @@ -350,6 +365,8 @@ static zend_result php_uri_parser_rfc3986_path_write(struct uri_internal_t *inte return FAILURE; } + reset_normalized_uri_after_writing(internal_uri); + return SUCCESS; } @@ -382,6 +399,8 @@ static zend_result php_uri_parser_rfc3986_query_write(struct uri_internal_t *int return FAILURE; } + reset_normalized_uri_after_writing(internal_uri); + return SUCCESS; } @@ -414,6 +433,8 @@ static zend_result php_uri_parser_rfc3986_fragment_write(struct uri_internal_t * return FAILURE; } + reset_normalized_uri_after_writing(internal_uri); + return SUCCESS; } From c22ffa1728a041e52cdb67fcb4ae3e84d7fa7a3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Mon, 1 Sep 2025 23:39:33 +0200 Subject: [PATCH 3/8] Update uriparser --- ext/uri/tests/026.phpt | 12 ++++++++++++ ext/uri/tests/035.phpt | 8 ++++++++ ext/uri/uri_parser_rfc3986.c | 4 ++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/ext/uri/tests/026.phpt b/ext/uri/tests/026.phpt index 2d55667108cf7..ef4da2a28d9a6 100644 --- a/ext/uri/tests/026.phpt +++ b/ext/uri/tests/026.phpt @@ -9,12 +9,16 @@ $uri1 = Uri\Rfc3986\Uri::parse("https://example.com"); $uri2 = $uri1->withHost("test.com"); $uri3 = $uri2->withHost("t%65st.com"); // test.com $uri4 = $uri3->withHost(null); +$uri5 = $uri4->withHost("192.168.0.1"); +$uri6 = $uri5->withHost("[2001:db8:3333:4444:5555:6666:7777:8888]"); var_dump($uri1->getHost()); var_dump($uri2->getHost()); var_dump($uri3->getHost()); var_dump($uri3->getRawHost()); var_dump($uri4->getRawHost()); +var_dump($uri5->getRawHost()); +var_dump($uri6->getRawHost()); try { $uri3->withHost("test.com:8080"); @@ -55,10 +59,14 @@ try { $url1 = Uri\WhatWg\Url::parse("https://example.com"); $url2 = $url1->withHost("test.com"); $url3 = $url2->withHost("t%65st.com"); // test.com +$url4 = $url3->withHost("192.168.0.1"); +$url5 = $url4->withHost("[2001:db8:3333:4444:5555:6666:7777:8888]"); var_dump($url1->getAsciiHost()); var_dump($url2->getAsciiHost()); var_dump($url3->getAsciiHost()); +var_dump($url4->getAsciiHost()); +var_dump($url5->getAsciiHost()); try { $url3->withHost("test.com:8080"); @@ -97,6 +105,8 @@ string(8) "test.com" string(8) "test.com" string(10) "t%65st.com" NULL +string(11) "192.168.0.1" +string(40) "[2001:db8:3333:4444:5555:6666:7777:8888]" The specified host is malformed The specified host is malformed string(7) "foo.com" @@ -105,6 +115,8 @@ The specified host is malformed string(11) "example.com" string(8) "test.com" string(8) "test.com" +string(11) "192.168.0.1" +string(40) "[2001:db8:3333:4444:5555:6666:7777:8888]" The specified host is malformed The specified host is malformed (DomainInvalidCodePoint) The specified host is malformed diff --git a/ext/uri/tests/035.phpt b/ext/uri/tests/035.phpt index 2712038d4e3d7..3c39870373742 100644 --- a/ext/uri/tests/035.phpt +++ b/ext/uri/tests/035.phpt @@ -11,6 +11,13 @@ try { echo $e->getMessage() . "\n"; } +$uri = new Uri\Rfc3986\Uri("https://example.com"); +try { + $uri->withHost("exam\0ple.com"); +} catch (Error $e) { + echo $e->getMessage() . "\n"; +} + try { new Uri\WhatWg\Url("https://exam\0ple.com"); } catch (Error $e) { @@ -27,5 +34,6 @@ try { ?> --EXPECT-- Uri\Rfc3986\Uri::__construct(): Argument #1 ($uri) must not contain any null bytes +Uri\Rfc3986\Uri::withHost(): Argument #1 ($host) must not contain any null bytes Uri\WhatWg\Url::__construct(): Argument #1 ($uri) must not contain any null bytes Uri\WhatWg\Url::withHost(): Argument #1 ($host) must not contain any null bytes diff --git a/ext/uri/uri_parser_rfc3986.c b/ext/uri/uri_parser_rfc3986.c index 0cacef730c867..90e9c8b157d03 100644 --- a/ext/uri/uri_parser_rfc3986.c +++ b/ext/uri/uri_parser_rfc3986.c @@ -253,9 +253,9 @@ static zend_result php_uri_parser_rfc3986_host_write(struct uri_internal_t *inte int result; if (Z_TYPE_P(value) == IS_NULL) { - result = uriSetHostRegNameMmA(uriparser_uri, NULL, NULL, mm); + result = uriSetHostAutoMmA(uriparser_uri, NULL, NULL, mm); } else { - result = uriSetHostRegNameMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm); + result = uriSetHostAutoMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm); } if (result != URI_SUCCESS) { From b34c8c627a531ac0c631bf98f0e31833eaeb5fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Tue, 2 Sep 2025 21:39:11 +0200 Subject: [PATCH 4/8] Update uriparser + verify behavior --- ext/uri/tests/026_userinfo.phpt | 5 +++++ ext/uri/tests/027.phpt | 10 +++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ext/uri/tests/026_userinfo.phpt b/ext/uri/tests/026_userinfo.phpt index b84523ff1d1dd..2eabc81061ef2 100644 --- a/ext/uri/tests/026_userinfo.phpt +++ b/ext/uri/tests/026_userinfo.phpt @@ -16,6 +16,10 @@ var_dump($uri3->getUserInfo()); var_dump($uri4->getUserInfo()); var_dump($uri4->getRawUserInfo()); +$uri1 = Uri\Rfc3986\Uri::parse("/foo"); +$uri2 = $uri1->withUserInfo(null); +var_dump($uri2->getPort()); + try { $uri4->withUserInfo("u:s/r"); } catch (Uri\InvalidUriException $e) { @@ -35,6 +39,7 @@ string(4) "user" NULL string(11) "us%2Fr:pass" string(13) "%75s%2Fr:pass" +NULL The specified userinfo is malformed NULL string(9) "user:pass" diff --git a/ext/uri/tests/027.phpt b/ext/uri/tests/027.phpt index 51ed796c403e7..fbf36a46aacdf 100644 --- a/ext/uri/tests/027.phpt +++ b/ext/uri/tests/027.phpt @@ -26,12 +26,8 @@ var_dump($uri1->getPort()); var_dump($uri2->getPort()); $uri1 = Uri\Rfc3986\Uri::parse("/foo"); - -try { - $uri1->withPort(null); // TODO should not throw -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; -} +$uri2 = $uri1->withPort(null); +var_dump($uri2->getPort()); try { $uri1->withPort(1); @@ -68,7 +64,7 @@ int(443) int(8080) NULL int(80) -The specified port is malformed +NULL The specified port is malformed int(8080) int(22) From 3794154b998f002c87a60c91550dc98e709513b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Tue, 2 Sep 2025 22:00:55 +0200 Subject: [PATCH 5/8] Some review fixes --- ext/uri/tests/026.phpt | 2 +- ext/uri/tests/027.phpt | 2 +- ext/uri/uri_parser_rfc3986.c | 63 ++++++++++++++++++++++-------------- 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/ext/uri/tests/026.phpt b/ext/uri/tests/026.phpt index ef4da2a28d9a6..00c679ab87057 100644 --- a/ext/uri/tests/026.phpt +++ b/ext/uri/tests/026.phpt @@ -111,7 +111,7 @@ The specified host is malformed The specified host is malformed string(7) "foo.com" string(8) "test.com" -The specified host is malformed +Cannot remove the host from a URI that has a userinfo string(11) "example.com" string(8) "test.com" string(8) "test.com" diff --git a/ext/uri/tests/027.phpt b/ext/uri/tests/027.phpt index fbf36a46aacdf..781932f5889ea 100644 --- a/ext/uri/tests/027.phpt +++ b/ext/uri/tests/027.phpt @@ -65,7 +65,7 @@ int(8080) NULL int(80) NULL -The specified port is malformed +Cannot set a port without having a host int(8080) int(22) NULL diff --git a/ext/uri/uri_parser_rfc3986.c b/ext/uri/uri_parser_rfc3986.c index 90e9c8b157d03..34569544a1ba6 100644 --- a/ext/uri/uri_parser_rfc3986.c +++ b/ext/uri/uri_parser_rfc3986.c @@ -172,14 +172,17 @@ zend_result php_uri_parser_rfc3986_userinfo_write(struct uri_internal_t *interna result = uriSetUserInfoMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm); } - if (result != URI_SUCCESS) { - zend_throw_exception(uri_invalid_uri_exception_ce, "The specified userinfo is malformed", 0); - return FAILURE; + switch (result) { + case URI_SUCCESS: + reset_normalized_uri_after_writing(internal_uri); + return SUCCESS; + case URI_ERROR_SETUSERINFO_HOST_NOT_SET: + zend_throw_exception(uri_invalid_uri_exception_ce, "Cannot set a userinfo without having a host", 0); + return FAILURE; + default: + zend_throw_exception(uri_invalid_uri_exception_ce, "The specified userinfo is malformed", 0); + return FAILURE; } - - reset_normalized_uri_after_writing(internal_uri); - - return SUCCESS; } ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_username_read(const uri_internal_t *internal_uri, uri_component_read_mode_t read_mode, zval *retval) @@ -258,14 +261,20 @@ static zend_result php_uri_parser_rfc3986_host_write(struct uri_internal_t *inte result = uriSetHostAutoMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm); } - if (result != URI_SUCCESS) { - zend_throw_exception(uri_invalid_uri_exception_ce, "The specified host is malformed", 0); - return FAILURE; + switch (result) { + case URI_SUCCESS: + reset_normalized_uri_after_writing(internal_uri); + return SUCCESS; + case URI_ERROR_SETHOST_PORT_SET: + zend_throw_exception(uri_invalid_uri_exception_ce, "Cannot remove the host from a URI that has a port", 0); + return FAILURE; + case URI_ERROR_SETHOST_USERINFO_SET: + zend_throw_exception(uri_invalid_uri_exception_ce, "Cannot remove the host from a URI that has a userinfo", 0); + return FAILURE; + default: + zend_throw_exception(uri_invalid_uri_exception_ce, "The specified host is malformed", 0); + return FAILURE; } - - reset_normalized_uri_after_writing(internal_uri); - - return SUCCESS; } ZEND_ATTRIBUTE_NONNULL static zend_long port_str_to_zend_long_checked(const char *str, size_t len) @@ -307,18 +316,22 @@ static zend_result php_uri_parser_rfc3986_port_write(struct uri_internal_t *inte if (Z_TYPE_P(value) == IS_NULL) { result = uriSetPortTextMmA(uriparser_uri, NULL, NULL, mm); } else { - ZVAL_STR(value, zend_long_to_str(Z_LVAL_P(value))); - result = uriSetPortTextMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm); - } - - if (result != URI_SUCCESS) { - zend_throw_exception(uri_invalid_uri_exception_ce, "The specified port is malformed", 0); - return FAILURE; + zend_string *tmp = zend_long_to_str(Z_LVAL_P(value)); + result = uriSetPortTextMmA(uriparser_uri, ZSTR_VAL(tmp), ZSTR_VAL(tmp) + ZSTR_LEN(tmp), mm); + zend_string_release(tmp); + } + + switch (result) { + case URI_SUCCESS: + reset_normalized_uri_after_writing(internal_uri); + return SUCCESS; + case URI_ERROR_SETPORT_HOST_NOT_SET: + zend_throw_exception(uri_invalid_uri_exception_ce, "Cannot set a port without having a host", 0); + return FAILURE; + default: + zend_throw_exception(uri_invalid_uri_exception_ce, "The specified port is malformed", 0); + return FAILURE; } - - reset_normalized_uri_after_writing(internal_uri); - - return SUCCESS; } ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_path_read(const uri_internal_t *internal_uri, uri_component_read_mode_t read_mode, zval *retval) From eb2e9cb5e9109d46776d0031ae2d90d3c35b3c5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Fri, 5 Sep 2025 22:45:06 +0200 Subject: [PATCH 6/8] Correctly assert normalization after RFC 3986 URI modification --- ext/uri/tests/023.phpt | 13 ++++++++--- ext/uri/tests/026.phpt | 28 ++++++++++++++++++------ ext/uri/tests/026_userinfo.phpt | 20 +++++++++++------ ext/uri/tests/028.phpt | 25 +++++++++++++++------- ext/uri/tests/029.phpt | 38 ++++++++++++++++++++++++--------- ext/uri/tests/030.phpt | 13 ++++++++--- 6 files changed, 100 insertions(+), 37 deletions(-) diff --git a/ext/uri/tests/023.phpt b/ext/uri/tests/023.phpt index e3a1324685219..d1b97eaa9ae5c 100644 --- a/ext/uri/tests/023.phpt +++ b/ext/uri/tests/023.phpt @@ -6,11 +6,15 @@ uri withScheme("http"); -$uri3 = $uri2->withScheme(null); - +var_dump($uri1->getRawScheme()); var_dump($uri1->getScheme()); + +$uri2 = $uri1->withScheme("http"); +var_dump($uri2->getRawScheme()); var_dump($uri2->getScheme()); + +$uri3 = $uri2->withScheme(null); +var_dump($uri3->getRawScheme()); var_dump($uri3->getScheme()); try { @@ -46,7 +50,10 @@ try { ?> --EXPECT-- string(5) "https" +string(5) "https" string(4) "http" +string(4) "http" +NULL NULL The specified scheme is malformed The specified scheme is malformed diff --git a/ext/uri/tests/026.phpt b/ext/uri/tests/026.phpt index 00c679ab87057..ba4e18935b042 100644 --- a/ext/uri/tests/026.phpt +++ b/ext/uri/tests/026.phpt @@ -6,19 +6,28 @@ uri withHost("test.com"); -$uri3 = $uri2->withHost("t%65st.com"); // test.com -$uri4 = $uri3->withHost(null); -$uri5 = $uri4->withHost("192.168.0.1"); -$uri6 = $uri5->withHost("[2001:db8:3333:4444:5555:6666:7777:8888]"); - +var_dump($uri1->getRawHost()); var_dump($uri1->getHost()); + +$uri2 = $uri1->withHost("test.com"); +var_dump($uri2->getRawHost()); var_dump($uri2->getHost()); -var_dump($uri3->getHost()); + +$uri3 = $uri2->withHost("t%65st.com"); // test.com var_dump($uri3->getRawHost()); +var_dump($uri3->getHost()); + +$uri4 = $uri3->withHost(null); var_dump($uri4->getRawHost()); +var_dump($uri4->getHost()); + +$uri5 = $uri4->withHost("192.168.0.1"); var_dump($uri5->getRawHost()); +var_dump($uri5->getHost()); + +$uri6 = $uri5->withHost("[2001:db8:3333:4444:5555:6666:7777:8888]"); var_dump($uri6->getRawHost()); +var_dump($uri6->getHost()); try { $uri3->withHost("test.com:8080"); @@ -101,11 +110,16 @@ var_dump($url2->getAsciiHost()); ?> --EXPECTF-- string(11) "example.com" +string(11) "example.com" string(8) "test.com" string(8) "test.com" string(10) "t%65st.com" +string(8) "test.com" +NULL NULL string(11) "192.168.0.1" +string(11) "192.168.0.1" +string(40) "[2001:db8:3333:4444:5555:6666:7777:8888]" string(40) "[2001:db8:3333:4444:5555:6666:7777:8888]" The specified host is malformed The specified host is malformed diff --git a/ext/uri/tests/026_userinfo.phpt b/ext/uri/tests/026_userinfo.phpt index 2eabc81061ef2..75f54fccf6124 100644 --- a/ext/uri/tests/026_userinfo.phpt +++ b/ext/uri/tests/026_userinfo.phpt @@ -6,15 +6,20 @@ uri withUserInfo("user"); -$uri3 = $uri2->withUserInfo(null); -$uri4 = $uri3->withUserInfo("%75s%2Fr:pass"); // us/r:pass - +var_dump($uri1->getRawUserInfo()); var_dump($uri1->getUserInfo()); + +$uri2 = $uri1->withUserInfo("user"); +var_dump($uri2->getRawUserInfo()); var_dump($uri2->getUserInfo()); + +$uri3 = $uri2->withUserInfo(null); +var_dump($uri3->getRawUserInfo()); var_dump($uri3->getUserInfo()); -var_dump($uri4->getUserInfo()); + +$uri4 = $uri3->withUserInfo("%75s%2Fr:pass"); // us/r:pass var_dump($uri4->getRawUserInfo()); +var_dump($uri4->getUserInfo()); $uri1 = Uri\Rfc3986\Uri::parse("/foo"); $uri2 = $uri1->withUserInfo(null); @@ -35,10 +40,13 @@ var_dump($uri6->getUserInfo()); ?> --EXPECT-- NULL +NULL +string(4) "user" string(4) "user" NULL -string(11) "us%2Fr:pass" +NULL string(13) "%75s%2Fr:pass" +string(11) "us%2Fr:pass" NULL The specified userinfo is malformed NULL diff --git a/ext/uri/tests/028.phpt b/ext/uri/tests/028.phpt index 80f331c259379..9c9c92c00cc0f 100644 --- a/ext/uri/tests/028.phpt +++ b/ext/uri/tests/028.phpt @@ -6,17 +6,23 @@ uri withPath("/foo"); -$uri3 = $uri2->withPath("/t%65st"); -$uri4 = $uri3->withPath("/foo%2Fbar"); -$uri5 = $uri4->withPath(""); - +var_dump($uri1->getRawPath()); var_dump($uri1->getPath()); + +$uri2 = $uri1->withPath("/foo"); +var_dump($uri2->getRawPath()); var_dump($uri2->getPath()); -var_dump($uri3->getPath()); + +$uri3 = $uri2->withPath("/t%65st"); var_dump($uri3->getRawPath()); -var_dump($uri4->getPath()); +var_dump($uri3->getPath()); + +$uri4 = $uri3->withPath("/foo%2Fbar"); var_dump($uri4->getRawPath()); +var_dump($uri4->getPath()); + +$uri5 = $uri4->withPath(""); +var_dump($uri5->getRawPath()); var_dump($uri5->getPath()); try { @@ -60,12 +66,15 @@ var_dump($url2->getPath()); ?> --EXPECT-- string(9) "/foo/bar/" +string(9) "/foo/bar/" +string(4) "/foo" string(4) "/foo" -string(5) "/test" string(7) "/t%65st" +string(5) "/test" string(10) "/foo%2Fbar" string(10) "/foo%2Fbar" string(0) "" +string(0) "" The specified path is malformed The specified path is malformed string(4) "/foo" diff --git a/ext/uri/tests/029.phpt b/ext/uri/tests/029.phpt index 99bb15eba6454..f7a1a0989910d 100644 --- a/ext/uri/tests/029.phpt +++ b/ext/uri/tests/029.phpt @@ -6,24 +6,35 @@ uri withQuery("foo=baz"); -$uri3 = $uri2->withQuery(null); - +var_dump($uri1->getRawQuery()); var_dump($uri1->getQuery()); + +$uri2 = $uri1->withQuery("foo=baz"); +var_dump($uri2->getRawQuery()); var_dump($uri2->getQuery()); + +$uri3 = $uri2->withQuery(null); +var_dump($uri3->getRawQuery()); var_dump($uri3->getQuery()); $uri1 = Uri\Rfc3986\Uri::parse("https://example.com"); -$uri2 = $uri1->withQuery("?foo=bar&foo=baz"); -$uri3 = $uri1->withQuery("foo=bar&foo=baz"); -$uri4 = $uri3->withQuery("t%65st"); -$uri5 = $uri4->withQuery("foo=foo%26bar&baz=/qux%3D"); - +var_dump($uri1->getRawQuery()); var_dump($uri1->getQuery()); + +$uri2 = $uri1->withQuery("?foo=bar&foo=baz"); +var_dump($uri2->getRawQuery()); var_dump($uri2->getQuery()); + +$uri3 = $uri1->withQuery("foo=bar&foo=baz"); +var_dump($uri3->getRawQuery()); var_dump($uri3->getQuery()); -var_dump($uri4->getQuery()); + +$uri4 = $uri3->withQuery("t%65st"); var_dump($uri4->getRawQuery()); +var_dump($uri4->getQuery()); + +$uri5 = $uri4->withQuery("foo=foo%26bar&baz=/qux%3D"); +var_dump($uri5->getRawQuery()); var_dump($uri5->getQuery()); try { @@ -57,13 +68,20 @@ var_dump($url6->getQuery()); ?> --EXPECT-- string(7) "foo=bar" +string(7) "foo=bar" +string(7) "foo=baz" string(7) "foo=baz" NULL NULL +NULL +NULL +string(16) "?foo=bar&foo=baz" string(16) "?foo=bar&foo=baz" string(15) "foo=bar&foo=baz" -string(4) "test" +string(15) "foo=bar&foo=baz" string(6) "t%65st" +string(4) "test" +string(25) "foo=foo%26bar&baz=/qux%3D" string(25) "foo=foo%26bar&baz=/qux%3D" The specified query is malformed string(7) "foo=bar" diff --git a/ext/uri/tests/030.phpt b/ext/uri/tests/030.phpt index 87714069b2882..92e99d01f8929 100644 --- a/ext/uri/tests/030.phpt +++ b/ext/uri/tests/030.phpt @@ -6,11 +6,15 @@ uri withFragment("fragment2"); -$uri3 = $uri2->withFragment(null); - +var_dump($uri1->getRawFragment()); var_dump($uri1->getFragment()); + +$uri2 = $uri1->withFragment("fragment2"); +var_dump($uri2->getRawFragment()); var_dump($uri2->getFragment()); + +$uri3 = $uri2->withFragment(null); +var_dump($uri3->getRawFragment()); var_dump($uri3->getFragment()); try { @@ -50,7 +54,10 @@ var_dump($url2->getFragment()); ?> --EXPECT-- string(9) "fragment1" +string(9) "fragment1" string(9) "fragment2" +string(9) "fragment2" +NULL NULL The specified fragment is malformed The specified fragment is malformed From a81ba2ee2b3d8ea062ca7bee35cd981ca25d7340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Sat, 6 Sep 2025 14:12:29 +0200 Subject: [PATCH 7/8] Remove unused variable --- ext/uri/php_uri.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c index caa0fc92f2fcf..5340558fc4d7f 100644 --- a/ext/uri/php_uri.c +++ b/ext/uri/php_uri.c @@ -550,15 +550,11 @@ PHP_METHOD(Uri_Rfc3986_Uri, withUserInfo) uri_internal_t *new_internal_uri = uri_internal_from_obj(new_object); URI_ASSERT_INITIALIZATION(new_internal_uri); - zval errors; - ZVAL_UNDEF(&errors); if (UNEXPECTED(php_uri_parser_rfc3986_userinfo_write(new_internal_uri, &zv, NULL) == FAILURE)) { - zval_ptr_dtor(&errors); zend_object_release(new_object); RETURN_THROWS(); } - ZEND_ASSERT(Z_ISUNDEF(errors)); RETVAL_OBJ(new_object); } From 509b6d54b3fe961ee07ca0ef4255660420726714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Sun, 7 Sep 2025 23:14:30 +0200 Subject: [PATCH 8/8] Review fixes --- NEWS | 1 + ext/uri/php_uri.c | 14 +++++++++----- ext/uri/tests/026.phpt | 2 +- ext/uri/uri_parser_rfc3986.c | 21 +-------------------- 4 files changed, 12 insertions(+), 26 deletions(-) diff --git a/NEWS b/NEWS index 2954fa75d3895..1c7b4b8f5e8ed 100644 --- a/NEWS +++ b/NEWS @@ -64,6 +64,7 @@ PHP NEWS . Avoid double conversion to string in php_userstreamop_readdir(). (nielsdos) - URI: + . Added support for Uri\Rfc3986\Uri::with*() methods. (kocsismate) . Fixed memory management of Uri\WhatWg\Url objects. (timwolla) . Fixed memory management of the internal "parse_url" URI parser. (timwolla) diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c index 5340558fc4d7f..3aff91efc6588 100644 --- a/ext/uri/php_uri.c +++ b/ext/uri/php_uri.c @@ -541,21 +541,25 @@ PHP_METHOD(Uri_Rfc3986_Uri, withUserInfo) ZVAL_STR(&zv, value); } + zend_object *old_object = Z_OBJ_P(ZEND_THIS); uri_internal_t *internal_uri = Z_URI_INTERNAL_P(ZEND_THIS); URI_ASSERT_INITIALIZATION(internal_uri); - zend_object *new_object = uri_clone_obj_handler(Z_OBJ_P(ZEND_THIS)); - ZEND_ASSERT(new_object != NULL); + zend_object *new_object = old_object->handlers->clone_obj(old_object); + if (new_object == NULL) { + RETURN_THROWS(); + } + + /* Assign the object early. The engine will take care of destruction in + * case of an exception being thrown. */ + RETVAL_OBJ(new_object); uri_internal_t *new_internal_uri = uri_internal_from_obj(new_object); URI_ASSERT_INITIALIZATION(new_internal_uri); if (UNEXPECTED(php_uri_parser_rfc3986_userinfo_write(new_internal_uri, &zv, NULL) == FAILURE)) { - zend_object_release(new_object); RETURN_THROWS(); } - - RETVAL_OBJ(new_object); } PHP_METHOD(Uri_Rfc3986_Uri, getUsername) diff --git a/ext/uri/tests/026.phpt b/ext/uri/tests/026.phpt index ba4e18935b042..2f50e41bb8c1f 100644 --- a/ext/uri/tests/026.phpt +++ b/ext/uri/tests/026.phpt @@ -42,7 +42,7 @@ try { } try { - $uri3->withHost("t:s/t.com"); // t:s/t.com + $uri3->withHost("t:s/t.com"); } catch (Uri\InvalidUriException $e) { echo $e->getMessage() . "\n"; } diff --git a/ext/uri/uri_parser_rfc3986.c b/ext/uri/uri_parser_rfc3986.c index 34569544a1ba6..01f89f4ccc7c7 100644 --- a/ext/uri/uri_parser_rfc3986.c +++ b/ext/uri/uri_parser_rfc3986.c @@ -106,14 +106,6 @@ ZEND_ATTRIBUTE_NONNULL static UriUriA *get_uri_for_writing(uri_internal_t *inter return &uriparser_uris->uri; } -ZEND_ATTRIBUTE_NONNULL static void reset_normalized_uri_after_writing(uri_internal_t *internal_uri) -{ - php_uri_parser_rfc3986_uris *uriparser_uris = internal_uri->uri; - - uriFreeUriMembersMmA(&uriparser_uris->normalized_uri, mm); - uriparser_uris->normalized_uri_initialized = false; -} - ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_scheme_read(const uri_internal_t *internal_uri, uri_component_read_mode_t read_mode, zval *retval) { const UriUriA *uriparser_uri = get_uri_for_reading(internal_uri->uri, read_mode); @@ -143,8 +135,6 @@ static zend_result php_uri_parser_rfc3986_scheme_write(struct uri_internal_t *in return FAILURE; } - reset_normalized_uri_after_writing(internal_uri); - return SUCCESS; } @@ -174,7 +164,6 @@ zend_result php_uri_parser_rfc3986_userinfo_write(struct uri_internal_t *interna switch (result) { case URI_SUCCESS: - reset_normalized_uri_after_writing(internal_uri); return SUCCESS; case URI_ERROR_SETUSERINFO_HOST_NOT_SET: zend_throw_exception(uri_invalid_uri_exception_ce, "Cannot set a userinfo without having a host", 0); @@ -263,7 +252,6 @@ static zend_result php_uri_parser_rfc3986_host_write(struct uri_internal_t *inte switch (result) { case URI_SUCCESS: - reset_normalized_uri_after_writing(internal_uri); return SUCCESS; case URI_ERROR_SETHOST_PORT_SET: zend_throw_exception(uri_invalid_uri_exception_ce, "Cannot remove the host from a URI that has a port", 0); @@ -318,12 +306,11 @@ static zend_result php_uri_parser_rfc3986_port_write(struct uri_internal_t *inte } else { zend_string *tmp = zend_long_to_str(Z_LVAL_P(value)); result = uriSetPortTextMmA(uriparser_uri, ZSTR_VAL(tmp), ZSTR_VAL(tmp) + ZSTR_LEN(tmp), mm); - zend_string_release(tmp); + zend_string_release_ex(tmp, false); } switch (result) { case URI_SUCCESS: - reset_normalized_uri_after_writing(internal_uri); return SUCCESS; case URI_ERROR_SETPORT_HOST_NOT_SET: zend_throw_exception(uri_invalid_uri_exception_ce, "Cannot set a port without having a host", 0); @@ -378,8 +365,6 @@ static zend_result php_uri_parser_rfc3986_path_write(struct uri_internal_t *inte return FAILURE; } - reset_normalized_uri_after_writing(internal_uri); - return SUCCESS; } @@ -412,8 +397,6 @@ static zend_result php_uri_parser_rfc3986_query_write(struct uri_internal_t *int return FAILURE; } - reset_normalized_uri_after_writing(internal_uri); - return SUCCESS; } @@ -446,8 +429,6 @@ static zend_result php_uri_parser_rfc3986_fragment_write(struct uri_internal_t * return FAILURE; } - reset_normalized_uri_after_writing(internal_uri); - return SUCCESS; }