Commit 3f285f41fca for php.net
commit 3f285f41fcaa4eee6ecd1afccccf555232b3a906
Author: Máté Kocsis <kocsismate@woohoolabs.com>
Date: Wed Jun 24 11:04:22 2026 +0200
Implement "Followup improvements for ext/uri" RFC - RFC 3986 URI building (#22173)
RFC: https://wiki.php.net/rfc/uri_followup#uri_building
diff --git a/NEWS b/NEWS
index 3e24c2fa0bc..231e1788bbe 100644
--- a/NEWS
+++ b/NEWS
@@ -299,6 +299,8 @@ PHP NEWS
(kocsismate)
. Added Uri\Rfc3986\Uri:getHostType() and Uri\WhatWg\Url:getHostType().
(kocsismate)
+ . Added Uri\Rfc3986\UriBuilder.
+ (kocsismate)
- Zip:
. Fixed ZipArchive callback being called after executor has shut down.
diff --git a/UPGRADING b/UPGRADING
index 18afee1a5c9..14bbb9ecc39 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -254,6 +254,8 @@ PHP 8.6 UPGRADE NOTES
RFC: https://wiki.php.net/rfc/uri_followup#uri_type_detection
. Added Uri\Rfc3986\Uri:getHostType() and Uri\WhatWg\Url:getHostType().
RFC: https://wiki.php.net/rfc/uri_followup#host_type_detection
+ . Added Uri\Rfc3986\UriBuilder.
+ RFC: https://wiki.php.net/rfc/uri_followup#uri_building
========================================
3. Changes in SAPI modules
diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c
index 58f34a37015..74a559fd591 100644
--- a/ext/uri/php_uri.c
+++ b/ext/uri/php_uri.c
@@ -30,6 +30,7 @@
#include "php_uri_arginfo.h"
#include "uriparser/Uri.h"
+zend_class_entry *php_uri_ce_rfc3986_uri_builder;
zend_class_entry *php_uri_ce_rfc3986_uri;
zend_class_entry *php_uri_ce_rfc3986_uri_type;
zend_class_entry *php_uri_ce_rfc3986_uri_host_type;
@@ -46,6 +47,9 @@ zend_class_entry *php_uri_ce_whatwg_url_validation_error;
static zend_object_handlers object_handlers_rfc3986_uri;
static zend_object_handlers object_handlers_whatwg_uri;
+typedef zend_result (*php_uri_component_validator_string)(const zend_string *component);
+typedef zend_result (*php_uri_component_validator_long)(zend_long component);
+
static const zend_module_dep uri_deps[] = {
ZEND_MOD_REQUIRED("lexbor")
ZEND_MOD_END
@@ -53,6 +57,23 @@ static const zend_module_dep uri_deps[] = {
static zend_array uri_parsers;
+static zend_always_inline zval *php_uri_deref(zval *zv)
+{
+ if (UNEXPECTED(Z_TYPE_P(zv) == IS_REFERENCE)) {
+ return Z_REFVAL_P(zv);
+ }
+
+ return zv;
+}
+
+#define Z_RFC3986_URI_PROP_SCHEME_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 0))
+#define Z_RFC3986_URI_PROP_USERINFO_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 1))
+#define Z_RFC3986_URI_PROP_HOST_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 2))
+#define Z_RFC3986_URI_PROP_PORT_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 3))
+#define Z_RFC3986_URI_PROP_PATH_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 4))
+#define Z_RFC3986_URI_PROP_QUERY_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 5))
+#define Z_RFC3986_URI_PROP_FRAGMENT_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 6))
+
static HashTable *uri_get_debug_properties(php_uri_object *object)
{
const HashTable *std_properties = zend_std_get_properties(&object->std);
@@ -1044,6 +1065,186 @@ PHP_METHOD(Uri_WhatWg_Url, __debugInfo)
RETURN_ARR(uri_get_debug_properties(uri_object));
}
+PHP_METHOD(Uri_Rfc3986_UriBuilder, reset)
+{
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ convert_to_null(Z_RFC3986_URI_PROP_SCHEME_P(ZEND_THIS));
+ convert_to_null(Z_RFC3986_URI_PROP_USERINFO_P(ZEND_THIS));
+ convert_to_null(Z_RFC3986_URI_PROP_HOST_P(ZEND_THIS));
+ convert_to_null(Z_RFC3986_URI_PROP_PORT_P(ZEND_THIS));
+ zval_ptr_dtor(Z_RFC3986_URI_PROP_PATH_P(ZEND_THIS));
+ ZVAL_EMPTY_STRING(Z_RFC3986_URI_PROP_PATH_P(ZEND_THIS));
+ convert_to_null(Z_RFC3986_URI_PROP_QUERY_P(ZEND_THIS));
+ convert_to_null(Z_RFC3986_URI_PROP_FRAGMENT_P(ZEND_THIS));
+
+ RETVAL_COPY(ZEND_THIS);
+}
+
+ZEND_ATTRIBUTE_NONNULL static void php_uri_builder_set_component_string(
+ INTERNAL_FUNCTION_PARAMETERS, const char *name, const size_t name_length,
+ const php_uri_component_validator_string validator
+) {
+ zend_string *component;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_STR(component)
+ ZEND_PARSE_PARAMETERS_END();
+
+ if (validator(component) == FAILURE) {
+ RETURN_THROWS();
+ }
+
+ zend_update_property_str(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), name, name_length, component);
+
+ RETVAL_COPY(ZEND_THIS);
+}
+
+ZEND_ATTRIBUTE_NONNULL static void php_uri_builder_set_component_string_or_null(
+ INTERNAL_FUNCTION_PARAMETERS, const char *name, const size_t name_length,
+ const php_uri_component_validator_string validator
+) {
+ zend_string *component;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_STR_OR_NULL(component)
+ ZEND_PARSE_PARAMETERS_END();
+
+ if (component == NULL) {
+ zend_update_property_null(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), name, name_length);
+ } else {
+ if (validator(component) == FAILURE) {
+ RETURN_THROWS();
+ }
+
+ zend_update_property_str(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), name, name_length, component);
+ }
+
+ RETVAL_COPY(ZEND_THIS);
+}
+
+ZEND_ATTRIBUTE_NONNULL_ARGS(1) static void php_uri_builder_set_component_long_or_null(
+ INTERNAL_FUNCTION_PARAMETERS, const char *name, const size_t name_length,
+ const php_uri_component_validator_long validator
+) {
+ zend_long component;
+ bool component_is_null;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_LONG_OR_NULL(component, component_is_null)
+ ZEND_PARSE_PARAMETERS_END();
+
+ if (component_is_null) {
+ zend_update_property_null(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), name, name_length);
+ } else {
+ if (validator(component) == FAILURE) {
+ RETURN_THROWS();
+ }
+
+ zend_update_property_long(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), name, name_length, component);
+ }
+
+ RETVAL_COPY(ZEND_THIS);
+}
+
+PHP_METHOD(Uri_Rfc3986_UriBuilder, setScheme)
+{
+ php_uri_builder_set_component_string_or_null(
+ INTERNAL_FUNCTION_PARAM_PASSTHRU,
+ ZEND_STRL("scheme"),
+ php_uri_parser_rfc3986_validate_scheme
+ );
+}
+
+PHP_METHOD(Uri_Rfc3986_UriBuilder, setUserInfo)
+{
+ php_uri_builder_set_component_string_or_null(
+ INTERNAL_FUNCTION_PARAM_PASSTHRU,
+ ZEND_STRL("userinfo"),
+ php_uri_parser_rfc3986_validate_userinfo
+ );
+}
+
+PHP_METHOD(Uri_Rfc3986_UriBuilder, setHost)
+{
+ php_uri_builder_set_component_string_or_null(
+ INTERNAL_FUNCTION_PARAM_PASSTHRU,
+ ZEND_STRL("host"),
+ php_uri_parser_rfc3986_validate_host
+ );
+}
+
+PHP_METHOD(Uri_Rfc3986_UriBuilder, setPort)
+{
+ php_uri_builder_set_component_long_or_null(
+ INTERNAL_FUNCTION_PARAM_PASSTHRU,
+ ZEND_STRL("port"),
+ php_uri_parser_rfc3986_validate_port
+ );
+}
+
+PHP_METHOD(Uri_Rfc3986_UriBuilder, setPath)
+{
+ php_uri_builder_set_component_string(
+ INTERNAL_FUNCTION_PARAM_PASSTHRU,
+ ZEND_STRL("path"),
+ php_uri_parser_rfc3986_validate_path
+ );
+}
+
+PHP_METHOD(Uri_Rfc3986_UriBuilder, setQuery)
+{
+ php_uri_builder_set_component_string_or_null(
+ INTERNAL_FUNCTION_PARAM_PASSTHRU,
+ ZEND_STRL("query"),
+ php_uri_parser_rfc3986_validate_query
+ );
+}
+
+PHP_METHOD(Uri_Rfc3986_UriBuilder, setFragment)
+{
+ php_uri_builder_set_component_string_or_null(
+ INTERNAL_FUNCTION_PARAM_PASSTHRU,
+ ZEND_STRL("fragment"),
+ php_uri_parser_rfc3986_validate_fragment
+ );
+}
+
+PHP_METHOD(Uri_Rfc3986_UriBuilder, build)
+{
+ zval *base_url = NULL;
+
+ ZEND_PARSE_PARAMETERS_START(0, 1)
+ Z_PARAM_OPTIONAL
+ Z_PARAM_OBJECT_OF_CLASS_OR_NULL(base_url, php_uri_ce_rfc3986_uri)
+ ZEND_PARSE_PARAMETERS_END();
+
+ const zval *scheme = Z_RFC3986_URI_PROP_SCHEME_P(ZEND_THIS);
+ const zval *userinfo = Z_RFC3986_URI_PROP_USERINFO_P(ZEND_THIS);
+ const zval *host = Z_RFC3986_URI_PROP_HOST_P(ZEND_THIS);
+ const zval *port = Z_RFC3986_URI_PROP_PORT_P(ZEND_THIS);
+ const zval *path = Z_RFC3986_URI_PROP_PATH_P(ZEND_THIS);
+ const zval *query = Z_RFC3986_URI_PROP_QUERY_P(ZEND_THIS);
+ const zval *fragment = Z_RFC3986_URI_PROP_FRAGMENT_P(ZEND_THIS);
+
+ php_uri_parser_rfc3986_uris *base_uris = NULL;
+ if (base_url != NULL) {
+ base_uris = Z_URI_OBJECT_P(base_url)->uri;
+ }
+
+ php_uri_parser_rfc3986_uris *uriparser_uris = php_uri_parser_rfc3986_build_from_zval(
+ base_uris, scheme, userinfo, host, port, path, query, fragment
+ );
+ if (uriparser_uris == NULL) {
+ RETURN_THROWS();
+ }
+
+ object_init_ex(return_value, php_uri_ce_rfc3986_uri);
+ php_uri_object *uri_object = Z_URI_OBJECT_P(return_value);
+ uri_object->parser = &php_uri_parser_rfc3986;
+ uri_object->uri = uriparser_uris;
+}
+
PHPAPI php_uri_object *php_uri_object_create(zend_class_entry *class_type, const php_uri_parser *parser)
{
php_uri_object *uri_object = zend_object_alloc(sizeof(*uri_object), class_type);
@@ -1113,6 +1314,8 @@ PHPAPI zend_result php_uri_parser_register(const php_uri_parser *uri_parser)
static PHP_MINIT_FUNCTION(uri)
{
+ php_uri_ce_rfc3986_uri_builder = register_class_Uri_Rfc3986_UriBuilder();
+
php_uri_ce_rfc3986_uri = register_class_Uri_Rfc3986_Uri();
php_uri_ce_rfc3986_uri->create_object = php_uri_object_create_rfc3986;
php_uri_ce_rfc3986_uri->default_object_handlers = &object_handlers_rfc3986_uri;
diff --git a/ext/uri/php_uri.stub.php b/ext/uri/php_uri.stub.php
index b0b83fcf83e..d00ef45cb86 100644
--- a/ext/uri/php_uri.stub.php
+++ b/ext/uri/php_uri.stub.php
@@ -45,6 +45,35 @@ enum UriHostType
case RegisteredName;
}
+ final class UriBuilder
+ {
+ private ?string $scheme = null;
+ private ?string $userinfo = null;
+ private ?string $host = null;
+ private ?int $port = null;
+ private string $path = "";
+ private ?string $query = null;
+ private ?string $fragment = null;
+
+ public function reset(): static {}
+
+ public function setScheme(?string $scheme): static {}
+
+ public function setUserInfo(#[\SensitiveParameter] ?string $userInfo): static {}
+
+ public function setHost(?string $host): static {}
+
+ public function setPort(?int $port): static {}
+
+ public function setPath(string $path): static {}
+
+ public function setQuery(?string $query): static {}
+
+ public function setFragment(?string $fragment): static {}
+
+ public function build(?\Uri\Rfc3986\Uri $baseUrl = null): \Uri\Rfc3986\Uri {}
+ }
+
/** @strict-properties */
final readonly class Uri
{
diff --git a/ext/uri/php_uri_arginfo.h b/ext/uri/php_uri_arginfo.h
index 0fb464ee74a..e63e495526f 100644
Binary files a/ext/uri/php_uri_arginfo.h and b/ext/uri/php_uri_arginfo.h differ
diff --git a/ext/uri/php_uri_common.h b/ext/uri/php_uri_common.h
index b79d092ae72..9106f6acd15 100644
--- a/ext/uri/php_uri_common.h
+++ b/ext/uri/php_uri_common.h
@@ -17,6 +17,7 @@
#include "php_uri_decl.h"
+extern zend_class_entry *php_uri_ce_rfc3986_uri_builder;
extern zend_class_entry *php_uri_ce_rfc3986_uri;
extern zend_class_entry *php_uri_ce_rfc3986_uri_type;
extern zend_class_entry *php_uri_ce_rfc3986_uri_host_type;
diff --git a/ext/uri/php_uri_decl.h b/ext/uri/php_uri_decl.h
index d1fd58d04b2..784ac5b4c0e 100644
Binary files a/ext/uri/php_uri_decl.h and b/ext/uri/php_uri_decl.h differ
diff --git a/ext/uri/tests/rfc3986/builder/all_success_with_reset.phpt b/ext/uri/tests/rfc3986/builder/all_success_with_reset.phpt
new file mode 100644
index 00000000000..9c11fb2e668
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/all_success_with_reset.phpt
@@ -0,0 +1,67 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder all components - success - calling reset() afterwards
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder()
+ ->setScheme("https")
+ ->setUserInfo("user:info")
+ ->setHost("example.com")
+ ->setPort(443)
+ ->setPath("/foo/bar/baz")
+ ->setQuery("foo=1&bar=baz")
+ ->setFragment("fragment");
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+$uri = $builder->reset()->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(68) "https://user:info@example.com:443/foo/bar/baz?foo=1&bar=baz#fragment"
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ string(5) "https"
+ ["username"]=>
+ string(4) "user"
+ ["password"]=>
+ string(4) "info"
+ ["host"]=>
+ string(11) "example.com"
+ ["port"]=>
+ int(443)
+ ["path"]=>
+ string(12) "/foo/bar/baz"
+ ["query"]=>
+ string(13) "foo=1&bar=baz"
+ ["fragment"]=>
+ string(8) "fragment"
+}
+bool(true)
+string(0) ""
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ NULL
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ NULL
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/basic_error_with_base.phpt b/ext/uri/tests/rfc3986/builder/basic_error_with_base.phpt
new file mode 100644
index 00000000000..284de4e6a52
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/basic_error_with_base.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder basic - error - with base URL
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder()
+ ->setPath("/foo/bar/baz");
+
+try {
+ $builder->build(new Uri\Rfc3986\Uri("/foo/bar"));
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The specified base URI must be absolute
diff --git a/ext/uri/tests/rfc3986/builder/basic_success_with_base.phpt b/ext/uri/tests/rfc3986/builder/basic_success_with_base.phpt
new file mode 100644
index 00000000000..b7a84dc00cf
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/basic_success_with_base.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder basic - success - with base URL
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder()
+ ->setPath("/foo/bar/baz");
+$uri = $builder->build(new Uri\Rfc3986\Uri("https://example.com"));
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(31) "https://example.com/foo/bar/baz"
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ string(5) "https"
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ string(11) "example.com"
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(12) "/foo/bar/baz"
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/fragment_error_special_char.phpt b/ext/uri/tests/rfc3986/builder/fragment_error_special_char.phpt
new file mode 100644
index 00000000000..8ed2a8938e9
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/fragment_error_special_char.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setFragment() - error - contains invalid special character
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+
+try {
+ $builder->setFragment("#foo");
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The specified fragment is malformed
diff --git a/ext/uri/tests/rfc3986/builder/fragment_error_unicode_char.phpt b/ext/uri/tests/rfc3986/builder/fragment_error_unicode_char.phpt
new file mode 100644
index 00000000000..2a15d5e85a8
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/fragment_error_unicode_char.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setFragment() - error - contains Unicode character
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+
+try {
+ $builder->setFragment("főő");
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The specified fragment is malformed
diff --git a/ext/uri/tests/rfc3986/builder/fragment_success_basic.phpt b/ext/uri/tests/rfc3986/builder/fragment_success_basic.phpt
new file mode 100644
index 00000000000..a59d9322e1b
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/fragment_success_basic.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setFragment() - success - basic
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setFragment("foo");
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(4) "#foo"
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ NULL
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ NULL
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ string(3) "foo"
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/fragment_success_null.phpt b/ext/uri/tests/rfc3986/builder/fragment_success_null.phpt
new file mode 100644
index 00000000000..69fc4d6bca4
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/fragment_success_null.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setFragment() - success - null
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setFragment("foo");
+$builder->setFragment(null);
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(0) ""
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ NULL
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ NULL
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/host_error_ipv6_closing_brace.phpt b/ext/uri/tests/rfc3986/builder/host_error_ipv6_closing_brace.phpt
new file mode 100644
index 00000000000..45eef13faf7
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/host_error_ipv6_closing_brace.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setHost() - error - missing IPv6 closing brace
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+
+try {
+ $builder->setHost("[2001:%30db8:85a3:0000:0000:8a2e:0370:7334");
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The specified host is malformed
diff --git a/ext/uri/tests/rfc3986/builder/host_error_percent_encoding1.phpt b/ext/uri/tests/rfc3986/builder/host_error_percent_encoding1.phpt
new file mode 100644
index 00000000000..ae06238a1f8
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/host_error_percent_encoding1.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setHost() - error - invalid percent encoding octet in registered name
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+
+try {
+ $builder->setHost("ex%3mple.co");
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The specified host is malformed
diff --git a/ext/uri/tests/rfc3986/builder/host_error_percent_encoding2.phpt b/ext/uri/tests/rfc3986/builder/host_error_percent_encoding2.phpt
new file mode 100644
index 00000000000..cc514ec6919
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/host_error_percent_encoding2.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setHost() - error - invalid percent encoded octet in IPv6
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+
+try {
+ $builder->setHost("[2001:%308:85a3:0000:0000:8a2e:0370:7334]");
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The specified host is malformed
diff --git a/ext/uri/tests/rfc3986/builder/host_success_ip4_to_regname.phpt b/ext/uri/tests/rfc3986/builder/host_success_ip4_to_regname.phpt
new file mode 100644
index 00000000000..561a66c4037
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/host_success_ip4_to_regname.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setHost() - success - invalid IPv4 address falls back to a registered name
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setHost("192.168.%30.1");
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri->getHostType());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(15) "//192.168.%30.1"
+enum(Uri\Rfc3986\UriHostType::RegisteredName)
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ NULL
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ string(13) "192.168.%30.1"
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/host_success_ipv4.phpt b/ext/uri/tests/rfc3986/builder/host_success_ipv4.phpt
new file mode 100644
index 00000000000..7ae3b901085
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/host_success_ipv4.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setHost() - success - IPv4 address
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setHost("192.168.0.1");
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(13) "//192.168.0.1"
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ NULL
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ string(11) "192.168.0.1"
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/host_success_ipv6.phpt b/ext/uri/tests/rfc3986/builder/host_success_ipv6.phpt
new file mode 100644
index 00000000000..f0216f7e7e0
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/host_success_ipv6.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setHost() - success - IPv6 address
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setHost("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]");
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(43) "//[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ NULL
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ string(41) "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/host_success_ipvfuture.phpt b/ext/uri/tests/rfc3986/builder/host_success_ipvfuture.phpt
new file mode 100644
index 00000000000..470ef1da97d
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/host_success_ipvfuture.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setHost() - success - IPvFuture address
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setHost("[v1.2001:db8::1]");
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(18) "//[v1.2001:db8::1]"
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ NULL
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ string(16) "[v1.2001:db8::1]"
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/host_success_null.phpt b/ext/uri/tests/rfc3986/builder/host_success_null.phpt
new file mode 100644
index 00000000000..87217d0ef28
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/host_success_null.phpt
@@ -0,0 +1,34 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setHost() - success - null
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setHost("example.com");
+$builder->setHost(null);
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+
+?>
+--EXPECTF--
+string(0) ""
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ NULL
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ NULL
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
diff --git a/ext/uri/tests/rfc3986/builder/host_success_regname.phpt b/ext/uri/tests/rfc3986/builder/host_success_regname.phpt
new file mode 100644
index 00000000000..81e93ccfc10
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/host_success_regname.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setHost() - success - Registered name
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setHost("www.example.com");
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(17) "//www.example.com"
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ NULL
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ string(15) "www.example.com"
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/path_error_first_segment_colon.phpt b/ext/uri/tests/rfc3986/builder/path_error_first_segment_colon.phpt
new file mode 100644
index 00000000000..8cce8d471aa
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/path_error_first_segment_colon.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setPath() - error - contains a colon in the first path segment when the URI doesn't contain a scheme
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setPath("fo:o/bar/baz");
+
+try {
+ $builder->build();
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The path must not begin with ":" when the URI does not contain a scheme
diff --git a/ext/uri/tests/rfc3986/builder/path_error_leading_double_slash.phpt b/ext/uri/tests/rfc3986/builder/path_error_leading_double_slash.phpt
new file mode 100644
index 00000000000..b1f0a2518f1
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/path_error_leading_double_slash.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setPath() - error - contains leading double slash when the host is not present
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setPath("//foo/bar/baz");
+
+try {
+ $builder->build();
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The path must not begin with "//" when the URI does not contain a host
diff --git a/ext/uri/tests/rfc3986/builder/path_error_missing_leading_slash.phpt b/ext/uri/tests/rfc3986/builder/path_error_missing_leading_slash.phpt
new file mode 100644
index 00000000000..97b6c3e8b85
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/path_error_missing_leading_slash.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setPath() - error - missing a leading slash when the URI contains a host
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setPath("foo/bar/baz");
+$builder->setHost("example.com");
+
+try {
+ $builder->build();
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The specified path is malformed
diff --git a/ext/uri/tests/rfc3986/builder/path_error_special_char.phpt b/ext/uri/tests/rfc3986/builder/path_error_special_char.phpt
new file mode 100644
index 00000000000..039b2277024
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/path_error_special_char.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setPath() - error - contains invalid special character
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+
+try {
+ $builder->setPath("#foo");
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The specified path is malformed
diff --git a/ext/uri/tests/rfc3986/builder/path_success_empty_string.phpt b/ext/uri/tests/rfc3986/builder/path_success_empty_string.phpt
new file mode 100644
index 00000000000..00ed0c95355
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/path_success_empty_string.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setPath() - success - empty string
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setPath("");
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(0) ""
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ NULL
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ NULL
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/path_success_first_segment_colon.phpt b/ext/uri/tests/rfc3986/builder/path_success_first_segment_colon.phpt
new file mode 100644
index 00000000000..e1fc689ba2f
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/path_success_first_segment_colon.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setPath() - success - contains a colon in the first segment when the scheme is present
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setScheme("https");
+$builder->setPath(":foo/bar/baz");
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(%d) "https::foo/bar/baz"
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ string(5) "https"
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ NULL
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(12) ":foo/bar/baz"
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/path_success_leading_double_slash.phpt b/ext/uri/tests/rfc3986/builder/path_success_leading_double_slash.phpt
new file mode 100644
index 00000000000..d00fc87b440
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/path_success_leading_double_slash.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setPath() - success - begins with double slashes when the URI contains a host
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setHost("example.com");
+$builder->setPath("//foo/bar/baz");
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(26) "//example.com//foo/bar/baz"
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ NULL
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ string(11) "example.com"
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(13) "//foo/bar/baz"
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/port_error_missing_host.phpt b/ext/uri/tests/rfc3986/builder/port_error_missing_host.phpt
new file mode 100644
index 00000000000..4b40e84c78f
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/port_error_missing_host.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setPort() - error - missing host
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setPort(443);
+
+try {
+ $builder->build();
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: Cannot set a port without having a host
diff --git a/ext/uri/tests/rfc3986/builder/port_error_negative.phpt b/ext/uri/tests/rfc3986/builder/port_error_negative.phpt
new file mode 100644
index 00000000000..4cbc0e1c69d
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/port_error_negative.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setPort() - error - negative number
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+
+try {
+ $builder->setPort(-1);
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The specified port is malformed
diff --git a/ext/uri/tests/rfc3986/builder/port_success_large.phpt b/ext/uri/tests/rfc3986/builder/port_success_large.phpt
new file mode 100644
index 00000000000..14d252a7d9c
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/port_success_large.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setPort() - success - large number
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setPort(PHP_INT_MAX);
+$builder->setHost("example.com");
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(%d) "//example.com:%d"
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ NULL
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ string(11) "example.com"
+ ["port"]=>
+ int(%d)
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/port_success_null.phpt b/ext/uri/tests/rfc3986/builder/port_success_null.phpt
new file mode 100644
index 00000000000..6c2ffaf7109
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/port_success_null.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setPort() - success - null
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setPort(433);
+$builder->setPort(null);
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(0) ""
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ NULL
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ NULL
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/query_error_special_char.phpt b/ext/uri/tests/rfc3986/builder/query_error_special_char.phpt
new file mode 100644
index 00000000000..ecfced24a44
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/query_error_special_char.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setQuery() - error - contains invalid special character
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+
+try {
+ $builder->setQuery("#foo");
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The specified query is malformed
diff --git a/ext/uri/tests/rfc3986/builder/query_error_unicode_char.phpt b/ext/uri/tests/rfc3986/builder/query_error_unicode_char.phpt
new file mode 100644
index 00000000000..4c212cddd63
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/query_error_unicode_char.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setQuery() - error - contains Unicode character
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+
+try {
+ $builder->setQuery("főő");
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The specified query is malformed
diff --git a/ext/uri/tests/rfc3986/builder/query_success_basic.phpt b/ext/uri/tests/rfc3986/builder/query_success_basic.phpt
new file mode 100644
index 00000000000..423b03b0b01
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/query_success_basic.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setQuery() - success - basic
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setQuery("foo=1&bar=baz");
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(14) "?foo=1&bar=baz"
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ NULL
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ NULL
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ string(13) "foo=1&bar=baz"
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/query_success_null.phpt b/ext/uri/tests/rfc3986/builder/query_success_null.phpt
new file mode 100644
index 00000000000..8e233c02c84
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/query_success_null.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setQuery() - success - null
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setQuery("foo");
+$builder->setQuery(null);
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(0) ""
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ NULL
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ NULL
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/scheme_error_empty.phpt b/ext/uri/tests/rfc3986/builder/scheme_error_empty.phpt
new file mode 100644
index 00000000000..13594c16336
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/scheme_error_empty.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setScheme() - error - empty
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+
+try {
+ $builder->setScheme("");
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The specified scheme is malformed
diff --git a/ext/uri/tests/rfc3986/builder/scheme_error_first_char.phpt b/ext/uri/tests/rfc3986/builder/scheme_error_first_char.phpt
new file mode 100644
index 00000000000..c178a0fa1f1
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/scheme_error_first_char.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setScheme() - error - first character is not alpha
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+
+try {
+ $builder->setScheme("1");
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The specified scheme is malformed
diff --git a/ext/uri/tests/rfc3986/builder/scheme_error_special_char.phpt b/ext/uri/tests/rfc3986/builder/scheme_error_special_char.phpt
new file mode 100644
index 00000000000..4941db44aa7
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/scheme_error_special_char.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setScheme() - error - contains invalid special character
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+
+try {
+ $builder->setScheme(":");
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The specified scheme is malformed
diff --git a/ext/uri/tests/rfc3986/builder/scheme_success_basic.phpt b/ext/uri/tests/rfc3986/builder/scheme_success_basic.phpt
new file mode 100644
index 00000000000..279ecb27362
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/scheme_success_basic.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setScheme() - success - basic
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setScheme("scheme");
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(7) "scheme:"
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ string(6) "scheme"
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ NULL
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/scheme_success_null.phpt b/ext/uri/tests/rfc3986/builder/scheme_success_null.phpt
new file mode 100644
index 00000000000..1a9510156b0
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/scheme_success_null.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setScheme() - success - null
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setScheme("https");
+$builder->setScheme(null);
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(0) ""
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ NULL
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ NULL
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/scheme_success_special.phpt b/ext/uri/tests/rfc3986/builder/scheme_success_special.phpt
new file mode 100644
index 00000000000..f387bc09f2e
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/scheme_success_special.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setScheme() - success - contains digit & special characters
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setScheme("my-12+34.scheme");
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(16) "my-12+34.scheme:"
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ string(15) "my-12+34.scheme"
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ NULL
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/tests/rfc3986/builder/userinfo_error_missing_host.phpt b/ext/uri/tests/rfc3986/builder/userinfo_error_missing_host.phpt
new file mode 100644
index 00000000000..a6919e8ffd0
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/userinfo_error_missing_host.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setUserInfo() - error - missing host
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setUserInfo("user:pass");
+
+try {
+ $builder->build();
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: Cannot set a userinfo without having a host
diff --git a/ext/uri/tests/rfc3986/builder/userinfo_error_percent_encoding.phpt b/ext/uri/tests/rfc3986/builder/userinfo_error_percent_encoding.phpt
new file mode 100644
index 00000000000..0830a57864d
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/userinfo_error_percent_encoding.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setUserInfo() - error - invalid percent encoding
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+
+try {
+ $builder->setUserInfo("%3");
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The specified userinfo is malformed
diff --git a/ext/uri/tests/rfc3986/builder/userinfo_error_special_char.phpt b/ext/uri/tests/rfc3986/builder/userinfo_error_special_char.phpt
new file mode 100644
index 00000000000..9d3fb2544da
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/userinfo_error_special_char.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setUserInfo() - error - contains invalid special character
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+
+try {
+ $builder->setUserInfo("<>");
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Uri\InvalidUriException: The specified userinfo is malformed
diff --git a/ext/uri/tests/rfc3986/builder/userinfo_success_null.phpt b/ext/uri/tests/rfc3986/builder/userinfo_success_null.phpt
new file mode 100644
index 00000000000..3decca97e79
--- /dev/null
+++ b/ext/uri/tests/rfc3986/builder/userinfo_success_null.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Test Uri\Rfc3986\UriBuilder::setUserInfo() - success - null
+--FILE--
+<?php
+
+$builder = new Uri\Rfc3986\UriBuilder();
+$builder->setUserInfo("user:pass");
+$builder->setUserInfo(null);
+$uri = $builder->build();
+
+var_dump($uri->toRawString());
+var_dump($uri);
+var_dump($uri->equals(new Uri\Rfc3986\Uri($uri->toRawString())));
+
+?>
+--EXPECTF--
+string(0) ""
+object(Uri\Rfc3986\Uri)#%d (%d) {
+ ["scheme"]=>
+ NULL
+ ["username"]=>
+ NULL
+ ["password"]=>
+ NULL
+ ["host"]=>
+ NULL
+ ["port"]=>
+ NULL
+ ["path"]=>
+ string(0) ""
+ ["query"]=>
+ NULL
+ ["fragment"]=>
+ NULL
+}
+bool(true)
diff --git a/ext/uri/uri_parser_rfc3986.c b/ext/uri/uri_parser_rfc3986.c
index b8a14a31c10..88201914d59 100644
--- a/ext/uri/uri_parser_rfc3986.c
+++ b/ext/uri/uri_parser_rfc3986.c
@@ -545,12 +545,35 @@ static php_uri_parser_rfc3986_uris *uriparser_create_uris(void)
return uriparser_uris;
}
-php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_parse_ex(const char *uri_str, size_t uri_str_len, const php_uri_parser_rfc3986_uris *uriparser_base_urls, bool silent)
+static zend_result php_uri_parser_rfc3986_add_base_url(
+ UriUriA *tmp, const UriUriA *uri, const php_uri_parser_rfc3986_uris *uriparser_base_urls, const bool silent
+) {
+ const int result = uriAddBaseUriExMmA(tmp, uri, &uriparser_base_urls->uri, URI_RESOLVE_STRICTLY, mm);
+ if (result != URI_SUCCESS) {
+ if (!silent) {
+ switch (result) {
+ case URI_ERROR_ADDBASE_REL_BASE:
+ zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified base URI must be absolute", 0);
+ break;
+ default:
+ /* This should be unreachable in practice. */
+ zend_throw_exception(php_uri_ce_error, "Failed to resolve the specified URI against the base URI", 0);
+ break;
+ }
+ }
+
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_parse_ex(const char *uri_str, const size_t uri_str_len, const php_uri_parser_rfc3986_uris *uriparser_base_urls, const bool silent)
{
UriUriA uri = {0};
/* Parse the URI. */
- int result = uriParseSingleUriExMmA(&uri, uri_str, uri_str + uri_str_len, NULL, mm);
+ const int result = uriParseSingleUriExMmA(&uri, uri_str, uri_str + uri_str_len, NULL, mm);
if (result != URI_SUCCESS) {
if (!silent) {
switch (result) {
@@ -572,20 +595,7 @@ php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_parse_ex(const char *uri_str
/* Combine the parsed URI with the base URI and store the result in 'tmp',
* since the target and source URLs must be distinct. */
- int result = uriAddBaseUriExMmA(&tmp, &uri, &uriparser_base_urls->uri, URI_RESOLVE_STRICTLY, mm);
- if (result != URI_SUCCESS) {
- if (!silent) {
- switch (result) {
- case URI_ERROR_ADDBASE_REL_BASE:
- zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified base URI must be absolute", 0);
- break;
- default:
- /* This should be unreachable in practice. */
- zend_throw_exception(php_uri_ce_error, "Failed to resolve the specified URI against the base URI", 0);
- break;
- }
- }
-
+ if (php_uri_parser_rfc3986_add_base_url(&tmp, &uri, uriparser_base_urls, silent) == FAILURE) {
goto fail;
}
@@ -683,6 +693,189 @@ static void php_uri_parser_rfc3986_destroy(void *uri)
efree(uriparser_uris);
}
+static zend_always_inline zend_result php_uri_parser_rfc3986_validate_component_result(const bool well_formed, const char *component_name)
+{
+ if (well_formed) {
+ return SUCCESS;
+ }
+
+ zend_throw_exception_ex(php_uri_ce_invalid_uri_exception, 0, "The specified %s is malformed", component_name);
+ return FAILURE;
+}
+
+ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_scheme(const zend_string *scheme)
+{
+ const char *p = ZSTR_VAL(scheme);
+ const size_t len = ZSTR_LEN(scheme);
+ const bool well_formed = uriIsWellFormedSchemeA(p, p + len) == URI_TRUE;
+
+ return php_uri_parser_rfc3986_validate_component_result(well_formed, "scheme");
+}
+
+ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_userinfo(const zend_string *userinfo)
+{
+ const char *p = ZSTR_VAL(userinfo);
+ const size_t len = ZSTR_LEN(userinfo);
+ const bool well_formed = uriIsWellFormedUserInfoA(p, p + len) == URI_TRUE;
+
+ return php_uri_parser_rfc3986_validate_component_result(well_formed, "userinfo");
+}
+
+ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_host(const zend_string *host)
+{
+ const char *p = ZSTR_VAL(host);
+ const size_t len = ZSTR_LEN(host);
+
+ if (len == 0) {
+ return SUCCESS;
+ }
+
+ if (p[0] == '[') {
+ if (p[len - 1] != ']') {
+ return php_uri_parser_rfc3986_validate_component_result(false, "host");
+ }
+
+ if (len >= 2 && (p[1] == 'v' || p[1] == 'V')) {
+ return php_uri_parser_rfc3986_validate_component_result(
+ uriIsWellFormedHostIpFutureA(p + 1, p + len - 1) == URI_SUCCESS,
+ "host"
+ );
+ }
+
+ return php_uri_parser_rfc3986_validate_component_result(
+ uriIsWellFormedHostIp6A(p + 1, p + len - 1) == URI_SUCCESS,
+ "host"
+ );
+ }
+
+ if (uriIsWellFormedHostIp4A(p, p + len) == URI_TRUE) {
+ return SUCCESS;
+ }
+
+ return php_uri_parser_rfc3986_validate_component_result(
+ uriIsWellFormedHostRegNameA(p, p + len) == URI_TRUE,
+ "host"
+ );
+}
+
+ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_port(const zend_long port)
+{
+ char buf[MAX_LENGTH_OF_LONG + 1];
+ const char *res = zend_print_long_to_buf(buf + sizeof(buf) - 1, port);
+
+ const bool well_formed = uriIsWellFormedPortA(res, res + strlen(res));
+
+ return php_uri_parser_rfc3986_validate_component_result(well_formed, "port");
+}
+
+ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_path(const zend_string *path)
+{
+ const char *p = ZSTR_VAL(path);
+ const size_t len = ZSTR_LEN(path);
+ /* The build() method checks whether the path begins with a "/" when there's a host.
+ * In order to skip doing the same check, a false hasHost argument is passed to uriIsWellFormedPathA(). */
+ const bool well_formed = uriIsWellFormedPathA(p, p + len, /* hasHost */ false);
+
+ return php_uri_parser_rfc3986_validate_component_result(well_formed, "path");
+}
+
+ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_query(const zend_string *query)
+{
+ const char *p = ZSTR_VAL(query);
+ const size_t len = ZSTR_LEN(query);
+ const bool well_formed = uriIsWellFormedQueryA(p, p + len);
+
+ return php_uri_parser_rfc3986_validate_component_result(well_formed, "query");
+}
+
+ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_fragment(const zend_string *fragment)
+{
+ const char *p = ZSTR_VAL(fragment);
+ const size_t len = ZSTR_LEN(fragment);
+ const bool well_formed = uriIsWellFormedFragmentA(p, p + len);
+
+ return php_uri_parser_rfc3986_validate_component_result(well_formed, "fragment");
+}
+
+ZEND_ATTRIBUTE_NONNULL_ARGS(2,3,4,5,6,7,8) php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_build_from_zval(
+ const php_uri_parser_rfc3986_uris *uriparser_base_uris,
+ const zval *scheme, const zval *userinfo, const zval *host, const zval *port,
+ const zval *path, const zval *query, const zval *fragment
+) {
+ php_uri_parser_rfc3986_uris *uriparser_uris = uriparser_create_uris();
+
+ if (Z_STRLEN_P(path) > 0) {
+ /* The first segment of the path must not contain ":" if the URI does not contain a scheme */
+ if (Z_TYPE_P(scheme) == IS_NULL) {
+ const char *p = Z_STRVAL_P(path);
+ while (*p != '\0' && *p != '/') {
+ if (*p == ':') {
+ zend_throw_exception(php_uri_ce_invalid_uri_exception, "The path must not begin with \":\" when the URI does not contain a scheme", 0);
+ goto failure;
+ }
+
+ p++;
+ }
+ }
+
+ /* The path must not begin with "//" if the URI does not contain a host */
+ if (Z_TYPE_P(host) == IS_NULL && zend_string_starts_with_literal(Z_STR_P(path), "//")) {
+ zend_throw_exception(php_uri_ce_invalid_uri_exception, "The path must not begin with \"//\" when the URI does not contain a host", 0);
+ goto failure;
+ }
+ }
+
+ zend_result result = php_uri_parser_rfc3986_scheme_write(uriparser_uris, scheme, NULL);
+ if (result == FAILURE) {
+ goto failure;
+ }
+ result = php_uri_parser_rfc3986_host_write(uriparser_uris, host, NULL);
+ if (result == FAILURE) {
+ goto failure;
+ }
+ /* Intentionally writing userinfo after host to avoid error when the userinfo is set but the host is missing */
+ result = php_uri_parser_rfc3986_userinfo_write(uriparser_uris, userinfo, NULL);
+ if (result == FAILURE) {
+ goto failure;
+ }
+ /* Intentionally writing userinfo after host to avoid error when the port is set but the host is missing */
+ result = php_uri_parser_rfc3986_port_write(uriparser_uris, port, NULL);
+ if (result == FAILURE) {
+ goto failure;
+ }
+ result = php_uri_parser_rfc3986_path_write(uriparser_uris, path, NULL);
+ if (result == FAILURE) {
+ goto failure;
+ }
+ result = php_uri_parser_rfc3986_query_write(uriparser_uris, query, NULL);
+ if (result == FAILURE) {
+ goto failure;
+ }
+ result = php_uri_parser_rfc3986_fragment_write(uriparser_uris, fragment, NULL);
+ if (result == FAILURE) {
+ goto failure;
+ }
+
+ if (uriparser_base_uris != NULL) {
+ UriUriA tmp = {0};
+
+ if (php_uri_parser_rfc3986_add_base_url(&tmp, &uriparser_uris->uri, uriparser_base_uris, false) == FAILURE) {
+ goto failure;
+ }
+
+ uriMakeOwnerMmA(&tmp, mm);
+ uriFreeUriMembersMmA(&uriparser_uris->uri, mm);
+ uriparser_uris->uri = tmp;
+ }
+
+ return uriparser_uris;
+
+failure:
+ uriFreeUriMembersMmA(&uriparser_uris->uri, mm);
+ efree(uriparser_uris);
+ return NULL;
+}
+
PHPAPI const php_uri_parser php_uri_parser_rfc3986 = {
.name = PHP_URI_PARSER_RFC3986,
.parse = php_uri_parser_rfc3986_parse,
diff --git a/ext/uri/uri_parser_rfc3986.h b/ext/uri/uri_parser_rfc3986.h
index a5fde707432..9a9f0b38a0c 100644
--- a/ext/uri/uri_parser_rfc3986.h
+++ b/ext/uri/uri_parser_rfc3986.h
@@ -29,4 +29,18 @@ zend_result php_uri_parser_rfc3986_userinfo_write(php_uri_parser_rfc3986_uris *u
php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_parse_ex(const char *uri_str, size_t uri_str_len, const php_uri_parser_rfc3986_uris *uriparser_base_url, bool silent);
+ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_scheme(const zend_string *scheme);
+ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_userinfo(const zend_string *userinfo);
+ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_host(const zend_string *host);
+ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_port(zend_long port);
+ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_path(const zend_string *path);
+ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_query(const zend_string *query);
+ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_validate_fragment(const zend_string *fragment);
+
+ZEND_ATTRIBUTE_NONNULL_ARGS(2,3,4,5,6,7,8) php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_build_from_zval(
+ const php_uri_parser_rfc3986_uris *uriparser_base_uris,
+ const zval *scheme, const zval *userinfo, const zval *host, const zval *port,
+ const zval *path, const zval *query, const zval *fragment
+);
+
#endif