From 27da2ed9e7155a118ee7ef536abc546606192bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Thu, 28 May 2026 20:14:42 +0200 Subject: [PATCH 1/9] Implement "Followup improvements for ext/uri" RFC - WHATWG URL building RFC: https://wiki.php.net/rfc/uri_followup#uri_building --- ext/uri/php_uri.c | 140 +++++++++++ ext/uri/php_uri.stub.php | 33 +++ ext/uri/php_uri_arginfo.h | 112 ++++++++- ext/uri/php_uri_common.c | 25 ++ ext/uri/php_uri_common.h | 2 + ext/uri/php_uri_decl.h | 8 +- .../builder/all_success_with_reset.phpt | 72 ++++++ .../whatwg/builder/basic_error_with_base.phpt | 17 ++ .../builder/basic_success_with_base.phpt | 35 +++ .../builder/fragment_error_unicode_char.phpt | 16 ++ .../builder/fragment_success_hashmark.phpt | 37 +++ .../whatwg/builder/fragment_success_null.phpt | 38 +++ .../fragment_success_special_char.phpt | 37 +++ .../builder/fragment_success_tab_newline.phpt | 60 +++++ .../host_error_ipv6_closing_brace.phpt | 18 ++ .../builder/host_error_null_special.phpt | 18 ++ .../builder/host_error_percent_encoding1.phpt | 18 ++ .../builder/host_error_percent_encoding2.phpt | 18 ++ .../builder/host_error_percent_encoding3.phpt | 18 ++ .../builder/host_success_empty_opaque.phpt | 36 +++ .../whatwg/builder/host_success_ipv4.phpt | 36 +++ .../host_success_ipv4_percent_encoding.phpt | 36 +++ .../whatwg/builder/host_success_ipv6.phpt | 36 +++ .../builder/host_success_opaque_null.phpt | 37 +++ .../whatwg/builder/host_success_regname.phpt | 36 +++ .../builder/password_error_missing_host.phpt | 18 ++ .../password_success_empty_opaque_host.phpt | 37 +++ .../builder/password_success_file_scheme.phpt | 37 +++ .../whatwg/builder/password_success_null.phpt | 38 +++ .../builder/path_success_empty_string.phpt | 37 +++ .../path_success_first_segment_colon.phpt | 36 +++ .../path_success_leading_double_slash1.phpt | 36 +++ .../path_success_leading_double_slash2.phpt | 37 +++ .../builder/path_success_special_char.phpt | 37 +++ .../path_success_without_leading_slash.phpt | 37 +++ .../whatwg/builder/port_error_large.phpt | 21 ++ .../port_error_missing_opaque_host.phpt | 36 +++ .../whatwg/builder/port_error_negative.phpt | 16 ++ .../whatwg/builder/port_success_default.phpt | 37 +++ .../port_success_empty_opaque_host.phpt | 37 +++ .../builder/port_success_file_scheme.phpt | 37 +++ .../builder/port_success_non_default.phpt | 37 +++ .../whatwg/builder/port_success_null.phpt | 38 +++ .../whatwg/builder/query_success_basic.phpt | 37 +++ .../whatwg/builder/query_success_null.phpt | 38 +++ .../builder/query_success_question_mark.phpt | 37 +++ .../builder/query_success_unicode_char.phpt | 37 +++ .../whatwg/builder/scheme_error_empty.phpt | 16 ++ .../builder/scheme_error_empty_string.phpt | 16 ++ .../builder/scheme_error_first_char.phpt | 16 ++ .../builder/scheme_error_special_char.phpt | 16 ++ .../builder/scheme_success_non_special.phpt | 36 +++ .../builder/scheme_success_special.phpt | 35 +++ .../builder/username_error_missing_host.phpt | 18 ++ .../username_success_empty_opaque_host.phpt | 37 +++ .../builder/username_success_file_scheme.phpt | 37 +++ ...name_success_invalid_percent_encoding.phpt | 37 +++ .../whatwg/builder/username_success_null.phpt | 38 +++ .../username_success_special_char.phpt | 38 +++ .../builder/username_success_tab_newline.phpt | 51 ++++ ext/uri/uri_parser_whatwg.c | 227 +++++++++++++++++- ext/uri/uri_parser_whatwg.h | 11 + 62 files changed, 2302 insertions(+), 24 deletions(-) create mode 100644 ext/uri/tests/whatwg/builder/all_success_with_reset.phpt create mode 100644 ext/uri/tests/whatwg/builder/basic_error_with_base.phpt create mode 100644 ext/uri/tests/whatwg/builder/basic_success_with_base.phpt create mode 100644 ext/uri/tests/whatwg/builder/fragment_error_unicode_char.phpt create mode 100644 ext/uri/tests/whatwg/builder/fragment_success_hashmark.phpt create mode 100644 ext/uri/tests/whatwg/builder/fragment_success_null.phpt create mode 100644 ext/uri/tests/whatwg/builder/fragment_success_special_char.phpt create mode 100644 ext/uri/tests/whatwg/builder/fragment_success_tab_newline.phpt create mode 100644 ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace.phpt create mode 100644 ext/uri/tests/whatwg/builder/host_error_null_special.phpt create mode 100644 ext/uri/tests/whatwg/builder/host_error_percent_encoding1.phpt create mode 100644 ext/uri/tests/whatwg/builder/host_error_percent_encoding2.phpt create mode 100644 ext/uri/tests/whatwg/builder/host_error_percent_encoding3.phpt create mode 100644 ext/uri/tests/whatwg/builder/host_success_empty_opaque.phpt create mode 100644 ext/uri/tests/whatwg/builder/host_success_ipv4.phpt create mode 100644 ext/uri/tests/whatwg/builder/host_success_ipv4_percent_encoding.phpt create mode 100644 ext/uri/tests/whatwg/builder/host_success_ipv6.phpt create mode 100644 ext/uri/tests/whatwg/builder/host_success_opaque_null.phpt create mode 100644 ext/uri/tests/whatwg/builder/host_success_regname.phpt create mode 100644 ext/uri/tests/whatwg/builder/password_error_missing_host.phpt create mode 100644 ext/uri/tests/whatwg/builder/password_success_empty_opaque_host.phpt create mode 100644 ext/uri/tests/whatwg/builder/password_success_file_scheme.phpt create mode 100644 ext/uri/tests/whatwg/builder/password_success_null.phpt create mode 100644 ext/uri/tests/whatwg/builder/path_success_empty_string.phpt create mode 100644 ext/uri/tests/whatwg/builder/path_success_first_segment_colon.phpt create mode 100644 ext/uri/tests/whatwg/builder/path_success_leading_double_slash1.phpt create mode 100644 ext/uri/tests/whatwg/builder/path_success_leading_double_slash2.phpt create mode 100644 ext/uri/tests/whatwg/builder/path_success_special_char.phpt create mode 100644 ext/uri/tests/whatwg/builder/path_success_without_leading_slash.phpt create mode 100644 ext/uri/tests/whatwg/builder/port_error_large.phpt create mode 100644 ext/uri/tests/whatwg/builder/port_error_missing_opaque_host.phpt create mode 100644 ext/uri/tests/whatwg/builder/port_error_negative.phpt create mode 100644 ext/uri/tests/whatwg/builder/port_success_default.phpt create mode 100644 ext/uri/tests/whatwg/builder/port_success_empty_opaque_host.phpt create mode 100644 ext/uri/tests/whatwg/builder/port_success_file_scheme.phpt create mode 100644 ext/uri/tests/whatwg/builder/port_success_non_default.phpt create mode 100644 ext/uri/tests/whatwg/builder/port_success_null.phpt create mode 100644 ext/uri/tests/whatwg/builder/query_success_basic.phpt create mode 100644 ext/uri/tests/whatwg/builder/query_success_null.phpt create mode 100644 ext/uri/tests/whatwg/builder/query_success_question_mark.phpt create mode 100644 ext/uri/tests/whatwg/builder/query_success_unicode_char.phpt create mode 100644 ext/uri/tests/whatwg/builder/scheme_error_empty.phpt create mode 100644 ext/uri/tests/whatwg/builder/scheme_error_empty_string.phpt create mode 100644 ext/uri/tests/whatwg/builder/scheme_error_first_char.phpt create mode 100644 ext/uri/tests/whatwg/builder/scheme_error_special_char.phpt create mode 100644 ext/uri/tests/whatwg/builder/scheme_success_non_special.phpt create mode 100644 ext/uri/tests/whatwg/builder/scheme_success_special.phpt create mode 100644 ext/uri/tests/whatwg/builder/username_error_missing_host.phpt create mode 100644 ext/uri/tests/whatwg/builder/username_success_empty_opaque_host.phpt create mode 100644 ext/uri/tests/whatwg/builder/username_success_file_scheme.phpt create mode 100644 ext/uri/tests/whatwg/builder/username_success_invalid_percent_encoding.phpt create mode 100644 ext/uri/tests/whatwg/builder/username_success_null.phpt create mode 100644 ext/uri/tests/whatwg/builder/username_success_special_char.phpt create mode 100644 ext/uri/tests/whatwg/builder/username_success_tab_newline.phpt diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c index 6ede828b69fe..15ae3a686f03 100644 --- a/ext/uri/php_uri.c +++ b/ext/uri/php_uri.c @@ -12,6 +12,7 @@ +----------------------------------------------------------------------+ */ +#include "zend_smart_str.h" #ifdef HAVE_CONFIG_H # include #endif @@ -34,6 +35,7 @@ zend_class_entry *php_uri_ce_rfc3986_uri_builder; zend_class_entry *php_uri_ce_rfc3986_uri; zend_class_entry *php_uri_ce_rfc3986_uri_type; zend_class_entry *php_uri_ce_rfc3986_uri_host_type; +zend_class_entry *php_uri_ce_whatwg_url_builder; zend_class_entry *php_uri_ce_whatwg_url; zend_class_entry *php_uri_ce_comparison_mode; zend_class_entry *php_uri_ce_exception; @@ -75,6 +77,15 @@ static zend_always_inline zval *php_uri_deref(zval *zv) #define Z_RFC3986_URI_PROP_QUERY_DEREF_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 5)) #define Z_RFC3986_URI_PROP_FRAGMENT_DEREF_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 6)) +#define Z_WHATWG_URL_PROP_SCHEME_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 0)) +#define Z_WHATWG_URL_PROP_USERNAME_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 1)) +#define Z_WHATWG_URL_PROP_PASSWORD_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 2)) +#define Z_WHATWG_URL_PROP_HOST_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 3)) +#define Z_WHATWG_URL_PROP_PORT_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 4)) +#define Z_WHATWG_URL_PROP_PATH_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 5)) +#define Z_WHATWG_URL_PROP_QUERY_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 6)) +#define Z_WHATWG_URL_PROP_FRAGMENT_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 7)) + static HashTable *uri_get_debug_properties(php_uri_object *object) { const HashTable *std_properties = zend_std_get_properties(&object->std); @@ -1249,6 +1260,133 @@ PHP_METHOD(Uri_Rfc3986_UriBuilder, build) uri_object->uri = uriparser_uris; } +PHP_METHOD(Uri_WhatWg_UrlBuilder, reset) +{ + zval_ptr_dtor(Z_WHATWG_URL_PROP_SCHEME_P(ZEND_THIS)); + ZVAL_EMPTY_STRING(Z_WHATWG_URL_PROP_SCHEME_P(ZEND_THIS)); + convert_to_null(Z_WHATWG_URL_PROP_USERNAME_P(ZEND_THIS)); + convert_to_null(Z_WHATWG_URL_PROP_PASSWORD_P(ZEND_THIS)); + convert_to_null(Z_WHATWG_URL_PROP_HOST_P(ZEND_THIS)); + convert_to_null(Z_WHATWG_URL_PROP_PORT_P(ZEND_THIS)); + zval_ptr_dtor(Z_WHATWG_URL_PROP_PATH_P(ZEND_THIS)); + ZVAL_EMPTY_STRING(Z_WHATWG_URL_PROP_PATH_P(ZEND_THIS)); + convert_to_null(Z_WHATWG_URL_PROP_QUERY_P(ZEND_THIS)); + convert_to_null(Z_WHATWG_URL_PROP_FRAGMENT_P(ZEND_THIS)); + + RETVAL_COPY(ZEND_THIS); +} + +PHP_METHOD(Uri_WhatWg_UrlBuilder, setScheme) +{ + php_uri_builder_set_component_string( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("scheme"), + php_uri_parser_whatwg_validate_scheme + ); +} + +PHP_METHOD(Uri_WhatWg_UrlBuilder, setUsername) +{ + php_uri_builder_set_component_string_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("username"), + php_uri_parser_whatwg_validate_none + ); +} + +PHP_METHOD(Uri_WhatWg_UrlBuilder, setPassword) +{ + php_uri_builder_set_component_string_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("password"), + php_uri_parser_whatwg_validate_none + ); +} + +PHP_METHOD(Uri_WhatWg_UrlBuilder, setHost) +{ + php_uri_builder_set_component_string_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("host"), + php_uri_parser_whatwg_validate_none + ); +} + +PHP_METHOD(Uri_WhatWg_UrlBuilder, setPort) +{ + php_uri_builder_set_component_long_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("port"), + php_uri_parser_whatwg_validate_port + ); +} + +PHP_METHOD(Uri_WhatWg_UrlBuilder, setPath) +{ + php_uri_builder_set_component_string( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("path"), + php_uri_parser_whatwg_validate_none + ); +} + +PHP_METHOD(Uri_WhatWg_UrlBuilder, setQuery) +{ + php_uri_builder_set_component_string_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("query"), + php_uri_parser_whatwg_validate_none + ); +} + +PHP_METHOD(Uri_WhatWg_UrlBuilder, setFragment) +{ + php_uri_builder_set_component_string_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("fragment"), + php_uri_parser_whatwg_validate_none + ); +} + +PHP_METHOD(Uri_WhatWg_UrlBuilder, build) +{ + zval *base_url_zv = NULL; + zval *errors = NULL; + + ZEND_PARSE_PARAMETERS_START(0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS_OR_NULL(base_url_zv, php_uri_ce_whatwg_url) + Z_PARAM_ZVAL(errors) + ZEND_PARSE_PARAMETERS_END(); + + const zval *scheme = Z_WHATWG_URL_PROP_SCHEME_P(ZEND_THIS); + const zval *username = Z_WHATWG_URL_PROP_USERNAME_P(ZEND_THIS); + const zval *password = Z_WHATWG_URL_PROP_PASSWORD_P(ZEND_THIS); + const zval *host = Z_WHATWG_URL_PROP_HOST_P(ZEND_THIS); + const zval *port = Z_WHATWG_URL_PROP_PORT_P(ZEND_THIS); + const zval *path = Z_WHATWG_URL_PROP_PATH_P(ZEND_THIS); + const zval *query = Z_WHATWG_URL_PROP_QUERY_P(ZEND_THIS); + const zval *fragment = Z_WHATWG_URL_PROP_FRAGMENT_P(ZEND_THIS); + + lxb_url_t *base_url = NULL; + if (base_url_zv != NULL) { + base_url = Z_URI_OBJECT_P(base_url_zv)->uri; + } + + lxb_url_t *lexbor_url = php_uri_parser_whatwg_build_from_zval( + base_url, scheme, username, password, host, port, path, query, fragment, + errors + ); + if (lexbor_url == NULL) { + RETURN_THROWS(); + } + + object_init_ex(return_value, php_uri_ce_whatwg_url); + php_uri_object *uri_object = Z_URI_OBJECT_P(return_value); + uri_object->parser = &php_uri_parser_whatwg; + uri_object->uri = lexbor_url; +} + PHPAPI php_uri_object *php_uri_object_create(zend_class_entry *class_type, const php_uri_parser *parser) { php_uri_object *uri_object = zend_object_alloc(sizeof(*uri_object), class_type); @@ -1331,6 +1469,8 @@ static PHP_MINIT_FUNCTION(uri) php_uri_ce_rfc3986_uri_type = register_class_Uri_Rfc3986_UriType(); php_uri_ce_rfc3986_uri_host_type = register_class_Uri_Rfc3986_UriHostType(); + php_uri_ce_whatwg_url_builder = register_class_Uri_WhatWg_UrlBuilder(); + php_uri_ce_whatwg_url = register_class_Uri_WhatWg_Url(); php_uri_ce_whatwg_url->create_object = php_uri_object_create_whatwg; php_uri_ce_whatwg_url->default_object_handlers = &object_handlers_whatwg_uri; diff --git a/ext/uri/php_uri.stub.php b/ext/uri/php_uri.stub.php index 81a766c0e16d..98f8873d9845 100644 --- a/ext/uri/php_uri.stub.php +++ b/ext/uri/php_uri.stub.php @@ -211,6 +211,39 @@ enum UrlHostType case Empty; } + final class UrlBuilder + { + private string $scheme = ""; + private ?string $username = null; + private ?string $password = null; + private ?string $host = null; + private ?int $port = null; + private string $path = ""; + private ?string $query = null; + private ?string $fragment = null; + + public function reset(): static {} + + public function setScheme(string $scheme): static {} + + public function setUsername(?string $username): static {} + + public function setPassword(#[\SensitiveParameter] ?string $password): static {} + + public function setHost(?string $host): static {} + + public function setPort(?int $port): static {} + + public function setPath(string $path): static {} + + public function setQuery(?string $query): static {} + + public function setFragment(?string $fragment): static {} + + /** @param array $errors */ + public function build(?\Uri\WhatWg\Url $baseUrl = null, &$errors = null): \Uri\WhatWg\Url {} + } + /** @strict-properties */ final readonly class Url { diff --git a/ext/uri/php_uri_arginfo.h b/ext/uri/php_uri_arginfo.h index 1231408fbeb6..93cc3ee45a2b 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 php_uri.stub.php instead. - * Stub hash: bfd9247fa79baf877d600134c9ef615ec5ca9cea + * Stub hash: 54e953b1da0d08c64509666b9278c59483d1e171 * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_reset, 0, 0, IS_STATIC, 0) @@ -141,6 +141,35 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Uri_WhatWg_UrlValidationError___construct, ZEND_ARG_TYPE_INFO(0, failure, _IS_BOOL, 0) ZEND_END_ARG_INFO() +#define arginfo_class_Uri_WhatWg_UrlBuilder_reset arginfo_class_Uri_Rfc3986_UriBuilder_reset + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_UrlBuilder_setScheme, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, scheme, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_UrlBuilder_setUsername, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, username, IS_STRING, 1) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_UrlBuilder_setPassword, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, password, IS_STRING, 1) +ZEND_END_ARG_INFO() + +#define arginfo_class_Uri_WhatWg_UrlBuilder_setHost arginfo_class_Uri_Rfc3986_UriBuilder_setHost + +#define arginfo_class_Uri_WhatWg_UrlBuilder_setPort arginfo_class_Uri_Rfc3986_UriBuilder_setPort + +#define arginfo_class_Uri_WhatWg_UrlBuilder_setPath arginfo_class_Uri_Rfc3986_UriBuilder_setPath + +#define arginfo_class_Uri_WhatWg_UrlBuilder_setQuery arginfo_class_Uri_Rfc3986_UriBuilder_setQuery + +#define arginfo_class_Uri_WhatWg_UrlBuilder_setFragment arginfo_class_Uri_Rfc3986_UriBuilder_setFragment + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Uri_WhatWg_UrlBuilder_build, 0, 0, Uri\\WhatWg\\\125rl, 0) + ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, baseUrl, Uri\\WhatWg\\\125rl, 1, "null") + ZEND_ARG_INFO_WITH_DEFAULT_VALUE(1, errors, "null") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_Url_parse, 0, 1, IS_STATIC, 1) ZEND_ARG_TYPE_INFO(0, uri, IS_STRING, 0) ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, baseUrl, Uri\\WhatWg\\\125rl, 1, "null") @@ -155,24 +184,18 @@ ZEND_END_ARG_INFO() #define arginfo_class_Uri_WhatWg_Url_getScheme arginfo_class_Uri_Rfc3986_Uri_getPath -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_Url_withScheme, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, scheme, IS_STRING, 0) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_WhatWg_Url_withScheme arginfo_class_Uri_WhatWg_UrlBuilder_setScheme ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_Url_isSpecialScheme, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() #define arginfo_class_Uri_WhatWg_Url_getUsername arginfo_class_Uri_Rfc3986_Uri_getScheme -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_Url_withUsername, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, username, IS_STRING, 1) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_WhatWg_Url_withUsername arginfo_class_Uri_WhatWg_UrlBuilder_setUsername #define arginfo_class_Uri_WhatWg_Url_getPassword arginfo_class_Uri_Rfc3986_Uri_getScheme -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_Url_withPassword, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, password, IS_STRING, 1) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_WhatWg_Url_withPassword arginfo_class_Uri_WhatWg_UrlBuilder_setPassword #define arginfo_class_Uri_WhatWg_Url_getAsciiHost arginfo_class_Uri_Rfc3986_Uri_getScheme @@ -265,6 +288,16 @@ ZEND_METHOD(Uri_Rfc3986_Uri, __unserialize); ZEND_METHOD(Uri_Rfc3986_Uri, __debugInfo); ZEND_METHOD(Uri_WhatWg_InvalidUrlException, __construct); ZEND_METHOD(Uri_WhatWg_UrlValidationError, __construct); +ZEND_METHOD(Uri_WhatWg_UrlBuilder, reset); +ZEND_METHOD(Uri_WhatWg_UrlBuilder, setScheme); +ZEND_METHOD(Uri_WhatWg_UrlBuilder, setUsername); +ZEND_METHOD(Uri_WhatWg_UrlBuilder, setPassword); +ZEND_METHOD(Uri_WhatWg_UrlBuilder, setHost); +ZEND_METHOD(Uri_WhatWg_UrlBuilder, setPort); +ZEND_METHOD(Uri_WhatWg_UrlBuilder, setPath); +ZEND_METHOD(Uri_WhatWg_UrlBuilder, setQuery); +ZEND_METHOD(Uri_WhatWg_UrlBuilder, setFragment); +ZEND_METHOD(Uri_WhatWg_UrlBuilder, build); ZEND_METHOD(Uri_WhatWg_Url, parse); ZEND_METHOD(Uri_WhatWg_Url, __construct); ZEND_METHOD(Uri_WhatWg_Url, getScheme); @@ -345,6 +378,20 @@ static const zend_function_entry class_Uri_WhatWg_UrlValidationError_methods[] = ZEND_FE_END }; +static const zend_function_entry class_Uri_WhatWg_UrlBuilder_methods[] = { + ZEND_ME(Uri_WhatWg_UrlBuilder, reset, arginfo_class_Uri_WhatWg_UrlBuilder_reset, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_WhatWg_UrlBuilder, setScheme, arginfo_class_Uri_WhatWg_UrlBuilder_setScheme, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_WhatWg_UrlBuilder, setUsername, arginfo_class_Uri_WhatWg_UrlBuilder_setUsername, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_WhatWg_UrlBuilder, setPassword, arginfo_class_Uri_WhatWg_UrlBuilder_setPassword, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_WhatWg_UrlBuilder, setHost, arginfo_class_Uri_WhatWg_UrlBuilder_setHost, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_WhatWg_UrlBuilder, setPort, arginfo_class_Uri_WhatWg_UrlBuilder_setPort, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_WhatWg_UrlBuilder, setPath, arginfo_class_Uri_WhatWg_UrlBuilder_setPath, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_WhatWg_UrlBuilder, setQuery, arginfo_class_Uri_WhatWg_UrlBuilder_setQuery, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_WhatWg_UrlBuilder, setFragment, arginfo_class_Uri_WhatWg_UrlBuilder_setFragment, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_WhatWg_UrlBuilder, build, arginfo_class_Uri_WhatWg_UrlBuilder_build, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + static const zend_function_entry class_Uri_WhatWg_Url_methods[] = { ZEND_ME(Uri_WhatWg_Url, parse, arginfo_class_Uri_WhatWg_Url_parse, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(Uri_WhatWg_Url, __construct, arginfo_class_Uri_WhatWg_Url___construct, ZEND_ACC_PUBLIC) @@ -629,6 +676,51 @@ static zend_class_entry *register_class_Uri_WhatWg_UrlHostType(void) return class_entry; } +static zend_class_entry *register_class_Uri_WhatWg_UrlBuilder(void) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Uri\\WhatWg", "UrlBuilder", class_Uri_WhatWg_UrlBuilder_methods); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL); + + zval property_scheme_default_value; + ZVAL_EMPTY_STRING(&property_scheme_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_SCHEME), &property_scheme_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + + zval property_username_default_value; + ZVAL_NULL(&property_username_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_USERNAME), &property_username_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + + zval property_password_default_value; + ZVAL_NULL(&property_password_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_PASSWORD), &property_password_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + + zval property_host_default_value; + ZVAL_NULL(&property_host_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_HOST), &property_host_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + + zval property_port_default_value; + ZVAL_NULL(&property_port_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_PORT), &property_port_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG|MAY_BE_NULL)); + + zval property_path_default_value; + ZVAL_EMPTY_STRING(&property_path_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_PATH), &property_path_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + + zval property_query_default_value; + ZVAL_NULL(&property_query_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_QUERY), &property_query_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + + zval property_fragment_default_value; + ZVAL_NULL(&property_fragment_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_FRAGMENT), &property_fragment_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + + + zend_add_parameter_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "setpassword", sizeof("setpassword") - 1), 0, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); + + return class_entry; +} + static zend_class_entry *register_class_Uri_WhatWg_Url(void) { zend_class_entry ce, *class_entry; diff --git a/ext/uri/php_uri_common.c b/ext/uri/php_uri_common.c index 0644afda3105..e4850d8059ee 100644 --- a/ext/uri/php_uri_common.c +++ b/ext/uri/php_uri_common.c @@ -40,6 +40,31 @@ static zend_string *get_known_string_by_property_name(php_uri_property_name prop } } +zend_result php_uri_pass_errors_by_ref_and_free(zval *errors_zv, zval *errors) +{ + ZEND_ASSERT(Z_TYPE_P(errors) == IS_UNDEF || Z_TYPE_P(errors) == IS_ARRAY); + + /* There was no error during parsing */ + if (Z_ISUNDEF_P(errors)) { + return SUCCESS; + } + + /* The errors parameter is an array, but the pass-by ref argument stored by + * errors_zv was not passed - the URI implementation either doesn't support + * returning additional error information, or the caller is not interested in it */ + if (errors_zv == NULL) { + zval_ptr_dtor(errors); + return SUCCESS; + } + + ZEND_TRY_ASSIGN_REF_TMP(errors_zv, errors); + if (EG(exception)) { + return FAILURE; + } + + return SUCCESS; +} + void php_uri_property_read_helper(INTERNAL_FUNCTION_PARAMETERS, php_uri_property_name property_name, php_uri_component_read_mode component_read_mode) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/ext/uri/php_uri_common.h b/ext/uri/php_uri_common.h index 9106f6acd15f..31ef1dd2130c 100644 --- a/ext/uri/php_uri_common.h +++ b/ext/uri/php_uri_common.h @@ -21,6 +21,7 @@ extern zend_class_entry *php_uri_ce_rfc3986_uri_builder; extern zend_class_entry *php_uri_ce_rfc3986_uri; extern zend_class_entry *php_uri_ce_rfc3986_uri_type; extern zend_class_entry *php_uri_ce_rfc3986_uri_host_type; +extern zend_class_entry *php_uri_ce_whatwg_url_builder; extern zend_class_entry *php_uri_ce_whatwg_url; extern zend_class_entry *php_uri_ce_comparison_mode; extern zend_class_entry *php_uri_ce_exception; @@ -186,6 +187,7 @@ static inline const php_uri_property_handler *php_uri_parser_property_handler_by } } +zend_result php_uri_pass_errors_by_ref_and_free(zval *errors_zv, zval *errors); void php_uri_property_read_helper(INTERNAL_FUNCTION_PARAMETERS, php_uri_property_name property_name, php_uri_component_read_mode component_read_mode); void php_uri_property_write_str_helper(INTERNAL_FUNCTION_PARAMETERS, php_uri_property_name property_name); void php_uri_property_write_str_or_null_helper(INTERNAL_FUNCTION_PARAMETERS, php_uri_property_name property_name); diff --git a/ext/uri/php_uri_decl.h b/ext/uri/php_uri_decl.h index 9c817659cf76..71f748b71070 100644 --- a/ext/uri/php_uri_decl.h +++ b/ext/uri/php_uri_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit php_uri.stub.php instead. - * Stub hash: bfd9247fa79baf877d600134c9ef615ec5ca9cea */ + * Stub hash: 54e953b1da0d08c64509666b9278c59483d1e171 */ -#ifndef ZEND_PHP_URI_DECL_bfd9247fa79baf877d600134c9ef615ec5ca9cea_H -#define ZEND_PHP_URI_DECL_bfd9247fa79baf877d600134c9ef615ec5ca9cea_H +#ifndef ZEND_PHP_URI_DECL_54e953b1da0d08c64509666b9278c59483d1e171_H +#define ZEND_PHP_URI_DECL_54e953b1da0d08c64509666b9278c59483d1e171_H typedef enum zend_enum_Uri_UriComparisonMode { ZEND_ENUM_Uri_UriComparisonMode_IncludeFragment = 1, @@ -63,4 +63,4 @@ typedef enum zend_enum_Uri_WhatWg_UrlHostType { ZEND_ENUM_Uri_WhatWg_UrlHostType_Empty = 5, } zend_enum_Uri_WhatWg_UrlHostType; -#endif /* ZEND_PHP_URI_DECL_bfd9247fa79baf877d600134c9ef615ec5ca9cea_H */ +#endif /* ZEND_PHP_URI_DECL_54e953b1da0d08c64509666b9278c59483d1e171_H */ diff --git a/ext/uri/tests/whatwg/builder/all_success_with_reset.phpt b/ext/uri/tests/whatwg/builder/all_success_with_reset.phpt new file mode 100644 index 000000000000..224f07b7ef91 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/all_success_with_reset.phpt @@ -0,0 +1,72 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder all components - success - calling reset() afterwards +--FILE-- +setScheme("https") + ->setUsername("user") + ->setPassword("pass") + ->setHost("example.com") + ->setPort(444) + ->setPath("/foo/bar/baz") + ->setQuery("foo=1&bar=baz") + ->setFragment("fragment"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +$url = $builder + ->reset() + ->setScheme("scheme") + ->setHost("www.example.com") + ->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(68) "https://user:pass@example.com:444/foo/bar/baz?foo=1&bar=baz#fragment" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + string(4) "user" + ["password"]=> + string(4) "pass" + ["host"]=> + string(11) "example.com" + ["port"]=> + int(444) + ["path"]=> + string(12) "/foo/bar/baz" + ["query"]=> + string(13) "foo=1&bar=baz" + ["fragment"]=> + string(8) "fragment" +} +bool(true) +string(24) "scheme://www.example.com" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(6) "scheme" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(15) "www.example.com" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/basic_error_with_base.phpt b/ext/uri/tests/whatwg/builder/basic_error_with_base.phpt new file mode 100644 index 000000000000..dbf3fe00c3c2 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/basic_error_with_base.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder basic - error - with base URL +--FILE-- +setPath("/foo/bar/baz"); + +try { + $builder->build(new Uri\WhatWg\Url("/foo/bar")); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified base URI must be absolute diff --git a/ext/uri/tests/whatwg/builder/basic_success_with_base.phpt b/ext/uri/tests/whatwg/builder/basic_success_with_base.phpt new file mode 100644 index 000000000000..adda5bc65892 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/basic_success_with_base.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder basic - success - with base URL +--FILE-- +setPath("/foo/bar/baz"); +$url = $builder->build(new Uri\WhatWg\Url("https://example.com")); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(31) "https://example.com/foo/bar/baz" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(12) "/foo/bar/baz" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/fragment_error_unicode_char.phpt b/ext/uri/tests/whatwg/builder/fragment_error_unicode_char.phpt new file mode 100644 index 000000000000..fc46470f489a --- /dev/null +++ b/ext/uri/tests/whatwg/builder/fragment_error_unicode_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setFragment() - error - contains Unicode character +--FILE-- +setFragment("főő"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- + diff --git a/ext/uri/tests/whatwg/builder/fragment_success_hashmark.phpt b/ext/uri/tests/whatwg/builder/fragment_success_hashmark.phpt new file mode 100644 index 000000000000..430ba470ba79 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/fragment_success_hashmark.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setFragment() - success - leading hashmark is ignored +--FILE-- +setScheme("https"); +$builder->setHost("example.com"); +$builder->setFragment("#foo"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(24) "https://example.com/#foo" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + string(3) "foo" +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/fragment_success_null.phpt b/ext/uri/tests/whatwg/builder/fragment_success_null.phpt new file mode 100644 index 000000000000..bff74443bbb9 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/fragment_success_null.phpt @@ -0,0 +1,38 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setFragment() - success - null +--FILE-- +setScheme("https"); +$builder->setHost("example.com"); +$builder->setFragment("foo"); +$builder->setFragment(null); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(20) "https://example.com/" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/fragment_success_special_char.phpt b/ext/uri/tests/whatwg/builder/fragment_success_special_char.phpt new file mode 100644 index 000000000000..d153274de0fc --- /dev/null +++ b/ext/uri/tests/whatwg/builder/fragment_success_special_char.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setFragment() - success - contains special character +--FILE-- +setScheme("https"); +$builder->setHost("example.com"); +$builder->setFragment(" "); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(33) "https://example.com/#%20%3Cfoo%3E" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + string(12) "%20%3Cfoo%3E" +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/fragment_success_tab_newline.phpt b/ext/uri/tests/whatwg/builder/fragment_success_tab_newline.phpt new file mode 100644 index 000000000000..f30ca259e839 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/fragment_success_tab_newline.phpt @@ -0,0 +1,60 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setFragment() - success - contains tab and newline characters +--FILE-- +setScheme("\tfo\no"); +$builder->setHost("example.com"); +$builder->setFragment("\tfoo"); +$errors = []; +$url = $builder->build(errors: $errors); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($errors); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(21) "foo://example.com#foo" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(3) "foo" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + string(3) "foo" +} +array(%d) { + [0]=> + object(Uri\WhatWg\UrlValidationError)#%d (%d) { + ["context"]=> + string(5) " fo +o" + ["type"]=> + enum(Uri\WhatWg\UrlValidationErrorType::InvalidUrlUnit) + ["failure"]=> + bool(false) + } + [1]=> + object(Uri\WhatWg\UrlValidationError)#%d (%d) { + ["context"]=> + string(4) " foo" + ["type"]=> + enum(Uri\WhatWg\UrlValidationErrorType::InvalidUrlUnit) + ["failure"]=> + bool(false) + } +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace.phpt b/ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace.phpt new file mode 100644 index 000000000000..c19c0e5d0dea --- /dev/null +++ b/ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setHost() - error - missing IPv6 closing brace +--FILE-- +setScheme("https"); +$builder->setHost("[2001:%30db8:85a3:0000:0000:8a2e:0370:7334"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified host is malformed (Ipv6Unclosed) diff --git a/ext/uri/tests/whatwg/builder/host_error_null_special.phpt b/ext/uri/tests/whatwg/builder/host_error_null_special.phpt new file mode 100644 index 000000000000..973f4ef9aca3 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/host_error_null_special.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setHost() - success - null in case of special URLs +--FILE-- +setScheme("https"); +$builder->setHost(null); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified host is malformed (HostMissing) diff --git a/ext/uri/tests/whatwg/builder/host_error_percent_encoding1.phpt b/ext/uri/tests/whatwg/builder/host_error_percent_encoding1.phpt new file mode 100644 index 000000000000..2dc8c76189fd --- /dev/null +++ b/ext/uri/tests/whatwg/builder/host_error_percent_encoding1.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setHost() - error - invalid percent encoding octet in registered name +--FILE-- +setScheme("https"); +$builder->setHost("ex%3mple.co"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified host is malformed (DomainInvalidCodePoint) diff --git a/ext/uri/tests/whatwg/builder/host_error_percent_encoding2.phpt b/ext/uri/tests/whatwg/builder/host_error_percent_encoding2.phpt new file mode 100644 index 000000000000..6b5e120792ce --- /dev/null +++ b/ext/uri/tests/whatwg/builder/host_error_percent_encoding2.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setHost() - error - invalid percent encoded octet in IPv6 +--FILE-- +setScheme("https"); +$builder->setHost("[2001:%308:85a3:0000:0000:8a2e:0370:7334]"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified host is malformed (Ipv6InvalidCodePoint) diff --git a/ext/uri/tests/whatwg/builder/host_error_percent_encoding3.phpt b/ext/uri/tests/whatwg/builder/host_error_percent_encoding3.phpt new file mode 100644 index 000000000000..aee9f95d1f44 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/host_error_percent_encoding3.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setHost() - error - invalid percent encoded octet in IPv4 +--FILE-- +setScheme("https"); +$builder->setHost("192.168.%8.1"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified host is malformed (DomainInvalidCodePoint) diff --git a/ext/uri/tests/whatwg/builder/host_success_empty_opaque.phpt b/ext/uri/tests/whatwg/builder/host_success_empty_opaque.phpt new file mode 100644 index 000000000000..e9668f466c43 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/host_success_empty_opaque.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setHost() - success - empty in case of opaque URLs +--FILE-- +setScheme("scheme"); +$builder->setHost(""); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(9) "scheme://" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(6) "scheme" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(0) "" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/host_success_ipv4.phpt b/ext/uri/tests/whatwg/builder/host_success_ipv4.phpt new file mode 100644 index 000000000000..9f8bb7b5c244 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/host_success_ipv4.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setHost() - success - IPv4 address +--FILE-- +setScheme("https") + ->setHost("192.168.0.1"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(20) "https://192.168.0.1/" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "192.168.0.1" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/host_success_ipv4_percent_encoding.phpt b/ext/uri/tests/whatwg/builder/host_success_ipv4_percent_encoding.phpt new file mode 100644 index 000000000000..955796860d66 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/host_success_ipv4_percent_encoding.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setHost() - success - percent-encoding in IPv4 address +--FILE-- +setScheme("https"); +$builder->setHost("192.168.%30.1"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(20) "https://192.168.0.1/" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "192.168.0.1" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/host_success_ipv6.phpt b/ext/uri/tests/whatwg/builder/host_success_ipv6.phpt new file mode 100644 index 000000000000..9775aa58482f --- /dev/null +++ b/ext/uri/tests/whatwg/builder/host_success_ipv6.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setHost() - success - IPv6 address +--FILE-- +setScheme("https") + ->setHost("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(39) "https://[2001:db8:85a3::8a2e:370:7334]/" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(30) "[2001:db8:85a3::8a2e:370:7334]" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/host_success_opaque_null.phpt b/ext/uri/tests/whatwg/builder/host_success_opaque_null.phpt new file mode 100644 index 000000000000..9921721af17c --- /dev/null +++ b/ext/uri/tests/whatwg/builder/host_success_opaque_null.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setHost() - success - null in case of opaque hosts +--FILE-- +setScheme("scheme"); +$builder->setHost("example.com"); +$builder->setHost(null); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(9) "scheme://" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(6) "scheme" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(0) "" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/host_success_regname.phpt b/ext/uri/tests/whatwg/builder/host_success_regname.phpt new file mode 100644 index 000000000000..d72b919c0afb --- /dev/null +++ b/ext/uri/tests/whatwg/builder/host_success_regname.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setHost() - success - Registered name +--FILE-- +setScheme("https"); +$builder->setHost("www.example.com"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(24) "https://www.example.com/" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(15) "www.example.com" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/password_error_missing_host.phpt b/ext/uri/tests/whatwg/builder/password_error_missing_host.phpt new file mode 100644 index 000000000000..1d55b74a4f50 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/password_error_missing_host.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPassword() - error - missing host +--FILE-- +setScheme("https"); +$builder->setPassword("pass"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified host is malformed (HostMissing) diff --git a/ext/uri/tests/whatwg/builder/password_success_empty_opaque_host.phpt b/ext/uri/tests/whatwg/builder/password_success_empty_opaque_host.phpt new file mode 100644 index 000000000000..eed39a2332ec --- /dev/null +++ b/ext/uri/tests/whatwg/builder/password_success_empty_opaque_host.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setUsername() - success - empty opaque host +--FILE-- +setScheme("scheme"); +$builder->setHost(""); +$builder->setUsername("user"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(9) "scheme://" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(6) "scheme" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(0) "" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/password_success_file_scheme.phpt b/ext/uri/tests/whatwg/builder/password_success_file_scheme.phpt new file mode 100644 index 000000000000..32b3e9d09ce2 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/password_success_file_scheme.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPassword() - success - ignored for file scheme +--FILE-- +setScheme("file"); +$builder->setPath("C:/a.txt"); +$builder->setPassword("password"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(16) "file:///C:/a.txt" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(4) "file" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(0) "" + ["port"]=> + NULL + ["path"]=> + string(9) "/C:/a.txt" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/password_success_null.phpt b/ext/uri/tests/whatwg/builder/password_success_null.phpt new file mode 100644 index 000000000000..842b85807d79 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/password_success_null.phpt @@ -0,0 +1,38 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPassword() - success - null +--FILE-- +setScheme("https"); +$builder->setHost("example.com"); +$builder->setPassword("pass"); +$builder->setPassword(null); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(20) "https://example.com/" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/path_success_empty_string.phpt b/ext/uri/tests/whatwg/builder/path_success_empty_string.phpt new file mode 100644 index 000000000000..d3a4509cbf63 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/path_success_empty_string.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPath() - success - empty string +--FILE-- +setScheme("file"); +$builder->setPath("/foo/bar"); +$builder->setPath(""); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(8) "file:///" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(4) "file" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(0) "" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/path_success_first_segment_colon.phpt b/ext/uri/tests/whatwg/builder/path_success_first_segment_colon.phpt new file mode 100644 index 000000000000..544dcc0e346f --- /dev/null +++ b/ext/uri/tests/whatwg/builder/path_success_first_segment_colon.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPath() - success - contains a colon in the first segment when the scheme is present +--FILE-- +setScheme("file"); +$builder->setPath(":foo/bar/baz"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(20) "file:///:foo/bar/baz" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(4) "file" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(0) "" + ["port"]=> + NULL + ["path"]=> + string(13) "/:foo/bar/baz" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/path_success_leading_double_slash1.phpt b/ext/uri/tests/whatwg/builder/path_success_leading_double_slash1.phpt new file mode 100644 index 000000000000..b979e704550c --- /dev/null +++ b/ext/uri/tests/whatwg/builder/path_success_leading_double_slash1.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPath() - success - begins with double slashes when the host is not present +--FILE-- +setScheme("file"); +$builder->setPath("//foo/bar/baz"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(20) "file:////foo/bar/baz" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(4) "file" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(0) "" + ["port"]=> + NULL + ["path"]=> + string(13) "//foo/bar/baz" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/path_success_leading_double_slash2.phpt b/ext/uri/tests/whatwg/builder/path_success_leading_double_slash2.phpt new file mode 100644 index 000000000000..cc090409a39d --- /dev/null +++ b/ext/uri/tests/whatwg/builder/path_success_leading_double_slash2.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPath() - success - begins with double slashes when the opaque URL contains a host +--FILE-- +setScheme("scheme"); +$builder->setHost("example.com"); +$builder->setPath("//foo/bar/baz"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(33) "scheme://example.com//foo/bar/baz" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(6) "scheme" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(13) "//foo/bar/baz" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/path_success_special_char.phpt b/ext/uri/tests/whatwg/builder/path_success_special_char.phpt new file mode 100644 index 000000000000..b47a44b1e133 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/path_success_special_char.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPath() - success - contains special character +--FILE-- +setScheme("scheme"); +$builder->setPath("#foo"); + +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(16) "scheme:///%23foo" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(6) "scheme" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(0) "" + ["port"]=> + NULL + ["path"]=> + string(7) "/%23foo" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/path_success_without_leading_slash.phpt b/ext/uri/tests/whatwg/builder/path_success_without_leading_slash.phpt new file mode 100644 index 000000000000..1f66344ae64d --- /dev/null +++ b/ext/uri/tests/whatwg/builder/path_success_without_leading_slash.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPath() - success - without a leading slash when the URL contains a host +--FILE-- +setScheme("https"); +$builder->setPath("foo/bar/baz"); +$builder->setHost("example.com"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(31) "https://example.com/foo/bar/baz" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(12) "/foo/bar/baz" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/port_error_large.phpt b/ext/uri/tests/whatwg/builder/port_error_large.phpt new file mode 100644 index 000000000000..94205613a70c --- /dev/null +++ b/ext/uri/tests/whatwg/builder/port_error_large.phpt @@ -0,0 +1,21 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPort() - error - too large number +--FILE-- +setPort(PHP_INT_MAX); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified port is malformed diff --git a/ext/uri/tests/whatwg/builder/port_error_missing_opaque_host.phpt b/ext/uri/tests/whatwg/builder/port_error_missing_opaque_host.phpt new file mode 100644 index 000000000000..963c7fa7e1df --- /dev/null +++ b/ext/uri/tests/whatwg/builder/port_error_missing_opaque_host.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPort() - success - missing opaque host +--FILE-- +setScheme("scheme"); +$builder->setPort(443); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(9) "scheme://" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(6) "scheme" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(0) "" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/port_error_negative.phpt b/ext/uri/tests/whatwg/builder/port_error_negative.phpt new file mode 100644 index 000000000000..eb2b5449ccb3 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/port_error_negative.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPort() - error - negative number +--FILE-- +setPort(-1); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified port is malformed diff --git a/ext/uri/tests/whatwg/builder/port_success_default.phpt b/ext/uri/tests/whatwg/builder/port_success_default.phpt new file mode 100644 index 000000000000..6ea1bcd50021 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/port_success_default.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPort() - success - default port +--FILE-- +setScheme("https"); +$builder->setPort(444); +$builder->setHost("example.com"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(24) "https://example.com:444/" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + int(444) + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/port_success_empty_opaque_host.phpt b/ext/uri/tests/whatwg/builder/port_success_empty_opaque_host.phpt new file mode 100644 index 000000000000..603c2d8ed89b --- /dev/null +++ b/ext/uri/tests/whatwg/builder/port_success_empty_opaque_host.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPort() - success - empty opaque host +--FILE-- +setScheme("scheme"); +$builder->setHost(""); +$builder->setPort(443); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(9) "scheme://" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(6) "scheme" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(0) "" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/port_success_file_scheme.phpt b/ext/uri/tests/whatwg/builder/port_success_file_scheme.phpt new file mode 100644 index 000000000000..c67f4c120fc2 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/port_success_file_scheme.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPort() - success - ignored for file scheme +--FILE-- +setScheme("file"); +$builder->setPath("C:/a.txt"); +$builder->setPort(443); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(16) "file:///C:/a.txt" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(4) "file" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(0) "" + ["port"]=> + NULL + ["path"]=> + string(9) "/C:/a.txt" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/port_success_non_default.phpt b/ext/uri/tests/whatwg/builder/port_success_non_default.phpt new file mode 100644 index 000000000000..b8588844372a --- /dev/null +++ b/ext/uri/tests/whatwg/builder/port_success_non_default.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPort() - success - non-default port +--FILE-- +setScheme("https"); +$builder->setPort(443); +$builder->setHost("example.com"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(20) "https://example.com/" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/port_success_null.phpt b/ext/uri/tests/whatwg/builder/port_success_null.phpt new file mode 100644 index 000000000000..2c097112092f --- /dev/null +++ b/ext/uri/tests/whatwg/builder/port_success_null.phpt @@ -0,0 +1,38 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPort() - success - null +--FILE-- +setScheme("https"); +$builder->setHost("example.com"); +$builder->setPort(123); +$builder->setPort(null); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(20) "https://example.com/" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/query_success_basic.phpt b/ext/uri/tests/whatwg/builder/query_success_basic.phpt new file mode 100644 index 000000000000..e3257c5d0cef --- /dev/null +++ b/ext/uri/tests/whatwg/builder/query_success_basic.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setQuery() - success - basic +--FILE-- +setScheme("https"); +$builder->setHost("example.com"); +$builder->setQuery("foo=1&bar=baz"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(34) "https://example.com/?foo=1&bar=baz" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + string(13) "foo=1&bar=baz" + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/query_success_null.phpt b/ext/uri/tests/whatwg/builder/query_success_null.phpt new file mode 100644 index 000000000000..a634a5403752 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/query_success_null.phpt @@ -0,0 +1,38 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setQuery() - success - null +--FILE-- +setScheme("https"); +$builder->setHost("example.com"); +$builder->setQuery("foo"); +$builder->setQuery(null); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(20) "https://example.com/" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/query_success_question_mark.phpt b/ext/uri/tests/whatwg/builder/query_success_question_mark.phpt new file mode 100644 index 000000000000..b88a4aacb627 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/query_success_question_mark.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setQuery() - success - leading question mark is ignored +--FILE-- +setScheme("https"); +$builder->setHost("example.com"); +$builder->setQuery("?foo"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(24) "https://example.com/?foo" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + string(3) "foo" + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/query_success_unicode_char.phpt b/ext/uri/tests/whatwg/builder/query_success_unicode_char.phpt new file mode 100644 index 000000000000..9874fb9a47df --- /dev/null +++ b/ext/uri/tests/whatwg/builder/query_success_unicode_char.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setQuery() - success - contains Unicode character +--FILE-- +setScheme("https"); +$builder->setHost("example.com"); +$builder->setQuery("főő"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(34) "https://example.com/?f%C5%91%C5%91" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + string(13) "f%C5%91%C5%91" + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/scheme_error_empty.phpt b/ext/uri/tests/whatwg/builder/scheme_error_empty.phpt new file mode 100644 index 000000000000..985cfe30eeac --- /dev/null +++ b/ext/uri/tests/whatwg/builder/scheme_error_empty.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setScheme() - error - empty +--FILE-- +setScheme(""); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified scheme is malformed diff --git a/ext/uri/tests/whatwg/builder/scheme_error_empty_string.phpt b/ext/uri/tests/whatwg/builder/scheme_error_empty_string.phpt new file mode 100644 index 000000000000..163949df8b46 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/scheme_error_empty_string.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setScheme() - error - empty string +--FILE-- +setScheme(""); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified scheme is malformed diff --git a/ext/uri/tests/whatwg/builder/scheme_error_first_char.phpt b/ext/uri/tests/whatwg/builder/scheme_error_first_char.phpt new file mode 100644 index 000000000000..c5b80a866c33 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/scheme_error_first_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setScheme() - error - first character is not alpha +--FILE-- +setScheme("1"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified scheme is malformed diff --git a/ext/uri/tests/whatwg/builder/scheme_error_special_char.phpt b/ext/uri/tests/whatwg/builder/scheme_error_special_char.phpt new file mode 100644 index 000000000000..eaf59f922ecd --- /dev/null +++ b/ext/uri/tests/whatwg/builder/scheme_error_special_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setScheme() - error - contains invalid special character +--FILE-- +setScheme(":"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified scheme is malformed diff --git a/ext/uri/tests/whatwg/builder/scheme_success_non_special.phpt b/ext/uri/tests/whatwg/builder/scheme_success_non_special.phpt new file mode 100644 index 000000000000..e2c06826bd53 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/scheme_success_non_special.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setScheme() - success - non-special scheme +--FILE-- +setScheme("foo"); + +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(6) "foo://" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(3) "foo" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(0) "" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) \ No newline at end of file diff --git a/ext/uri/tests/whatwg/builder/scheme_success_special.phpt b/ext/uri/tests/whatwg/builder/scheme_success_special.phpt new file mode 100644 index 000000000000..60a175a5f207 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/scheme_success_special.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setScheme() - success - contains digit & special characters +--FILE-- +setScheme("my-12+34.scheme"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(18) "my-12+34.scheme://" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(15) "my-12+34.scheme" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(0) "" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/username_error_missing_host.phpt b/ext/uri/tests/whatwg/builder/username_error_missing_host.phpt new file mode 100644 index 000000000000..609d1d49a5c3 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/username_error_missing_host.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setUsername() - error - missing host +--FILE-- +setScheme("https"); +$builder->setUsername("user"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified host is malformed (HostMissing) diff --git a/ext/uri/tests/whatwg/builder/username_success_empty_opaque_host.phpt b/ext/uri/tests/whatwg/builder/username_success_empty_opaque_host.phpt new file mode 100644 index 000000000000..1f4e7541aa05 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/username_success_empty_opaque_host.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPassword() - success - empty opaque host +--FILE-- +setScheme("scheme"); +$builder->setHost(""); +$builder->setPassword("user"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(9) "scheme://" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(6) "scheme" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(0) "" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/username_success_file_scheme.phpt b/ext/uri/tests/whatwg/builder/username_success_file_scheme.phpt new file mode 100644 index 000000000000..b2c02cbf2757 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/username_success_file_scheme.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setUsername() - success - ignored for file scheme +--FILE-- +setScheme("file"); +$builder->setPath("C:/a.txt"); +$builder->setUsername("user"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(16) "file:///C:/a.txt" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(4) "file" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(0) "" + ["port"]=> + NULL + ["path"]=> + string(9) "/C:/a.txt" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/username_success_invalid_percent_encoding.phpt b/ext/uri/tests/whatwg/builder/username_success_invalid_percent_encoding.phpt new file mode 100644 index 000000000000..63eea145c77e --- /dev/null +++ b/ext/uri/tests/whatwg/builder/username_success_invalid_percent_encoding.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setUsername() - success - invalid percent encoding +--FILE-- +setScheme("https"); +$builder->setHost("example.com"); +$builder->setUsername("%3"); /* TODO double check why does it work? */ +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(23) "https://%3@example.com/" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + string(2) "%3" + ["password"]=> + string(0) "" + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/username_success_null.phpt b/ext/uri/tests/whatwg/builder/username_success_null.phpt new file mode 100644 index 000000000000..fae92f661a2d --- /dev/null +++ b/ext/uri/tests/whatwg/builder/username_success_null.phpt @@ -0,0 +1,38 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setUsername() - success - null +--FILE-- +setScheme("https"); +$builder->setHost("example.com"); +$builder->setUsername("user"); +$builder->setUsername(null); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(20) "https://example.com/" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/username_success_special_char.phpt b/ext/uri/tests/whatwg/builder/username_success_special_char.phpt new file mode 100644 index 000000000000..5e18a2c66cc4 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/username_success_special_char.phpt @@ -0,0 +1,38 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setUsername() - success - contains special characters +--FILE-- +setScheme("https"); +$builder->setHost("example.com"); +$builder->setUsername("~%#"); +$errors = []; +$url = $builder->build(null, $errors); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(26) "https://~%%23@example.com/" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + string(5) "~%%23" + ["password"]=> + string(0) "" + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/username_success_tab_newline.phpt b/ext/uri/tests/whatwg/builder/username_success_tab_newline.phpt new file mode 100644 index 000000000000..956bd1653ad8 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/username_success_tab_newline.phpt @@ -0,0 +1,51 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setUsername() - success - contains tab and newline characters +--FILE-- +setScheme("\tfo\no"); +$builder->setHost("example.com"); +$builder->setUsername("f\no\ro\t"); +$errors = []; +$url = $builder->build(errors: $errors); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($errors); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECT-- +string(30) "foo://f%0Ao%0Do%09@example.com" +object(Uri\WhatWg\Url)#4 (8) { + ["scheme"]=> + string(3) "foo" + ["username"]=> + string(12) "f%0Ao%0Do%09" + ["password"]=> + string(0) "" + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +array(1) { + [0]=> + object(Uri\WhatWg\UrlValidationError)#2 (3) { + ["context"]=> + string(5) " fo +o" + ["type"]=> + enum(Uri\WhatWg\UrlValidationErrorType::InvalidUrlUnit) + ["failure"]=> + bool(false) + } +} +bool(true) diff --git a/ext/uri/uri_parser_whatwg.c b/ext/uri/uri_parser_whatwg.c index 6d46feb51aae..2b461e562f0e 100644 --- a/ext/uri/uri_parser_whatwg.c +++ b/ext/uri/uri_parser_whatwg.c @@ -28,7 +28,7 @@ ZEND_TLS lxb_unicode_idna_t lexbor_idna = {0}; static const size_t lexbor_mraw_byte_size = 8192; -static zend_always_inline void zval_string_or_null_to_lexbor_str(const zval *value, lexbor_str_t *lexbor_str) +ZEND_ATTRIBUTE_NONNULL static zend_always_inline void zval_string_or_null_to_lexbor_str(const zval *value, lexbor_str_t *lexbor_str) { if (Z_TYPE_P(value) == IS_STRING && Z_STRLEN_P(value) > 0) { lexbor_str->data = (lxb_char_t *) Z_STRVAL_P(value); @@ -40,7 +40,7 @@ static zend_always_inline void zval_string_or_null_to_lexbor_str(const zval *val } } -static zend_always_inline void zval_long_or_null_to_lexbor_str(const zval *value, lexbor_str_t *lexbor_str) +ZEND_ATTRIBUTE_NONNULL static zend_always_inline void zval_long_or_null_to_lexbor_str(const zval *value, lexbor_str_t *lexbor_str) { if (Z_TYPE_P(value) == IS_LONG) { char buf[MAX_LENGTH_OF_LONG + 1]; @@ -58,15 +58,8 @@ static zend_always_inline void zval_long_or_null_to_lexbor_str(const zval *value * https://url.spec.whatwg.org/#writing to a Uri\WhatWg\UrlValidationErrorType enum. * The result is passed by reference to the errors parameter. */ -static const char *fill_errors(zval *errors) +ZEND_ATTRIBUTE_NONNULL static const char *fill_errors_inner(zval *errors) { - size_t log_len; - if (lexbor_parser.log == NULL || (log_len = lexbor_plog_length(lexbor_parser.log)) == 0) { - ZVAL_EMPTY_ARRAY(errors); - return NULL; - } - - array_init_size(errors, log_len); const char *result = NULL; lexbor_plog_entry_t *lxb_error; @@ -213,6 +206,24 @@ static const char *fill_errors(zval *errors) return result; } +/** + * Creates a Uri\WhatWg\UrlValidationError class by mapping error codes listed in + * https://url.spec.whatwg.org/#writing to a Uri\WhatWg\UrlValidationErrorType enum. + * The result is passed by reference to the errors parameter. + */ +ZEND_ATTRIBUTE_NONNULL static const char *fill_errors(zval *errors) +{ + size_t log_len; + if (lexbor_parser.log == NULL || (log_len = lexbor_plog_length(lexbor_parser.log)) == 0) { + ZVAL_EMPTY_ARRAY(errors); + return NULL; + } + + array_init_size(errors, log_len); + + return fill_errors_inner(errors); +} + static void throw_invalid_url_exception_during_write(zval *errors, const char *component) { zval err; @@ -672,6 +683,202 @@ static void php_uri_parser_whatwg_destroy(void *uri) lxb_url_destroy(lexbor_uri); } +static zend_always_inline zend_result php_uri_parser_whatwg_validate_component_result(const bool well_formed, const char *component_name) +{ + if (well_formed) { + return SUCCESS; + } + + zend_throw_exception_ex(php_uri_ce_whatwg_invalid_url_exception, 0, "The specified %s is malformed", component_name); + return FAILURE; +} + +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_none(const zend_string *component) +{ + return SUCCESS; +} + +static zend_always_inline bool php_uri_parser_whatwg_is_alpha(unsigned char c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +static zend_always_inline bool php_uri_parser_whatwg_is_digit(unsigned char c) +{ + return c >= '0' && c <= '9'; +} + +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_scheme(const zend_string *scheme) +{ + const char *p = ZSTR_VAL(scheme); + const size_t len = ZSTR_LEN(scheme); + bool seen_first = false; + + for (size_t i = 0; i < len; i++) { + const unsigned char c = (unsigned char)p[i]; + + if (c == '\t' || c == '\n' || c == '\r') { + continue; + } + + if (!seen_first) { + if (!php_uri_parser_whatwg_is_alpha(c)) { + return php_uri_parser_whatwg_validate_component_result(false, "scheme"); + } + seen_first = true; + } else { + if (!php_uri_parser_whatwg_is_alpha(c) && !php_uri_parser_whatwg_is_digit(c) && c != '+' && c != '-' && c != '.') { + return php_uri_parser_whatwg_validate_component_result(false, "scheme"); + } + } + } + + if (!seen_first) { + return php_uri_parser_whatwg_validate_component_result(false, "scheme"); + } + + return true; +} + +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_port(const zend_long port) +{ + const bool well_formed = port >= 0 && port <= 65535; + + return php_uri_parser_whatwg_validate_component_result(well_formed, "port"); +} + +ZEND_ATTRIBUTE_NONNULL static bool php_uri_parser_whatwg_is_special_scheme(const zval *scheme) +{ + ZEND_ASSERT(Z_TYPE_P(scheme) == IS_STRING); + + const zend_string *str = Z_STR_P(scheme); + + switch (ZSTR_LEN(str)) { + case 2: + return zend_string_equals_literal_ci(str, "ws"); + case 3: + return zend_string_equals_literal_ci(str, "ftp") + || zend_string_equals_literal_ci(str, "wss"); + case 4: + return zend_string_equals_literal_ci(str, "http") + || zend_string_equals_literal_ci(str, "file"); + case 5: + return zend_string_equals_literal_ci(str, "https"); + default: + return false; + } +} + +ZEND_ATTRIBUTE_NONNULL static void php_uri_parser_whatwg_build_errors(zval *errors) +{ + size_t log_len; + + if (lexbor_parser.log == NULL || (log_len = lexbor_plog_length(lexbor_parser.log)) == 0) { + return; + } + + if (Z_TYPE_P(errors) != IS_ARRAY) { + zval_ptr_dtor(errors); + ZVAL_EMPTY_ARRAY(errors); + array_init_size(errors, log_len); + } + + fill_errors_inner(errors); +} + +ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_whatwg_build_from_zval( + lxb_url_t *lexbor_base_url, const zval *scheme, const zval *username, const zval *password, + const zval *host, const zval *port, const zval *path, const zval *query, const zval *fragment, + zval *errors_zv +) { + lxb_url_parser_clean(&lexbor_parser); + + lxb_url_t *lexbor_url = lexbor_mraw_calloc(lexbor_parser.mraw, sizeof(lxb_url_t)); + if (lexbor_url == NULL) { + zend_throw_exception(php_uri_ce_whatwg_invalid_url_exception, "Memory allocation error", 0); + return NULL; + } + + lexbor_url->mraw = lexbor_parser.mraw; + + /* + * The URL is initialized as LXB_URL_SCHEMEL_TYPE__UNDEF but this would prevent the scheme to be updated + * in case of non-special schemes due to https://github.com/php/php-src/blob/27d7b799c0a13578ee0506b428b8ddc209ffb010/ext/lexbor/lexbor/url/url.c#L1402 + */ + if (!php_uri_parser_whatwg_is_special_scheme(scheme)) { + lexbor_url->scheme.type = LXB_URL_SCHEMEL_TYPE__UNKNOWN; + } + + zval errors; + ZVAL_UNDEF(&errors); + + zend_result result = php_uri_parser_whatwg_scheme_write(lexbor_url, scheme, NULL); + php_uri_parser_whatwg_build_errors(&errors); + if (result == FAILURE) { + goto failure; + } + + result = php_uri_parser_whatwg_host_write(lexbor_url, host, NULL); + php_uri_parser_whatwg_build_errors(&errors); + if (result == FAILURE) { + goto failure; + } + + /* Intentionally writing username after host to avoid error when the username is set but the host is missing */ + result = php_uri_parser_whatwg_username_write(lexbor_url, username, NULL); + php_uri_parser_whatwg_build_errors(&errors); + if (result == FAILURE) { + goto failure; + } + + /* Intentionally writing password after host to avoid error when the password is set but the password is missing */ + result = php_uri_parser_whatwg_password_write(lexbor_url, password, NULL); + php_uri_parser_whatwg_build_errors(&errors); + if (result == FAILURE) { + goto failure; + } + + /* Intentionally writing port after host to avoid error when the port is set but the host is missing */ + result = php_uri_parser_whatwg_port_write(lexbor_url, port, NULL); + php_uri_parser_whatwg_build_errors(&errors); + if (result == FAILURE) { + goto failure; + } + + result = php_uri_parser_whatwg_path_write(lexbor_url, path, NULL); + php_uri_parser_whatwg_build_errors(&errors); + if (result == FAILURE) { + goto failure; + } + + result = php_uri_parser_whatwg_query_write(lexbor_url, query, NULL); + php_uri_parser_whatwg_build_errors(&errors); + if (result == FAILURE) { + goto failure; + } + + result = php_uri_parser_whatwg_fragment_write(lexbor_url, fragment, NULL); + php_uri_parser_whatwg_build_errors(&errors); + if (result == FAILURE) { + goto failure; + } + + if (lexbor_base_url != NULL) { + /* TODO */ + } + + if (php_uri_pass_errors_by_ref_and_free(errors_zv, &errors) == FAILURE) { + goto failure; + } + + return lexbor_url; + +failure: + zval_ptr_dtor(&errors); + lxb_url_destroy(lexbor_url); + return NULL; +} + PHPAPI const php_uri_parser php_uri_parser_whatwg = { .name = PHP_URI_PARSER_WHATWG, .parse = php_uri_parser_whatwg_parse, diff --git a/ext/uri/uri_parser_whatwg.h b/ext/uri/uri_parser_whatwg.h index f714ee483680..8d9ea5d4b242 100644 --- a/ext/uri/uri_parser_whatwg.h +++ b/ext/uri/uri_parser_whatwg.h @@ -25,7 +25,18 @@ ZEND_ATTRIBUTE_NONNULL void php_uri_parser_whatwg_host_type_read(const lxb_url_t 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); +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_none(const zend_string *component); +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_scheme(const zend_string *scheme); +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_port(zend_long port); + PHP_RINIT_FUNCTION(uri_parser_whatwg); + +ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_whatwg_build_from_zval( + lxb_url_t *lexbor_base_url, const zval *scheme, const zval *username, const zval *password, + const zval *host, const zval *port, const zval *path, const zval *query, const zval *fragment, + zval *errors_zv +); + ZEND_MODULE_POST_ZEND_DEACTIVATE_D(uri_parser_whatwg); #endif From ce23fe3a4b7efec8fc81fe7c03886197d2990530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Fri, 3 Jul 2026 10:24:26 +0200 Subject: [PATCH 2/9] Address review comments --- ext/uri/php_uri.c | 3 +- .../scheme_error_c0_control_space_char.phpt | 16 ++++ .../scheme_success_ignorable_char.phpt | 36 ++++++++ ext/uri/uri_parser_whatwg.c | 84 ++++++++++++------- 4 files changed, 110 insertions(+), 29 deletions(-) create mode 100644 ext/uri/tests/whatwg/builder/scheme_error_c0_control_space_char.phpt create mode 100644 ext/uri/tests/whatwg/builder/scheme_success_ignorable_char.phpt diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c index 15ae3a686f03..ae49eaf8fa6f 100644 --- a/ext/uri/php_uri.c +++ b/ext/uri/php_uri.c @@ -12,7 +12,6 @@ +----------------------------------------------------------------------+ */ -#include "zend_smart_str.h" #ifdef HAVE_CONFIG_H # include #endif @@ -1262,6 +1261,8 @@ PHP_METHOD(Uri_Rfc3986_UriBuilder, build) PHP_METHOD(Uri_WhatWg_UrlBuilder, reset) { + ZEND_PARSE_PARAMETERS_NONE(); + zval_ptr_dtor(Z_WHATWG_URL_PROP_SCHEME_P(ZEND_THIS)); ZVAL_EMPTY_STRING(Z_WHATWG_URL_PROP_SCHEME_P(ZEND_THIS)); convert_to_null(Z_WHATWG_URL_PROP_USERNAME_P(ZEND_THIS)); diff --git a/ext/uri/tests/whatwg/builder/scheme_error_c0_control_space_char.phpt b/ext/uri/tests/whatwg/builder/scheme_error_c0_control_space_char.phpt new file mode 100644 index 000000000000..1687e3127ece --- /dev/null +++ b/ext/uri/tests/whatwg/builder/scheme_error_c0_control_space_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setScheme() - success - contains leading and trailing C0 control and space characters +--FILE-- +setScheme(" \x01https \x02"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified scheme is malformed diff --git a/ext/uri/tests/whatwg/builder/scheme_success_ignorable_char.phpt b/ext/uri/tests/whatwg/builder/scheme_success_ignorable_char.phpt new file mode 100644 index 000000000000..d60b3e274b34 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/scheme_success_ignorable_char.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setScheme() - success - contains ignorable newline and tab characters +--FILE-- +setScheme("ht\ttp\ns"); +$builder->setHost("example.com"); +$url = $builder->build(); + +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); + +?> +--EXPECTF-- +string(20) "https://example.com/" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +bool(true) diff --git a/ext/uri/uri_parser_whatwg.c b/ext/uri/uri_parser_whatwg.c index 2b461e562f0e..dfac04a07dd0 100644 --- a/ext/uri/uri_parser_whatwg.c +++ b/ext/uri/uri_parser_whatwg.c @@ -698,36 +698,46 @@ ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_none(const zen return SUCCESS; } -static zend_always_inline bool php_uri_parser_whatwg_is_alpha(unsigned char c) +static zend_always_inline bool php_uri_whatwg_is_ascii_tab_or_newline(const unsigned char c) +{ + return c == 0x09 || c == 0x0A || c == 0x0D; /* \t \n \r */ +} + +static zend_always_inline bool php_uri_parser_whatwg_is_alpha(const unsigned char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } -static zend_always_inline bool php_uri_parser_whatwg_is_digit(unsigned char c) +static zend_always_inline bool php_uri_parser_whatwg_is_digit(const unsigned char c) { return c >= '0' && c <= '9'; } +static zend_always_inline unsigned char php_uri_ascii_to_lowercase(const unsigned char c) +{ + return c >= 'A' && c <= 'Z' ? (unsigned char) (c + ('a' - 'A')) : c; +} + ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_scheme(const zend_string *scheme) { const char *p = ZSTR_VAL(scheme); - const size_t len = ZSTR_LEN(scheme); + const char *end = p + ZSTR_LEN(scheme); bool seen_first = false; - for (size_t i = 0; i < len; i++) { - const unsigned char c = (unsigned char)p[i]; + for (const char *c = p; c < end; c++) { + const unsigned char uc = (unsigned char) *c; - if (c == '\t' || c == '\n' || c == '\r') { + if (php_uri_whatwg_is_ascii_tab_or_newline(uc)) { continue; } if (!seen_first) { - if (!php_uri_parser_whatwg_is_alpha(c)) { + if (!php_uri_parser_whatwg_is_alpha(uc)) { return php_uri_parser_whatwg_validate_component_result(false, "scheme"); } seen_first = true; } else { - if (!php_uri_parser_whatwg_is_alpha(c) && !php_uri_parser_whatwg_is_digit(c) && c != '+' && c != '-' && c != '.') { + if (!php_uri_parser_whatwg_is_alpha(uc) && !php_uri_parser_whatwg_is_digit(uc) && uc != '+' && uc != '-' && uc != '.') { return php_uri_parser_whatwg_validate_component_result(false, "scheme"); } } @@ -747,26 +757,45 @@ ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_port(const zen return php_uri_parser_whatwg_validate_component_result(well_formed, "port"); } -ZEND_ATTRIBUTE_NONNULL static bool php_uri_parser_whatwg_is_special_scheme(const zval *scheme) +ZEND_ATTRIBUTE_NONNULL static bool php_uri_parser_whatwg_is_special_scheme(const zend_string *scheme) { - ZEND_ASSERT(Z_TYPE_P(scheme) == IS_STRING); + const char *p = ZSTR_VAL(scheme); + const char *end = p + ZSTR_LEN(scheme); - const zend_string *str = Z_STR_P(scheme); + /* + * Create a normalized buffer from the rest of the characters, leaving out tab and newline characters. + * The longest special scheme "https" is 5 characters, therefore 6 bytes is enough. + */ + char buf[6]; + size_t buf_len = 0; - switch (ZSTR_LEN(str)) { - case 2: - return zend_string_equals_literal_ci(str, "ws"); - case 3: - return zend_string_equals_literal_ci(str, "ftp") - || zend_string_equals_literal_ci(str, "wss"); - case 4: - return zend_string_equals_literal_ci(str, "http") - || zend_string_equals_literal_ci(str, "file"); - case 5: - return zend_string_equals_literal_ci(str, "https"); - default: - return false; - } + for (const char *c = p; c < end; c++) { + const unsigned char uc = (unsigned char) *c; + + if (php_uri_whatwg_is_ascii_tab_or_newline(uc)) { + continue; + } + + if (buf_len == sizeof(buf) - 1) { + /* Longer than any special scheme */ + return false; + } + + buf[buf_len++] = (char) php_uri_ascii_to_lowercase(uc); + } + + switch (buf_len) { + case 2: + return memcmp(buf, "ws", 2) == 0; + case 3: + return memcmp(buf, "ftp", 3) == 0 || memcmp(buf, "wss", 3) == 0; + case 4: + return memcmp(buf, "http", 4) == 0 || memcmp(buf, "file", 4) == 0; + case 5: + return memcmp(buf, "https", 5) == 0; + default: + return false; + } } ZEND_ATTRIBUTE_NONNULL static void php_uri_parser_whatwg_build_errors(zval *errors) @@ -779,7 +808,6 @@ ZEND_ATTRIBUTE_NONNULL static void php_uri_parser_whatwg_build_errors(zval *erro if (Z_TYPE_P(errors) != IS_ARRAY) { zval_ptr_dtor(errors); - ZVAL_EMPTY_ARRAY(errors); array_init_size(errors, log_len); } @@ -793,7 +821,7 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_wh ) { lxb_url_parser_clean(&lexbor_parser); - lxb_url_t *lexbor_url = lexbor_mraw_calloc(lexbor_parser.mraw, sizeof(lxb_url_t)); + lxb_url_t *lexbor_url = lexbor_mraw_calloc(lexbor_parser.mraw, sizeof(*lexbor_url)); if (lexbor_url == NULL) { zend_throw_exception(php_uri_ce_whatwg_invalid_url_exception, "Memory allocation error", 0); return NULL; @@ -805,7 +833,7 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_wh * The URL is initialized as LXB_URL_SCHEMEL_TYPE__UNDEF but this would prevent the scheme to be updated * in case of non-special schemes due to https://github.com/php/php-src/blob/27d7b799c0a13578ee0506b428b8ddc209ffb010/ext/lexbor/lexbor/url/url.c#L1402 */ - if (!php_uri_parser_whatwg_is_special_scheme(scheme)) { + if (!php_uri_parser_whatwg_is_special_scheme(Z_STR_P(scheme))) { lexbor_url->scheme.type = LXB_URL_SCHEMEL_TYPE__UNKNOWN; } From 367b190ec62832f24ab5336c67648b5829ef54a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Fri, 24 Jul 2026 14:45:07 +0200 Subject: [PATCH 3/9] Address review feedback + fixes --- ext/uri/php_uri.c | 2 +- .../whatwg/builder/basic_error_with_base.phpt | 4 +- .../builder/basic_success_with_base.phpt | 4 +- .../builder/fragment_error_unicode_char.phpt | 35 +- .../host_error_ipv6_closing_brace_opaque.phpt | 17 + ...ost_error_ipv6_closing_brace_special.phpt} | 5 +- .../builder/host_error_percent_encoding2.phpt | 3 +- ...ile_scheme.phpt => host_success_file.phpt} | 12 +- .../whatwg/builder/host_success_ipv4.phpt | 6 +- .../whatwg/builder/host_success_ipv6.phpt | 6 +- .../password_error_empty_opaque_host.phpt | 19 + .../builder/password_error_file_scheme.phpt | 19 + .../builder/password_error_missing_host.phpt | 2 +- .../password_error_missing_opaque_host.phpt | 18 + .../password_success_empty_opaque_host.phpt | 37 -- .../builder/password_success_file_scheme.phpt | 37 -- .../builder/port_error_empty_opaque_host.phpt | 19 + .../builder/port_error_file_scheme.phpt | 19 + .../port_error_file_with_host_and_port.phpt | 19 + .../whatwg/builder/port_error_large.phpt | 2 +- .../port_error_missing_opaque_host.phpt | 34 +- .../whatwg/builder/port_error_negative.phpt | 2 +- .../port_success_empty_opaque_host.phpt | 37 -- .../scheme_error_c0_control_space_char.phpt | 4 +- .../whatwg/builder/scheme_error_empty.phpt | 2 +- .../builder/scheme_error_empty_string.phpt | 16 - .../builder/scheme_error_first_char.phpt | 2 +- .../builder/scheme_error_special_char.phpt | 2 +- .../builder/scheme_success_non_special.phpt | 2 +- .../username_error_empty_opaque_host.phpt | 19 + .../builder/username_error_file_scheme.phpt | 19 + ...> username_error_missing_opaque_host.phpt} | 6 +- .../username_success_empty_opaque_host.phpt | 37 -- .../builder/username_success_file_scheme.phpt | 37 -- ...name_success_invalid_percent_encoding.phpt | 4 +- ext/uri/uri_parser_whatwg.c | 375 +++++++++++------- ext/uri/uri_parser_whatwg.h | 1 + 37 files changed, 471 insertions(+), 413 deletions(-) create mode 100644 ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace_opaque.phpt rename ext/uri/tests/whatwg/builder/{host_error_ipv6_closing_brace.phpt => host_error_ipv6_closing_brace_special.phpt} (77%) rename ext/uri/tests/whatwg/builder/{port_success_file_scheme.phpt => host_success_file.phpt} (70%) create mode 100644 ext/uri/tests/whatwg/builder/password_error_empty_opaque_host.phpt create mode 100644 ext/uri/tests/whatwg/builder/password_error_file_scheme.phpt create mode 100644 ext/uri/tests/whatwg/builder/password_error_missing_opaque_host.phpt delete mode 100644 ext/uri/tests/whatwg/builder/password_success_empty_opaque_host.phpt delete mode 100644 ext/uri/tests/whatwg/builder/password_success_file_scheme.phpt create mode 100644 ext/uri/tests/whatwg/builder/port_error_empty_opaque_host.phpt create mode 100644 ext/uri/tests/whatwg/builder/port_error_file_scheme.phpt create mode 100644 ext/uri/tests/whatwg/builder/port_error_file_with_host_and_port.phpt delete mode 100644 ext/uri/tests/whatwg/builder/port_success_empty_opaque_host.phpt delete mode 100644 ext/uri/tests/whatwg/builder/scheme_error_empty_string.phpt create mode 100644 ext/uri/tests/whatwg/builder/username_error_empty_opaque_host.phpt create mode 100644 ext/uri/tests/whatwg/builder/username_error_file_scheme.phpt rename ext/uri/tests/whatwg/builder/{username_error_missing_host.phpt => username_error_missing_opaque_host.phpt} (56%) delete mode 100644 ext/uri/tests/whatwg/builder/username_success_empty_opaque_host.phpt delete mode 100644 ext/uri/tests/whatwg/builder/username_success_file_scheme.phpt diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c index ae49eaf8fa6f..1e03ed81f8dd 100644 --- a/ext/uri/php_uri.c +++ b/ext/uri/php_uri.c @@ -1309,7 +1309,7 @@ PHP_METHOD(Uri_WhatWg_UrlBuilder, setHost) php_uri_builder_set_component_string_or_null( INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("host"), - php_uri_parser_whatwg_validate_none + php_uri_parser_whatwg_validate_host ); } diff --git a/ext/uri/tests/whatwg/builder/basic_error_with_base.phpt b/ext/uri/tests/whatwg/builder/basic_error_with_base.phpt index dbf3fe00c3c2..6079628af517 100644 --- a/ext/uri/tests/whatwg/builder/basic_error_with_base.phpt +++ b/ext/uri/tests/whatwg/builder/basic_error_with_base.phpt @@ -3,8 +3,8 @@ Test Uri\WhatWg\UrlBuilder basic - error - with base URL --FILE-- setPath("/foo/bar/baz"); +$builder = new Uri\WhatWg\UrlBuilder(); +$builder->setPath("/foo/bar/baz"); try { $builder->build(new Uri\WhatWg\Url("/foo/bar")); diff --git a/ext/uri/tests/whatwg/builder/basic_success_with_base.phpt b/ext/uri/tests/whatwg/builder/basic_success_with_base.phpt index adda5bc65892..fcf4c42085e5 100644 --- a/ext/uri/tests/whatwg/builder/basic_success_with_base.phpt +++ b/ext/uri/tests/whatwg/builder/basic_success_with_base.phpt @@ -3,8 +3,8 @@ Test Uri\WhatWg\UrlBuilder basic - success - with base URL --FILE-- setPath("/foo/bar/baz"); +$builder = new Uri\WhatWg\UrlBuilder(); +$builder->setPath("/foo/bar/baz"); $url = $builder->build(new Uri\WhatWg\Url("https://example.com")); var_dump($url->toAsciiString()); diff --git a/ext/uri/tests/whatwg/builder/fragment_error_unicode_char.phpt b/ext/uri/tests/whatwg/builder/fragment_error_unicode_char.phpt index fc46470f489a..ded8ce5b1c2e 100644 --- a/ext/uri/tests/whatwg/builder/fragment_error_unicode_char.phpt +++ b/ext/uri/tests/whatwg/builder/fragment_error_unicode_char.phpt @@ -4,13 +4,34 @@ Test Uri\WhatWg\UrlBuilder::setFragment() - error - contains Unicode character setScheme("https"); +$builder->setHost("example.com"); +$builder->setFragment("főő"); +$url = $builder->build(); -try { - $builder->setFragment("főő"); -} catch (Throwable $e) { - echo $e::class, ": ", $e->getMessage(), PHP_EOL; -} +var_dump($url->toAsciiString()); +var_dump($url); +var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); ?> ---EXPECT-- - +--EXPECTF-- +string(34) "https://example.com/#f%C5%91%C5%91" +object(Uri\WhatWg\Url)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + string(13) "f%C5%91%C5%91" +} +bool(true) diff --git a/ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace_opaque.phpt b/ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace_opaque.phpt new file mode 100644 index 000000000000..b38851ed502d --- /dev/null +++ b/ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace_opaque.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setHost() - error - missing IPv6 closing brace of an opaque host +--FILE-- +setScheme("scheme"); + +try { + $builder->setHost("[2001:%30db8:85a3:0000:0000:8a2e:0370:7334"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified host is malformed (Ipv6Unclosed) diff --git a/ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace.phpt b/ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace_special.phpt similarity index 77% rename from ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace.phpt rename to ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace_special.phpt index c19c0e5d0dea..94ba827ca325 100644 --- a/ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace.phpt +++ b/ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace_special.phpt @@ -1,14 +1,13 @@ --TEST-- -Test Uri\WhatWg\UrlBuilder::setHost() - error - missing IPv6 closing brace +Test Uri\WhatWg\UrlBuilder::setHost() - error - missing IPv6 closing brace of a special host --FILE-- setScheme("https"); -$builder->setHost("[2001:%30db8:85a3:0000:0000:8a2e:0370:7334"); try { - $builder->build(); + $builder->setHost("[2001:%30db8:85a3:0000:0000:8a2e:0370:7334"); } catch (Throwable $e) { echo $e::class, ": ", $e->getMessage(), PHP_EOL; } diff --git a/ext/uri/tests/whatwg/builder/host_error_percent_encoding2.phpt b/ext/uri/tests/whatwg/builder/host_error_percent_encoding2.phpt index 6b5e120792ce..90a228ded22b 100644 --- a/ext/uri/tests/whatwg/builder/host_error_percent_encoding2.phpt +++ b/ext/uri/tests/whatwg/builder/host_error_percent_encoding2.phpt @@ -5,10 +5,9 @@ Test Uri\WhatWg\UrlBuilder::setHost() - error - invalid percent encoded octet in $builder = new Uri\WhatWg\UrlBuilder(); $builder->setScheme("https"); -$builder->setHost("[2001:%308:85a3:0000:0000:8a2e:0370:7334]"); try { - $builder->build(); + $builder->setHost("[2001:%308:85a3:0000:0000:8a2e:0370:7334]"); } catch (Throwable $e) { echo $e::class, ": ", $e->getMessage(), PHP_EOL; } diff --git a/ext/uri/tests/whatwg/builder/port_success_file_scheme.phpt b/ext/uri/tests/whatwg/builder/host_success_file.phpt similarity index 70% rename from ext/uri/tests/whatwg/builder/port_success_file_scheme.phpt rename to ext/uri/tests/whatwg/builder/host_success_file.phpt index c67f4c120fc2..2264c2faffbe 100644 --- a/ext/uri/tests/whatwg/builder/port_success_file_scheme.phpt +++ b/ext/uri/tests/whatwg/builder/host_success_file.phpt @@ -1,12 +1,11 @@ --TEST-- -Test Uri\WhatWg\UrlBuilder::setPort() - success - ignored for file scheme +Test Uri\WhatWg\UrlBuilder::setHost() - success - file scheme --FILE-- setScheme("file"); -$builder->setPath("C:/a.txt"); -$builder->setPort(443); +$builder->setHost("example.com"); $url = $builder->build(); var_dump($url->toAsciiString()); @@ -15,7 +14,7 @@ var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); ?> --EXPECTF-- -string(16) "file:///C:/a.txt" +string(19) "file://example.com/" object(Uri\WhatWg\Url)#%d (%d) { ["scheme"]=> string(4) "file" @@ -24,14 +23,15 @@ object(Uri\WhatWg\Url)#%d (%d) { ["password"]=> NULL ["host"]=> - string(0) "" + string(11) "example.com" ["port"]=> NULL ["path"]=> - string(9) "/C:/a.txt" + string(1) "/" ["query"]=> NULL ["fragment"]=> NULL } bool(true) + diff --git a/ext/uri/tests/whatwg/builder/host_success_ipv4.phpt b/ext/uri/tests/whatwg/builder/host_success_ipv4.phpt index 9f8bb7b5c244..2dad23e1f214 100644 --- a/ext/uri/tests/whatwg/builder/host_success_ipv4.phpt +++ b/ext/uri/tests/whatwg/builder/host_success_ipv4.phpt @@ -3,9 +3,9 @@ Test Uri\WhatWg\UrlBuilder::setHost() - success - IPv4 address --FILE-- setScheme("https") - ->setHost("192.168.0.1"); +$builder = new Uri\WhatWg\UrlBuilder(); +$builder->setScheme("https"); +$builder->setHost("192.168.0.1"); $url = $builder->build(); var_dump($url->toAsciiString()); diff --git a/ext/uri/tests/whatwg/builder/host_success_ipv6.phpt b/ext/uri/tests/whatwg/builder/host_success_ipv6.phpt index 9775aa58482f..48e37d22a8ce 100644 --- a/ext/uri/tests/whatwg/builder/host_success_ipv6.phpt +++ b/ext/uri/tests/whatwg/builder/host_success_ipv6.phpt @@ -3,9 +3,9 @@ Test Uri\WhatWg\UrlBuilder::setHost() - success - IPv6 address --FILE-- setScheme("https") - ->setHost("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"); +$builder = new Uri\WhatWg\UrlBuilder(); +$builder->setScheme("https"); +$builder->setHost("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"); $url = $builder->build(); var_dump($url->toAsciiString()); diff --git a/ext/uri/tests/whatwg/builder/password_error_empty_opaque_host.phpt b/ext/uri/tests/whatwg/builder/password_error_empty_opaque_host.phpt new file mode 100644 index 000000000000..7544e1a7d487 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/password_error_empty_opaque_host.phpt @@ -0,0 +1,19 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setUsername() - error - empty opaque host +--FILE-- +setScheme("scheme"); +$builder->setHost(""); +$builder->setUsername("user"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified URI is malformed diff --git a/ext/uri/tests/whatwg/builder/password_error_file_scheme.phpt b/ext/uri/tests/whatwg/builder/password_error_file_scheme.phpt new file mode 100644 index 000000000000..88849b7843f5 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/password_error_file_scheme.phpt @@ -0,0 +1,19 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPassword() - error - file scheme +--FILE-- +setScheme("file"); +$builder->setPath("C:/a.txt"); +$builder->setPassword("password"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified URI is malformed diff --git a/ext/uri/tests/whatwg/builder/password_error_missing_host.phpt b/ext/uri/tests/whatwg/builder/password_error_missing_host.phpt index 1d55b74a4f50..9826fffdace0 100644 --- a/ext/uri/tests/whatwg/builder/password_error_missing_host.phpt +++ b/ext/uri/tests/whatwg/builder/password_error_missing_host.phpt @@ -15,4 +15,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified host is malformed (HostMissing) +Uri\WhatWg\InvalidUrlException: The specified URI is malformed diff --git a/ext/uri/tests/whatwg/builder/password_error_missing_opaque_host.phpt b/ext/uri/tests/whatwg/builder/password_error_missing_opaque_host.phpt new file mode 100644 index 000000000000..b91bd8c9bd4f --- /dev/null +++ b/ext/uri/tests/whatwg/builder/password_error_missing_opaque_host.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPassword() - error - missing opaque host +--FILE-- +setScheme("scheme"); +$builder->setPassword("user"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified URI is malformed diff --git a/ext/uri/tests/whatwg/builder/password_success_empty_opaque_host.phpt b/ext/uri/tests/whatwg/builder/password_success_empty_opaque_host.phpt deleted file mode 100644 index eed39a2332ec..000000000000 --- a/ext/uri/tests/whatwg/builder/password_success_empty_opaque_host.phpt +++ /dev/null @@ -1,37 +0,0 @@ ---TEST-- -Test Uri\WhatWg\UrlBuilder::setUsername() - success - empty opaque host ---FILE-- -setScheme("scheme"); -$builder->setHost(""); -$builder->setUsername("user"); -$url = $builder->build(); - -var_dump($url->toAsciiString()); -var_dump($url); -var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); - -?> ---EXPECTF-- -string(9) "scheme://" -object(Uri\WhatWg\Url)#%d (%d) { - ["scheme"]=> - string(6) "scheme" - ["username"]=> - NULL - ["password"]=> - NULL - ["host"]=> - string(0) "" - ["port"]=> - NULL - ["path"]=> - string(0) "" - ["query"]=> - NULL - ["fragment"]=> - NULL -} -bool(true) diff --git a/ext/uri/tests/whatwg/builder/password_success_file_scheme.phpt b/ext/uri/tests/whatwg/builder/password_success_file_scheme.phpt deleted file mode 100644 index 32b3e9d09ce2..000000000000 --- a/ext/uri/tests/whatwg/builder/password_success_file_scheme.phpt +++ /dev/null @@ -1,37 +0,0 @@ ---TEST-- -Test Uri\WhatWg\UrlBuilder::setPassword() - success - ignored for file scheme ---FILE-- -setScheme("file"); -$builder->setPath("C:/a.txt"); -$builder->setPassword("password"); -$url = $builder->build(); - -var_dump($url->toAsciiString()); -var_dump($url); -var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); - -?> ---EXPECTF-- -string(16) "file:///C:/a.txt" -object(Uri\WhatWg\Url)#%d (%d) { - ["scheme"]=> - string(4) "file" - ["username"]=> - NULL - ["password"]=> - NULL - ["host"]=> - string(0) "" - ["port"]=> - NULL - ["path"]=> - string(9) "/C:/a.txt" - ["query"]=> - NULL - ["fragment"]=> - NULL -} -bool(true) diff --git a/ext/uri/tests/whatwg/builder/port_error_empty_opaque_host.phpt b/ext/uri/tests/whatwg/builder/port_error_empty_opaque_host.phpt new file mode 100644 index 000000000000..a372e9be1554 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/port_error_empty_opaque_host.phpt @@ -0,0 +1,19 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPort() - error - empty opaque host +--FILE-- +setScheme("scheme"); +$builder->setHost(""); +$builder->setPort(443); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified URI is malformed diff --git a/ext/uri/tests/whatwg/builder/port_error_file_scheme.phpt b/ext/uri/tests/whatwg/builder/port_error_file_scheme.phpt new file mode 100644 index 000000000000..a223b510cc94 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/port_error_file_scheme.phpt @@ -0,0 +1,19 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPort() - error - file scheme +--FILE-- +setScheme("file"); +$builder->setPath("C:/a.txt"); +$builder->setPort(443); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified URI is malformed diff --git a/ext/uri/tests/whatwg/builder/port_error_file_with_host_and_port.phpt b/ext/uri/tests/whatwg/builder/port_error_file_with_host_and_port.phpt new file mode 100644 index 000000000000..b8af3bf45a95 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/port_error_file_with_host_and_port.phpt @@ -0,0 +1,19 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setPort() - error - file scheme with host and port +--FILE-- +setScheme("file"); +$builder->setHost("example.com"); +$builder->setPort(123); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified URI is malformed diff --git a/ext/uri/tests/whatwg/builder/port_error_large.phpt b/ext/uri/tests/whatwg/builder/port_error_large.phpt index 94205613a70c..46e80e1689b7 100644 --- a/ext/uri/tests/whatwg/builder/port_error_large.phpt +++ b/ext/uri/tests/whatwg/builder/port_error_large.phpt @@ -18,4 +18,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified port is malformed +Uri\WhatWg\InvalidUrlException: The specified port is malformed (PortOutOfRange) diff --git a/ext/uri/tests/whatwg/builder/port_error_missing_opaque_host.phpt b/ext/uri/tests/whatwg/builder/port_error_missing_opaque_host.phpt index 963c7fa7e1df..ceadc80ea696 100644 --- a/ext/uri/tests/whatwg/builder/port_error_missing_opaque_host.phpt +++ b/ext/uri/tests/whatwg/builder/port_error_missing_opaque_host.phpt @@ -1,36 +1,18 @@ --TEST-- -Test Uri\WhatWg\UrlBuilder::setPort() - success - missing opaque host +Test Uri\WhatWg\UrlBuilder::setPort() - error - missing opaque host --FILE-- setScheme("scheme"); $builder->setPort(443); -$url = $builder->build(); -var_dump($url->toAsciiString()); -var_dump($url); -var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} ?> ---EXPECTF-- -string(9) "scheme://" -object(Uri\WhatWg\Url)#%d (%d) { - ["scheme"]=> - string(6) "scheme" - ["username"]=> - NULL - ["password"]=> - NULL - ["host"]=> - string(0) "" - ["port"]=> - NULL - ["path"]=> - string(0) "" - ["query"]=> - NULL - ["fragment"]=> - NULL -} -bool(true) +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified URI is malformed diff --git a/ext/uri/tests/whatwg/builder/port_error_negative.phpt b/ext/uri/tests/whatwg/builder/port_error_negative.phpt index eb2b5449ccb3..a1a723f43ca5 100644 --- a/ext/uri/tests/whatwg/builder/port_error_negative.phpt +++ b/ext/uri/tests/whatwg/builder/port_error_negative.phpt @@ -13,4 +13,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified port is malformed +Uri\WhatWg\InvalidUrlException: The specified port is malformed (PortInvalid) diff --git a/ext/uri/tests/whatwg/builder/port_success_empty_opaque_host.phpt b/ext/uri/tests/whatwg/builder/port_success_empty_opaque_host.phpt deleted file mode 100644 index 603c2d8ed89b..000000000000 --- a/ext/uri/tests/whatwg/builder/port_success_empty_opaque_host.phpt +++ /dev/null @@ -1,37 +0,0 @@ ---TEST-- -Test Uri\WhatWg\UrlBuilder::setPort() - success - empty opaque host ---FILE-- -setScheme("scheme"); -$builder->setHost(""); -$builder->setPort(443); -$url = $builder->build(); - -var_dump($url->toAsciiString()); -var_dump($url); -var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); - -?> ---EXPECTF-- -string(9) "scheme://" -object(Uri\WhatWg\Url)#%d (%d) { - ["scheme"]=> - string(6) "scheme" - ["username"]=> - NULL - ["password"]=> - NULL - ["host"]=> - string(0) "" - ["port"]=> - NULL - ["path"]=> - string(0) "" - ["query"]=> - NULL - ["fragment"]=> - NULL -} -bool(true) diff --git a/ext/uri/tests/whatwg/builder/scheme_error_c0_control_space_char.phpt b/ext/uri/tests/whatwg/builder/scheme_error_c0_control_space_char.phpt index 1687e3127ece..285e91132469 100644 --- a/ext/uri/tests/whatwg/builder/scheme_error_c0_control_space_char.phpt +++ b/ext/uri/tests/whatwg/builder/scheme_error_c0_control_space_char.phpt @@ -1,5 +1,5 @@ --TEST-- -Test Uri\WhatWg\UrlBuilder::setScheme() - success - contains leading and trailing C0 control and space characters +Test Uri\WhatWg\UrlBuilder::setScheme() - error - contains leading and trailing C0 control and space characters --FILE-- --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified scheme is malformed +Uri\WhatWg\InvalidUrlException: The specified scheme is malformed (MissingSchemeNonRelativeUrl) diff --git a/ext/uri/tests/whatwg/builder/scheme_error_empty.phpt b/ext/uri/tests/whatwg/builder/scheme_error_empty.phpt index 985cfe30eeac..f4090a9c4aac 100644 --- a/ext/uri/tests/whatwg/builder/scheme_error_empty.phpt +++ b/ext/uri/tests/whatwg/builder/scheme_error_empty.phpt @@ -13,4 +13,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified scheme is malformed +Uri\WhatWg\InvalidUrlException: The specified scheme is malformed (MissingSchemeNonRelativeUrl) diff --git a/ext/uri/tests/whatwg/builder/scheme_error_empty_string.phpt b/ext/uri/tests/whatwg/builder/scheme_error_empty_string.phpt deleted file mode 100644 index 163949df8b46..000000000000 --- a/ext/uri/tests/whatwg/builder/scheme_error_empty_string.phpt +++ /dev/null @@ -1,16 +0,0 @@ ---TEST-- -Test Uri\WhatWg\UrlBuilder::setScheme() - error - empty string ---FILE-- -setScheme(""); -} catch (Throwable $e) { - echo $e::class, ": ", $e->getMessage(), PHP_EOL; -} - -?> ---EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified scheme is malformed diff --git a/ext/uri/tests/whatwg/builder/scheme_error_first_char.phpt b/ext/uri/tests/whatwg/builder/scheme_error_first_char.phpt index c5b80a866c33..34a70c043aa3 100644 --- a/ext/uri/tests/whatwg/builder/scheme_error_first_char.phpt +++ b/ext/uri/tests/whatwg/builder/scheme_error_first_char.phpt @@ -13,4 +13,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified scheme is malformed +Uri\WhatWg\InvalidUrlException: The specified scheme is malformed (MissingSchemeNonRelativeUrl) diff --git a/ext/uri/tests/whatwg/builder/scheme_error_special_char.phpt b/ext/uri/tests/whatwg/builder/scheme_error_special_char.phpt index eaf59f922ecd..c37abc1ebb7c 100644 --- a/ext/uri/tests/whatwg/builder/scheme_error_special_char.phpt +++ b/ext/uri/tests/whatwg/builder/scheme_error_special_char.phpt @@ -13,4 +13,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified scheme is malformed +Uri\WhatWg\InvalidUrlException: The specified scheme is malformed (MissingSchemeNonRelativeUrl) diff --git a/ext/uri/tests/whatwg/builder/scheme_success_non_special.phpt b/ext/uri/tests/whatwg/builder/scheme_success_non_special.phpt index e2c06826bd53..c805105ee992 100644 --- a/ext/uri/tests/whatwg/builder/scheme_success_non_special.phpt +++ b/ext/uri/tests/whatwg/builder/scheme_success_non_special.phpt @@ -33,4 +33,4 @@ object(Uri\WhatWg\Url)#%d (%d) { ["fragment"]=> NULL } -bool(true) \ No newline at end of file +bool(true) diff --git a/ext/uri/tests/whatwg/builder/username_error_empty_opaque_host.phpt b/ext/uri/tests/whatwg/builder/username_error_empty_opaque_host.phpt new file mode 100644 index 000000000000..7544e1a7d487 --- /dev/null +++ b/ext/uri/tests/whatwg/builder/username_error_empty_opaque_host.phpt @@ -0,0 +1,19 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setUsername() - error - empty opaque host +--FILE-- +setScheme("scheme"); +$builder->setHost(""); +$builder->setUsername("user"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified URI is malformed diff --git a/ext/uri/tests/whatwg/builder/username_error_file_scheme.phpt b/ext/uri/tests/whatwg/builder/username_error_file_scheme.phpt new file mode 100644 index 000000000000..273f0ef373fe --- /dev/null +++ b/ext/uri/tests/whatwg/builder/username_error_file_scheme.phpt @@ -0,0 +1,19 @@ +--TEST-- +Test Uri\WhatWg\UrlBuilder::setUsername() - error - file scheme +--FILE-- +setScheme("file"); +$builder->setPath("C:/a.txt"); +$builder->setUsername("user"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\WhatWg\InvalidUrlException: The specified URI is malformed diff --git a/ext/uri/tests/whatwg/builder/username_error_missing_host.phpt b/ext/uri/tests/whatwg/builder/username_error_missing_opaque_host.phpt similarity index 56% rename from ext/uri/tests/whatwg/builder/username_error_missing_host.phpt rename to ext/uri/tests/whatwg/builder/username_error_missing_opaque_host.phpt index 609d1d49a5c3..d6771a5be1cc 100644 --- a/ext/uri/tests/whatwg/builder/username_error_missing_host.phpt +++ b/ext/uri/tests/whatwg/builder/username_error_missing_opaque_host.phpt @@ -1,10 +1,10 @@ --TEST-- -Test Uri\WhatWg\UrlBuilder::setUsername() - error - missing host +Test Uri\WhatWg\UrlBuilder::setUsername() - error - missing opaque host --FILE-- setScheme("https"); +$builder->setScheme("scheme"); $builder->setUsername("user"); try { @@ -15,4 +15,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified host is malformed (HostMissing) +Uri\WhatWg\InvalidUrlException: The specified URI is malformed diff --git a/ext/uri/tests/whatwg/builder/username_success_empty_opaque_host.phpt b/ext/uri/tests/whatwg/builder/username_success_empty_opaque_host.phpt deleted file mode 100644 index 1f4e7541aa05..000000000000 --- a/ext/uri/tests/whatwg/builder/username_success_empty_opaque_host.phpt +++ /dev/null @@ -1,37 +0,0 @@ ---TEST-- -Test Uri\WhatWg\UrlBuilder::setPassword() - success - empty opaque host ---FILE-- -setScheme("scheme"); -$builder->setHost(""); -$builder->setPassword("user"); -$url = $builder->build(); - -var_dump($url->toAsciiString()); -var_dump($url); -var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); - -?> ---EXPECTF-- -string(9) "scheme://" -object(Uri\WhatWg\Url)#%d (%d) { - ["scheme"]=> - string(6) "scheme" - ["username"]=> - NULL - ["password"]=> - NULL - ["host"]=> - string(0) "" - ["port"]=> - NULL - ["path"]=> - string(0) "" - ["query"]=> - NULL - ["fragment"]=> - NULL -} -bool(true) diff --git a/ext/uri/tests/whatwg/builder/username_success_file_scheme.phpt b/ext/uri/tests/whatwg/builder/username_success_file_scheme.phpt deleted file mode 100644 index b2c02cbf2757..000000000000 --- a/ext/uri/tests/whatwg/builder/username_success_file_scheme.phpt +++ /dev/null @@ -1,37 +0,0 @@ ---TEST-- -Test Uri\WhatWg\UrlBuilder::setUsername() - success - ignored for file scheme ---FILE-- -setScheme("file"); -$builder->setPath("C:/a.txt"); -$builder->setUsername("user"); -$url = $builder->build(); - -var_dump($url->toAsciiString()); -var_dump($url); -var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); - -?> ---EXPECTF-- -string(16) "file:///C:/a.txt" -object(Uri\WhatWg\Url)#%d (%d) { - ["scheme"]=> - string(4) "file" - ["username"]=> - NULL - ["password"]=> - NULL - ["host"]=> - string(0) "" - ["port"]=> - NULL - ["path"]=> - string(9) "/C:/a.txt" - ["query"]=> - NULL - ["fragment"]=> - NULL -} -bool(true) diff --git a/ext/uri/tests/whatwg/builder/username_success_invalid_percent_encoding.phpt b/ext/uri/tests/whatwg/builder/username_success_invalid_percent_encoding.phpt index 63eea145c77e..273fc18c72e3 100644 --- a/ext/uri/tests/whatwg/builder/username_success_invalid_percent_encoding.phpt +++ b/ext/uri/tests/whatwg/builder/username_success_invalid_percent_encoding.phpt @@ -1,12 +1,12 @@ --TEST-- -Test Uri\WhatWg\UrlBuilder::setUsername() - success - invalid percent encoding +Test Uri\WhatWg\UrlBuilder::setUsername() - success - invalid percent encoding is untouched --FILE-- setScheme("https"); $builder->setHost("example.com"); -$builder->setUsername("%3"); /* TODO double check why does it work? */ +$builder->setUsername("%3"); $url = $builder->build(); var_dump($url->toAsciiString()); diff --git a/ext/uri/uri_parser_whatwg.c b/ext/uri/uri_parser_whatwg.c index dfac04a07dd0..39f17317b017 100644 --- a/ext/uri/uri_parser_whatwg.c +++ b/ext/uri/uri_parser_whatwg.c @@ -53,6 +53,100 @@ ZEND_ATTRIBUTE_NONNULL static zend_always_inline void zval_long_or_null_to_lexbo } } +ZEND_ATTRIBUTE_NONNULL static bool get_reason_from_error_type(const lxb_url_error_type_t error_type, const char **error_str) +{ + switch (error_type) { + case LXB_URL_ERROR_TYPE_DOMAIN_TO_ASCII: + *error_str = "DomainToAscii"; + return true; + case LXB_URL_ERROR_TYPE_DOMAIN_TO_UNICODE: + *error_str = "DomainToUnicode"; + return false; + case LXB_URL_ERROR_TYPE_DOMAIN_INVALID_CODE_POINT: + *error_str = "DomainInvalidCodePoint"; + return true; + case LXB_URL_ERROR_TYPE_HOST_INVALID_CODE_POINT: + *error_str = "HostInvalidCodePoint"; + return true; + case LXB_URL_ERROR_TYPE_IPV4_EMPTY_PART: + *error_str = "Ipv4EmptyPart"; + return false; + case LXB_URL_ERROR_TYPE_IPV4_TOO_MANY_PARTS: + *error_str = "Ipv4TooManyParts"; + return true; + case LXB_URL_ERROR_TYPE_IPV4_NON_NUMERIC_PART: + *error_str = "Ipv4NonNumericPart"; + return true; + case LXB_URL_ERROR_TYPE_IPV4_NON_DECIMAL_PART: + *error_str = "Ipv4NonDecimalPart"; + return false; + case LXB_URL_ERROR_TYPE_IPV4_OUT_OF_RANGE_PART: + *error_str = "Ipv4OutOfRangePart"; + return true; + case LXB_URL_ERROR_TYPE_IPV6_UNCLOSED: + *error_str = "Ipv6Unclosed"; + return true; + case LXB_URL_ERROR_TYPE_IPV6_INVALID_COMPRESSION: + *error_str = "Ipv6InvalidCompression"; + return true; + case LXB_URL_ERROR_TYPE_IPV6_TOO_MANY_PIECES: + *error_str = "Ipv6TooManyPieces"; + return true; + case LXB_URL_ERROR_TYPE_IPV6_MULTIPLE_COMPRESSION: + *error_str = "Ipv6MultipleCompression"; + return true; + case LXB_URL_ERROR_TYPE_IPV6_INVALID_CODE_POINT: + *error_str = "Ipv6InvalidCodePoint"; + return true; + case LXB_URL_ERROR_TYPE_IPV6_TOO_FEW_PIECES: + *error_str = "Ipv6TooFewPieces"; + return true; + case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_MANY_PIECES: + *error_str = "Ipv4InIpv6TooManyPieces"; + return true; + case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_INVALID_CODE_POINT: + *error_str = "Ipv4InIpv6InvalidCodePoint"; + return true; + case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_OUT_OF_RANGE_PART: + *error_str = "Ipv4InIpv6OutOfRangePart"; + return true; + case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_FEW_PARTS: + *error_str = "Ipv4InIpv6TooFewParts"; + return true; + case LXB_URL_ERROR_TYPE_INVALID_URL_UNIT: + *error_str = "InvalidUrlUnit"; + return false; + case LXB_URL_ERROR_TYPE_SPECIAL_SCHEME_MISSING_FOLLOWING_SOLIDUS: + *error_str = "SpecialSchemeMissingFollowingSolidus"; + return false; + case LXB_URL_ERROR_TYPE_MISSING_SCHEME_NON_RELATIVE_URL: + *error_str = "MissingSchemeNonRelativeUrl"; + return true; + case LXB_URL_ERROR_TYPE_INVALID_REVERSE_SOLIDUS: + *error_str = "InvalidReverseSoldius"; + return false; + case LXB_URL_ERROR_TYPE_INVALID_CREDENTIALS: + *error_str = "InvalidCredentials"; + return false; + case LXB_URL_ERROR_TYPE_HOST_MISSING: + *error_str = "HostMissing"; + return true; + case LXB_URL_ERROR_TYPE_PORT_OUT_OF_RANGE: + *error_str = "PortOutOfRange"; + return true; + case LXB_URL_ERROR_TYPE_PORT_INVALID: + *error_str = "PortInvalid"; + return true; + case LXB_URL_ERROR_TYPE_FILE_INVALID_WINDOWS_DRIVE_LETTER: + *error_str = "FileInvalidWindowsDriveLetter"; + return false; + case LXB_URL_ERROR_TYPE_FILE_INVALID_WINDOWS_DRIVE_LETTER_HOST: + *error_str = "FileInvalidWindowsDriveLetterHost"; + return false; + default: ZEND_UNREACHABLE(); + } +} + /** * Creates a Uri\WhatWg\UrlValidationError class by mapping error codes listed in * https://url.spec.whatwg.org/#writing to a Uri\WhatWg\UrlValidationErrorType enum. @@ -70,125 +164,8 @@ ZEND_ATTRIBUTE_NONNULL static const char *fill_errors_inner(zval *errors) const char *error_str; zval failure; - switch (lxb_error->id) { - case LXB_URL_ERROR_TYPE_DOMAIN_TO_ASCII: - error_str = "DomainToAscii"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_DOMAIN_TO_UNICODE: - error_str = "DomainToUnicode"; - ZVAL_FALSE(&failure); - break; - case LXB_URL_ERROR_TYPE_DOMAIN_INVALID_CODE_POINT: - error_str = "DomainInvalidCodePoint"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_HOST_INVALID_CODE_POINT: - error_str = "HostInvalidCodePoint"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_IPV4_EMPTY_PART: - error_str = "Ipv4EmptyPart"; - ZVAL_FALSE(&failure); - break; - case LXB_URL_ERROR_TYPE_IPV4_TOO_MANY_PARTS: - error_str = "Ipv4TooManyParts"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_IPV4_NON_NUMERIC_PART: - error_str = "Ipv4NonNumericPart"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_IPV4_NON_DECIMAL_PART: - error_str = "Ipv4NonDecimalPart"; - ZVAL_FALSE(&failure); - break; - case LXB_URL_ERROR_TYPE_IPV4_OUT_OF_RANGE_PART: - error_str = "Ipv4OutOfRangePart"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_IPV6_UNCLOSED: - error_str = "Ipv6Unclosed"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_IPV6_INVALID_COMPRESSION: - error_str = "Ipv6InvalidCompression"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_IPV6_TOO_MANY_PIECES: - error_str = "Ipv6TooManyPieces"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_IPV6_MULTIPLE_COMPRESSION: - error_str = "Ipv6MultipleCompression"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_IPV6_INVALID_CODE_POINT: - error_str = "Ipv6InvalidCodePoint"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_IPV6_TOO_FEW_PIECES: - error_str = "Ipv6TooFewPieces"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_MANY_PIECES: - error_str = "Ipv4InIpv6TooManyPieces"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_INVALID_CODE_POINT: - error_str = "Ipv4InIpv6InvalidCodePoint"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_OUT_OF_RANGE_PART: - error_str = "Ipv4InIpv6OutOfRangePart"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_FEW_PARTS: - error_str = "Ipv4InIpv6TooFewParts"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_INVALID_URL_UNIT: - error_str = "InvalidUrlUnit"; - ZVAL_FALSE(&failure); - break; - case LXB_URL_ERROR_TYPE_SPECIAL_SCHEME_MISSING_FOLLOWING_SOLIDUS: - error_str = "SpecialSchemeMissingFollowingSolidus"; - ZVAL_FALSE(&failure); - break; - case LXB_URL_ERROR_TYPE_MISSING_SCHEME_NON_RELATIVE_URL: - error_str = "MissingSchemeNonRelativeUrl"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_INVALID_REVERSE_SOLIDUS: - error_str = "InvalidReverseSoldius"; - ZVAL_FALSE(&failure); - break; - case LXB_URL_ERROR_TYPE_INVALID_CREDENTIALS: - error_str = "InvalidCredentials"; - ZVAL_FALSE(&failure); - break; - case LXB_URL_ERROR_TYPE_HOST_MISSING: - error_str = "HostMissing"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_PORT_OUT_OF_RANGE: - error_str = "PortOutOfRange"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_PORT_INVALID: - error_str = "PortInvalid"; - ZVAL_TRUE(&failure); - break; - case LXB_URL_ERROR_TYPE_FILE_INVALID_WINDOWS_DRIVE_LETTER: - error_str = "FileInvalidWindowsDriveLetter"; - ZVAL_FALSE(&failure); - break; - case LXB_URL_ERROR_TYPE_FILE_INVALID_WINDOWS_DRIVE_LETTER_HOST: - error_str = "FileInvalidWindowsDriveLetterHost"; - ZVAL_FALSE(&failure); - break; - default: ZEND_UNREACHABLE(); - } + + ZVAL_BOOL(&failure, get_reason_from_error_type(lxb_error->id, &error_str)); zval error_type; ZVAL_OBJ(&error_type, zend_enum_get_case_cstr(php_uri_ce_whatwg_url_validation_error_type, error_str)); @@ -683,13 +660,23 @@ static void php_uri_parser_whatwg_destroy(void *uri) lxb_url_destroy(lexbor_uri); } -static zend_always_inline zend_result php_uri_parser_whatwg_validate_component_result(const bool well_formed, const char *component_name) -{ - if (well_formed) { - return SUCCESS; +ZEND_ATTRIBUTE_NONNULL static zend_always_inline zend_result php_uri_parser_whatwg_component_error( + const char *component_name, const lxb_url_error_type_t error_type +) { + const char *reason = ""; + if (error_type != LXB_URL_ERROR_TYPE__LAST_ENTRY) { + get_reason_from_error_type(error_type, &reason); } - zend_throw_exception_ex(php_uri_ce_whatwg_invalid_url_exception, 0, "The specified %s is malformed", component_name); + zend_throw_exception_ex(php_uri_ce_whatwg_invalid_url_exception, + 0, + "The specified %s is malformed%s%s%s", + component_name, + reason ? " (" : "", + reason ? reason : "", + reason ? ")" : "" + ); + return FAILURE; } @@ -733,38 +720,106 @@ ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_scheme(const z if (!seen_first) { if (!php_uri_parser_whatwg_is_alpha(uc)) { - return php_uri_parser_whatwg_validate_component_result(false, "scheme"); + return php_uri_parser_whatwg_component_error("scheme", LXB_URL_ERROR_TYPE_MISSING_SCHEME_NON_RELATIVE_URL); } seen_first = true; } else { if (!php_uri_parser_whatwg_is_alpha(uc) && !php_uri_parser_whatwg_is_digit(uc) && uc != '+' && uc != '-' && uc != '.') { - return php_uri_parser_whatwg_validate_component_result(false, "scheme"); + return php_uri_parser_whatwg_component_error("scheme", LXB_URL_ERROR_TYPE_MISSING_SCHEME_NON_RELATIVE_URL); } } } if (!seen_first) { - return php_uri_parser_whatwg_validate_component_result(false, "scheme"); + return php_uri_parser_whatwg_component_error("scheme", LXB_URL_ERROR_TYPE_MISSING_SCHEME_NON_RELATIVE_URL); + } + + return SUCCESS; +} + +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_host(const zend_string *host) +{ + if (ZSTR_LEN(host) == 0) { + /* Skip validation - an empty string may or may not be a valid host depending on whether the URL is special */ + return SUCCESS; + } + + const char *first = ZSTR_VAL(host); + const char *last = ZSTR_VAL(host) + ZSTR_LEN(host) - 1; + + while (first <= last && php_uri_whatwg_is_ascii_tab_or_newline((unsigned char) *first)) { + first++; + } + + if (*first != '[') { + /* Skip validation - The host is not an IPv6 address */ + return SUCCESS; } - return true; + while (first <= last && php_uri_whatwg_is_ascii_tab_or_newline((unsigned char) *last)) { + last--; + } + + if (*last != ']') { + return php_uri_parser_whatwg_component_error("host", LXB_URL_ERROR_TYPE_IPV6_UNCLOSED); + } + + lxb_char_t *stripped_host = emalloc(last - first); + size_t stripped_host_len = 0; + + for (const char *i = first + 1; i < last; i++) { + const unsigned char uc = (unsigned char) *i; + + if (!php_uri_whatwg_is_ascii_tab_or_newline(uc)) { + stripped_host[stripped_host_len++] = (lxb_char_t) uc; + } + } + stripped_host[stripped_host_len] = '\0'; + + uint16_t ipv6[8]; + const lxb_status_t result = lxb_url_ipv6_parse( + &lexbor_parser, + stripped_host, + stripped_host + stripped_host_len, + ipv6 + ); + + efree(stripped_host); + + if (result != LXB_STATUS_OK) { + lexbor_plog_entry_t *lxb_error; + + if (lexbor_parser.log != NULL && (lxb_error = lexbor_array_obj_pop(&lexbor_parser.log->list)) != NULL) { + return php_uri_parser_whatwg_component_error("host", lxb_error->id); + } else { + return php_uri_parser_whatwg_component_error("host", LXB_URL_ERROR_TYPE__LAST_ENTRY); + } + } + + return SUCCESS; } ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_port(const zend_long port) { - const bool well_formed = port >= 0 && port <= 65535; + if (port < 0) { + return php_uri_parser_whatwg_component_error("port", LXB_URL_ERROR_TYPE_PORT_INVALID); + } + + if (port > 65535) { + return php_uri_parser_whatwg_component_error("port", LXB_URL_ERROR_TYPE_PORT_OUT_OF_RANGE); + } - return php_uri_parser_whatwg_validate_component_result(well_formed, "port"); + return SUCCESS; } -ZEND_ATTRIBUTE_NONNULL static bool php_uri_parser_whatwg_is_special_scheme(const zend_string *scheme) +ZEND_ATTRIBUTE_NONNULL static lxb_url_scheme_type_t php_uri_parser_whatwg_get_special_scheme(const zend_string *scheme) { const char *p = ZSTR_VAL(scheme); const char *end = p + ZSTR_LEN(scheme); /* - * Create a normalized buffer from the rest of the characters, leaving out tab and newline characters. - * The longest special scheme "https" is 5 characters, therefore 6 bytes is enough. + * Create a normalized buffer from the scheme by leaving out tab and newline characters. + * The longest special scheme "https" is 5 characters, therefore 6 bytes is enough to store. */ char buf[6]; size_t buf_len = 0; @@ -785,17 +840,41 @@ ZEND_ATTRIBUTE_NONNULL static bool php_uri_parser_whatwg_is_special_scheme(const } switch (buf_len) { - case 2: - return memcmp(buf, "ws", 2) == 0; + case 2: + if (memcmp(buf, "ws", 2) == 0) { + return LXB_URL_SCHEMEL_TYPE_WS; + } + + break; case 3: - return memcmp(buf, "ftp", 3) == 0 || memcmp(buf, "wss", 3) == 0; - case 4: - return memcmp(buf, "http", 4) == 0 || memcmp(buf, "file", 4) == 0; + if (memcmp(buf, "ftp", 3) == 0) { + return LXB_URL_SCHEMEL_TYPE_FTP; + } + + if (memcmp(buf, "wss", 3) == 0) { + return LXB_URL_SCHEMEL_TYPE_WSS; + } + + break; + case 4: + if (memcmp(buf, "http", 4) == 0) { + return LXB_URL_SCHEMEL_TYPE_HTTP; + } + + if (memcmp(buf, "file", 4) == 0) { + return LXB_URL_SCHEMEL_TYPE_FILE; + } + + break; case 5: - return memcmp(buf, "https", 5) == 0; - default: - return false; - } + if (memcmp(buf, "https", 5) == 0) { + return LXB_URL_SCHEMEL_TYPE_HTTPS; + } + + break; + } + + return LXB_URL_SCHEMEL_TYPE__UNDEF; } ZEND_ATTRIBUTE_NONNULL static void php_uri_parser_whatwg_build_errors(zval *errors) @@ -819,6 +898,16 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_wh const zval *host, const zval *port, const zval *path, const zval *query, const zval *fragment, zval *errors_zv ) { + if (Z_TYPE_P(host) == IS_NULL || + Z_STRLEN_P(host) == 0 || + php_uri_parser_whatwg_get_special_scheme(Z_STR_P(scheme)) == LXB_URL_SCHEMEL_TYPE_FILE + ) { + if (Z_TYPE_P(username) != IS_NULL || Z_TYPE_P(password) != IS_NULL || Z_TYPE_P(port) != IS_NULL) { + zend_throw_exception_ex(php_uri_ce_whatwg_invalid_url_exception, 0, "The specified URI is malformed"); + return NULL; + } + } + lxb_url_parser_clean(&lexbor_parser); lxb_url_t *lexbor_url = lexbor_mraw_calloc(lexbor_parser.mraw, sizeof(*lexbor_url)); @@ -833,7 +922,7 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_wh * The URL is initialized as LXB_URL_SCHEMEL_TYPE__UNDEF but this would prevent the scheme to be updated * in case of non-special schemes due to https://github.com/php/php-src/blob/27d7b799c0a13578ee0506b428b8ddc209ffb010/ext/lexbor/lexbor/url/url.c#L1402 */ - if (!php_uri_parser_whatwg_is_special_scheme(Z_STR_P(scheme))) { + if (php_uri_parser_whatwg_get_special_scheme(Z_STR_P(scheme)) == LXB_URL_SCHEMEL_TYPE__UNDEF) { lexbor_url->scheme.type = LXB_URL_SCHEMEL_TYPE__UNKNOWN; } diff --git a/ext/uri/uri_parser_whatwg.h b/ext/uri/uri_parser_whatwg.h index 8d9ea5d4b242..0a03c8e76a93 100644 --- a/ext/uri/uri_parser_whatwg.h +++ b/ext/uri/uri_parser_whatwg.h @@ -27,6 +27,7 @@ lxb_url_t *php_uri_parser_whatwg_parse_ex(const char *uri_str, size_t uri_str_le ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_none(const zend_string *component); ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_scheme(const zend_string *scheme); +ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_host(const zend_string *host); ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_port(zend_long port); PHP_RINIT_FUNCTION(uri_parser_whatwg); From a9a3b19ff3a6c77de25c26117f34bb847a86ccae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Fri, 24 Jul 2026 20:25:57 +0200 Subject: [PATCH 4/9] Fix test name --- ...ror_unicode_char.phpt => fragment_success_unicode_char.phpt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename ext/uri/tests/whatwg/builder/{fragment_error_unicode_char.phpt => fragment_success_unicode_char.phpt} (88%) diff --git a/ext/uri/tests/whatwg/builder/fragment_error_unicode_char.phpt b/ext/uri/tests/whatwg/builder/fragment_success_unicode_char.phpt similarity index 88% rename from ext/uri/tests/whatwg/builder/fragment_error_unicode_char.phpt rename to ext/uri/tests/whatwg/builder/fragment_success_unicode_char.phpt index ded8ce5b1c2e..0153432eedb4 100644 --- a/ext/uri/tests/whatwg/builder/fragment_error_unicode_char.phpt +++ b/ext/uri/tests/whatwg/builder/fragment_success_unicode_char.phpt @@ -1,5 +1,5 @@ --TEST-- -Test Uri\WhatWg\UrlBuilder::setFragment() - error - contains Unicode character +Test Uri\WhatWg\UrlBuilder::setFragment() - success - contains Unicode character --FILE-- Date: Sat, 1 Aug 2026 14:22:52 +0200 Subject: [PATCH 5/9] zval *errors to HashTable *errors --- ext/uri/uri_parser_whatwg.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/uri/uri_parser_whatwg.c b/ext/uri/uri_parser_whatwg.c index 39f17317b017..5efd4904a65a 100644 --- a/ext/uri/uri_parser_whatwg.c +++ b/ext/uri/uri_parser_whatwg.c @@ -152,7 +152,7 @@ ZEND_ATTRIBUTE_NONNULL static bool get_reason_from_error_type(const lxb_url_erro * https://url.spec.whatwg.org/#writing to a Uri\WhatWg\UrlValidationErrorType enum. * The result is passed by reference to the errors parameter. */ -ZEND_ATTRIBUTE_NONNULL static const char *fill_errors_inner(zval *errors) +ZEND_ATTRIBUTE_NONNULL static const char *fill_errors_inner(HashTable *errors) { const char *result = NULL; @@ -177,7 +177,7 @@ ZEND_ATTRIBUTE_NONNULL static const char *fill_errors_inner(zval *errors) result = error_str; } - add_next_index_zval(errors, &error); + zend_hash_next_index_insert(errors, &error); } return result; @@ -198,7 +198,7 @@ ZEND_ATTRIBUTE_NONNULL static const char *fill_errors(zval *errors) array_init_size(errors, log_len); - return fill_errors_inner(errors); + return fill_errors_inner(Z_ARRVAL_P(errors)); } static void throw_invalid_url_exception_during_write(zval *errors, const char *component) @@ -890,7 +890,7 @@ ZEND_ATTRIBUTE_NONNULL static void php_uri_parser_whatwg_build_errors(zval *erro array_init_size(errors, log_len); } - fill_errors_inner(errors); + fill_errors_inner(Z_ARRVAL_P(errors)); } ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_whatwg_build_from_zval( From b8d9786927329eeecec4789033bf2268cc0171da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Fri, 7 Aug 2026 08:39:00 +0200 Subject: [PATCH 6/9] Fix code review suggestions again --- ext/uri/php_uri.c | 52 ++++++++++--------- .../host_error_ipv6_closing_brace.phpt | 2 +- ...t => fragment_success_ignorable_char.phpt} | 13 +---- .../host_error_ipv6_closing_brace_opaque.phpt | 2 +- .../password_error_empty_opaque_host.phpt | 6 +-- .../builder/password_error_file_scheme.phpt | 2 +- .../builder/password_error_missing_host.phpt | 2 +- .../password_error_missing_opaque_host.phpt | 4 +- .../builder/port_error_empty_opaque_host.phpt | 2 +- .../builder/port_error_file_scheme.phpt | 2 +- .../port_error_file_with_host_and_port.phpt | 2 +- .../port_error_missing_opaque_host.phpt | 2 +- .../whatwg/builder/port_success_default.phpt | 6 +-- .../builder/port_success_non_default.phpt | 6 +-- .../username_error_empty_opaque_host.phpt | 2 +- .../builder/username_error_file_scheme.phpt | 2 +- .../username_error_missing_opaque_host.phpt | 2 +- ext/uri/uri_parser_whatwg.c | 14 ++++- 18 files changed, 64 insertions(+), 59 deletions(-) rename ext/uri/tests/whatwg/builder/{fragment_success_tab_newline.phpt => fragment_success_ignorable_char.phpt} (77%) diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c index 1e03ed81f8dd..a783f1ce63b4 100644 --- a/ext/uri/php_uri.c +++ b/ext/uri/php_uri.c @@ -76,14 +76,16 @@ static zend_always_inline zval *php_uri_deref(zval *zv) #define Z_RFC3986_URI_PROP_QUERY_DEREF_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 5)) #define Z_RFC3986_URI_PROP_FRAGMENT_DEREF_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 6)) -#define Z_WHATWG_URL_PROP_SCHEME_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 0)) -#define Z_WHATWG_URL_PROP_USERNAME_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 1)) -#define Z_WHATWG_URL_PROP_PASSWORD_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 2)) -#define Z_WHATWG_URL_PROP_HOST_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 3)) -#define Z_WHATWG_URL_PROP_PORT_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 4)) -#define Z_WHATWG_URL_PROP_PATH_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 5)) -#define Z_WHATWG_URL_PROP_QUERY_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 6)) -#define Z_WHATWG_URL_PROP_FRAGMENT_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 7)) +#define Z_WHATWG_URL_PROP_SCHEME_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 0) +#define Z_WHATWG_URL_PROP_SCHEME_DEREF_P(zv) php_uri_deref(Z_WHATWG_URL_PROP_SCHEME_P(zv)) +#define Z_WHATWG_URL_PROP_USERNAME_DEREF_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 1)) +#define Z_WHATWG_URL_PROP_PASSWORD_DEREF_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 2)) +#define Z_WHATWG_URL_PROP_HOST_DEREF_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 3)) +#define Z_WHATWG_URL_PROP_PORT_DEREF_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 4)) +#define Z_WHATWG_URL_PROP_PATH_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 5) +#define Z_WHATWG_URL_PROP_PATH_DEREF_P(zv) php_uri_deref(Z_WHATWG_URL_PROP_PATH_P(zv)) +#define Z_WHATWG_URL_PROP_QUERY_DEREF_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 6)) +#define Z_WHATWG_URL_PROP_FRAGMENT_DEREF_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 7)) static HashTable *uri_get_debug_properties(php_uri_object *object) { @@ -1263,16 +1265,18 @@ PHP_METHOD(Uri_WhatWg_UrlBuilder, reset) { ZEND_PARSE_PARAMETERS_NONE(); - zval_ptr_dtor(Z_WHATWG_URL_PROP_SCHEME_P(ZEND_THIS)); + zend_object *object = Z_OBJ_P(ZEND_THIS); + zval *property = object->properties_table; + const zval *end = property + object->ce->default_properties_count; + + while (property != end) { + zend_object_dtor_property(object, property); + ZVAL_NULL(property); + property++; + } + ZVAL_EMPTY_STRING(Z_WHATWG_URL_PROP_SCHEME_P(ZEND_THIS)); - convert_to_null(Z_WHATWG_URL_PROP_USERNAME_P(ZEND_THIS)); - convert_to_null(Z_WHATWG_URL_PROP_PASSWORD_P(ZEND_THIS)); - convert_to_null(Z_WHATWG_URL_PROP_HOST_P(ZEND_THIS)); - convert_to_null(Z_WHATWG_URL_PROP_PORT_P(ZEND_THIS)); - zval_ptr_dtor(Z_WHATWG_URL_PROP_PATH_P(ZEND_THIS)); ZVAL_EMPTY_STRING(Z_WHATWG_URL_PROP_PATH_P(ZEND_THIS)); - convert_to_null(Z_WHATWG_URL_PROP_QUERY_P(ZEND_THIS)); - convert_to_null(Z_WHATWG_URL_PROP_FRAGMENT_P(ZEND_THIS)); RETVAL_COPY(ZEND_THIS); } @@ -1360,14 +1364,14 @@ PHP_METHOD(Uri_WhatWg_UrlBuilder, build) Z_PARAM_ZVAL(errors) ZEND_PARSE_PARAMETERS_END(); - const zval *scheme = Z_WHATWG_URL_PROP_SCHEME_P(ZEND_THIS); - const zval *username = Z_WHATWG_URL_PROP_USERNAME_P(ZEND_THIS); - const zval *password = Z_WHATWG_URL_PROP_PASSWORD_P(ZEND_THIS); - const zval *host = Z_WHATWG_URL_PROP_HOST_P(ZEND_THIS); - const zval *port = Z_WHATWG_URL_PROP_PORT_P(ZEND_THIS); - const zval *path = Z_WHATWG_URL_PROP_PATH_P(ZEND_THIS); - const zval *query = Z_WHATWG_URL_PROP_QUERY_P(ZEND_THIS); - const zval *fragment = Z_WHATWG_URL_PROP_FRAGMENT_P(ZEND_THIS); + const zval *scheme = Z_WHATWG_URL_PROP_SCHEME_DEREF_P(ZEND_THIS); + const zval *username = Z_WHATWG_URL_PROP_USERNAME_DEREF_P(ZEND_THIS); + const zval *password = Z_WHATWG_URL_PROP_PASSWORD_DEREF_P(ZEND_THIS); + const zval *host = Z_WHATWG_URL_PROP_HOST_DEREF_P(ZEND_THIS); + const zval *port = Z_WHATWG_URL_PROP_PORT_DEREF_P(ZEND_THIS); + const zval *path = Z_WHATWG_URL_PROP_PATH_DEREF_P(ZEND_THIS); + const zval *query = Z_WHATWG_URL_PROP_QUERY_DEREF_P(ZEND_THIS); + const zval *fragment = Z_WHATWG_URL_PROP_FRAGMENT_DEREF_P(ZEND_THIS); lxb_url_t *base_url = NULL; if (base_url_zv != NULL) { diff --git a/ext/uri/tests/rfc3986/builder/host_error_ipv6_closing_brace.phpt b/ext/uri/tests/rfc3986/builder/host_error_ipv6_closing_brace.phpt index 45eef13faf71..feda6170b38e 100644 --- a/ext/uri/tests/rfc3986/builder/host_error_ipv6_closing_brace.phpt +++ b/ext/uri/tests/rfc3986/builder/host_error_ipv6_closing_brace.phpt @@ -6,7 +6,7 @@ Test Uri\Rfc3986\UriBuilder::setHost() - error - missing IPv6 closing brace $builder = new Uri\Rfc3986\UriBuilder(); try { - $builder->setHost("[2001:%30db8:85a3:0000:0000:8a2e:0370:7334"); + $builder->setHost("[2001:db8:85a3:0000:0000:8a2e:0370:7334"); } catch (Throwable $e) { echo $e::class, ": ", $e->getMessage(), PHP_EOL; } diff --git a/ext/uri/tests/whatwg/builder/fragment_success_tab_newline.phpt b/ext/uri/tests/whatwg/builder/fragment_success_ignorable_char.phpt similarity index 77% rename from ext/uri/tests/whatwg/builder/fragment_success_tab_newline.phpt rename to ext/uri/tests/whatwg/builder/fragment_success_ignorable_char.phpt index f30ca259e839..f4940291f75d 100644 --- a/ext/uri/tests/whatwg/builder/fragment_success_tab_newline.phpt +++ b/ext/uri/tests/whatwg/builder/fragment_success_ignorable_char.phpt @@ -4,9 +4,9 @@ Test Uri\WhatWg\UrlBuilder::setFragment() - success - contains tab and newline c setScheme("\tfo\no"); +$builder->setScheme("foo"); $builder->setHost("example.com"); -$builder->setFragment("\tfoo"); +$builder->setFragment("\tfo\no"); $errors = []; $url = $builder->build(errors: $errors); @@ -47,14 +47,5 @@ o" ["failure"]=> bool(false) } - [1]=> - object(Uri\WhatWg\UrlValidationError)#%d (%d) { - ["context"]=> - string(4) " foo" - ["type"]=> - enum(Uri\WhatWg\UrlValidationErrorType::InvalidUrlUnit) - ["failure"]=> - bool(false) - } } bool(true) diff --git a/ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace_opaque.phpt b/ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace_opaque.phpt index b38851ed502d..7f3fbb35da63 100644 --- a/ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace_opaque.phpt +++ b/ext/uri/tests/whatwg/builder/host_error_ipv6_closing_brace_opaque.phpt @@ -7,7 +7,7 @@ $builder = new Uri\WhatWg\UrlBuilder(); $builder->setScheme("scheme"); try { - $builder->setHost("[2001:%30db8:85a3:0000:0000:8a2e:0370:7334"); + $builder->setHost("[2001:db8:85a3:0000:0000:8a2e:0370:7334"); } catch (Throwable $e) { echo $e::class, ": ", $e->getMessage(), PHP_EOL; } diff --git a/ext/uri/tests/whatwg/builder/password_error_empty_opaque_host.phpt b/ext/uri/tests/whatwg/builder/password_error_empty_opaque_host.phpt index 7544e1a7d487..5c79856b93c9 100644 --- a/ext/uri/tests/whatwg/builder/password_error_empty_opaque_host.phpt +++ b/ext/uri/tests/whatwg/builder/password_error_empty_opaque_host.phpt @@ -1,12 +1,12 @@ --TEST-- -Test Uri\WhatWg\UrlBuilder::setUsername() - error - empty opaque host +Test Uri\WhatWg\UrlBuilder::setPassword() - error - empty opaque host --FILE-- setScheme("scheme"); $builder->setHost(""); -$builder->setUsername("user"); +$builder->setPassword("password"); try { $builder->build(); @@ -16,4 +16,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified URI is malformed +Uri\WhatWg\InvalidUrlException: The specified URL cannot have password diff --git a/ext/uri/tests/whatwg/builder/password_error_file_scheme.phpt b/ext/uri/tests/whatwg/builder/password_error_file_scheme.phpt index 88849b7843f5..e6f4f993c4d2 100644 --- a/ext/uri/tests/whatwg/builder/password_error_file_scheme.phpt +++ b/ext/uri/tests/whatwg/builder/password_error_file_scheme.phpt @@ -16,4 +16,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified URI is malformed +Uri\WhatWg\InvalidUrlException: The specified URL cannot have password diff --git a/ext/uri/tests/whatwg/builder/password_error_missing_host.phpt b/ext/uri/tests/whatwg/builder/password_error_missing_host.phpt index 9826fffdace0..9b3ba0876336 100644 --- a/ext/uri/tests/whatwg/builder/password_error_missing_host.phpt +++ b/ext/uri/tests/whatwg/builder/password_error_missing_host.phpt @@ -15,4 +15,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified URI is malformed +Uri\WhatWg\InvalidUrlException: The specified URL cannot have password diff --git a/ext/uri/tests/whatwg/builder/password_error_missing_opaque_host.phpt b/ext/uri/tests/whatwg/builder/password_error_missing_opaque_host.phpt index b91bd8c9bd4f..efc14b06a413 100644 --- a/ext/uri/tests/whatwg/builder/password_error_missing_opaque_host.phpt +++ b/ext/uri/tests/whatwg/builder/password_error_missing_opaque_host.phpt @@ -5,7 +5,7 @@ Test Uri\WhatWg\UrlBuilder::setPassword() - error - missing opaque host $builder = new Uri\WhatWg\UrlBuilder(); $builder->setScheme("scheme"); -$builder->setPassword("user"); +$builder->setPassword("password"); try { $builder->build(); @@ -15,4 +15,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified URI is malformed +Uri\WhatWg\InvalidUrlException: The specified URL cannot have password diff --git a/ext/uri/tests/whatwg/builder/port_error_empty_opaque_host.phpt b/ext/uri/tests/whatwg/builder/port_error_empty_opaque_host.phpt index a372e9be1554..acf82a1a12cb 100644 --- a/ext/uri/tests/whatwg/builder/port_error_empty_opaque_host.phpt +++ b/ext/uri/tests/whatwg/builder/port_error_empty_opaque_host.phpt @@ -16,4 +16,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified URI is malformed +Uri\WhatWg\InvalidUrlException: The specified URL cannot have port diff --git a/ext/uri/tests/whatwg/builder/port_error_file_scheme.phpt b/ext/uri/tests/whatwg/builder/port_error_file_scheme.phpt index a223b510cc94..08559dbfd208 100644 --- a/ext/uri/tests/whatwg/builder/port_error_file_scheme.phpt +++ b/ext/uri/tests/whatwg/builder/port_error_file_scheme.phpt @@ -16,4 +16,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified URI is malformed +Uri\WhatWg\InvalidUrlException: The specified URL cannot have port diff --git a/ext/uri/tests/whatwg/builder/port_error_file_with_host_and_port.phpt b/ext/uri/tests/whatwg/builder/port_error_file_with_host_and_port.phpt index b8af3bf45a95..9e4e17ca3ae3 100644 --- a/ext/uri/tests/whatwg/builder/port_error_file_with_host_and_port.phpt +++ b/ext/uri/tests/whatwg/builder/port_error_file_with_host_and_port.phpt @@ -16,4 +16,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified URI is malformed +Uri\WhatWg\InvalidUrlException: The specified URL cannot have port diff --git a/ext/uri/tests/whatwg/builder/port_error_missing_opaque_host.phpt b/ext/uri/tests/whatwg/builder/port_error_missing_opaque_host.phpt index ceadc80ea696..3130b27cfcb7 100644 --- a/ext/uri/tests/whatwg/builder/port_error_missing_opaque_host.phpt +++ b/ext/uri/tests/whatwg/builder/port_error_missing_opaque_host.phpt @@ -15,4 +15,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified URI is malformed +Uri\WhatWg\InvalidUrlException: The specified URL cannot have port diff --git a/ext/uri/tests/whatwg/builder/port_success_default.phpt b/ext/uri/tests/whatwg/builder/port_success_default.phpt index 6ea1bcd50021..cb4f55c609ca 100644 --- a/ext/uri/tests/whatwg/builder/port_success_default.phpt +++ b/ext/uri/tests/whatwg/builder/port_success_default.phpt @@ -5,7 +5,7 @@ Test Uri\WhatWg\UrlBuilder::setPort() - success - default port $builder = new Uri\WhatWg\UrlBuilder(); $builder->setScheme("https"); -$builder->setPort(444); +$builder->setPort(443); $builder->setHost("example.com"); $url = $builder->build(); @@ -15,7 +15,7 @@ var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); ?> --EXPECTF-- -string(24) "https://example.com:444/" +string(20) "https://example.com/" object(Uri\WhatWg\Url)#%d (%d) { ["scheme"]=> string(5) "https" @@ -26,7 +26,7 @@ object(Uri\WhatWg\Url)#%d (%d) { ["host"]=> string(11) "example.com" ["port"]=> - int(444) + NULL ["path"]=> string(1) "/" ["query"]=> diff --git a/ext/uri/tests/whatwg/builder/port_success_non_default.phpt b/ext/uri/tests/whatwg/builder/port_success_non_default.phpt index b8588844372a..919e9eb6983c 100644 --- a/ext/uri/tests/whatwg/builder/port_success_non_default.phpt +++ b/ext/uri/tests/whatwg/builder/port_success_non_default.phpt @@ -5,7 +5,7 @@ Test Uri\WhatWg\UrlBuilder::setPort() - success - non-default port $builder = new Uri\WhatWg\UrlBuilder(); $builder->setScheme("https"); -$builder->setPort(443); +$builder->setPort(444); $builder->setHost("example.com"); $url = $builder->build(); @@ -15,7 +15,7 @@ var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString()))); ?> --EXPECTF-- -string(20) "https://example.com/" +string(24) "https://example.com:444/" object(Uri\WhatWg\Url)#%d (%d) { ["scheme"]=> string(5) "https" @@ -26,7 +26,7 @@ object(Uri\WhatWg\Url)#%d (%d) { ["host"]=> string(11) "example.com" ["port"]=> - NULL + int(444) ["path"]=> string(1) "/" ["query"]=> diff --git a/ext/uri/tests/whatwg/builder/username_error_empty_opaque_host.phpt b/ext/uri/tests/whatwg/builder/username_error_empty_opaque_host.phpt index 7544e1a7d487..c97ffd49afad 100644 --- a/ext/uri/tests/whatwg/builder/username_error_empty_opaque_host.phpt +++ b/ext/uri/tests/whatwg/builder/username_error_empty_opaque_host.phpt @@ -16,4 +16,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified URI is malformed +Uri\WhatWg\InvalidUrlException: The specified URL cannot have username diff --git a/ext/uri/tests/whatwg/builder/username_error_file_scheme.phpt b/ext/uri/tests/whatwg/builder/username_error_file_scheme.phpt index 273f0ef373fe..aee81ee3b835 100644 --- a/ext/uri/tests/whatwg/builder/username_error_file_scheme.phpt +++ b/ext/uri/tests/whatwg/builder/username_error_file_scheme.phpt @@ -16,4 +16,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified URI is malformed +Uri\WhatWg\InvalidUrlException: The specified URL cannot have username diff --git a/ext/uri/tests/whatwg/builder/username_error_missing_opaque_host.phpt b/ext/uri/tests/whatwg/builder/username_error_missing_opaque_host.phpt index d6771a5be1cc..257266f97c91 100644 --- a/ext/uri/tests/whatwg/builder/username_error_missing_opaque_host.phpt +++ b/ext/uri/tests/whatwg/builder/username_error_missing_opaque_host.phpt @@ -15,4 +15,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified URI is malformed +Uri\WhatWg\InvalidUrlException: The specified URL cannot have username diff --git a/ext/uri/uri_parser_whatwg.c b/ext/uri/uri_parser_whatwg.c index 5efd4904a65a..df0dd90f0178 100644 --- a/ext/uri/uri_parser_whatwg.c +++ b/ext/uri/uri_parser_whatwg.c @@ -902,8 +902,18 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_wh Z_STRLEN_P(host) == 0 || php_uri_parser_whatwg_get_special_scheme(Z_STR_P(scheme)) == LXB_URL_SCHEMEL_TYPE_FILE ) { - if (Z_TYPE_P(username) != IS_NULL || Z_TYPE_P(password) != IS_NULL || Z_TYPE_P(port) != IS_NULL) { - zend_throw_exception_ex(php_uri_ce_whatwg_invalid_url_exception, 0, "The specified URI is malformed"); + if (Z_TYPE_P(username) != IS_NULL) { + zend_throw_exception_ex(php_uri_ce_whatwg_invalid_url_exception, 0, "The specified URL cannot have username"); + return NULL; + } + + if (Z_TYPE_P(password) != IS_NULL) { + zend_throw_exception_ex(php_uri_ce_whatwg_invalid_url_exception, 0, "The specified URL cannot have password"); + return NULL; + } + + if (Z_TYPE_P(port) != IS_NULL) { + zend_throw_exception_ex(php_uri_ce_whatwg_invalid_url_exception, 0, "The specified URL cannot have port"); return NULL; } } From a7145f34b179db9bd90523ef6ffa083f15e9c4c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Fri, 14 Aug 2026 14:20:41 +0200 Subject: [PATCH 7/9] Migrate to the new lxb_url_parse_host_ipv6() API --- ext/uri/uri_parser_whatwg.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/ext/uri/uri_parser_whatwg.c b/ext/uri/uri_parser_whatwg.c index df0dd90f0178..68600caca56d 100644 --- a/ext/uri/uri_parser_whatwg.c +++ b/ext/uri/uri_parser_whatwg.c @@ -745,9 +745,9 @@ ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_host(const zen } const char *first = ZSTR_VAL(host); - const char *last = ZSTR_VAL(host) + ZSTR_LEN(host) - 1; + const char *last = ZSTR_VAL(host) + ZSTR_LEN(host); - while (first <= last && php_uri_whatwg_is_ascii_tab_or_newline((unsigned char) *first)) { + while (first < last && php_uri_whatwg_is_ascii_tab_or_newline((unsigned char) *first)) { first++; } @@ -756,18 +756,14 @@ ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_host(const zen return SUCCESS; } - while (first <= last && php_uri_whatwg_is_ascii_tab_or_newline((unsigned char) *last)) { + while (first < last && php_uri_whatwg_is_ascii_tab_or_newline((unsigned char) *last)) { last--; } - if (*last != ']') { - return php_uri_parser_whatwg_component_error("host", LXB_URL_ERROR_TYPE_IPV6_UNCLOSED); - } - - lxb_char_t *stripped_host = emalloc(last - first); + lxb_char_t *stripped_host = emalloc(last - first + 1); size_t stripped_host_len = 0; - for (const char *i = first + 1; i < last; i++) { + for (const char *i = first; i < last; i++) { const unsigned char uc = (unsigned char) *i; if (!php_uri_whatwg_is_ascii_tab_or_newline(uc)) { @@ -777,10 +773,10 @@ ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_host(const zen stripped_host[stripped_host_len] = '\0'; uint16_t ipv6[8]; - const lxb_status_t result = lxb_url_ipv6_parse( + const lxb_status_t result = lxb_url_parse_host_ipv6( &lexbor_parser, stripped_host, - stripped_host + stripped_host_len, + stripped_host_len, ipv6 ); From 24d382a84304bd451f42c0a4c3896ddfdd2440f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Wed, 19 Aug 2026 10:45:41 +0200 Subject: [PATCH 8/9] Some code review fixes --- ext/uri/php_uri.c | 2 ++ ext/uri/tests/whatwg/builder/basic_error_with_base.phpt | 2 +- ext/uri/tests/whatwg/builder/basic_success_with_base.phpt | 2 ++ ext/uri/uri_parser_whatwg.c | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c index a783f1ce63b4..ed8a967578ff 100644 --- a/ext/uri/php_uri.c +++ b/ext/uri/php_uri.c @@ -1375,6 +1375,8 @@ PHP_METHOD(Uri_WhatWg_UrlBuilder, build) lxb_url_t *base_url = NULL; if (base_url_zv != NULL) { + zend_argument_error(NULL, 1, "is not supported yet, and therefore, null must be passed"); + RETURN_THROWS(); base_url = Z_URI_OBJECT_P(base_url_zv)->uri; } diff --git a/ext/uri/tests/whatwg/builder/basic_error_with_base.phpt b/ext/uri/tests/whatwg/builder/basic_error_with_base.phpt index 6079628af517..6f65cdacdb4a 100644 --- a/ext/uri/tests/whatwg/builder/basic_error_with_base.phpt +++ b/ext/uri/tests/whatwg/builder/basic_error_with_base.phpt @@ -14,4 +14,4 @@ try { ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException: The specified base URI must be absolute +Uri\WhatWg\InvalidUrlException: The specified URI is malformed (MissingSchemeNonRelativeUrl) diff --git a/ext/uri/tests/whatwg/builder/basic_success_with_base.phpt b/ext/uri/tests/whatwg/builder/basic_success_with_base.phpt index fcf4c42085e5..1fc354581db6 100644 --- a/ext/uri/tests/whatwg/builder/basic_success_with_base.phpt +++ b/ext/uri/tests/whatwg/builder/basic_success_with_base.phpt @@ -1,5 +1,7 @@ --TEST-- Test Uri\WhatWg\UrlBuilder basic - success - with base URL +--XFAIL-- +Support for passing $baseUrl to Uri\WhatWg\UrlBuilder::build() is not implemented yet. --FILE-- Date: Wed, 19 Aug 2026 16:23:37 +0200 Subject: [PATCH 9/9] Add upgrading note --- UPGRADING | 2 ++ 1 file changed, 2 insertions(+) diff --git a/UPGRADING b/UPGRADING index 8f588bbf877c..0269f1366784 100644 --- a/UPGRADING +++ b/UPGRADING @@ -439,6 +439,8 @@ PHP 8.6 UPGRADE NOTES RFC: https://wiki.php.net/rfc/uri_followup#host_type_detection . Added Uri\Rfc3986\UriBuilder. RFC: https://wiki.php.net/rfc/uri_followup#uri_building + . Added Uri\WhatWg\UrlBuilder. + RFC: https://wiki.php.net/rfc/uri_followup#uri_building ======================================== 3. Changes in SAPI modules