Commit a357c011cec for php.net

commit a357c011cec984bd1a4837bf960b1b3f7ef2acfd
Author: Máté Kocsis <kocsismate@woohoolabs.com>
Date:   Tue Sep 8 21:32:47 2026 +0200

    ext/uri: Reject URL delimiters in builder hosts

    Reject literal path, query and fragment delimiters before the hostname setter can silently discard the rest of the input.

diff --git a/ext/uri/tests/whatwg/builder/host_error_trailing_backslash.phpt b/ext/uri/tests/whatwg/builder/host_error_trailing_backslash.phpt
new file mode 100644
index 00000000000..20dcd8b3e8f
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/host_error_trailing_backslash.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::setHost() - error - contains a backslash delimiter
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+
+try {
+    $builder->setHost("example.com\\path");
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Uri\WhatWg\InvalidUrlException: The specified host is malformed (HostInvalidCodePoint)
diff --git a/ext/uri/tests/whatwg/builder/host_error_trailing_fragment.phpt b/ext/uri/tests/whatwg/builder/host_error_trailing_fragment.phpt
new file mode 100644
index 00000000000..ba9c1bf1337
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/host_error_trailing_fragment.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::setHost() - error - contains a fragment delimiter
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+
+try {
+    $builder->setHost("example.com#fragment");
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Uri\WhatWg\InvalidUrlException: The specified host is malformed (HostInvalidCodePoint)
diff --git a/ext/uri/tests/whatwg/builder/host_error_trailing_path.phpt b/ext/uri/tests/whatwg/builder/host_error_trailing_path.phpt
new file mode 100644
index 00000000000..372a402f1f4
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/host_error_trailing_path.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::setHost() - error - contains a path delimiter
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+
+try {
+    $builder->setHost("example.com/path");
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Uri\WhatWg\InvalidUrlException: The specified host is malformed (HostInvalidCodePoint)
diff --git a/ext/uri/tests/whatwg/builder/host_error_trailing_query.phpt b/ext/uri/tests/whatwg/builder/host_error_trailing_query.phpt
new file mode 100644
index 00000000000..c5449d37d50
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/host_error_trailing_query.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::setHost() - error - contains a query delimiter
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+
+try {
+    $builder->setHost("example.com?query");
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Uri\WhatWg\InvalidUrlException: The specified host is malformed (HostInvalidCodePoint)
diff --git a/ext/uri/uri_parser_whatwg.c b/ext/uri/uri_parser_whatwg.c
index 903f2568d59..1c347471796 100644
--- a/ext/uri/uri_parser_whatwg.c
+++ b/ext/uri/uri_parser_whatwg.c
@@ -826,8 +826,18 @@ ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_host_validate(const zen
 		first++;
 	}

+	/* Validate the entire host before the hostname setter can stop at a URL delimiter.
+	 * Backslash is a delimiter only for special URLs, but is also forbidden in opaque hosts.
+	 * https://url.spec.whatwg.org/#hostname-state
+	 * https://url.spec.whatwg.org/#opaque-host-parser */
+	for (const char *p = first; p < last; p++) {
+		if (*p == '/' || *p == '?' || *p == '#' || *p == '\\') {
+			return php_uri_parser_whatwg_component_error("host", LXB_URL_ERROR_TYPE_HOST_INVALID_CODE_POINT);
+		}
+	}
+
 	if (*first != '[') {
-		/* Skip validation - The host is not an IPv6 address */
+		/* Skip further validation - The host is not an IPv6 address */
 		return SUCCESS;
 	}