[php-src] master: Merge branch 'PHP-8.6'

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

« previous php.cvs (#141834) next »