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