[php-src] master: Implement "Followup improvements for ext/uri" RFC - WHATWG URL percent-encoding (#22820)

Máté Kocsis via GitHub <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Máté Kocsis (kocsismate)
Committer: GitHub (web-flow)
Pusher: kocsismate
Date: 2026-08-30T14:25:46+02:00

Commit: https://github.com/php/php-src/commit/1273587b05ad44e917990ef8740e5fb8b610b6ce
Raw diff: https://github.com/php/php-src/commit/1273587b05ad44e917990ef8740e5fb8b610b6ce.diff

Implement "Followup improvements for ext/uri" RFC - WHATWG URL percent-encoding (#22820)

RFC: https://wiki.php.net/rfc/uri_followup#percent-encoding_support

Changed paths:
  A  ext/uri/tests/whatwg/percent_encoding/form_query_success_control.phpt
  A  ext/uri/tests/whatwg/percent_encoding/form_query_success_percent.phpt
  A  ext/uri/tests/whatwg/percent_encoding/form_query_success_space.phpt
  A  ext/uri/tests/whatwg/percent_encoding/form_query_success_special_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/form_query_success_special_not_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/form_query_success_unicode.phpt
  A  ext/uri/tests/whatwg/percent_encoding/fragment_success_control.phpt
  A  ext/uri/tests/whatwg/percent_encoding/fragment_success_percent.phpt
  A  ext/uri/tests/whatwg/percent_encoding/fragment_success_special_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/fragment_success_special_not_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/fragment_success_unicode.phpt
  A  ext/uri/tests/whatwg/percent_encoding/opaque_host_success_control.phpt
  A  ext/uri/tests/whatwg/percent_encoding/opaque_host_success_percent.phpt
  A  ext/uri/tests/whatwg/percent_encoding/opaque_host_success_special_not_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/opaque_host_success_unicode.phpt
  A  ext/uri/tests/whatwg/percent_encoding/opaque_path_success_control.phpt
  A  ext/uri/tests/whatwg/percent_encoding/opaque_path_success_percent.phpt
  A  ext/uri/tests/whatwg/percent_encoding/opaque_path_success_special_not_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/opaque_path_success_unicode.phpt
  A  ext/uri/tests/whatwg/percent_encoding/password_success_control.phpt
  A  ext/uri/tests/whatwg/percent_encoding/password_success_percent.phpt
  A  ext/uri/tests/whatwg/percent_encoding/password_success_special_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/password_success_special_not_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/password_success_unicode.phpt
  A  ext/uri/tests/whatwg/percent_encoding/path_segment_success_control.phpt
  A  ext/uri/tests/whatwg/percent_encoding/path_segment_success_percent.phpt
  A  ext/uri/tests/whatwg/percent_encoding/path_segment_success_special_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/path_segment_success_special_not_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/path_segment_success_unicode.phpt
  A  ext/uri/tests/whatwg/percent_encoding/path_success_control.phpt
  A  ext/uri/tests/whatwg/percent_encoding/path_success_percent.phpt
  A  ext/uri/tests/whatwg/percent_encoding/path_success_special_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/path_success_special_not_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/path_success_unicode.phpt
  A  ext/uri/tests/whatwg/percent_encoding/query_success_control.phpt
  A  ext/uri/tests/whatwg/percent_encoding/query_success_percent.phpt
  A  ext/uri/tests/whatwg/percent_encoding/query_success_space.phpt
  A  ext/uri/tests/whatwg/percent_encoding/query_success_special_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/query_success_special_not_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/query_success_unicode.phpt
  A  ext/uri/tests/whatwg/percent_encoding/special_query_success_control.phpt
  A  ext/uri/tests/whatwg/percent_encoding/special_query_success_percent.phpt
  A  ext/uri/tests/whatwg/percent_encoding/special_query_success_space.phpt
  A  ext/uri/tests/whatwg/percent_encoding/special_query_success_special_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/special_query_success_special_not_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/special_query_success_unicode.phpt
  A  ext/uri/tests/whatwg/percent_encoding/username_success_control.phpt
  A  ext/uri/tests/whatwg/percent_encoding/username_success_percent.phpt
  A  ext/uri/tests/whatwg/percent_encoding/username_success_special_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/username_success_special_not_in_set.phpt
  A  ext/uri/tests/whatwg/percent_encoding/username_success_unicode.phpt
  M  UPGRADING
  M  ext/uri/php_uri.c
  M  ext/uri/php_uri.stub.php
  M  ext/uri/php_uri_arginfo.h
  M  ext/uri/php_uri_common.h
  M  ext/uri/php_uri_decl.h
  M  ext/uri/uri_parser_whatwg.c
  M  ext/uri/uri_parser_whatwg.h


Diff:

diff --git a/UPGRADING b/UPGRADING
index 74ecf90408aa..148cad212238 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -484,10 +484,10 @@ 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
-  . Added Uri\WhatWg\UrlBuilder.
+  . Added Uri\Rfc3986\UriBuilder and Uri\WhatWg\UrlBuilder.
     RFC: https://wiki.php.net/rfc/uri_followup#uri_building
+  . Added Uri\url_percent_encode().
+    RFC: https://wiki.php.net/rfc/uri_followup#percent-encoding_support
 
 ========================================
 3. Changes in SAPI modules
diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c
index 4caeca9f1465..bb1d8c8bb13c 100644
--- a/ext/uri/php_uri.c
+++ b/ext/uri/php_uri.c
@@ -36,6 +36,7 @@ zend_class_entry *php_uri_ce_rfc3986_uri_type;
 zend_class_entry *php_uri_ce_rfc3986_uri_host_type;
 zend_class_entry *php_uri_ce_whatwg_url_builder;
 zend_class_entry *php_uri_ce_whatwg_url;
+zend_class_entry *php_uri_ce_whatwg_url_percent_encoding_mode;
 zend_class_entry *php_uri_ce_comparison_mode;
 zend_class_entry *php_uri_ce_exception;
 zend_class_entry *php_uri_ce_error;
@@ -1073,6 +1074,60 @@ PHP_METHOD(Uri_WhatWg_Url, __debugInfo)
 	RETURN_ARR(uri_get_debug_properties(uri_object));
 }
 
+PHP_FUNCTION(Uri_WhatWg_url_percent_encode)
+{
+	zend_string *input;
+	zend_enum_Uri_WhatWg_UrlPercentEncodingMode mode;
+
+	ZEND_PARSE_PARAMETERS_START(2, 2)
+		Z_PARAM_STR(input)
+		Z_PARAM_ENUM(mode, php_uri_ce_whatwg_url_percent_encoding_mode)
+	ZEND_PARSE_PARAMETERS_END();
+
+	zend_string *str;
+
+	switch (mode) {
+		case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Username:
+			ZEND_FALLTHROUGH;
+		case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Password:
+			str = php_uri_parser_whatwg_percent_encode_userinfo_component(ZSTR_VAL(input), ZSTR_LEN(input));
+			break;
+		case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_OpaqueHost:
+			str = php_uri_parser_whatwg_percent_encode_opaque_host_component(ZSTR_VAL(input), ZSTR_LEN(input));
+			break;
+		case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Path:
+			str = php_uri_parser_whatwg_percent_encode_path_component(ZSTR_VAL(input), ZSTR_LEN(input));
+			break;
+		case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_OpaquePath:
+			str = php_uri_parser_whatwg_percent_encode_opaque_path_component(ZSTR_VAL(input), ZSTR_LEN(input));
+			break;
+		case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_PathSegment:
+			str = php_uri_parser_whatwg_percent_encode_path_segment_component(ZSTR_VAL(input), ZSTR_LEN(input));
+			break;
+		case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Query:
+			str = php_uri_parser_whatwg_percent_encode_query_component(ZSTR_VAL(input), ZSTR_LEN(input));
+			break;
+		case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_SpecialQuery:
+			str = php_uri_parser_whatwg_percent_encode_special_query_component(ZSTR_VAL(input), ZSTR_LEN(input));
+			break;
+		case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_FormQuery:
+			str = php_uri_parser_whatwg_percent_encode_form_query_component(ZSTR_VAL(input), ZSTR_LEN(input));
+			break;
+		case ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Fragment:
+			str = php_uri_parser_whatwg_percent_encode_fragment_component(ZSTR_VAL(input), ZSTR_LEN(input));
+			break;
+		default: ZEND_UNREACHABLE();
+	}
+
+	/* This should be unreachable in practice, as str is null only due to memory errors. */
+	if (str == NULL) {
+		zend_throw_exception(php_uri_ce_error, "Cannot percent-encode input", 0);
+		RETURN_THROWS();
+	}
+
+	RETURN_NEW_STR(str);
+}
+
 PHP_METHOD(Uri_Rfc3986_UriBuilder, reset)
 {
 	ZEND_PARSE_PARAMETERS_NONE();
@@ -1481,6 +1536,8 @@ static PHP_MINIT_FUNCTION(uri)
 	object_handlers_whatwg_uri.free_obj = php_uri_object_handler_free;
 	object_handlers_whatwg_uri.clone_obj = php_uri_object_handler_clone;
 
+	php_uri_ce_whatwg_url_percent_encoding_mode = register_class_Uri_WhatWg_UrlPercentEncodingMode();
+
 	php_uri_ce_comparison_mode = register_class_Uri_UriComparisonMode();
 	php_uri_ce_exception = register_class_Uri_UriException(zend_ce_exception);
 	php_uri_ce_error = register_class_Uri_UriError(zend_ce_error);
@@ -1548,14 +1605,14 @@ ZEND_MODULE_POST_ZEND_DEACTIVATE_D(uri)
 zend_module_entry uri_module_entry = {
 	STANDARD_MODULE_HEADER_EX, NULL,
 	uri_deps,
-	"uri",                          /* Extension name */
-	NULL,                           /* zend_function_entry */
+	"uri",                                     /* Extension name */
+	ext_functions,                                   /* zend_function_entry */
 	PHP_MINIT(uri),                 /* PHP_MINIT - Module initialization */
-	PHP_MSHUTDOWN(uri),             /* PHP_MSHUTDOWN - Module shutdown */
+	PHP_MSHUTDOWN(uri),           /* PHP_MSHUTDOWN - Module shutdown */
 	PHP_RINIT(uri),                 /* PHP_RINIT - Request initialization */
-	NULL,                           /* PHP_RSHUTDOWN - Request shutdown */
-	PHP_MINFO(uri),                 /* PHP_MINFO - Module info */
-	PHP_VERSION,                    /* Version */
+	NULL,                         /* PHP_RSHUTDOWN - Request shutdown */
+	PHP_MINFO(uri),                                  /* PHP_MINFO - Module info */
+	PHP_VERSION,                              /* Version */
 	NO_MODULE_GLOBALS,
 	ZEND_MODULE_POST_ZEND_DEACTIVATE_N(uri),
 	STANDARD_MODULE_PROPERTIES_EX
diff --git a/ext/uri/php_uri.stub.php b/ext/uri/php_uri.stub.php
index 98f8873d9845..ad1d2fe32dee 100644
--- a/ext/uri/php_uri.stub.php
+++ b/ext/uri/php_uri.stub.php
@@ -317,4 +317,20 @@ public function __unserialize(array $data): void {}
 
         public function __debugInfo(): array {}
     }
+
+    enum UrlPercentEncodingMode
+    {
+        case Username;
+        case Password;
+        case OpaqueHost;
+        case Path;
+        case OpaquePath;
+        case PathSegment;
+        case Query;
+        case SpecialQuery;
+        case FormQuery;
+        case Fragment;
+    }
+
+    function url_percent_encode(string $input, \Uri\WhatWg\UrlPercentEncodingMode $mode): string {}
 }
diff --git a/ext/uri/php_uri_arginfo.h b/ext/uri/php_uri_arginfo.h
index 93cc3ee45a2b..c77d0485d052 100644
--- a/ext/uri/php_uri_arginfo.h
+++ b/ext/uri/php_uri_arginfo.h
@@ -1,7 +1,12 @@
 /* This is a generated file, edit php_uri.stub.php instead.
- * Stub hash: 54e953b1da0d08c64509666b9278c59483d1e171
+ * Stub hash: 9e087e3aefdab5662892e7fad9de87857aa63057
  * Has decl header: yes */
 
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_Uri_WhatWg_url_percent_encode, 0, 2, IS_STRING, 0)
+	ZEND_ARG_TYPE_INFO(0, input, IS_STRING, 0)
+	ZEND_ARG_OBJ_INFO(0, mode, Uri\\WhatWg\\\125rlPercentEncodingMode, 0)
+ZEND_END_ARG_INFO()
+
 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_reset, 0, 0, IS_STATIC, 0)
 ZEND_END_ARG_INFO()
 
@@ -242,6 +247,7 @@ ZEND_END_ARG_INFO()
 
 #define arginfo_class_Uri_WhatWg_Url___debugInfo arginfo_class_Uri_Rfc3986_Uri___serialize
 
+ZEND_FUNCTION(Uri_WhatWg_url_percent_encode);
 ZEND_METHOD(Uri_Rfc3986_UriBuilder, reset);
 ZEND_METHOD(Uri_Rfc3986_UriBuilder, setScheme);
 ZEND_METHOD(Uri_Rfc3986_UriBuilder, setUserInfo);
@@ -316,6 +322,11 @@ ZEND_METHOD(Uri_WhatWg_Url, __serialize);
 ZEND_METHOD(Uri_WhatWg_Url, __unserialize);
 ZEND_METHOD(Uri_WhatWg_Url, __debugInfo);
 
+static const zend_function_entry ext_functions[] = {
+	ZEND_RAW_FENTRY(ZEND_NS_NAME("Uri\\WhatWg", "url_percent_encode"), zif_Uri_WhatWg_url_percent_encode, arginfo_Uri_WhatWg_url_percent_encode, 0, NULL, NULL)
+	ZEND_FE_END
+};
+
 static const zend_function_entry class_Uri_Rfc3986_UriBuilder_methods[] = {
 	ZEND_ME(Uri_Rfc3986_UriBuilder, reset, arginfo_class_Uri_Rfc3986_UriBuilder_reset, ZEND_ACC_PUBLIC)
 	ZEND_ME(Uri_Rfc3986_UriBuilder, setScheme, arginfo_class_Uri_Rfc3986_UriBuilder_setScheme, ZEND_ACC_PUBLIC)
@@ -733,3 +744,30 @@ static zend_class_entry *register_class_Uri_WhatWg_Url(void)
 
 	return class_entry;
 }
+
+static zend_class_entry *register_class_Uri_WhatWg_UrlPercentEncodingMode(void)
+{
+	zend_class_entry *class_entry = zend_register_internal_enum("Uri\\WhatWg\\UrlPercentEncodingMode", IS_UNDEF, NULL);
+
+	zend_enum_add_case_cstr(class_entry, "Username", NULL);
+
+	zend_enum_add_case_cstr(class_entry, "Password", NULL);
+
+	zend_enum_add_case_cstr(class_entry, "OpaqueHost", NULL);
+
+	zend_enum_add_case_cstr(class_entry, "Path", NULL);
+
+	zend_enum_add_case_cstr(class_entry, "OpaquePath", NULL);
+
+	zend_enum_add_case_cstr(class_entry, "PathSegment", NULL);
+
+	zend_enum_add_case_cstr(class_entry, "Query", NULL);
+
+	zend_enum_add_case_cstr(class_entry, "SpecialQuery", NULL);
+
+	zend_enum_add_case_cstr(class_entry, "FormQuery", NULL);
+
+	zend_enum_add_case_cstr(class_entry, "Fragment", NULL);
+
+	return class_entry;
+}
diff --git a/ext/uri/php_uri_common.h b/ext/uri/php_uri_common.h
index 31ef1dd2130c..a1d9d852f3b7 100644
--- a/ext/uri/php_uri_common.h
+++ b/ext/uri/php_uri_common.h
@@ -23,6 +23,7 @@ extern zend_class_entry *php_uri_ce_rfc3986_uri_type;
 extern zend_class_entry *php_uri_ce_rfc3986_uri_host_type;
 extern zend_class_entry *php_uri_ce_whatwg_url_builder;
 extern zend_class_entry *php_uri_ce_whatwg_url;
+extern zend_class_entry *php_uri_ce_whatwg_url_percent_encoding_mode;
 extern zend_class_entry *php_uri_ce_comparison_mode;
 extern zend_class_entry *php_uri_ce_exception;
 extern zend_class_entry *php_uri_ce_error;
diff --git a/ext/uri/php_uri_decl.h b/ext/uri/php_uri_decl.h
index 71f748b71070..a55b44a95205 100644
--- a/ext/uri/php_uri_decl.h
+++ b/ext/uri/php_uri_decl.h
@@ -1,8 +1,8 @@
 /* This is a generated file, edit php_uri.stub.php instead.
- * Stub hash: 54e953b1da0d08c64509666b9278c59483d1e171 */
+ * Stub hash: 9e087e3aefdab5662892e7fad9de87857aa63057 */
 
-#ifndef ZEND_PHP_URI_DECL_54e953b1da0d08c64509666b9278c59483d1e171_H
-#define ZEND_PHP_URI_DECL_54e953b1da0d08c64509666b9278c59483d1e171_H
+#ifndef ZEND_PHP_URI_DECL_9e087e3aefdab5662892e7fad9de87857aa63057_H
+#define ZEND_PHP_URI_DECL_9e087e3aefdab5662892e7fad9de87857aa63057_H
 
 typedef enum zend_enum_Uri_UriComparisonMode {
 	ZEND_ENUM_Uri_UriComparisonMode_IncludeFragment = 1,
@@ -63,4 +63,17 @@ typedef enum zend_enum_Uri_WhatWg_UrlHostType {
 	ZEND_ENUM_Uri_WhatWg_UrlHostType_Empty = 5,
 } zend_enum_Uri_WhatWg_UrlHostType;
 
-#endif /* ZEND_PHP_URI_DECL_54e953b1da0d08c64509666b9278c59483d1e171_H */
+typedef enum zend_enum_Uri_WhatWg_UrlPercentEncodingMode {
+	ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Username = 1,
+	ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Password = 2,
+	ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_OpaqueHost = 3,
+	ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Path = 4,
+	ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_OpaquePath = 5,
+	ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_PathSegment = 6,
+	ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Query = 7,
+	ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_SpecialQuery = 8,
+	ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_FormQuery = 9,
+	ZEND_ENUM_Uri_WhatWg_UrlPercentEncodingMode_Fragment = 10,
+} zend_enum_Uri_WhatWg_UrlPercentEncodingMode;
+
+#endif /* ZEND_PHP_URI_DECL_9e087e3aefdab5662892e7fad9de87857aa63057_H */
diff --git a/ext/uri/tests/whatwg/percent_encoding/form_query_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/form_query_success_control.phpt
new file mode 100644
index 000000000000..a5dd0ce69038
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/form_query_success_control.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - form query - control code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("\x11", Uri\WhatWg\UrlPercentEncodingMode::FormQuery));
+
+?>
+--EXPECT--
+string(3) "%11"
diff --git a/ext/uri/tests/whatwg/percent_encoding/form_query_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/form_query_success_percent.phpt
new file mode 100644
index 000000000000..238f1b0b01f7
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/form_query_success_percent.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - form query - percent sign code point
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("WHATWG%20url", Uri\WhatWg\UrlPercentEncodingMode::FormQuery));
+
+?>
+--EXPECT--
+string(14) "WHATWG%2520url"
diff --git a/ext/uri/tests/whatwg/percent_encoding/form_query_success_space.phpt b/ext/uri/tests/whatwg/percent_encoding/form_query_success_space.phpt
new file mode 100644
index 000000000000..50d649454213
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/form_query_success_space.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - form query - space code point
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("WHATWG url", Uri\WhatWg\UrlPercentEncodingMode::FormQuery));
+
+?>
+--EXPECT--
+string(10) "WHATWG+url"
diff --git a/ext/uri/tests/whatwg/percent_encoding/form_query_success_special_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/form_query_success_special_in_set.phpt
new file mode 100644
index 000000000000..82f622575917
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/form_query_success_special_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - form query - special code points in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode('"#<>&+,', Uri\WhatWg\UrlPercentEncodingMode::FormQuery));
+
+?>
+--EXPECT--
+string(21) "%22%23%3C%3E%26%2B%2C"
diff --git a/ext/uri/tests/whatwg/percent_encoding/form_query_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/form_query_success_special_not_in_set.phpt
new file mode 100644
index 000000000000..590f6dd31023
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/form_query_success_special_not_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - form query - special code points not in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("*-._", Uri\WhatWg\UrlPercentEncodingMode::FormQuery));
+
+?>
+--EXPECT--
+string(4) "*-._"
diff --git a/ext/uri/tests/whatwg/percent_encoding/form_query_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/form_query_success_unicode.phpt
new file mode 100644
index 000000000000..bcb843ca67a6
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/form_query_success_unicode.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - form query - Unicode code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("föő", Uri\WhatWg\UrlPercentEncodingMode::FormQuery));
+
+?>
+--EXPECT--
+string(13) "f%C3%B6%C5%91"
diff --git a/ext/uri/tests/whatwg/percent_encoding/fragment_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/fragment_success_control.phpt
new file mode 100644
index 000000000000..c0992a9e260f
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/fragment_success_control.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - fragment - control code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("\x11", Uri\WhatWg\UrlPercentEncodingMode::Fragment));
+
+?>
+--EXPECT--
+string(3) "%11"
diff --git a/ext/uri/tests/whatwg/percent_encoding/fragment_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/fragment_success_percent.phpt
new file mode 100644
index 000000000000..12b2bada8fae
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/fragment_success_percent.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - fragment - percent sign code point
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("WHATWG%20url", Uri\WhatWg\UrlPercentEncodingMode::Username));
+
+?>
+--EXPECT--
+string(14) "WHATWG%2520url"
diff --git a/ext/uri/tests/whatwg/percent_encoding/fragment_success_special_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/fragment_success_special_in_set.phpt
new file mode 100644
index 000000000000..fe129d66895f
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/fragment_success_special_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - fragment - special code points in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode(' "<>`', Uri\WhatWg\UrlPercentEncodingMode::Fragment));
+
+?>
+--EXPECT--
+string(15) "%20%22%3C%3E%60"
diff --git a/ext/uri/tests/whatwg/percent_encoding/fragment_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/fragment_success_special_not_in_set.phpt
new file mode 100644
index 000000000000..255c5173ef83
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/fragment_success_special_not_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - fragment - special code points not in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("('$+)", Uri\WhatWg\UrlPercentEncodingMode::Fragment));
+
+?>
+--EXPECT--
+string(5) "('$+)"
diff --git a/ext/uri/tests/whatwg/percent_encoding/fragment_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/fragment_success_unicode.phpt
new file mode 100644
index 000000000000..0a14a113f386
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/fragment_success_unicode.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - fragment - Unicode code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("föő", Uri\WhatWg\UrlPercentEncodingMode::Fragment));
+
+?>
+--EXPECT--
+string(13) "f%C3%B6%C5%91"
diff --git a/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_control.phpt
new file mode 100644
index 000000000000..7adc5b963787
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_control.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - opaque host - control code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("\x11", Uri\WhatWg\UrlPercentEncodingMode::OpaqueHost));
+
+?>
+--EXPECT--
+string(3) "%11"
diff --git a/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_percent.phpt
new file mode 100644
index 000000000000..f6c6d12e4c7b
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_percent.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - opaque host - percent sign code point
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("WHATWG%20url", Uri\WhatWg\UrlPercentEncodingMode::OpaqueHost));
+
+?>
+--EXPECT--
+string(14) "WHATWG%2520url"
diff --git a/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_special_not_in_set.phpt
new file mode 100644
index 000000000000..a170079625f9
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_special_not_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - opaque host - special code points not in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode(" \"#<>?^`{}@'$+,", Uri\WhatWg\UrlPercentEncodingMode::OpaqueHost));
+
+?>
+--EXPECT--
+string(15) " "#<>?^`{}@'$+,"
diff --git a/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_unicode.phpt
new file mode 100644
index 000000000000..b51ad97b54dc
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/opaque_host_success_unicode.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - opaque host - Unicode code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("föő", Uri\WhatWg\UrlPercentEncodingMode::OpaquePath));
+
+?>
+--EXPECT--
+string(13) "f%C3%B6%C5%91"
diff --git a/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_control.phpt
new file mode 100644
index 000000000000..adf1bee19b6c
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_control.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - opaque path - control code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("\x11", Uri\WhatWg\UrlPercentEncodingMode::OpaquePath));
+
+?>
+--EXPECT--
+string(3) "%11"
diff --git a/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_percent.phpt
new file mode 100644
index 000000000000..7fa6a7c3973f
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_percent.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - opaque path - percent sign code point
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("WHATWG%20url", Uri\WhatWg\UrlPercentEncodingMode::OpaquePath));
+
+?>
+--EXPECT--
+string(14) "WHATWG%2520url"
diff --git a/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_special_not_in_set.phpt
new file mode 100644
index 000000000000..fa9075856a5c
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_special_not_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - opaque path - special code points not in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode(" \"#<>?^`{}@'$+,", Uri\WhatWg\UrlPercentEncodingMode::OpaquePath));
+
+?>
+--EXPECT--
+string(15) " "#<>?^`{}@'$+,"
diff --git a/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_unicode.phpt
new file mode 100644
index 000000000000..3fbae7c2c156
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/opaque_path_success_unicode.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - opaque path - Unicode code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("föő", Uri\WhatWg\UrlPercentEncodingMode::OpaquePath));
+
+?>
+--EXPECT--
+string(13) "f%C3%B6%C5%91"
diff --git a/ext/uri/tests/whatwg/percent_encoding/password_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/password_success_control.phpt
new file mode 100644
index 000000000000..57ff019878df
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/password_success_control.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - password - control code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("\x11", Uri\WhatWg\UrlPercentEncodingMode::Password));
+
+?>
+--EXPECT--
+string(3) "%11"
diff --git a/ext/uri/tests/whatwg/percent_encoding/password_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/password_success_percent.phpt
new file mode 100644
index 000000000000..536a2702a428
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/password_success_percent.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - password - percent sign code point
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("WHATWG%20url", Uri\WhatWg\UrlPercentEncodingMode::Password));
+
+?>
+--EXPECT--
+string(14) "WHATWG%2520url"
diff --git a/ext/uri/tests/whatwg/percent_encoding/password_success_special_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/password_success_special_in_set.phpt
new file mode 100644
index 000000000000..f4041e059e88
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/password_success_special_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - password - special code points in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("[#=? `]", Uri\WhatWg\UrlPercentEncodingMode::Password));
+
+?>
+--EXPECT--
+string(21) "%5B%23%3D%3F%20%60%5D"
diff --git a/ext/uri/tests/whatwg/percent_encoding/password_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/password_success_special_not_in_set.phpt
new file mode 100644
index 000000000000..a4a151c44487
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/password_success_special_not_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - password - special code points not in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("('$+)", Uri\WhatWg\UrlPercentEncodingMode::Password));
+
+?>
+--EXPECT--
+string(5) "('$+)"
diff --git a/ext/uri/tests/whatwg/percent_encoding/password_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/password_success_unicode.phpt
new file mode 100644
index 000000000000..d2d0247f98f3
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/password_success_unicode.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - password - Unicode code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("föő", Uri\WhatWg\UrlPercentEncodingMode::Password));
+
+?>
+--EXPECT--
+string(13) "f%C3%B6%C5%91"
diff --git a/ext/uri/tests/whatwg/percent_encoding/path_segment_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_control.phpt
new file mode 100644
index 000000000000..0c9bfcbb0b1e
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_control.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - path segment - control code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("\x11", Uri\WhatWg\UrlPercentEncodingMode::PathSegment));
+
+?>
+--EXPECT--
+string(3) "%11"
diff --git a/ext/uri/tests/whatwg/percent_encoding/path_segment_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_percent.phpt
new file mode 100644
index 000000000000..258cca36a2ee
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_percent.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - path segment - percent sign code point
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("WHATWG%20url", Uri\WhatWg\UrlPercentEncodingMode::PathSegment));
+
+?>
+--EXPECT--
+string(14) "WHATWG%2520url"
diff --git a/ext/uri/tests/whatwg/percent_encoding/path_segment_success_special_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_special_in_set.phpt
new file mode 100644
index 000000000000..03f43a8a0405
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_special_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - path segment - special code points in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode(' "#<>?^`{}/', Uri\WhatWg\UrlPercentEncodingMode::PathSegment));
+
+?>
+--EXPECT--
+string(33) "%20%22%23%3C%3E%3F%5E%60%7B%7D%2F"
diff --git a/ext/uri/tests/whatwg/percent_encoding/path_segment_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_special_not_in_set.phpt
new file mode 100644
index 000000000000..50790f66eb6c
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_special_not_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - path segment - special code points not in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("@'$+,", Uri\WhatWg\UrlPercentEncodingMode::PathSegment));
+
+?>
+--EXPECT--
+string(5) "@'$+,"
diff --git a/ext/uri/tests/whatwg/percent_encoding/path_segment_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_unicode.phpt
new file mode 100644
index 000000000000..218d120ae9ea
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/path_segment_success_unicode.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - path segment - Unicode code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("föő", Uri\WhatWg\UrlPercentEncodingMode::PathSegment));
+
+?>
+--EXPECT--
+string(13) "f%C3%B6%C5%91"
diff --git a/ext/uri/tests/whatwg/percent_encoding/path_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/path_success_control.phpt
new file mode 100644
index 000000000000..fa56acc3a3c5
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/path_success_control.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - path - control code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("\x11", Uri\WhatWg\UrlPercentEncodingMode::Path));
+
+?>
+--EXPECT--
+string(3) "%11"
diff --git a/ext/uri/tests/whatwg/percent_encoding/path_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/path_success_percent.phpt
new file mode 100644
index 000000000000..013c9e770740
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/path_success_percent.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - path - percent sign code point
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("WHATWG%20url", Uri\WhatWg\UrlPercentEncodingMode::Path));
+
+?>
+--EXPECT--
+string(14) "WHATWG%2520url"
diff --git a/ext/uri/tests/whatwg/percent_encoding/path_success_special_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/path_success_special_in_set.phpt
new file mode 100644
index 000000000000..785c82902620
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/path_success_special_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - path - special code points in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode(' "#<>?^`{}', Uri\WhatWg\UrlPercentEncodingMode::Path));
+
+?>
+--EXPECT--
+string(30) "%20%22%23%3C%3E%3F%5E%60%7B%7D"
diff --git a/ext/uri/tests/whatwg/percent_encoding/path_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/path_success_special_not_in_set.phpt
new file mode 100644
index 000000000000..17f6bf915407
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/path_success_special_not_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - path - special code points not in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("@'$+,", Uri\WhatWg\UrlPercentEncodingMode::Path));
+
+?>
+--EXPECT--
+string(5) "@'$+,"
diff --git a/ext/uri/tests/whatwg/percent_encoding/path_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/path_success_unicode.phpt
new file mode 100644
index 000000000000..edfa72359251
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/path_success_unicode.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - path - Unicode code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("föő", Uri\WhatWg\UrlPercentEncodingMode::Path));
+
+?>
+--EXPECT--
+string(13) "f%C3%B6%C5%91"
diff --git a/ext/uri/tests/whatwg/percent_encoding/query_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/query_success_control.phpt
new file mode 100644
index 000000000000..2502cc7a662c
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/query_success_control.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - query - control code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("\x11", Uri\WhatWg\UrlPercentEncodingMode::Query));
+
+?>
+--EXPECT--
+string(3) "%11"
diff --git a/ext/uri/tests/whatwg/percent_encoding/query_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/query_success_percent.phpt
new file mode 100644
index 000000000000..e9a4187b963d
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/query_success_percent.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - query - percent sign code point
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("WHATWG%20url", Uri\WhatWg\UrlPercentEncodingMode::Query));
+
+?>
+--EXPECT--
+string(14) "WHATWG%2520url"
diff --git a/ext/uri/tests/whatwg/percent_encoding/query_success_space.phpt b/ext/uri/tests/whatwg/percent_encoding/query_success_space.phpt
new file mode 100644
index 000000000000..b9a491018580
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/query_success_space.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - query - space code point
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("WHATWG url", Uri\WhatWg\UrlPercentEncodingMode::Query));
+
+?>
+--EXPECT--
+string(12) "WHATWG%20url"
diff --git a/ext/uri/tests/whatwg/percent_encoding/query_success_special_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/query_success_special_in_set.phpt
new file mode 100644
index 000000000000..a5fab6b7cd08
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/query_success_special_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - query - special code points in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode(' "#<>', Uri\WhatWg\UrlPercentEncodingMode::Query));
+
+?>
+--EXPECT--
+string(15) "%20%22%23%3C%3E"
diff --git a/ext/uri/tests/whatwg/percent_encoding/query_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/query_success_special_not_in_set.phpt
new file mode 100644
index 000000000000..c8e4123d93ff
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/query_success_special_not_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - query - special code points not in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("[@?&]'", Uri\WhatWg\UrlPercentEncodingMode::Query));
+
+?>
+--EXPECT--
+string(6) "[@?&]'"
diff --git a/ext/uri/tests/whatwg/percent_encoding/query_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/query_success_unicode.phpt
new file mode 100644
index 000000000000..f5b3988f08fe
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/query_success_unicode.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - query - Unicode code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("föő", Uri\WhatWg\UrlPercentEncodingMode::Query));
+
+?>
+--EXPECT--
+string(13) "f%C3%B6%C5%91"
diff --git a/ext/uri/tests/whatwg/percent_encoding/special_query_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/special_query_success_control.phpt
new file mode 100644
index 000000000000..f6d76153043f
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/special_query_success_control.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - special query - control code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("\x11", Uri\WhatWg\UrlPercentEncodingMode::SpecialQuery));
+
+?>
+--EXPECT--
+string(3) "%11"
diff --git a/ext/uri/tests/whatwg/percent_encoding/special_query_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/special_query_success_percent.phpt
new file mode 100644
index 000000000000..a1683a1ef77b
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/special_query_success_percent.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - special query - percent sign code point
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("WHATWG%20url", Uri\WhatWg\UrlPercentEncodingMode::SpecialQuery));
+
+?>
+--EXPECT--
+string(14) "WHATWG%2520url"
diff --git a/ext/uri/tests/whatwg/percent_encoding/special_query_success_space.phpt b/ext/uri/tests/whatwg/percent_encoding/special_query_success_space.phpt
new file mode 100644
index 000000000000..049a5da1d4ed
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/special_query_success_space.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - special query - space code point
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("WHATWG url", Uri\WhatWg\UrlPercentEncodingMode::SpecialQuery));
+
+?>
+--EXPECT--
+string(12) "WHATWG%20url"
diff --git a/ext/uri/tests/whatwg/percent_encoding/special_query_success_special_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/special_query_success_special_in_set.phpt
new file mode 100644
index 000000000000..e84df8465c9f
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/special_query_success_special_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - special query - special code points in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode(" '\"#<>", Uri\WhatWg\UrlPercentEncodingMode::SpecialQuery));
+
+?>
+--EXPECT--
+string(18) "%20%27%22%23%3C%3E"
diff --git a/ext/uri/tests/whatwg/percent_encoding/special_query_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/special_query_success_special_not_in_set.phpt
new file mode 100644
index 000000000000..0ee677b31fe1
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/special_query_success_special_not_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - special query - special code points not in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("[@?&]", Uri\WhatWg\UrlPercentEncodingMode::SpecialQuery));
+
+?>
+--EXPECT--
+string(5) "[@?&]"
diff --git a/ext/uri/tests/whatwg/percent_encoding/special_query_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/special_query_success_unicode.phpt
new file mode 100644
index 000000000000..b583d8de2ecc
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/special_query_success_unicode.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - special query - Unicode code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("föő", Uri\WhatWg\UrlPercentEncodingMode::SpecialQuery));
+
+?>
+--EXPECT--
+string(13) "f%C3%B6%C5%91"
diff --git a/ext/uri/tests/whatwg/percent_encoding/username_success_control.phpt b/ext/uri/tests/whatwg/percent_encoding/username_success_control.phpt
new file mode 100644
index 000000000000..5a64393065c9
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/username_success_control.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - username - control code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("\x11", Uri\WhatWg\UrlPercentEncodingMode::Username));
+
+?>
+--EXPECT--
+string(3) "%11"
diff --git a/ext/uri/tests/whatwg/percent_encoding/username_success_percent.phpt b/ext/uri/tests/whatwg/percent_encoding/username_success_percent.phpt
new file mode 100644
index 000000000000..9ea2831efa46
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/username_success_percent.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - username - percent sign code point
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("WHATWG%20url", Uri\WhatWg\UrlPercentEncodingMode::Username));
+
+?>
+--EXPECT--
+string(14) "WHATWG%2520url"
diff --git a/ext/uri/tests/whatwg/percent_encoding/username_success_special_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/username_success_special_in_set.phpt
new file mode 100644
index 000000000000..070250ef3630
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/username_success_special_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - username - special code points in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("[#=? `]", Uri\WhatWg\UrlPercentEncodingMode::Username));
+
+?>
+--EXPECT--
+string(21) "%5B%23%3D%3F%20%60%5D"
diff --git a/ext/uri/tests/whatwg/percent_encoding/username_success_special_not_in_set.phpt b/ext/uri/tests/whatwg/percent_encoding/username_success_special_not_in_set.phpt
new file mode 100644
index 000000000000..8626b96ab9b3
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/username_success_special_not_in_set.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - username - special code points not in the percent-encode set
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("('$+)", Uri\WhatWg\UrlPercentEncodingMode::Username));
+
+?>
+--EXPECT--
+string(5) "('$+)"
diff --git a/ext/uri/tests/whatwg/percent_encoding/username_success_unicode.phpt b/ext/uri/tests/whatwg/percent_encoding/username_success_unicode.phpt
new file mode 100644
index 000000000000..d85b0712c1f0
--- /dev/null
+++ b/ext/uri/tests/whatwg/percent_encoding/username_success_unicode.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Test Uri\WhatWg\url_percent_encode() - username - Unicode code points
+--FILE--
+<?php
+
+var_dump(Uri\WhatWg\url_percent_encode("föő", Uri\WhatWg\UrlPercentEncodingMode::Username));
+
+?>
+--EXPECT--
+string(13) "f%C3%B6%C5%91"
diff --git a/ext/uri/uri_parser_whatwg.c b/ext/uri/uri_parser_whatwg.c
index b7a74fc1e231..4f308ba224bd 100644
--- a/ext/uri/uri_parser_whatwg.c
+++ b/ext/uri/uri_parser_whatwg.c
@@ -25,6 +25,7 @@
 ZEND_TLS lexbor_mraw_t lexbor_mraw = {0};
 ZEND_TLS lxb_url_parser_t lexbor_parser = {0};
 ZEND_TLS lxb_unicode_idna_t lexbor_idna = {0};
+ZEND_TLS uint8_t lexbor_custom_url_map[256] = {0};
 
 static const size_t lexbor_mraw_byte_size = 8192;
 
@@ -549,9 +550,7 @@ static zend_result php_uri_parser_whatwg_fragment_write(void *uri, const zval *v
 
 PHP_RINIT_FUNCTION(uri_parser_whatwg)
 {
-	lxb_status_t status;
-	
-	status = lexbor_mraw_init(&lexbor_mraw, lexbor_mraw_byte_size);
+	lxb_status_t status = lexbor_mraw_init(&lexbor_mraw, lexbor_mraw_byte_size);
 	if (status != LXB_STATUS_OK) {
 		goto fail;
 	}
@@ -566,6 +565,9 @@ PHP_RINIT_FUNCTION(uri_parser_whatwg)
 		goto fail;
 	}
 
+	memcpy(lexbor_custom_url_map, lxb_url_get_percent_encoding_map(), sizeof(lexbor_custom_url_map));
+	lexbor_custom_url_map['%'] = -1; /* % is percent-encoded */
+
 	return SUCCESS;
 
  fail:
@@ -653,6 +655,79 @@ static zend_string *php_uri_parser_whatwg_to_string(void *uri, const php_uri_rec
 	return smart_str_extract(&uri_str);
 }
 
+static zend_string *php_uri_parser_whatwg_percent_encode_component(const char *str, const size_t str_length, const lxb_url_map_type_t map, const bool space_as_plus)
+{
+	lexbor_str_t lexbor_str = {0};
+
+	const lexbor_status_t status = lxb_url_percent_encode_utf_8(
+		(lxb_char_t *) str, str_length, &lexbor_str, lexbor_parser.mraw, lexbor_custom_url_map, map, space_as_plus
+	);
+
+	if (status != LXB_STATUS_OK) {
+		lexbor_str_destroy(&lexbor_str, lexbor_parser.mraw, false);
+		return NULL;
+	}
+
+	zend_string *result = zend_string_init((const char *) lexbor_str.data, lexbor_str.length, false);
+
+	lexbor_str_destroy(&lexbor_str, lexbor_parser.mraw, false);
+
+	return result;
+}
+
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_userinfo_component(const char *str, const size_t str_length)
+{
+	return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_USERINFO, false);
+}
+
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_opaque_host_component(const char *str, const size_t str_length)
+{
+	return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_C0, false);
+}
+
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_path_component(const char *str, const size_t str_length)
+{
+	return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_PATH, false);
+}
+
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_opaque_path_component(const char *str, const size_t str_length)
+{
+	return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_C0, false);
+}
+
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_path_segment_component(const char *str, const size_t str_length)
+{
+	ZEND_ASSERT((lexbor_custom_url_map['/'] & LXB_URL_MAP_PATH) == 0);
+
+	lexbor_custom_url_map['/'] |= LXB_URL_MAP_PATH;
+
+	zend_string *result = php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_PATH, false);
+
+	lexbor_custom_url_map['/'] &= ~LXB_URL_MAP_PATH;
+
+	return result;
+}
+
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_query_component(const char *str, const size_t str_length)
+{
+	return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_QUERY, false);
+}
+
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_special_query_component(const char *str, const size_t str_length)
+{
+	return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_SPECIAL_QUERY, false);
+}
+
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_form_query_component(const char *str, const size_t str_length)
+{
+	return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_X_WWW_FORM, true);
+}
+
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_fragment_component(const char *str, const size_t str_length)
+{
+	return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_FRAGMENT, false);
+}
+
 static void php_uri_parser_whatwg_destroy(void *uri)
 {
 	lxb_url_t *lexbor_uri = uri;
diff --git a/ext/uri/uri_parser_whatwg.h b/ext/uri/uri_parser_whatwg.h
index 0a03c8e76a93..ee387a6af95d 100644
--- a/ext/uri/uri_parser_whatwg.h
+++ b/ext/uri/uri_parser_whatwg.h
@@ -30,14 +30,24 @@ ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_scheme(const z
 ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_host(const zend_string *host);
 ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_validate_port(zend_long port);
 
-PHP_RINIT_FUNCTION(uri_parser_whatwg);
-
 ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_whatwg_build_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 *errors_zv
 );
 
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_userinfo_component(const char *str, size_t str_length);
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_opaque_host_component(const char *str, size_t str_length);
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_path_component(const char *str, size_t str_length);
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_opaque_path_component(const char *str, size_t str_length);
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_path_segment_component(const char *str, size_t str_length);
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_query_component(const char *str, size_t str_length);
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_special_query_component(const char *str, size_t str_length);
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_form_query_component(const char *str, size_t str_length);
+ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_percent_encode_fragment_component(const char *str, size_t str_length);
+
+PHP_RINIT_FUNCTION(uri_parser_whatwg);
+
 ZEND_MODULE_POST_ZEND_DEACTIVATE_D(uri_parser_whatwg);
 
 #endif
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.