[php-src] master: Merge branch 'PHP-8.6'
| From: | Máté Kocsis | Date: | Thu, 01 Oct 2026 12:38:15 +0000 |
| Subject: | [php-src] master: Merge branch 'PHP-8.6' | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-141834@lists.php.net to get a copy of this message | ||
Author: Máté Kocsis (kocsismate)
Date: 2026-10-01T14:38:03+02:00
Commit: https://github.com/php/php-src/commit/d7f966e073be8b1ad4bd829d60b0dfa730f53dda
Raw diff: https://github.com/php/php-src/commit/d7f966e073be8b1ad4bd829d60b0dfa730f53dda.diff
Merge branch 'PHP-8.6'
* PHP-8.6:
ext/uri: Allow empty credentials in the WHATWG URL Builder (#23816)
Changed paths:
A ext/uri/tests/whatwg/builder/basic_success_empty_reference_with_credentials_in_base.phpt
A ext/uri/tests/whatwg/builder/password_success_empty_string_with_base.phpt
A ext/uri/tests/whatwg/builder/password_success_empty_string_with_file_scheme.phpt
A ext/uri/tests/whatwg/builder/password_success_empty_string_with_localhost_and_file_base.phpt
A
ext/uri/tests/whatwg/builder/password_success_empty_string_with_normalized_empty_host_and_file_base.phpt
A ext/uri/tests/whatwg/builder/username_success_empty_string_with_base.phpt
A ext/uri/tests/whatwg/builder/username_success_empty_string_with_empty_host_and_file_base.phpt
A ext/uri/tests/whatwg/builder/username_success_empty_string_with_file_base.phpt
A ext/uri/tests/whatwg/modification/password_success_empty_string_with_existing_password.phpt
M ext/uri/uri_parser_whatwg.c
Diff:
diff --git
a/ext/uri/tests/whatwg/builder/basic_success_empty_reference_with_credentials_in_base.phpt
b/ext/uri/tests/whatwg/builder/basic_success_empty_reference_with_credentials_in_base.phpt
new file mode 100644
index 000000000000..b9187ab9d11e
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/basic_success_empty_reference_with_credentials_in_base.phpt
@@ -0,0 +1,38 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - success - empty reference with credentials in base URL
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url('https://user:pass@example.com:123/base/path?oldQuery#oldFragment');
+
+$url = new Uri\WhatWg\UrlBuilder()
+ ->build($base);
+
+var_dump($url->toAsciiString());
+var_dump($url);
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+var_dump($url->equals(new Uri\WhatWg\Url('', $base),
Uri\UriComparisonMode::IncludeFragment));
+
+?>
+--EXPECTF--
+string(52) "https://user:pass@example.com:123/base/path?oldQuery"
+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(123)
+ ["path"]=>
+ string(10) "/base/path"
+ ["query"]=>
+ string(8) "oldQuery"
+ ["fragment"]=>
+ NULL
+}
+bool(true)
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/password_success_empty_string_with_base.phpt
b/ext/uri/tests/whatwg/builder/password_success_empty_string_with_base.phpt
new file mode 100644
index 000000000000..e4ff80092e79
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/password_success_empty_string_with_base.phpt
@@ -0,0 +1,39 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::setPassword() - success - empty string with credentials in base URL
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url('https://user:pass@example.com/base/path?oldQuery#oldFragment');
+
+$url = new Uri\WhatWg\UrlBuilder()
+ ->setPassword('')
+ ->build($base);
+
+var_dump($url->toAsciiString());
+var_dump($url);
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+var_dump($url->equals(new Uri\WhatWg\Url('', $base),
Uri\UriComparisonMode::IncludeFragment));
+
+?>
+--EXPECTF--
+string(48) "https://user:pass@example.com/base/path?oldQuery"
+object(Uri\WhatWg\Url)#%d (%d) {
+ ["scheme"]=>
+ string(5) "https"
+ ["username"]=>
+ string(4) "user"
+ ["password"]=>
+ string(4) "pass"
+ ["host"]=>
+ string(11) "example.com"
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(10) "/base/path"
+ ["query"]=>
+ string(8) "oldQuery"
+ ["fragment"]=>
+ NULL
+}
+bool(true)
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/password_success_empty_string_with_file_scheme.phpt
b/ext/uri/tests/whatwg/builder/password_success_empty_string_with_file_scheme.phpt
new file mode 100644
index 000000000000..085c2d66ff52
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/password_success_empty_string_with_file_scheme.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::setPassword() - success - empty string with file scheme
+--FILE--
+<?php
+
+$url = new Uri\WhatWg\UrlBuilder()
+ ->setScheme('file')
+ ->setPassword('')
+ ->setHost('example.net')
+ ->build();
+
+var_dump($url->toAsciiString());
+var_dump($url);
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+
+?>
+--EXPECTF--
+string(19) "file://example.net/"
+object(Uri\WhatWg\Url)#%d (%d) {
+ ["scheme"]=>
+ string(4) "file"
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ string(11) "example.net"
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(1) "/"
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git
a/ext/uri/tests/whatwg/builder/password_success_empty_string_with_localhost_and_file_base.phpt
b/ext/uri/tests/whatwg/builder/password_success_empty_string_with_localhost_and_file_base.phpt
new file mode 100644
index 000000000000..5aa418282949
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/password_success_empty_string_with_localhost_and_file_base.phpt
@@ -0,0 +1,50 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::setPassword() - success - empty string with localhost and file base URL
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url('file:///base/path');
+
+$errors = [];
+$referenceErrors = [];
+
+$reference = new Uri\WhatWg\Url('//localhost', $base, $referenceErrors);
+
+$url = new Uri\WhatWg\UrlBuilder()
+ ->setHost('localhost')
+ ->setPassword('')
+ ->build($base, $errors);
+
+var_dump($url->toAsciiString());
+var_dump($url);
+var_dump($errors);
+var_dump($errors == $referenceErrors);
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+var_dump($url->equals($reference, Uri\UriComparisonMode::IncludeFragment));
+
+?>
+--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
+}
+array(0) {
+}
+bool(true)
+bool(true)
+bool(true)
diff --git
a/ext/uri/tests/whatwg/builder/password_success_empty_string_with_normalized_empty_host_and_file_base.phpt
b/ext/uri/tests/whatwg/builder/password_success_empty_string_with_normalized_empty_host_and_file_base.phpt
new file mode 100644
index 000000000000..f84710d23eb2
--- /dev/null
+++
b/ext/uri/tests/whatwg/builder/password_success_empty_string_with_normalized_empty_host_and_file_base.phpt
@@ -0,0 +1,59 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::setPassword() - success - empty string with host normalised to empty
and file base URL
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url('file:///base/path');
+
+$errors = [];
+$referenceErrors = [];
+
+$reference = new Uri\WhatWg\Url("//\t\n", $base, $referenceErrors);
+
+$url = new Uri\WhatWg\UrlBuilder()
+ ->setHost("\t\n")
+ ->setPassword('')
+ ->build($base, $errors);
+
+var_dump($url->toAsciiString());
+var_dump($url);
+var_dump($errors);
+var_dump($errors == $referenceErrors);
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+var_dump($url->equals($reference, Uri\UriComparisonMode::IncludeFragment));
+
+?>
+--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
+}
+array(1) {
+ [0]=>
+ object(Uri\WhatWg\UrlValidationError)#%d (%d) {
+ ["context"]=>
+ string(2) "%r\x09\x0A%r"
+ ["type"]=>
+ enum(Uri\WhatWg\UrlValidationErrorType::InvalidUrlUnit)
+ ["failure"]=>
+ bool(false)
+ }
+}
+bool(true)
+bool(true)
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/username_success_empty_string_with_base.phpt
b/ext/uri/tests/whatwg/builder/username_success_empty_string_with_base.phpt
new file mode 100644
index 000000000000..f5a60976ef20
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/username_success_empty_string_with_base.phpt
@@ -0,0 +1,39 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::setUsername() - success - empty string with credentials in base URL
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url('https://user:pass@example.com/base/path?oldQuery#oldFragment');
+
+$url = new Uri\WhatWg\UrlBuilder()
+ ->setUsername('')
+ ->build($base);
+
+var_dump($url->toAsciiString());
+var_dump($url);
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+var_dump($url->equals(new Uri\WhatWg\Url('', $base),
Uri\UriComparisonMode::IncludeFragment));
+
+?>
+--EXPECTF--
+string(48) "https://user:pass@example.com/base/path?oldQuery"
+object(Uri\WhatWg\Url)#%d (%d) {
+ ["scheme"]=>
+ string(5) "https"
+ ["username"]=>
+ string(4) "user"
+ ["password"]=>
+ string(4) "pass"
+ ["host"]=>
+ string(11) "example.com"
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(10) "/base/path"
+ ["query"]=>
+ string(8) "oldQuery"
+ ["fragment"]=>
+ NULL
+}
+bool(true)
+bool(true)
diff --git
a/ext/uri/tests/whatwg/builder/username_success_empty_string_with_empty_host_and_file_base.phpt
b/ext/uri/tests/whatwg/builder/username_success_empty_string_with_empty_host_and_file_base.phpt
new file mode 100644
index 000000000000..40d72e0ef082
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/username_success_empty_string_with_empty_host_and_file_base.phpt
@@ -0,0 +1,50 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::setUsername() - success - empty string with empty host and file base
URL
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url('file:///base/path');
+
+$errors = [];
+$referenceErrors = [];
+
+$reference = new Uri\WhatWg\Url('//', $base, $referenceErrors);
+
+$url = new Uri\WhatWg\UrlBuilder()
+ ->setHost('')
+ ->setUsername('')
+ ->build($base, $errors);
+
+var_dump($url->toAsciiString());
+var_dump($url);
+var_dump($errors);
+var_dump($errors == $referenceErrors);
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+var_dump($url->equals($reference, Uri\UriComparisonMode::IncludeFragment));
+
+?>
+--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
+}
+array(0) {
+}
+bool(true)
+bool(true)
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/username_success_empty_string_with_file_base.phpt
b/ext/uri/tests/whatwg/builder/username_success_empty_string_with_file_base.phpt
new file mode 100644
index 000000000000..5b4075dc41d3
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/username_success_empty_string_with_file_base.phpt
@@ -0,0 +1,40 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::setUsername() - success - empty string with file base URL
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url('file:///base/path?oldQuery#oldFragment');
+
+$url = new Uri\WhatWg\UrlBuilder()
+ ->setUsername('')
+ ->setHost('example.net')
+ ->build($base);
+
+var_dump($url->toAsciiString());
+var_dump($url);
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+var_dump($url->equals(new Uri\WhatWg\Url('//example.net', $base),
Uri\UriComparisonMode::IncludeFragment));
+
+?>
+--EXPECTF--
+string(19) "file://example.net/"
+object(Uri\WhatWg\Url)#%d (8) {
+ ["scheme"]=>
+ string(4) "file"
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ string(11) "example.net"
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(1) "/"
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
+bool(true)
diff --git
a/ext/uri/tests/whatwg/modification/password_success_empty_string_with_existing_password.phpt
b/ext/uri/tests/whatwg/modification/password_success_empty_string_with_existing_password.phpt
new file mode 100644
index 000000000000..23e94917cba3
--- /dev/null
+++ b/ext/uri/tests/whatwg/modification/password_success_empty_string_with_existing_password.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\WhatWg\Url::withPassword() - success - empty string removes existing password
+--FILE--
+<?php
+
+$url1 = new Uri\WhatWg\Url('https://user:pass@example.com/path');
+$url2 = $url1->withPassword('');
+
+var_dump($url1->getPassword());
+var_dump($url2->getPassword());
+var_dump($url2->toAsciiString());
+
+?>
+--EXPECT--
+string(4) "pass"
+string(0) ""
+string(29) "https://user@example.com/path"
diff --git a/ext/uri/uri_parser_whatwg.c b/ext/uri/uri_parser_whatwg.c
index 5170849e3285..2c123cd238d2 100644
--- a/ext/uri/uri_parser_whatwg.c
+++ b/ext/uri/uri_parser_whatwg.c
@@ -1071,12 +1071,12 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(1, 2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t
*php_uri_parser
}
/* Credentials and ports require an authority in the reference itself. */
- if (Z_TYPE_P(username) == IS_STRING) {
+ if (Z_TYPE_P(username) == IS_STRING && Z_STRLEN_P(username) > 0) {
php_uri_parser_whatwg_throw_exception("The specified URL cannot have username");
return NULL;
}
- if (Z_TYPE_P(password) == IS_STRING) {
+ if (Z_TYPE_P(password) == IS_STRING && Z_STRLEN_P(password) > 0) {
php_uri_parser_whatwg_throw_exception("The specified URL cannot have password");
return NULL;
}
@@ -1310,12 +1310,12 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t
*php_uri_parser_wh
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) {
+ if (Z_TYPE_P(username) == IS_STRING && Z_STRLEN_P(username) > 0) {
php_uri_parser_whatwg_throw_exception("The specified URL cannot have username");
goto failure;
}
- if (Z_TYPE_P(password) != IS_NULL) {
+ if (Z_TYPE_P(password) == IS_STRING && Z_STRLEN_P(password) > 0) {
php_uri_parser_whatwg_throw_exception("The specified URL cannot have password");
goto failure;
}