Commit 3bec7e4b667 for php.net
commit 3bec7e4b667032bf47eb6934bfcae19fa8a791eb
Author: Máté Kocsis <kocsismate@woohoolabs.com>
Date: Tue Sep 8 21:33:21 2026 +0200
ext/uri: Validate credentials against normalized builder hosts
Reject credentials and ports when a host becomes empty after normalization instead of silently discarding them. Perform validation once, after setting the host.
diff --git a/ext/uri/tests/whatwg/builder/build_error_normalized_empty_host_password.phpt b/ext/uri/tests/whatwg/builder/build_error_normalized_empty_host_password.phpt
new file mode 100644
index 00000000000..76a170f4e26
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/build_error_normalized_empty_host_password.phpt
@@ -0,0 +1,39 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - error - password with a host that normalizes to empty
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setScheme("foo");
+$builder->setHost("\t\n");
+$builder->setPassword("pass");
+$softErrors = ["unchanged"];
+
+try {
+ $builder->build(softErrors: $softErrors);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), "\n";
+ var_dump($e->errors);
+}
+
+var_dump($softErrors);
+
+?>
+--EXPECTF--
+Uri\WhatWg\InvalidUrlException: The specified URL cannot have password
+array(1) {
+ [0]=>
+ object(Uri\WhatWg\UrlValidationError)#%d (%d) {
+ ["context"]=>
+ string(2) "
+"
+ ["type"]=>
+ enum(Uri\WhatWg\UrlValidationErrorType::InvalidUrlUnit)
+ ["failure"]=>
+ bool(false)
+ }
+}
+array(1) {
+ [0]=>
+ string(9) "unchanged"
+}
diff --git a/ext/uri/tests/whatwg/builder/build_error_normalized_empty_host_port.phpt b/ext/uri/tests/whatwg/builder/build_error_normalized_empty_host_port.phpt
new file mode 100644
index 00000000000..6b63a86d2ee
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/build_error_normalized_empty_host_port.phpt
@@ -0,0 +1,39 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - error - port with a host that normalizes to empty
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setScheme("foo");
+$builder->setHost("\t\n");
+$builder->setPort(123);
+$softErrors = ["unchanged"];
+
+try {
+ $builder->build(softErrors: $softErrors);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), "\n";
+ var_dump($e->errors);
+}
+
+var_dump($softErrors);
+
+?>
+--EXPECTF--
+Uri\WhatWg\InvalidUrlException: The specified URL cannot have port
+array(1) {
+ [0]=>
+ object(Uri\WhatWg\UrlValidationError)#%d (%d) {
+ ["context"]=>
+ string(2) "
+"
+ ["type"]=>
+ enum(Uri\WhatWg\UrlValidationErrorType::InvalidUrlUnit)
+ ["failure"]=>
+ bool(false)
+ }
+}
+array(1) {
+ [0]=>
+ string(9) "unchanged"
+}
diff --git a/ext/uri/tests/whatwg/builder/build_error_normalized_empty_host_username.phpt b/ext/uri/tests/whatwg/builder/build_error_normalized_empty_host_username.phpt
new file mode 100644
index 00000000000..534271bd29e
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/build_error_normalized_empty_host_username.phpt
@@ -0,0 +1,39 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - error - username with a host that normalizes to empty
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setScheme("foo");
+$builder->setHost("\t\n");
+$builder->setUsername("user");
+$softErrors = ["unchanged"];
+
+try {
+ $builder->build(softErrors: $softErrors);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), "\n";
+ var_dump($e->errors);
+}
+
+var_dump($softErrors);
+
+?>
+--EXPECTF--
+Uri\WhatWg\InvalidUrlException: The specified URL cannot have username
+array(1) {
+ [0]=>
+ object(Uri\WhatWg\UrlValidationError)#%d (%d) {
+ ["context"]=>
+ string(2) "
+"
+ ["type"]=>
+ enum(Uri\WhatWg\UrlValidationErrorType::InvalidUrlUnit)
+ ["failure"]=>
+ bool(false)
+ }
+}
+array(1) {
+ [0]=>
+ string(9) "unchanged"
+}
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 482612573e5..f83a3336494 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 @@
?>
--EXPECT--
-Uri\WhatWg\InvalidUrlException: The specified URL cannot have password
+Uri\WhatWg\InvalidUrlException: The specified host is malformed (HostMissing)
diff --git a/ext/uri/uri_parser_whatwg.c b/ext/uri/uri_parser_whatwg.c
index f48f9205352..b1bd2bcd865 100644
--- a/ext/uri/uri_parser_whatwg.c
+++ b/ext/uri/uri_parser_whatwg.c
@@ -989,26 +989,6 @@ 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 *soft_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) {
- php_uri_parser_whatwg_throw_exception("The specified URL cannot have username");
- return NULL;
- }
-
- if (Z_TYPE_P(password) != IS_NULL) {
- php_uri_parser_whatwg_throw_exception("The specified URL cannot have password");
- return NULL;
- }
-
- if (Z_TYPE_P(port) != IS_NULL) {
- php_uri_parser_whatwg_throw_exception("The specified URL cannot have port");
- return NULL;
- }
- }
-
lxb_url_parser_clean(&lexbor_parser);
lxb_url_t *lexbor_url = lexbor_mraw_calloc(lexbor_parser.mraw, sizeof(*lexbor_url));
@@ -1042,6 +1022,25 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_wh
goto failure;
}
+ if (lexbor_url->host.type == LXB_URL_HOST_TYPE__UNDEF
+ || lexbor_url->host.type == LXB_URL_HOST_TYPE_EMPTY
+ || lexbor_url->scheme.type == LXB_URL_SCHEMEL_TYPE_FILE) {
+ if (Z_TYPE_P(username) != IS_NULL) {
+ php_uri_parser_whatwg_throw_exception("The specified URL cannot have username");
+ goto failure;
+ }
+
+ if (Z_TYPE_P(password) != IS_NULL) {
+ php_uri_parser_whatwg_throw_exception("The specified URL cannot have password");
+ goto failure;
+ }
+
+ if (Z_TYPE_P(port) != IS_NULL) {
+ php_uri_parser_whatwg_throw_exception("The specified URL cannot have port");
+ 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);