Commit c2f686a21d9 for php.net
commit c2f686a21d98c3aab08e020bda7198ad76da7679
Author: Máté Kocsis <kocsismate@woohoolabs.com>
Date: Sun Sep 20 12:31:00 2026 +0200
Implement "Followup improvements for ext/uri" RFC - URL building with base URL (#23526)
Add support for passing a non-null $baseUrl parameter for Uri\WhatWg\UrlBuilder::build().
RFC: https://wiki.php.net/rfc/uri_followup#uri_building
diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c
index aab94e94618..bdf1201e425 100644
--- a/ext/uri/php_uri.c
+++ b/ext/uri/php_uri.c
@@ -1425,8 +1425,6 @@ PHP_METHOD(Uri_WhatWg_UrlBuilder, build)
lxb_url_t *base_url = NULL;
if (base_url_zv != NULL) {
- zend_argument_error(NULL, 1, "is not supported yet, and therefore, null must be passed");
- RETURN_THROWS();
base_url = Z_URI_OBJECT_P(base_url_zv)->uri;
}
diff --git a/ext/uri/tests/whatwg/builder/authority_success_with_base.phpt b/ext/uri/tests/whatwg/builder/authority_success_with_base.phpt
new file mode 100644
index 00000000000..ad8bc581e11
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/authority_success_with_base.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - success - replaces the base authority with encoded credentials
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("https://old:secret@example.com:81/a?old#old");
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setHost("example.net");
+$builder->setUsername("a@b");
+$builder->setPassword("c:d");
+$url = $builder->build($base);
+
+var_dump($url->toAsciiString());
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+var_dump($base->toAsciiString());
+
+?>
+--EXPECT--
+string(32) "https://a%40b:c%3Ad@example.net/"
+bool(true)
+string(43) "https://old:secret@example.com:81/a?old#old"
diff --git a/ext/uri/tests/whatwg/builder/basic_error_empty_reference_with_opaque_base.phpt b/ext/uri/tests/whatwg/builder/basic_error_empty_reference_with_opaque_base.phpt
new file mode 100644
index 00000000000..d3fa225a61b
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/basic_error_empty_reference_with_opaque_base.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - error - empty reference with an opaque base
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("foo:opaque?old#old");
+$builder = new Uri\WhatWg\UrlBuilder();
+
+try {
+ $builder->build($base);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), "\n";
+ var_dump($e->errors[0]->type);
+}
+
+?>
+--EXPECT--
+Uri\WhatWg\InvalidUrlException: The specified path is malformed (MissingSchemeNonRelativeUrl)
+enum(Uri\WhatWg\UrlValidationErrorType::MissingSchemeNonRelativeUrl)
diff --git a/ext/uri/tests/whatwg/builder/basic_error_with_opaque_base.phpt b/ext/uri/tests/whatwg/builder/basic_error_with_opaque_base.phpt
new file mode 100644
index 00000000000..3e73978b86e
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/basic_error_with_opaque_base.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder basic - error - with base URL containing opaque path
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setPath("/foo/bar/baz");
+
+try {
+ $builder->build(new Uri\WhatWg\Url("scheme:opaque-path"));
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Uri\WhatWg\InvalidUrlException: The specified path is malformed (MissingSchemeNonRelativeUrl)
diff --git a/ext/uri/tests/whatwg/builder/basic_success_empty_reference_with_base.phpt b/ext/uri/tests/whatwg/builder/basic_success_empty_reference_with_base.phpt
new file mode 100644
index 00000000000..6d40f4b96f8
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/basic_success_empty_reference_with_base.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - success - inherits the path and query but not the fragment
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("https://example.com/a?old#old");
+$builder = new Uri\WhatWg\UrlBuilder();
+
+$url = $builder->build($base);
+
+var_dump($url->toAsciiString());
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+
+?>
+--EXPECT--
+string(25) "https://example.com/a?old"
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/basic_success_with_base.phpt b/ext/uri/tests/whatwg/builder/basic_success_with_base.phpt
index 1fc354581db..fcf4c42085e 100644
--- a/ext/uri/tests/whatwg/builder/basic_success_with_base.phpt
+++ b/ext/uri/tests/whatwg/builder/basic_success_with_base.phpt
@@ -1,7 +1,5 @@
--TEST--
Test Uri\WhatWg\UrlBuilder basic - success - with base URL
---XFAIL--
-Support for passing $baseUrl to Uri\WhatWg\UrlBuilder::build() is not implemented yet.
--FILE--
<?php
diff --git a/ext/uri/tests/whatwg/builder/basic_success_with_scheme_relative_url.phpt b/ext/uri/tests/whatwg/builder/basic_success_with_scheme_relative_url.phpt
new file mode 100644
index 00000000000..1f590e8a93c
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/basic_success_with_scheme_relative_url.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder basic - success - with scheme relative URL
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setHost("example.net");
+$builder->setPath("/foo/bar/baz");
+$builder->setPort(124);
+$url = $builder->build(new Uri\WhatWg\Url("https://user:pass@example.com:123/foo/bar?query#hash"));
+
+var_dump($url->toAsciiString());
+var_dump($url);
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+
+?>
+--EXPECTF--
+string(35) "https://example.net:124/foo/bar/baz"
+object(Uri\WhatWg\Url)#%d (%d) {
+ ["scheme"]=>
+ string(5) "https"
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ string(11) "example.net"
+ ["port"]=>
+ int(124)
+ ["path"]=>
+ string(12) "/foo/bar/baz"
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/build_error_soft_errors_reset_with_base.phpt b/ext/uri/tests/whatwg/builder/build_error_soft_errors_reset_with_base.phpt
new file mode 100644
index 00000000000..b1a8b221849
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/build_error_soft_errors_reset_with_base.phpt
@@ -0,0 +1,27 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - error - preserves soft errors output with an opaque base URL
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setPath("/a\tb");
+$softErrors = ["previous error"];
+
+try {
+ $builder->build(new Uri\WhatWg\Url("foo:opaque"), $softErrors);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), "\n";
+ foreach ($e->errors as $error) {
+ var_dump($error->type);
+ }
+}
+
+var_dump($softErrors);
+
+?>
+--EXPECT--
+Uri\WhatWg\InvalidUrlException: The specified path is malformed (MissingSchemeNonRelativeUrl)
+enum(Uri\WhatWg\UrlValidationErrorType::MissingSchemeNonRelativeUrl)
+enum(Uri\WhatWg\UrlValidationErrorType::InvalidUrlUnit)
+array(0) {
+}
diff --git a/ext/uri/tests/whatwg/builder/build_error_soft_errors_reset.phpt b/ext/uri/tests/whatwg/builder/build_error_soft_errors_reset_without_base.phpt
similarity index 100%
rename from ext/uri/tests/whatwg/builder/build_error_soft_errors_reset.phpt
rename to ext/uri/tests/whatwg/builder/build_error_soft_errors_reset_without_base.phpt
diff --git a/ext/uri/tests/whatwg/builder/build_error_soft_errors_typed_reference_with_base.phpt b/ext/uri/tests/whatwg/builder/build_error_soft_errors_typed_reference_with_base.phpt
new file mode 100644
index 00000000000..10b7e101dc9
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/build_error_soft_errors_typed_reference_with_base.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - error - soft errors assigned to a typed property with a base URL
+--FILE--
+<?php
+
+class Foo {
+ public string $errors = "unchanged";
+}
+
+$foo = new Foo();
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setFragment("a\tb");
+try {
+ $builder->build(new Uri\WhatWg\Url("https://example.com/"), $foo->errors);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), "\n";
+}
+var_dump($foo->errors);
+
+?>
+--EXPECT--
+TypeError: Cannot assign array to reference held by property Foo::$errors of type string
+string(9) "unchanged"
diff --git a/ext/uri/tests/whatwg/builder/build_success_soft_errors_with_base.phpt b/ext/uri/tests/whatwg/builder/build_success_soft_errors_with_base.phpt
new file mode 100644
index 00000000000..7f2b3714662
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/build_success_soft_errors_with_base.phpt
@@ -0,0 +1,46 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - success - collects and resets soft errors with a base URL
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setPath("/a\tb");
+$builder->setFragment("x\ny");
+$base = new Uri\WhatWg\Url("https://example.com/");
+$softErrors = ["previous error"];
+$url = $builder->build($base, $softErrors);
+
+var_dump($url->toAsciiString());
+var_dump($softErrors);
+
+$builder->setPath("/ab");
+$builder->setFragment("xy");
+$builder->build($base, $softErrors);
+var_dump($softErrors);
+
+?>
+--EXPECTF--
+string(25) "https://example.com/ab#xy"
+array(2) {
+ [0]=>
+ object(Uri\WhatWg\UrlValidationError)#%d (%d) {
+ ["context"]=>
+ string(2) " b"
+ ["type"]=>
+ enum(Uri\WhatWg\UrlValidationErrorType::InvalidUrlUnit)
+ ["failure"]=>
+ bool(false)
+ }
+ [1]=>
+ object(Uri\WhatWg\UrlValidationError)#%d (%d) {
+ ["context"]=>
+ string(2) "
+y"
+ ["type"]=>
+ enum(Uri\WhatWg\UrlValidationErrorType::InvalidUrlUnit)
+ ["failure"]=>
+ bool(false)
+ }
+}
+array(0) {
+}
diff --git a/ext/uri/tests/whatwg/builder/fragment_success_empty_with_base.phpt b/ext/uri/tests/whatwg/builder/fragment_success_empty_with_base.phpt
new file mode 100644
index 00000000000..04f92a8f776
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/fragment_success_empty_with_base.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - success - replaces the base fragment with an empty fragment
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("https://example.com/a?old#old");
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setFragment("");
+$url = $builder->build($base);
+
+var_dump($url->toAsciiString());
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+
+?>
+--EXPECT--
+string(26) "https://example.com/a?old#"
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/fragment_success_empty_with_opaque_base.phpt b/ext/uri/tests/whatwg/builder/fragment_success_empty_with_opaque_base.phpt
new file mode 100644
index 00000000000..bcdb4d277b9
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/fragment_success_empty_with_opaque_base.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - success - resolves an empty fragment against an opaque base
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("foo:opaque?old#old");
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setPath("\t\n");
+$builder->setFragment("");
+$url = $builder->build($base, $softErrors);
+var_dump($url->toAsciiString());
+var_dump($softErrors[0]->type);
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+var_dump($base->toAsciiString());
+
+?>
+--EXPECT--
+string(15) "foo:opaque?old#"
+enum(Uri\WhatWg\UrlValidationErrorType::InvalidUrlUnit)
+bool(true)
+string(18) "foo:opaque?old#old"
diff --git a/ext/uri/tests/whatwg/builder/fragment_success_with_base.phpt b/ext/uri/tests/whatwg/builder/fragment_success_with_base.phpt
new file mode 100644
index 00000000000..7d2a84b59ea
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/fragment_success_with_base.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::setFragment() - success - with base URL
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setFragment("foo");
+$url = $builder->build(new Uri\WhatWg\Url("https://example.com/#bar"));
+
+var_dump($url->toAsciiString());
+var_dump($url);
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+
+?>
+--EXPECTF--
+string(24) "https://example.com/#foo"
+object(Uri\WhatWg\Url)#%d (%d) {
+ ["scheme"]=>
+ string(5) "https"
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ string(11) "example.com"
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(1) "/"
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ string(3) "foo"
+}
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/fragment_success_with_opaque_base.phpt b/ext/uri/tests/whatwg/builder/fragment_success_with_opaque_base.phpt
new file mode 100644
index 00000000000..53e0af4f66f
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/fragment_success_with_opaque_base.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::setFragment() - success - with base URL with opaque path
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setFragment("foo");
+$url = $builder->build(new Uri\WhatWg\Url("scheme:opaque-path"));
+
+var_dump($url->toAsciiString());
+var_dump($url);
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+
+?>
+--EXPECTF--
+string(22) "scheme:opaque-path#foo"
+object(Uri\WhatWg\Url)#%d (%d) {
+ ["scheme"]=>
+ string(6) "scheme"
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ NULL
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(11) "opaque-path"
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ string(3) "foo"
+}
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/host_error_normalized_empty_with_base.phpt b/ext/uri/tests/whatwg/builder/host_error_normalized_empty_with_base.phpt
new file mode 100644
index 00000000000..7c16d8d5706
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/host_error_normalized_empty_with_base.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - error - rejects credentials after host normalization with a base URL
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setHost("\t");
+$builder->setUsername("user");
+
+try {
+ $builder->build(new Uri\WhatWg\Url("foo://example.com/"));
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), "\n";
+ foreach ($e->errors as $error) {
+ var_dump($error->type);
+ }
+}
+
+?>
+--EXPECT--
+Uri\WhatWg\InvalidUrlException: The specified URL cannot have username
+enum(Uri\WhatWg\UrlValidationErrorType::InvalidUrlUnit)
diff --git a/ext/uri/tests/whatwg/builder/host_error_with_opaque_base.phpt b/ext/uri/tests/whatwg/builder/host_error_with_opaque_base.phpt
new file mode 100644
index 00000000000..5194b7e6b6f
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/host_error_with_opaque_base.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - error - new authority with an opaque base
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("foo:opaque?old#old");
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setHost("example.com");
+
+try {
+ $builder->build($base);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), "\n";
+ var_dump($e->errors[0]->type);
+}
+
+?>
+--EXPECT--
+Uri\WhatWg\InvalidUrlException: The specified host is malformed (MissingSchemeNonRelativeUrl)
+enum(Uri\WhatWg\UrlValidationErrorType::MissingSchemeNonRelativeUrl)
diff --git a/ext/uri/tests/whatwg/builder/password_error_with_base.phpt b/ext/uri/tests/whatwg/builder/password_error_with_base.phpt
new file mode 100644
index 00000000000..c6ade2d69d3
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/password_error_with_base.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::setPassword() - error - missing opaque host with base URL
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setPassword("password");
+
+try {
+ $builder->build(new Uri\WhatWg\Url("https://example.com"));
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Uri\WhatWg\InvalidUrlException: The specified URL cannot have password
diff --git a/ext/uri/tests/whatwg/builder/path_error_tab_newline_with_opaque_base.phpt b/ext/uri/tests/whatwg/builder/path_error_tab_newline_with_opaque_base.phpt
new file mode 100644
index 00000000000..0f53f745487
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/path_error_tab_newline_with_opaque_base.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - error - ignored path characters without a fragment and with an opaque base
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("foo:opaque?old#old");
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setPath("\t\n");
+
+try {
+ $builder->build($base);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), "\n";
+ var_dump($e->errors[0]->type);
+}
+
+?>
+--EXPECT--
+Uri\WhatWg\InvalidUrlException: The specified path is malformed (MissingSchemeNonRelativeUrl)
+enum(Uri\WhatWg\UrlValidationErrorType::MissingSchemeNonRelativeUrl)
diff --git a/ext/uri/tests/whatwg/builder/path_success_absolute_with_file_base.phpt b/ext/uri/tests/whatwg/builder/path_success_absolute_with_file_base.phpt
new file mode 100644
index 00000000000..92a85ff9d15
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/path_success_absolute_with_file_base.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - success - inherits the file drive for an absolute path
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("file:///C:/a/b");
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setPath("/c");
+$url = $builder->build($base);
+
+var_dump($url->toAsciiString());
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+
+?>
+--EXPECT--
+string(12) "file:///C:/c"
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/path_success_drive_letter_with_file_base.phpt b/ext/uri/tests/whatwg/builder/path_success_drive_letter_with_file_base.phpt
new file mode 100644
index 00000000000..7d15133b421
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/path_success_drive_letter_with_file_base.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - success - replaces the file drive
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("file:///C:/a/b");
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setPath("D:/c");
+$url = $builder->build($base);
+
+var_dump($url->toAsciiString());
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+
+?>
+--EXPECT--
+string(12) "file:///D:/c"
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/path_success_first_segment_colon_with_base.phpt b/ext/uri/tests/whatwg/builder/path_success_first_segment_colon_with_base.phpt
new file mode 100644
index 00000000000..6e56b4c75f8
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/path_success_first_segment_colon_with_base.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - success - keeps a colon in the first path segment
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("https://example.com/a/b");
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setPath("c:d");
+$url = $builder->build($base);
+
+var_dump($url->toAsciiString());
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+
+?>
+--EXPECT--
+string(25) "https://example.com/a/c:d"
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/path_success_hierarchical_delimiters_with_base.phpt b/ext/uri/tests/whatwg/builder/path_success_hierarchical_delimiters_with_base.phpt
new file mode 100644
index 00000000000..ea942178d05
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/path_success_hierarchical_delimiters_with_base.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - success - keeps question mark and hashmark in the path
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("https://example.com/a/b");
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setPath("a?b#c");
+$url = $builder->build($base);
+
+var_dump($url->toAsciiString());
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+
+?>
+--EXPECT--
+string(31) "https://example.com/a/a%3Fb%23c"
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/path_success_leading_double_slash_with_base.phpt b/ext/uri/tests/whatwg/builder/path_success_leading_double_slash_with_base.phpt
new file mode 100644
index 00000000000..b38ec002d79
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/path_success_leading_double_slash_with_base.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - success - keeps a leading double slash in the path
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("https://example.com/a/b");
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setPath("//other/x");
+$url = $builder->build($base);
+
+var_dump($url->toAsciiString());
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+
+?>
+--EXPECT--
+string(28) "https://example.com//other/x"
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/path_success_leading_double_slash_with_file_base.phpt b/ext/uri/tests/whatwg/builder/path_success_leading_double_slash_with_file_base.phpt
new file mode 100644
index 00000000000..4b1cbf1289c
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/path_success_leading_double_slash_with_file_base.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - success - keeps a leading double slash in a file path
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("file:///C:/a/b");
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setPath("//other/x");
+$url = $builder->build($base);
+
+var_dump($url->toAsciiString());
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+
+?>
+--EXPECT--
+string(16) "file:////other/x"
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/path_success_parent_segment_with_base.phpt b/ext/uri/tests/whatwg/builder/path_success_parent_segment_with_base.phpt
new file mode 100644
index 00000000000..9c418b25ae6
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/path_success_parent_segment_with_base.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - success - resolves a parent path segment
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("https://example.com/a/b");
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setPath("../c");
+$url = $builder->build($base);
+
+var_dump($url->toAsciiString());
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+
+?>
+--EXPECT--
+string(21) "https://example.com/c"
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/path_success_relative_with_base.phpt b/ext/uri/tests/whatwg/builder/path_success_relative_with_base.phpt
new file mode 100644
index 00000000000..cf8ee60288a
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/path_success_relative_with_base.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - success - resolves a path against the base directory and clears its query and fragment
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("https://user:pass@example.com:81/a/b?old#old");
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setPath("c");
+$url = $builder->build($base);
+
+var_dump($url->toAsciiString());
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+
+?>
+--EXPECT--
+string(36) "https://user:pass@example.com:81/a/c"
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/path_success_tab_newline_with_base.phpt b/ext/uri/tests/whatwg/builder/path_success_tab_newline_with_base.phpt
new file mode 100644
index 00000000000..ce299716c0a
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/path_success_tab_newline_with_base.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - success - inherits the path and query after removing tabs and newlines
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("https://example.com/a?old#old");
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setPath("\t\n");
+$url = $builder->build($base, $softErrors);
+
+var_dump($url->toAsciiString());
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+var_dump($softErrors[0]->type);
+
+?>
+--EXPECT--
+string(25) "https://example.com/a?old"
+bool(true)
+enum(Uri\WhatWg\UrlValidationErrorType::InvalidUrlUnit)
diff --git a/ext/uri/tests/whatwg/builder/port_error_missing_opaque_host_with_base.phpt b/ext/uri/tests/whatwg/builder/port_error_missing_opaque_host_with_base.phpt
new file mode 100644
index 00000000000..dc6b021f084
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/port_error_missing_opaque_host_with_base.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::setPort() - error - missing opaque host with base URL
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setPort(123);
+
+try {
+ $builder->build(new Uri\WhatWg\Url("https://example.com"));
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Uri\WhatWg\InvalidUrlException: The specified URL cannot have port
diff --git a/ext/uri/tests/whatwg/builder/query_error_empty_with_opaque_base.phpt b/ext/uri/tests/whatwg/builder/query_error_empty_with_opaque_base.phpt
new file mode 100644
index 00000000000..f970ba3081c
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/query_error_empty_with_opaque_base.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - error - empty query with an opaque base and ignored path characters
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("foo:opaque?old#old");
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setPath("\t\n");
+$builder->setQuery("");
+$builder->setFragment("new");
+
+try {
+ $builder->build($base);
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), "\n";
+ var_dump($e->errors[0]->type);
+}
+
+?>
+--EXPECT--
+Uri\WhatWg\InvalidUrlException: The specified query is malformed (MissingSchemeNonRelativeUrl)
+enum(Uri\WhatWg\UrlValidationErrorType::MissingSchemeNonRelativeUrl)
diff --git a/ext/uri/tests/whatwg/builder/query_success_empty_with_base.phpt b/ext/uri/tests/whatwg/builder/query_success_empty_with_base.phpt
new file mode 100644
index 00000000000..e2e41cb7a12
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/query_success_empty_with_base.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - success - replaces the base query with an empty query
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("https://example.com/a?old#old");
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setQuery("");
+$url = $builder->build($base);
+
+var_dump($url->toAsciiString());
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+
+?>
+--EXPECT--
+string(22) "https://example.com/a?"
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/query_success_hashmark_with_base.phpt b/ext/uri/tests/whatwg/builder/query_success_hashmark_with_base.phpt
new file mode 100644
index 00000000000..ef17d3e9410
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/query_success_hashmark_with_base.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::build() - success - keeps a hashmark in the query
+--FILE--
+<?php
+
+$base = new Uri\WhatWg\Url("https://example.com/a?old#old");
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setQuery("a#b");
+$url = $builder->build($base);
+
+var_dump($url->toAsciiString());
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+
+?>
+--EXPECT--
+string(27) "https://example.com/a?a%23b"
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/query_success_with_base.phpt b/ext/uri/tests/whatwg/builder/query_success_with_base.phpt
new file mode 100644
index 00000000000..5dc39a92ecf
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/query_success_with_base.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::setQuery() - success - with base URL
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setQuery("foo=bar");
+$url = $builder->build(new Uri\WhatWg\Url("https://example.com/?baz"));
+
+var_dump($url->toAsciiString());
+var_dump($url);
+var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
+
+?>
+--EXPECTF--
+string(28) "https://example.com/?foo=bar"
+object(Uri\WhatWg\Url)#%d (%d) {
+ ["scheme"]=>
+ string(5) "https"
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ string(11) "example.com"
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(1) "/"
+ ["query"]=>
+ string(7) "foo=bar"
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/whatwg/builder/username_error_with_base.phpt b/ext/uri/tests/whatwg/builder/username_error_with_base.phpt
new file mode 100644
index 00000000000..f5870330945
--- /dev/null
+++ b/ext/uri/tests/whatwg/builder/username_error_with_base.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\WhatWg\UrlBuilder::setUsername() - error - missing opaque host with base URL
+--FILE--
+<?php
+
+$builder = new Uri\WhatWg\UrlBuilder();
+$builder->setUsername("username");
+
+try {
+ $builder->build(new Uri\WhatWg\Url("https://example.com"));
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Uri\WhatWg\InvalidUrlException: The specified URL cannot have username
diff --git a/ext/uri/uri_parser_whatwg.c b/ext/uri/uri_parser_whatwg.c
index 8fee839b1cc..1a258c6c940 100644
--- a/ext/uri/uri_parser_whatwg.c
+++ b/ext/uri/uri_parser_whatwg.c
@@ -203,10 +203,8 @@ ZEND_ATTRIBUTE_NONNULL static const char *fill_errors(zval *errors)
return fill_errors_inner(Z_ARRVAL_P(errors));
}
-static void throw_invalid_url_exception_during_write(zval *errors, const char *component)
+static void throw_invalid_url_exception_with_reason(zval *errors, const char *component, const char *reason, zval *err)
{
- zval err;
- const char *reason = fill_errors(&err);
zend_object *exception = zend_throw_exception_ex(
php_uri_ce_whatwg_invalid_url_exception,
0,
@@ -216,15 +214,23 @@ static void throw_invalid_url_exception_during_write(zval *errors, const char *c
reason ? reason : "",
reason ? ")" : ""
);
- zend_update_property(exception->ce, exception, ZEND_STRL("errors"), &err);
+ zend_update_property(exception->ce, exception, ZEND_STRL("errors"), err);
if (errors) {
zval_ptr_dtor(errors);
- ZVAL_COPY_VALUE(errors, &err);
+ ZVAL_COPY_VALUE(errors, err);
} else {
- zval_ptr_dtor(&err);
+ zval_ptr_dtor(err);
}
}
+static void throw_invalid_url_exception_during_write(zval *errors, const char *component)
+{
+ zval err;
+ const char *reason = fill_errors(&err);
+
+ throw_invalid_url_exception_with_reason(errors, component, reason, &err);
+}
+
static lxb_status_t serialize_to_smart_str_callback(const lxb_char_t *data, const size_t length, void *ctx)
{
smart_str *uri_str = ctx;
@@ -968,12 +974,12 @@ ZEND_ATTRIBUTE_NONNULL static lxb_url_scheme_type_t php_uri_parser_whatwg_get_sp
return LXB_URL_SCHEMEL_TYPE__UNDEF;
}
-ZEND_ATTRIBUTE_NONNULL static void php_uri_parser_whatwg_build_errors(zval *errors)
+ZEND_ATTRIBUTE_NONNULL static const char *php_uri_parser_whatwg_build_errors(zval *errors)
{
size_t log_len;
if (lexbor_parser.log == NULL || (log_len = lexbor_plog_length(lexbor_parser.log)) == 0) {
- return;
+ return NULL;
}
if (Z_TYPE_P(errors) != IS_ARRAY) {
@@ -981,7 +987,222 @@ ZEND_ATTRIBUTE_NONNULL static void php_uri_parser_whatwg_build_errors(zval *erro
array_init_size(errors, log_len);
}
- fill_errors_inner(Z_ARRVAL_P(errors));
+ return fill_errors_inner(Z_ARRVAL_P(errors));
+}
+
+ZEND_ATTRIBUTE_NONNULL static void php_uri_parser_whatwg_build_errors_into_exception(zval *errors)
+{
+ /* Include errors from earlier components in the exception raised by a later component. */
+ if (zend_hash_num_elements(Z_ARRVAL_P(errors)) > 0 && EG(exception)
+ && instanceof_function(EG(exception)->ce, php_uri_ce_whatwg_invalid_url_exception)) {
+ zval rv;
+ zval *exception_errors = zend_read_property(php_uri_ce_whatwg_invalid_url_exception,
+ EG(exception), ZEND_STRL("errors"), true, &rv);
+ ZEND_ASSERT(Z_TYPE_P(exception_errors) == IS_ARRAY);
+
+ zval *error;
+ ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(exception_errors), error) {
+ Z_TRY_ADDREF_P(error);
+ zend_hash_next_index_insert(Z_ARRVAL_P(errors), error);
+ } ZEND_HASH_FOREACH_END();
+
+ zval_ptr_dtor(exception_errors);
+ ZVAL_COPY(exception_errors, errors);
+ }
+}
+
+ZEND_ATTRIBUTE_NONNULL static void php_uri_parser_whatwg_build_errors_and_throw(const lxb_status_t status, const char *component, zval *errors)
+{
+ if (status != LXB_STATUS_OK) {
+ throw_invalid_url_exception_during_write(NULL, component);
+ } else {
+ php_uri_parser_whatwg_build_errors(errors);
+ }
+}
+
+/* TODO: Replace with lxb_url_path_set_null() once https://github.com/lexbor/lexbor/pull/415 is available. */
+ZEND_ATTRIBUTE_NONNULL static void php_uri_parser_whatwg_path_set_null(lxb_url_t *url)
+{
+ if (url->path.str.data == NULL) {
+ return;
+ }
+
+ (void) lexbor_str_destroy(&url->path.str, url->mraw, false);
+
+ url->path.str.length = 0;
+ url->path.length = 0;
+ url->path.opaque = false;
+}
+
+/* TODO: Replace with lxb_url_query_set_null() once https://github.com/lexbor/lexbor/pull/415 is available. */
+ZEND_ATTRIBUTE_NONNULL static void php_uri_parser_whatwg_query_set_null(lxb_url_t *url)
+{
+ if (url->query.data != NULL) {
+ (void) lexbor_str_destroy(&url->query, url->mraw, false);
+ }
+}
+
+/* TODO: Replace with lxb_url_fragment_set_null() once https://github.com/lexbor/lexbor/pull/415 is available. */
+ZEND_ATTRIBUTE_NONNULL static void php_uri_parser_whatwg_fragment_set_null(lxb_url_t *url)
+{
+ if (url->fragment.data != NULL) {
+ (void) lexbor_str_destroy(&url->fragment, url->mraw, false);
+ }
+}
+
+ZEND_ATTRIBUTE_NONNULL_ARGS(1, 2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_whatwg_resolve_reference_from_zval(
+ lxb_url_t *lexbor_base_url, const zval *scheme, const zval *username, const zval *password,
+ 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_STRING) {
+ if (lexbor_base_url->path.opaque) {
+ php_uri_parser_whatwg_component_error("host", LXB_URL_ERROR_TYPE_MISSING_SCHEME_NON_RELATIVE_URL);
+ return NULL;
+ }
+
+ /* A new authority inherits only the scheme, not the base URL's other components. */
+ zval base_scheme;
+ php_uri_parser_whatwg_scheme_read(lexbor_base_url, PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII, &base_scheme);
+ lxb_url_t *url = php_uri_parser_whatwg_build_from_zval(NULL, &base_scheme,
+ username, password, host, port, path, query, fragment, soft_errors_zv);
+ zval_ptr_dtor(&base_scheme);
+ return url;
+ }
+
+ /* Credentials and ports require an authority in the reference itself. */
+ if (Z_TYPE_P(username) == IS_STRING) {
+ php_uri_parser_whatwg_throw_exception("The specified URL cannot have username");
+ return NULL;
+ }
+
+ if (Z_TYPE_P(password) == IS_STRING) {
+ php_uri_parser_whatwg_throw_exception("The specified URL cannot have password");
+ return NULL;
+ }
+
+ if (Z_TYPE_P(port) == IS_LONG) {
+ php_uri_parser_whatwg_throw_exception("The specified URL cannot have port");
+ return NULL;
+ }
+
+ const char *first = Z_STRVAL_P(path);
+ const char *end = first + Z_STRLEN_P(path);
+ while (first < end && php_uri_whatwg_is_ascii_tab_or_newline(*first)) {
+ first++;
+ }
+
+ lxb_status_t status;
+ zval errors;
+ array_init(&errors);
+
+ lxb_url_t *lexbor_url = php_uri_parser_whatwg_clone(lexbor_base_url);
+ if (lexbor_url == NULL) {
+ zend_throw_exception(php_uri_ce_error, "Memory allocation error", 0);
+ goto failure;
+ }
+ if (lexbor_base_url->path.opaque && first == end
+ && (Z_TYPE_P(query) == IS_STRING || Z_TYPE_P(fragment) == IS_NULL)) {
+ php_uri_parser_whatwg_component_error(Z_TYPE_P(query) == IS_STRING ? "query" : "path",
+ LXB_URL_ERROR_TYPE_MISSING_SCHEME_NON_RELATIVE_URL);
+ goto failure;
+ }
+
+ /* Discard the base fragment; the reference fragment is applied below. */
+ php_uri_parser_whatwg_fragment_set_null(lexbor_url);
+
+ if (Z_STRLEN_P(path) > 0) {
+ /* Resolve relative paths against the base directory. Absolute paths use
+ * a path state so leading slashes cannot introduce a new authority. */
+ lxb_url_state_t state = LXB_URL_STATE_NO_SCHEME_STATE;
+ if (!lexbor_base_url->path.opaque && first < end
+ && (*first == '/' || (lxb_url_is_special(lexbor_base_url) && *first == '\\'))) {
+ /* A path beginning with // must not replace the authority. */
+ state = LXB_URL_STATE_PATH_START_STATE;
+ if (lexbor_base_url->scheme.type == LXB_URL_SCHEMEL_TYPE_FILE) {
+ const char *second = first + 1;
+ while (second < end && php_uri_whatwg_is_ascii_tab_or_newline(*second)) {
+ second++;
+ }
+ if (second == end || (*second != '/' && *second != '\\')) {
+ state = LXB_URL_STATE_FILE_STATE;
+ }
+ }
+ }
+
+ /* Keep delimiters inside the path when entering the reference parser. */
+ smart_str reference = {0};
+ for (const char *p = Z_STRVAL_P(path); p < end; p++) {
+ if (*p == '?') {
+ smart_str_appends(&reference, "%3F");
+ } else if (*p == '#') {
+ smart_str_appends(&reference, "%23");
+ } else {
+ smart_str_appendc(&reference, *p);
+ }
+ }
+
+ /* After removing tabs and newlines, the parser must see a fragment
+ * reference to accept an opaque base. Its actual value is applied below. */
+ if (lexbor_base_url->path.opaque && first == end
+ && Z_TYPE_P(query) == IS_NULL && Z_TYPE_P(fragment) != IS_NULL) {
+ smart_str_appendc(&reference, '#');
+ }
+
+ zend_string *input = smart_str_extract(&reference);
+ php_uri_parser_whatwg_path_set_null(lexbor_url);
+ lxb_url_parser_clean(&lexbor_parser);
+ status = lxb_url_parse_basic(&lexbor_parser, lexbor_url, lexbor_base_url,
+ (const lxb_char_t *) ZSTR_VAL(input), ZSTR_LEN(input), state, LXB_ENCODING_UTF_8);
+ php_uri_parser_whatwg_build_errors_and_throw(status, "path", &errors);
+ zend_string_release(input);
+ if (status != LXB_STATUS_OK) {
+ goto failure;
+ }
+ if (first < end) {
+ php_uri_parser_whatwg_query_set_null(lexbor_url);
+ }
+ }
+
+ if (Z_TYPE_P(query) == IS_STRING) {
+ php_uri_parser_whatwg_query_set_null(lexbor_url);
+ lxb_url_parser_clean(&lexbor_parser);
+ status = lxb_url_parse_basic(&lexbor_parser, lexbor_url, lexbor_base_url,
+ (lxb_char_t *) Z_STRVAL_P(query), Z_STRLEN_P(query),
+ LXB_URL_STATE_QUERY_STATE, LXB_ENCODING_AUTO
+ );
+ php_uri_parser_whatwg_build_errors_and_throw(status, "query", &errors);
+ if (status != LXB_STATUS_OK) {
+ goto failure;
+ }
+ }
+
+ if (Z_TYPE_P(fragment) == IS_STRING) {
+ php_uri_parser_whatwg_fragment_set_null(lexbor_url);
+ lxb_url_parser_clean(&lexbor_parser);
+ status = lxb_url_parse_basic(&lexbor_parser, lexbor_url, lexbor_base_url,
+ (lxb_char_t *) Z_STRVAL_P(fragment), Z_STRLEN_P(fragment),
+ LXB_URL_STATE_FRAGMENT_STATE, LXB_ENCODING_AUTO
+ );
+ php_uri_parser_whatwg_build_errors_and_throw(status, "fragment", &errors);
+ if (status != LXB_STATUS_OK) {
+ goto failure;
+ }
+ }
+
+ if (php_uri_pass_errors_by_ref_and_free(soft_errors_zv, &errors) == FAILURE) {
+ /* The errors zval was already consumed; goto failure would destroy it again. */
+ lxb_url_destroy(lexbor_url);
+ return NULL;
+ }
+
+ return lexbor_url;
+
+failure:
+ php_uri_parser_whatwg_build_errors_into_exception(&errors);
+ zval_ptr_dtor(&errors);
+ lxb_url_destroy(lexbor_url);
+ return NULL;
}
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_whatwg_build_path(
@@ -1041,6 +1262,12 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_wh
) {
lxb_url_parser_clean(&lexbor_parser);
+ if (lexbor_base_url != NULL && Z_TYPE_P(scheme) == IS_STRING && Z_STRLEN_P(scheme) == 0) {
+ return php_uri_parser_whatwg_resolve_reference_from_zval(
+ lexbor_base_url, scheme, username, password, host, port, path, query, fragment, soft_errors_zv
+ );
+ }
+
lxb_url_t *lexbor_url = lexbor_mraw_calloc(lexbor_parser.mraw, sizeof(*lexbor_url));
if (lexbor_url == NULL) {
zend_throw_exception(php_uri_ce_error, "Memory allocation error", 0);
@@ -1148,10 +1375,6 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_wh
}
}
- if (lexbor_base_url != NULL) {
- /* TODO */
- }
-
if (php_uri_pass_errors_by_ref_and_free(soft_errors_zv, &errors) == FAILURE) {
/* The errors zval was already consumed; goto failure would destroy it again. */
lxb_url_destroy(lexbor_url);
@@ -1159,26 +1382,9 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_wh
}
return lexbor_url;
-
failure:
ZEND_ASSERT(EG(exception));
-
- /* Include errors from earlier components in the exception raised by a later component. */
- if (zend_hash_num_elements(Z_ARRVAL(errors)) > 0
- && instanceof_function(EG(exception)->ce, php_uri_ce_whatwg_invalid_url_exception)) {
- zval rv;
- zval *exception_errors = zend_read_property(php_uri_ce_whatwg_invalid_url_exception,
- EG(exception), ZEND_STRL("errors"), true, &rv);
- ZEND_ASSERT(Z_TYPE_P(exception_errors) == IS_ARRAY);
-
- ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(exception_errors), zval *error) {
- Z_TRY_ADDREF_P(error);
- zend_hash_next_index_insert(Z_ARRVAL(errors), error);
- } ZEND_HASH_FOREACH_END();
-
- zval_ptr_dtor(exception_errors);
- ZVAL_COPY(exception_errors, &errors);
- }
+ php_uri_parser_whatwg_build_errors_into_exception(&errors);
zval_ptr_dtor(&errors);
lxb_url_destroy(lexbor_url);
return NULL;