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);