From 4f4f210f0949772e38c9b72bbbf7c3593e763e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Tue, 16 Jun 2026 13:35:35 +0200 Subject: [PATCH] Implement Qquery Parameter Manipulation Support RFC RFC: https://wiki.php.net/rfc/query_params --- ext/lexbor/lexbor/url/url.c | 6 +- ext/lexbor/lexbor/url/url.h | 4 + ext/uri/config.m4 | 3 +- ext/uri/php_uri.c | 53 +- ext/uri/php_uri.stub.php | 79 + ext/uri/php_uri_arginfo.h | 256 ++- ext/uri/php_uri_common.h | 32 + ext/uri/php_uri_decl.h | 8 +- ext/uri/php_uri_query.c | 1560 +++++++++++++++++ ext/uri/php_uri_query.h | 42 + .../append_error_enum_unbacked.phpt | 20 + .../query_params/append_error_object.phpt | 16 + .../query_params/append_error_resource.phpt | 16 + .../query_params/append_success_array.phpt | 28 + .../append_success_array_empty.phpt | 33 + ...append_success_array_multi_level_list.phpt | 20 + .../append_success_array_multi_level_map.phpt | 61 + .../query_params/append_success_basic.phpt | 37 + .../query_params/append_success_bool.phpt | 45 + .../append_success_enum_backed.phpt | 39 + .../query_params/append_success_existing.phpt | 37 + .../query_params/append_success_int.phpt | 38 + .../query_params/append_success_list.phpt | 47 + .../query_params/append_success_null.phpt | 37 + .../query_params/construct_success_basic.phpt | 31 + .../construct_success_options.phpt | 31 + .../query_params/delete_success_existing.phpt | 17 + .../query_params/delete_success_multiple.phpt | 23 + .../delete_success_non_existent.phpt | 30 + ...ue_success_existing_key_missing_value.phpt | 37 + ...lete_value_success_existing_key_value.phpt | 30 + .../delete_value_success_int.phpt | 30 + .../delete_value_success_missing_key.phpt | 37 + .../delete_value_success_null.phpt | 30 + .../query_params/get_iterator_basic.phpt | 31 + .../query_params/has_success_existing.phpt | 12 + .../has_success_non_existent.phpt | 12 + ...ue_success_existing_key_missing_value.phpt | 12 + .../has_value_success_existing_key_value.phpt | 12 + .../query_params/has_value_success_int.phpt | 12 + .../has_value_success_missing_key.phpt | 12 + .../parse_rfc3986_success_basic.phpt | 29 + .../parse_rfc3986_success_empty.phpt | 15 + .../parse_rfc3986_success_empty_key.phpt | 29 + .../parse_rfc3986_success_empty_pair.phpt | 29 + .../parse_rfc3986_success_empty_value.phpt | 22 + .../parse_rfc3986_success_null.phpt | 29 + .../parse_rfc3986_success_reserved.phpt | 29 + .../parse_rfc3986_success_space.phpt | 29 + .../to_array_success_multi_dimension.phpt | 23 + ...y_success_multiple_keys_with_brackets.phpt | 20 + ...uccess_multiple_keys_without_brackets.phpt | 20 + ...o_array_success_one_key_with_brackets.phpt | 18 + ...rray_success_one_key_without_brackets.phpt | 15 + ext/uri/uri_parser_rfc3986.c | 84 + ext/uri/uri_parser_rfc3986.h | 3 + ext/uri/uri_parser_whatwg.c | 16 + ext/uri/uri_parser_whatwg.h | 2 + ext/uri/uriparser/include/uriparser/Uri.h | 60 + ext/uri/uriparser/src/UriEscape.c | 52 +- 60 files changed, 3386 insertions(+), 54 deletions(-) create mode 100644 ext/uri/php_uri_query.c create mode 100644 ext/uri/php_uri_query.h create mode 100644 ext/uri/tests/query_params/append_error_enum_unbacked.phpt create mode 100644 ext/uri/tests/query_params/append_error_object.phpt create mode 100644 ext/uri/tests/query_params/append_error_resource.phpt create mode 100644 ext/uri/tests/query_params/append_success_array.phpt create mode 100644 ext/uri/tests/query_params/append_success_array_empty.phpt create mode 100644 ext/uri/tests/query_params/append_success_array_multi_level_list.phpt create mode 100644 ext/uri/tests/query_params/append_success_array_multi_level_map.phpt create mode 100644 ext/uri/tests/query_params/append_success_basic.phpt create mode 100644 ext/uri/tests/query_params/append_success_bool.phpt create mode 100644 ext/uri/tests/query_params/append_success_enum_backed.phpt create mode 100644 ext/uri/tests/query_params/append_success_existing.phpt create mode 100644 ext/uri/tests/query_params/append_success_int.phpt create mode 100644 ext/uri/tests/query_params/append_success_list.phpt create mode 100644 ext/uri/tests/query_params/append_success_null.phpt create mode 100644 ext/uri/tests/query_params/construct_success_basic.phpt create mode 100644 ext/uri/tests/query_params/construct_success_options.phpt create mode 100644 ext/uri/tests/query_params/delete_success_existing.phpt create mode 100644 ext/uri/tests/query_params/delete_success_multiple.phpt create mode 100644 ext/uri/tests/query_params/delete_success_non_existent.phpt create mode 100644 ext/uri/tests/query_params/delete_value_success_existing_key_missing_value.phpt create mode 100644 ext/uri/tests/query_params/delete_value_success_existing_key_value.phpt create mode 100644 ext/uri/tests/query_params/delete_value_success_int.phpt create mode 100644 ext/uri/tests/query_params/delete_value_success_missing_key.phpt create mode 100644 ext/uri/tests/query_params/delete_value_success_null.phpt create mode 100644 ext/uri/tests/query_params/get_iterator_basic.phpt create mode 100644 ext/uri/tests/query_params/has_success_existing.phpt create mode 100644 ext/uri/tests/query_params/has_success_non_existent.phpt create mode 100644 ext/uri/tests/query_params/has_value_success_existing_key_missing_value.phpt create mode 100644 ext/uri/tests/query_params/has_value_success_existing_key_value.phpt create mode 100644 ext/uri/tests/query_params/has_value_success_int.phpt create mode 100644 ext/uri/tests/query_params/has_value_success_missing_key.phpt create mode 100644 ext/uri/tests/query_params/parse_rfc3986_success_basic.phpt create mode 100644 ext/uri/tests/query_params/parse_rfc3986_success_empty.phpt create mode 100644 ext/uri/tests/query_params/parse_rfc3986_success_empty_key.phpt create mode 100644 ext/uri/tests/query_params/parse_rfc3986_success_empty_pair.phpt create mode 100644 ext/uri/tests/query_params/parse_rfc3986_success_empty_value.phpt create mode 100644 ext/uri/tests/query_params/parse_rfc3986_success_null.phpt create mode 100644 ext/uri/tests/query_params/parse_rfc3986_success_reserved.phpt create mode 100644 ext/uri/tests/query_params/parse_rfc3986_success_space.phpt create mode 100644 ext/uri/tests/query_params/to_array_success_multi_dimension.phpt create mode 100644 ext/uri/tests/query_params/to_array_success_multiple_keys_with_brackets.phpt create mode 100644 ext/uri/tests/query_params/to_array_success_multiple_keys_without_brackets.phpt create mode 100644 ext/uri/tests/query_params/to_array_success_one_key_with_brackets.phpt create mode 100644 ext/uri/tests/query_params/to_array_success_one_key_without_brackets.phpt diff --git a/ext/lexbor/lexbor/url/url.c b/ext/lexbor/lexbor/url/url.c index 1fd044bbf950..9c8b764c4c1e 100644 --- a/ext/lexbor/lexbor/url/url.c +++ b/ext/lexbor/lexbor/url/url.c @@ -613,10 +613,6 @@ lxb_url_percent_decode(const lxb_char_t *data, const lxb_char_t *end, lexbor_str_t *str, lexbor_mraw_t *mraw, lxb_url_host_opt_t *opt); -static lxb_status_t -lxb_url_percent_decode_plus(const lxb_char_t *data, const lxb_char_t *end, - lexbor_str_t *str, lexbor_mraw_t *mraw); - static const lxb_char_t * lxb_url_path_part_by_index(const lxb_url_t *url, size_t index, size_t *out_length); @@ -4071,7 +4067,7 @@ lxb_url_percent_decode(const lxb_char_t *data, const lxb_char_t *end, return LXB_STATUS_OK; } -static lxb_status_t +lxb_status_t lxb_url_percent_decode_plus(const lxb_char_t *data, const lxb_char_t *end, lexbor_str_t *str, lexbor_mraw_t *mraw) { diff --git a/ext/lexbor/lexbor/url/url.h b/ext/lexbor/lexbor/url/url.h index 6cc6f1081c8a..82470fe1559b 100644 --- a/ext/lexbor/lexbor/url/url.h +++ b/ext/lexbor/lexbor/url/url.h @@ -854,6 +854,10 @@ lxb_url_get(lxb_url_parser_t *parser) return parser->url; } +lxb_status_t +lxb_url_percent_decode_plus(const lxb_char_t *data, const lxb_char_t *end, + lexbor_str_t *str, lexbor_mraw_t *mraw); + #ifdef __cplusplus } /* extern "C" */ diff --git a/ext/uri/config.m4 b/ext/uri/config.m4 index a518cf84b3bd..bce573078dab 100644 --- a/ext/uri/config.m4 +++ b/ext/uri/config.m4 @@ -12,6 +12,7 @@ PHP_INSTALL_HEADERS([ext/uri], m4_normalize([ php_uri.h php_uri_common.h php_uri_decl.h + php_uri_query.h uri_parser_rfc3986.h uri_parser_whatwg.h uri_parser_php_parse_url.h @@ -39,7 +40,7 @@ else PHP_EVAL_INCLINE([$LIBURIPARSER_CFLAGS]) fi -PHP_NEW_EXTENSION(uri, [php_uri.c php_uri_common.c uri_parser_rfc3986.c uri_parser_whatwg.c uri_parser_php_parse_url.c $URIPARSER_SOURCES], [no],,[$URI_CFLAGS]) +PHP_NEW_EXTENSION(uri, [php_uri.c php_uri_common.c php_uri_query.c uri_parser_rfc3986.c uri_parser_whatwg.c uri_parser_php_parse_url.c $URIPARSER_SOURCES], [no],,[$URI_CFLAGS]) PHP_ADD_EXTENSION_DEP(uri, lexbor) if test "$PHP_EXTERNAL_URIPARSER" = "no"; then diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c index 74a559fd591c..283750d95d05 100644 --- a/ext/uri/php_uri.c +++ b/ext/uri/php_uri.c @@ -24,6 +24,7 @@ #include "ext/standard/info.h" #include "php_uri.h" +#include "php_uri_query.h" #include "uri_parser_whatwg.h" #include "uri_parser_rfc3986.h" #include "uri_parser_php_parse_url.h" @@ -39,6 +40,8 @@ zend_class_entry *php_uri_ce_comparison_mode; zend_class_entry *php_uri_ce_exception; zend_class_entry *php_uri_ce_error; zend_class_entry *php_uri_ce_invalid_uri_exception; +zend_class_entry *php_uri_ce_query_params; +zend_class_entry *php_uri_ce_query_param_options; zend_class_entry *php_uri_ce_whatwg_url_host_type; zend_class_entry *php_uri_ce_whatwg_invalid_url_exception; zend_class_entry *php_uri_ce_whatwg_url_validation_error_type; @@ -46,6 +49,8 @@ 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; +static zend_object_handlers object_handlers_query_params; +static zend_object_handlers object_handlers_query_param_options; typedef zend_result (*php_uri_component_validator_string)(const zend_string *component); typedef zend_result (*php_uri_component_validator_long)(zend_long component); @@ -1312,6 +1317,19 @@ PHPAPI zend_result php_uri_parser_register(const php_uri_parser *uri_parser) return result; } +static PHP_MINFO_FUNCTION(uri) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "URI support", "active"); +#ifdef URI_STATIC_BUILD + php_info_print_table_row(2, "uriparser bundled version", URI_VER_ANSI); +#else + php_info_print_table_row(2, "uriparser compiled version", URI_VER_ANSI); + php_info_print_table_row(2, "uriparser loaded version", uriBaseRuntimeVersionA()); +#endif + php_info_print_table_end(); +} + static PHP_MINIT_FUNCTION(uri) { php_uri_ce_rfc3986_uri_builder = register_class_Uri_Rfc3986_UriBuilder(); @@ -1339,6 +1357,28 @@ static PHP_MINIT_FUNCTION(uri) php_uri_ce_exception = register_class_Uri_UriException(zend_ce_exception); php_uri_ce_error = register_class_Uri_UriError(zend_ce_error); php_uri_ce_invalid_uri_exception = register_class_Uri_InvalidUriException(php_uri_ce_exception); + + php_uri_ce_query_params = register_class_Uri_QueryParams(zend_ce_aggregate, zend_ce_countable); + php_uri_ce_query_params->create_object = php_uri_object_query_params_create_object; + php_uri_ce_query_params->get_iterator = php_uri_object_query_params_get_iterator; + php_uri_ce_query_params->default_object_handlers = &object_handlers_query_params; + memcpy(&object_handlers_query_params, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + object_handlers_query_params.offset = offsetof(php_uri_query_params_object, std); + object_handlers_query_params.free_obj = php_uri_query_params_object_handler_free; + object_handlers_query_params.clone_obj = php_uri_query_params_object_handler_clone; + + php_uri_ce_query_param_options = register_class_Uri_QueryParamOptions(); + php_uri_ce_query_param_options->create_object = php_uri_object_create_query_param_options; + memcpy(&object_handlers_query_param_options, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + object_handlers_query_param_options.offset = offsetof(php_uri_query_param_options_object, std); + object_handlers_query_param_options.free_obj = php_uri_query_param_options_object_handler_free; + object_handlers_query_param_options.clone_obj = php_uri_query_param_options_object_handler_clone; + object_handlers_query_param_options.has_property = php_uri_query_param_options_object_handler_has_property; + object_handlers_query_param_options.read_property = php_uri_query_param_options_object_handler_read_property; + object_handlers_query_param_options.write_property = php_uri_query_param_options_object_handler_write_property; + object_handlers_query_param_options.unset_property = php_uri_query_param_options_object_handler_unset_property; + object_handlers_query_param_options.get_property_ptr_ptr = php_uri_query_param_options_object_handler_get_property_ptr_ptr; + php_uri_ce_whatwg_invalid_url_exception = register_class_Uri_WhatWg_InvalidUrlException(php_uri_ce_invalid_uri_exception); php_uri_ce_whatwg_url_host_type = register_class_Uri_WhatWg_UrlHostType(); php_uri_ce_whatwg_url_validation_error = register_class_Uri_WhatWg_UrlValidationError(); @@ -1361,19 +1401,6 @@ static PHP_MINIT_FUNCTION(uri) return SUCCESS; } -static PHP_MINFO_FUNCTION(uri) -{ - php_info_print_table_start(); - php_info_print_table_row(2, "URI support", "active"); -#ifdef URI_STATIC_BUILD - php_info_print_table_row(2, "uriparser bundled version", URI_VER_ANSI); -#else - php_info_print_table_row(2, "uriparser compiled version", URI_VER_ANSI); - php_info_print_table_row(2, "uriparser loaded version", uriBaseRuntimeVersionA()); -#endif - php_info_print_table_end(); -} - static PHP_MSHUTDOWN_FUNCTION(uri) { zend_hash_destroy(&uri_parsers); diff --git a/ext/uri/php_uri.stub.php b/ext/uri/php_uri.stub.php index d00ef45cb86e..be4c1674cfc0 100644 --- a/ext/uri/php_uri.stub.php +++ b/ext/uri/php_uri.stub.php @@ -26,6 +26,85 @@ enum UriComparisonMode case IncludeFragment; case ExcludeFragment; } + + final readonly class QueryParamOptions + { + /** @virtual */ + public int $parsingMaxQueryStringLength; + /** @virtual */ + public int $parsingMaxParamCount; + /** @virtual */ + public string $trueValue; + /** @virtual */ + public string $falseValue; + /** @virtual */ + public bool $useNullAsEmptyString; + + public function __construct( + int $parsingMaxQueryStringLength = 10000, + int $parsingMaxParamCount = 1000, + string $trueValue = "0", + string $falseValue = "0", + bool $useNullAsEmptyString = false + ) {} + + public function __debugInfo(): array {} + } + + final readonly class QueryParams implements \IteratorAggregate, \Countable + { + private QueryParamOptions $options; + + public static function parseRfc1866(string $queryString, ?QueryParamOptions $options = null): \Uri\QueryParams {} + + public static function parseRfc3986(string $queryString, ?QueryParamOptions $options = null): ?\Uri\QueryParams {} + + public static function parseWhatWg(string $queryString, ?QueryParamOptions $options = null): \Uri\QueryParams {} + + public static function fromArray(array $queryParams, ?QueryParamOptions $options = null): \Uri\QueryParams {} + + public function __construct(?QueryParamOptions $options = null) {} + + public function append(string $name, mixed $value): static {} + + public function delete(string $name): static {} + + public function deleteValue(string $name, ?string $value): static {} + + public function has(string $name): bool {} + + public function hasValue(string $name, mixed $value): bool {} + + public function getFirst(string $name): ?string {} + + public function getLast(string $name): ?string {} + + public function getAll(string $name): array {} + + public function list(): array {} + + public function getIterator(): \Traversable {} + + public function count(): int {} + + public function set(string $name, mixed $value): static {} + + public function sort(): static {} + + public function toArray(): array {} + + public function toRfc1866String(): string {} + + public function toRfc3986String(): string {} + + public function toWhatWgString(): string {} + + public function __serialize(): array {} + + public function __unserialize(array $data): void {} + + public function __debugInfo(): array {} + } } namespace Uri\Rfc3986 { diff --git a/ext/uri/php_uri_arginfo.h b/ext/uri/php_uri_arginfo.h index e63e495526f5..2fc47c47f575 100644 --- a/ext/uri/php_uri_arginfo.h +++ b/ext/uri/php_uri_arginfo.h @@ -1,10 +1,104 @@ /* This is a generated file, edit php_uri.stub.php instead. - * Stub hash: 1a396b7a5b170b968d84a2c64a5bcf7d962653db + * Stub hash: 5c99026c91273c8094ebbfb6809141649fc13c60 * Has decl header: yes */ -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_reset, 0, 0, IS_STATIC, 0) +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Uri_QueryParamOptions___construct, 0, 0, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, parsingMaxQueryStringLength, IS_LONG, 0, "10000") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, parsingMaxParamCount, IS_LONG, 0, "1000") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, trueValue, IS_STRING, 0, "\"0\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, falseValue, IS_STRING, 0, "\"0\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, useNullAsEmptyString, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_QueryParamOptions___debugInfo, 0, 0, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Uri_QueryParams_parseRfc1866, 0, 1, Uri\\QueryParams, 0) + ZEND_ARG_TYPE_INFO(0, queryString, IS_STRING, 0) + ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, options, Uri\\QueryParamOptions, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Uri_QueryParams_parseRfc3986, 0, 1, Uri\\QueryParams, 1) + ZEND_ARG_TYPE_INFO(0, queryString, IS_STRING, 0) + ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, options, Uri\\QueryParamOptions, 1, "null") +ZEND_END_ARG_INFO() + +#define arginfo_class_Uri_QueryParams_parseWhatWg arginfo_class_Uri_QueryParams_parseRfc1866 + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Uri_QueryParams_fromArray, 0, 1, Uri\\QueryParams, 0) + ZEND_ARG_TYPE_INFO(0, queryParams, IS_ARRAY, 0) + ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, options, Uri\\QueryParamOptions, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Uri_QueryParams___construct, 0, 0, 0) + ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, options, Uri\\QueryParamOptions, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_QueryParams_append, 0, 2, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_QueryParams_delete, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_QueryParams_deleteValue, 0, 2, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_STRING, 1) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_QueryParams_has, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_QueryParams_hasValue, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_QueryParams_getFirst, 0, 1, IS_STRING, 1) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_Uri_QueryParams_getLast arginfo_class_Uri_QueryParams_getFirst + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_QueryParams_getAll, 0, 1, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_Uri_QueryParams_list arginfo_class_Uri_QueryParamOptions___debugInfo + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Uri_QueryParams_getIterator, 0, 0, Traversable, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_QueryParams_count, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_Uri_QueryParams_set arginfo_class_Uri_QueryParams_append + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_QueryParams_sort, 0, 0, IS_STATIC, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_Uri_QueryParams_toArray arginfo_class_Uri_QueryParamOptions___debugInfo + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_QueryParams_toRfc1866String, 0, 0, IS_STRING, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_Uri_QueryParams_toRfc3986String arginfo_class_Uri_QueryParams_toRfc1866String + +#define arginfo_class_Uri_QueryParams_toWhatWgString arginfo_class_Uri_QueryParams_toRfc1866String + +#define arginfo_class_Uri_QueryParams___serialize arginfo_class_Uri_QueryParamOptions___debugInfo + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_QueryParams___unserialize, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, data, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_Uri_QueryParams___debugInfo arginfo_class_Uri_QueryParamOptions___debugInfo + +#define arginfo_class_Uri_Rfc3986_UriBuilder_reset arginfo_class_Uri_QueryParams_sort + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_setScheme, 0, 1, IS_STATIC, 0) ZEND_ARG_TYPE_INFO(0, scheme, IS_STRING, 1) ZEND_END_ARG_INFO() @@ -87,10 +181,9 @@ ZEND_END_ARG_INFO() #define arginfo_class_Uri_Rfc3986_Uri_withPort arginfo_class_Uri_Rfc3986_UriBuilder_setPort -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_getPath, 0, 0, IS_STRING, 0) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_Rfc3986_Uri_getPath arginfo_class_Uri_QueryParams_toRfc1866String -#define arginfo_class_Uri_Rfc3986_Uri_getRawPath arginfo_class_Uri_Rfc3986_Uri_getPath +#define arginfo_class_Uri_Rfc3986_Uri_getRawPath arginfo_class_Uri_QueryParams_toRfc1866String #define arginfo_class_Uri_Rfc3986_Uri_withPath arginfo_class_Uri_Rfc3986_UriBuilder_setPath @@ -111,22 +204,19 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_equals, 0, ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, comparisonMode, Uri\\\125riComparisonMode, 0, "Uri\\UriComparisonMode::ExcludeFragment") ZEND_END_ARG_INFO() -#define arginfo_class_Uri_Rfc3986_Uri_toString arginfo_class_Uri_Rfc3986_Uri_getPath +#define arginfo_class_Uri_Rfc3986_Uri_toString arginfo_class_Uri_QueryParams_toRfc1866String -#define arginfo_class_Uri_Rfc3986_Uri_toRawString arginfo_class_Uri_Rfc3986_Uri_getPath +#define arginfo_class_Uri_Rfc3986_Uri_toRawString arginfo_class_Uri_QueryParams_toRfc1866String ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_resolve, 0, 1, IS_STATIC, 0) ZEND_ARG_TYPE_INFO(0, uri, IS_STRING, 0) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri___serialize, 0, 0, IS_ARRAY, 0) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_Rfc3986_Uri___serialize arginfo_class_Uri_QueryParamOptions___debugInfo -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri___unserialize, 0, 1, IS_VOID, 0) - ZEND_ARG_TYPE_INFO(0, data, IS_ARRAY, 0) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_Rfc3986_Uri___unserialize arginfo_class_Uri_QueryParams___unserialize -#define arginfo_class_Uri_Rfc3986_Uri___debugInfo arginfo_class_Uri_Rfc3986_Uri___serialize +#define arginfo_class_Uri_Rfc3986_Uri___debugInfo arginfo_class_Uri_QueryParamOptions___debugInfo ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Uri_WhatWg_InvalidUrlException___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, message, IS_STRING, 0, "\"\"") @@ -153,7 +243,7 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Uri_WhatWg_Url___construct, 0, 0, 1) ZEND_ARG_INFO_WITH_DEFAULT_VALUE(1, softErrors, "null") ZEND_END_ARG_INFO() -#define arginfo_class_Uri_WhatWg_Url_getScheme arginfo_class_Uri_Rfc3986_Uri_getPath +#define arginfo_class_Uri_WhatWg_Url_getScheme arginfo_class_Uri_QueryParams_toRfc1866String ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_Url_withScheme, 0, 1, IS_STATIC, 0) ZEND_ARG_TYPE_INFO(0, scheme, IS_STRING, 0) @@ -187,7 +277,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_Uri_WhatWg_Url_withPort arginfo_class_Uri_Rfc3986_UriBuilder_setPort -#define arginfo_class_Uri_WhatWg_Url_getPath arginfo_class_Uri_Rfc3986_Uri_getPath +#define arginfo_class_Uri_WhatWg_Url_getPath arginfo_class_Uri_QueryParams_toRfc1866String #define arginfo_class_Uri_WhatWg_Url_withPath arginfo_class_Uri_Rfc3986_UriBuilder_setPath @@ -204,21 +294,48 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_Url_equals, 0, ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, comparisonMode, Uri\\\125riComparisonMode, 0, "Uri\\UriComparisonMode::ExcludeFragment") ZEND_END_ARG_INFO() -#define arginfo_class_Uri_WhatWg_Url_toAsciiString arginfo_class_Uri_Rfc3986_Uri_getPath +#define arginfo_class_Uri_WhatWg_Url_toAsciiString arginfo_class_Uri_QueryParams_toRfc1866String -#define arginfo_class_Uri_WhatWg_Url_toUnicodeString arginfo_class_Uri_Rfc3986_Uri_getPath +#define arginfo_class_Uri_WhatWg_Url_toUnicodeString arginfo_class_Uri_QueryParams_toRfc1866String ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_Url_resolve, 0, 1, IS_STATIC, 0) ZEND_ARG_TYPE_INFO(0, uri, IS_STRING, 0) ZEND_ARG_INFO_WITH_DEFAULT_VALUE(1, softErrors, "null") ZEND_END_ARG_INFO() -#define arginfo_class_Uri_WhatWg_Url___serialize arginfo_class_Uri_Rfc3986_Uri___serialize - -#define arginfo_class_Uri_WhatWg_Url___unserialize arginfo_class_Uri_Rfc3986_Uri___unserialize - -#define arginfo_class_Uri_WhatWg_Url___debugInfo arginfo_class_Uri_Rfc3986_Uri___serialize - +#define arginfo_class_Uri_WhatWg_Url___serialize arginfo_class_Uri_QueryParamOptions___debugInfo + +#define arginfo_class_Uri_WhatWg_Url___unserialize arginfo_class_Uri_QueryParams___unserialize + +#define arginfo_class_Uri_WhatWg_Url___debugInfo arginfo_class_Uri_QueryParamOptions___debugInfo + +ZEND_METHOD(Uri_QueryParamOptions, __construct); +ZEND_METHOD(Uri_QueryParamOptions, __debugInfo); +ZEND_METHOD(Uri_QueryParams, parseRfc1866); +ZEND_METHOD(Uri_QueryParams, parseRfc3986); +ZEND_METHOD(Uri_QueryParams, parseWhatWg); +ZEND_METHOD(Uri_QueryParams, fromArray); +ZEND_METHOD(Uri_QueryParams, __construct); +ZEND_METHOD(Uri_QueryParams, append); +ZEND_METHOD(Uri_QueryParams, delete); +ZEND_METHOD(Uri_QueryParams, deleteValue); +ZEND_METHOD(Uri_QueryParams, has); +ZEND_METHOD(Uri_QueryParams, hasValue); +ZEND_METHOD(Uri_QueryParams, getFirst); +ZEND_METHOD(Uri_QueryParams, getLast); +ZEND_METHOD(Uri_QueryParams, getAll); +ZEND_METHOD(Uri_QueryParams, list); +ZEND_METHOD(Uri_QueryParams, getIterator); +ZEND_METHOD(Uri_QueryParams, count); +ZEND_METHOD(Uri_QueryParams, set); +ZEND_METHOD(Uri_QueryParams, sort); +ZEND_METHOD(Uri_QueryParams, toArray); +ZEND_METHOD(Uri_QueryParams, toRfc1866String); +ZEND_METHOD(Uri_QueryParams, toRfc3986String); +ZEND_METHOD(Uri_QueryParams, toWhatWgString); +ZEND_METHOD(Uri_QueryParams, __serialize); +ZEND_METHOD(Uri_QueryParams, __unserialize); +ZEND_METHOD(Uri_QueryParams, __debugInfo); ZEND_METHOD(Uri_Rfc3986_UriBuilder, reset); ZEND_METHOD(Uri_Rfc3986_UriBuilder, setScheme); ZEND_METHOD(Uri_Rfc3986_UriBuilder, setUserInfo); @@ -283,6 +400,41 @@ ZEND_METHOD(Uri_WhatWg_Url, __serialize); ZEND_METHOD(Uri_WhatWg_Url, __unserialize); ZEND_METHOD(Uri_WhatWg_Url, __debugInfo); +static const zend_function_entry class_Uri_QueryParamOptions_methods[] = { + ZEND_ME(Uri_QueryParamOptions, __construct, arginfo_class_Uri_QueryParamOptions___construct, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParamOptions, __debugInfo, arginfo_class_Uri_QueryParamOptions___debugInfo, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + +static const zend_function_entry class_Uri_QueryParams_methods[] = { + ZEND_ME(Uri_QueryParams, parseRfc1866, arginfo_class_Uri_QueryParams_parseRfc1866, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + ZEND_ME(Uri_QueryParams, parseRfc3986, arginfo_class_Uri_QueryParams_parseRfc3986, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + ZEND_ME(Uri_QueryParams, parseWhatWg, arginfo_class_Uri_QueryParams_parseWhatWg, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + ZEND_ME(Uri_QueryParams, fromArray, arginfo_class_Uri_QueryParams_fromArray, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + ZEND_ME(Uri_QueryParams, __construct, arginfo_class_Uri_QueryParams___construct, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, append, arginfo_class_Uri_QueryParams_append, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, delete, arginfo_class_Uri_QueryParams_delete, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, deleteValue, arginfo_class_Uri_QueryParams_deleteValue, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, has, arginfo_class_Uri_QueryParams_has, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, hasValue, arginfo_class_Uri_QueryParams_hasValue, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, getFirst, arginfo_class_Uri_QueryParams_getFirst, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, getLast, arginfo_class_Uri_QueryParams_getLast, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, getAll, arginfo_class_Uri_QueryParams_getAll, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, list, arginfo_class_Uri_QueryParams_list, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, getIterator, arginfo_class_Uri_QueryParams_getIterator, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, count, arginfo_class_Uri_QueryParams_count, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, set, arginfo_class_Uri_QueryParams_set, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, sort, arginfo_class_Uri_QueryParams_sort, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, toArray, arginfo_class_Uri_QueryParams_toArray, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, toRfc1866String, arginfo_class_Uri_QueryParams_toRfc1866String, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, toRfc3986String, arginfo_class_Uri_QueryParams_toRfc3986String, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, toWhatWgString, arginfo_class_Uri_QueryParams_toWhatWgString, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, __serialize, arginfo_class_Uri_QueryParams___serialize, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, __unserialize, arginfo_class_Uri_QueryParams___unserialize, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_QueryParams, __debugInfo, arginfo_class_Uri_QueryParams___debugInfo, ZEND_ACC_PUBLIC) + 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) @@ -418,6 +570,64 @@ static zend_class_entry *register_class_Uri_UriComparisonMode(void) return class_entry; } +static zend_class_entry *register_class_Uri_QueryParamOptions(void) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Uri", "QueryParamOptions", class_Uri_QueryParamOptions_methods); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_READONLY_CLASS); + + zval property_parsingMaxQueryStringLength_default_value; + ZVAL_UNDEF(&property_parsingMaxQueryStringLength_default_value); + zend_string *property_parsingMaxQueryStringLength_name = zend_string_init("parsingMaxQueryStringLength", sizeof("parsingMaxQueryStringLength") - 1, true); + zend_declare_typed_property(class_entry, property_parsingMaxQueryStringLength_name, &property_parsingMaxQueryStringLength_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(property_parsingMaxQueryStringLength_name, true); + + zval property_parsingMaxParamCount_default_value; + ZVAL_UNDEF(&property_parsingMaxParamCount_default_value); + zend_string *property_parsingMaxParamCount_name = zend_string_init("parsingMaxParamCount", sizeof("parsingMaxParamCount") - 1, true); + zend_declare_typed_property(class_entry, property_parsingMaxParamCount_name, &property_parsingMaxParamCount_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(property_parsingMaxParamCount_name, true); + + zval property_trueValue_default_value; + ZVAL_UNDEF(&property_trueValue_default_value); + zend_string *property_trueValue_name = zend_string_init("trueValue", sizeof("trueValue") - 1, true); + zend_declare_typed_property(class_entry, property_trueValue_name, &property_trueValue_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_string_release_ex(property_trueValue_name, true); + + zval property_falseValue_default_value; + ZVAL_UNDEF(&property_falseValue_default_value); + zend_string *property_falseValue_name = zend_string_init("falseValue", sizeof("falseValue") - 1, true); + zend_declare_typed_property(class_entry, property_falseValue_name, &property_falseValue_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_string_release_ex(property_falseValue_name, true); + + zval property_useNullAsEmptyString_default_value; + ZVAL_UNDEF(&property_useNullAsEmptyString_default_value); + zend_string *property_useNullAsEmptyString_name = zend_string_init("useNullAsEmptyString", sizeof("useNullAsEmptyString") - 1, true); + zend_declare_typed_property(class_entry, property_useNullAsEmptyString_name, &property_useNullAsEmptyString_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL)); + zend_string_release_ex(property_useNullAsEmptyString_name, true); + + return class_entry; +} + +static zend_class_entry *register_class_Uri_QueryParams(zend_class_entry *class_entry_IteratorAggregate, zend_class_entry *class_entry_Countable) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Uri", "QueryParams", class_Uri_QueryParams_methods); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_READONLY_CLASS); + zend_class_implements(class_entry, 2, class_entry_IteratorAggregate, class_entry_Countable); + + zval property_options_default_value; + ZVAL_UNDEF(&property_options_default_value); + zend_string *property_options_name = zend_string_init("options", sizeof("options") - 1, true); + zend_string *property_options_class_Uri_QueryParamOptions = zend_string_init("Uri\\QueryParamOptions", sizeof("Uri\\QueryParamOptions")-1, 1); + zend_declare_typed_property(class_entry, property_options_name, &property_options_default_value, ZEND_ACC_PRIVATE|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_options_class_Uri_QueryParamOptions, 0, 0)); + zend_string_release_ex(property_options_name, true); + + return class_entry; +} + static zend_class_entry *register_class_Uri_Rfc3986_UriType(void) { zend_class_entry *class_entry = zend_register_internal_enum("Uri\\Rfc3986\\UriType", IS_UNDEF, NULL); diff --git a/ext/uri/php_uri_common.h b/ext/uri/php_uri_common.h index 9106f6acd15f..390f94347af6 100644 --- a/ext/uri/php_uri_common.h +++ b/ext/uri/php_uri_common.h @@ -26,6 +26,8 @@ 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; extern zend_class_entry *php_uri_ce_invalid_uri_exception; +extern zend_class_entry *php_uri_ce_query_params; +extern zend_class_entry *php_uri_ce_query_param_options; extern zend_class_entry *php_uri_ce_whatwg_invalid_url_exception; extern zend_class_entry *php_uri_ce_whatwg_url_validation_error_type; extern zend_class_entry *php_uri_ce_whatwg_url_validation_error; @@ -162,6 +164,7 @@ PHPAPI zend_object *php_uri_object_handler_clone(zend_object *object); #define PHP_URI_PARSER_WHATWG "Uri\\WhatWg\\Url" #define PHP_URI_PARSER_PHP_PARSE_URL "parse_url" #define PHP_URI_SERIALIZE_URI_FIELD_NAME "uri" +#define PHP_URI_SERIALIZE_URI_QUERY_PARAM_FIELD_NAME "query" static inline const php_uri_property_handler *php_uri_parser_property_handler_by_name(const php_uri_parser *parser, php_uri_property_name property_name) { @@ -191,4 +194,33 @@ void php_uri_property_write_str_helper(INTERNAL_FUNCTION_PARAMETERS, php_uri_pro void php_uri_property_write_str_or_null_helper(INTERNAL_FUNCTION_PARAMETERS, php_uri_property_name property_name); void php_uri_property_write_long_or_null_helper(INTERNAL_FUNCTION_PARAMETERS, php_uri_property_name property_name); +typedef struct php_uri_query_param_list_entry { + zend_string *key; + zend_string *value; + uint32_t next_same_key; +} php_uri_query_param_list_entry; + +typedef struct php_uri_query_param_lookup_entry { + uint32_t first_index; + uint32_t last_index; +} php_uri_query_param_lookup_entry; + +typedef struct php_uri_query_params_object { + HashTable entry_list; + HashTable entry_lookup_table; + uint32_t tombstone_count; + bool is_initialized; + zend_object std; +} php_uri_query_params_object; + +typedef struct php_uri_query_param_options_object { + size_t parsing_max_query_string_length; + uint32_t parsing_max_param_count; + zend_string *true_value; + zend_string *false_value; + bool use_null_as_string; + bool is_initialized; + zend_object std; +} php_uri_query_param_options_object; + #endif diff --git a/ext/uri/php_uri_decl.h b/ext/uri/php_uri_decl.h index 784ac5b4c0e5..9abf7572dd9a 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: 1a396b7a5b170b968d84a2c64a5bcf7d962653db */ + * Stub hash: 5c99026c91273c8094ebbfb6809141649fc13c60 */ -#ifndef ZEND_PHP_URI_DECL_1a396b7a5b170b968d84a2c64a5bcf7d962653db_H -#define ZEND_PHP_URI_DECL_1a396b7a5b170b968d84a2c64a5bcf7d962653db_H +#ifndef ZEND_PHP_URI_DECL_5c99026c91273c8094ebbfb6809141649fc13c60_H +#define ZEND_PHP_URI_DECL_5c99026c91273c8094ebbfb6809141649fc13c60_H typedef enum zend_enum_Uri_UriComparisonMode { ZEND_ENUM_Uri_UriComparisonMode_IncludeFragment = 1, @@ -63,4 +63,4 @@ typedef enum zend_enum_Uri_WhatWg_UrlHostType { ZEND_ENUM_Uri_WhatWg_UrlHostType_Empty = 5, } zend_enum_Uri_WhatWg_UrlHostType; -#endif /* ZEND_PHP_URI_DECL_1a396b7a5b170b968d84a2c64a5bcf7d962653db_H */ +#endif /* ZEND_PHP_URI_DECL_5c99026c91273c8094ebbfb6809141649fc13c60_H */ diff --git a/ext/uri/php_uri_query.c b/ext/uri/php_uri_query.c new file mode 100644 index 000000000000..120f27807fc8 --- /dev/null +++ b/ext/uri/php_uri_query.c @@ -0,0 +1,1560 @@ +/* + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Authors: Máté Kocsis | + +----------------------------------------------------------------------+ +*/ +#include "zend_enum.h" +#include "zend_smart_str.h" +#include "../../Zend/zend_hash.h" +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "php.h" +#include "Zend/zend_exceptions.h" +#include "Zend/zend_interfaces.h" + +#include "php_uri_common.h" +#include "php_uri_query.h" +#include "uri_parser_rfc3986.h" +#include "uri_parser_whatwg.h" + +#define MAX_TOMBSTONES 1000 + +#define PARSING_MAX_QUERY_STRING_LENGTH_DEFAULT 10000 +#define PARSING_MAX_QUERY_PARAM_COUNT_DEFAULT 1000 +#define USE_NULL_AS_EMPTY_STRING_DEFAULT false + +typedef enum php_uri_query_params_spec { + PHP_URI_QUERY_PARAMS_SPEC_RFC1866, + PHP_URI_QUERY_PARAMS_SPEC_RFC3986, + PHP_URI_QUERY_PARAMS_SPEC_WHATWG, +} php_uri_query_params_spec; + +ZEND_ATTRIBUTE_NONNULL_ARGS(1) static zend_result php_uri_query_param_options_update_properties( + zend_object *query_param_options_object, const zend_long parsing_max_query_string_length, const zend_long parsing_max_param_count, + zend_string *true_value, zend_string *false_value, const bool use_null_as_empty_string +) { + php_uri_query_param_options_object *options = php_uri_query_param_options_object_from_obj(query_param_options_object); + if (options->is_initialized) { + zend_throw_error(NULL, "Cannot modify readonly object of class %s", ZSTR_VAL(query_param_options_object->ce->name)); + return FAILURE; + } + + options->parsing_max_query_string_length = parsing_max_query_string_length; + options->parsing_max_param_count = parsing_max_param_count; + + if (true_value == NULL) { + options->true_value = zend_string_init(ZEND_STRL("1"), false); + } else { + options->true_value = true_value; + } + + if (false_value == NULL) { + options->false_value = zend_string_init(ZEND_STRL("0"), false); + } else { + options->false_value = false_value; + } + + options->use_null_as_string = use_null_as_empty_string; + options->is_initialized = true; + + return SUCCESS; +} + +ZEND_ATTRIBUTE_NONNULL_ARGS(1,2) static zend_string *php_uri_query_params_convert_value_to_str( + const php_uri_query_params_object *query_params_object, const zval *zv +) { + switch (Z_TYPE_P(zv)) { + case IS_STRING: { + return zend_string_copy(Z_STR_P(zv)); + } + case IS_FALSE: + ZEND_FALLTHROUGH; + case IS_TRUE: { + return zend_string_init(Z_TYPE_P(zv) == IS_FALSE ? "0" : "1", 1, false); + } + case IS_LONG: { + return zend_long_to_str(Z_LVAL_P(zv)); + } + case IS_DOUBLE: { + return zend_double_to_str(Z_LVAL_P(zv)); + } + case IS_NULL: { + return NULL; + } + case IS_RESOURCE: + zend_argument_value_error(2, "must not contain a resource"); + return NULL; + case IS_ARRAY: { + zend_argument_value_error(2, "must not contain an arra"); + return NULL; + } + case IS_OBJECT: + if (Z_OBJCE_P(zv)->ce_flags & ZEND_ACC_ENUM && Z_OBJCE_P(zv)->enum_backing_type != IS_UNDEF) { + const zval *tmp = zend_enum_fetch_case_value(Z_OBJ_P(zv)); + return php_uri_query_params_convert_value_to_str(query_params_object, tmp); + } + + zend_argument_value_error(2, "must not contain an object"); + return NULL; + default: ZEND_UNREACHABLE(); + } +} + +ZEND_ATTRIBUTE_NONNULL_ARGS(1,2) static bool php_uri_query_params_has_entry( + const php_uri_query_params_object *query_params_object, zend_string *name +) { + const php_uri_query_param_lookup_entry *lookup_entry = zend_hash_find_ptr(&query_params_object->entry_lookup_table, name); + + return lookup_entry != NULL; +} + +ZEND_ATTRIBUTE_NONNULL_ARGS(1,2) static bool php_uri_query_params_has_entry_with_value( + const php_uri_query_params_object *query_params_object, zend_string *name, const zend_string *value +) { + const php_uri_query_param_lookup_entry *lookup_entry = zend_hash_find_ptr(&query_params_object->entry_lookup_table, name); + if (lookup_entry == NULL) { + return false; + } + + uint32_t index = lookup_entry->first_index; + while (index != UINT32_MAX) { + const php_uri_query_param_list_entry *list_entry = zend_hash_index_find_ptr(&query_params_object->entry_list, index); + ZEND_ASSERT(list_entry != NULL); + + if ( + (value == NULL && list_entry->value == NULL) || + (value != NULL && list_entry->value != NULL && zend_string_equal_content(value, list_entry->value)) + ) { // TODO properly handle search for NULL values according to options + return true; + } + + index = list_entry->next_same_key; + } + + return false; +} + +static void php_uri_query_params_find_value( + const php_uri_query_params_object *query_params_object, const uint32_t index, zval *zv +) { + const php_uri_query_param_list_entry *list_entry = zend_hash_index_find_ptr(&query_params_object->entry_list, index); + ZEND_ASSERT(list_entry != NULL); + + if (list_entry->value == NULL) { + ZVAL_NULL(zv); + } else { + ZVAL_STR_COPY(zv, list_entry->value); + } +} + +ZEND_ATTRIBUTE_NONNULL_ARGS(1, 2) static void php_uri_query_params_find_first_value( + const php_uri_query_params_object *query_params_object, zend_string *name, zval *zv +) { + const php_uri_query_param_lookup_entry *lookup_entry = zend_hash_find_ptr(&query_params_object->entry_lookup_table, name); + if (lookup_entry == NULL) { + ZVAL_NULL(zv); + return; + } + + php_uri_query_params_find_value(query_params_object, lookup_entry->first_index, zv); +} + +ZEND_ATTRIBUTE_NONNULL_ARGS(1,2) static void php_uri_query_params_find_last_value( + const php_uri_query_params_object *query_params_object, zend_string *name, zval *zv +) { + const php_uri_query_param_lookup_entry *lookup_entry = zend_hash_find_ptr(&query_params_object->entry_lookup_table, name); + if (lookup_entry == NULL) { + ZVAL_NULL(zv); + return; + } + + php_uri_query_params_find_value(query_params_object, lookup_entry->last_index, zv); +} + +ZEND_ATTRIBUTE_NONNULL_ARGS(1,2) static void php_uri_query_params_find_all_values( + const php_uri_query_params_object *query_params_object, zend_string *name, zval *zv +) { + const php_uri_query_param_lookup_entry *lookup_entry = zend_hash_find_ptr(&query_params_object->entry_lookup_table, name); + if (lookup_entry == NULL) { + ZVAL_EMPTY_ARRAY(zv); + return; + } + + array_init(zv); + + uint32_t index = lookup_entry->first_index; + while (index != UINT32_MAX) { + const php_uri_query_param_list_entry *list_entry = zend_hash_index_find_ptr(&query_params_object->entry_list, index); + ZEND_ASSERT(list_entry != NULL); + + if (list_entry->key != NULL) { + zval value; + zend_hash_next_index_insert(Z_ARR_P(zv), &value); + } + + index = list_entry->next_same_key; + } +} + +ZEND_ATTRIBUTE_NONNULL static void php_uri_query_params_list_entries(php_uri_query_params_object *object, zval *params) +{ + array_init_size(params, zend_array_count(&object->entry_list)); + + ZEND_HASH_FOREACH_PTR(&object->entry_list, php_uri_query_param_list_entry *list_entry) { + if (list_entry->key != NULL) { + zval key; + ZVAL_STR_COPY(&key, list_entry->key); + + zval value; + if (list_entry->value == NULL) { + ZVAL_NULL(&value); + } else { + ZVAL_STR_COPY(&value, list_entry->value); + } + + zval zv; + array_init_size(&zv, 2); + zend_hash_index_add(Z_ARR(zv), 0, &key); + zend_hash_index_add(Z_ARR(zv), 1, &value); + + zend_hash_next_index_insert(Z_ARR_P(params), &zv); + } + } ZEND_HASH_FOREACH_END(); +} + +ZEND_ATTRIBUTE_NONNULL_ARGS(1,2) static zend_result php_uri_query_params_append_entry( + php_uri_query_params_object *query_params_object, zend_string *name, zend_string *value +) { + php_uri_query_param_list_entry new_list_entry = { + .key = zend_string_copy(name), + .value = value == NULL ? NULL : zend_string_copy(value), + .next_same_key = UINT32_MAX, + }; + + const void *result = zend_hash_next_index_insert_mem(&query_params_object->entry_list, &new_list_entry, sizeof(new_list_entry)); + if (result == NULL) { + return FAILURE; + } + + const zend_long new_index = query_params_object->entry_list.nNextFreeElement - 1; + + php_uri_query_param_lookup_entry *lookup_entry = zend_hash_find_ptr(&query_params_object->entry_lookup_table, name); + + if (lookup_entry == NULL) { + /* Insert a new lookup entry */ + + php_uri_query_param_lookup_entry new_lookup_entry = { + .first_index = new_index, + .last_index = new_index, + }; + if (zend_hash_add_mem(&query_params_object->entry_lookup_table, name, &new_lookup_entry, sizeof(new_lookup_entry)) == NULL) { + zend_hash_index_del(&query_params_object->entry_list, new_index); + return FAILURE; + } + } else { + /* Update the existing lookup entry + the same key list */ + + php_uri_query_param_list_entry *last_list_entry = zend_hash_index_find_ptr(&query_params_object->entry_list, lookup_entry->last_index); + ZEND_ASSERT(last_list_entry != NULL); + + last_list_entry->next_same_key = new_index; + lookup_entry->last_index = new_index; + } + + return SUCCESS; +} + +ZEND_ATTRIBUTE_NONNULL static void php_uri_query_params_cleanup_tombstones( + php_uri_query_params_object *query_params_object +) { + if (query_params_object->tombstone_count <= MAX_TOMBSTONES) { + return; + } + + HashTable new_entry_list; + zend_hash_init(&new_entry_list, query_params_object->tombstone_count, NULL, query_params_object->entry_list.pDestructor, 0); + + HashTable last_seen_index; + zend_hash_init(&last_seen_index, 8, NULL, NULL, 0); + + php_uri_query_param_list_entry *old_entry; + ZEND_HASH_FOREACH_PTR(&query_params_object->entry_list, old_entry) { + if (old_entry->key == NULL) { + continue; + } + + uint32_t new_index = zend_hash_num_elements(&new_entry_list); + + php_uri_query_param_list_entry new_entry = { + .key = old_entry->key, + .value = old_entry->value, + .next_same_key = UINT32_MAX, + }; + zend_hash_next_index_insert_mem(&new_entry_list, &new_entry, sizeof(new_entry)); + + php_uri_query_param_lookup_entry *lookup_entry = zend_hash_find_ptr(&query_params_object->entry_lookup_table, old_entry->key); + ZEND_ASSERT(lookup_entry != NULL); + + zval *last_index_zv = zend_hash_find(&last_seen_index, old_entry->key); + + if (last_index_zv == NULL) { + lookup_entry->first_index = new_index; + } else { + uint32_t prev_index = (uint32_t) Z_LVAL_P(last_index_zv); + php_uri_query_param_list_entry *prev_entry = zend_hash_index_find_ptr(&new_entry_list, prev_index); + ZEND_ASSERT(prev_entry != NULL); + prev_entry->next_same_key = new_index; + } + + lookup_entry->last_index = new_index; + + zval new_last_zv; + ZVAL_LONG(&new_last_zv, (zend_long) new_index); + zend_hash_update(&last_seen_index, old_entry->key, &new_last_zv); + + } ZEND_HASH_FOREACH_END(); + + zend_hash_destroy(&last_seen_index); + zend_hash_destroy(&query_params_object->entry_list); + query_params_object->entry_list = new_entry_list; +} + +static void php_uri_query_params_tombstone_entry( + php_uri_query_params_object *query_params_object, php_uri_query_param_list_entry *list_entry +) { + if (list_entry->key == NULL) { + return; + } + + zend_string_release(list_entry->key); + list_entry->key = NULL; + if (list_entry->value != NULL) { + zend_string_release(list_entry->value); + list_entry->value = NULL; + } + list_entry->next_same_key = UINT32_MAX; + query_params_object->tombstone_count++; +} + +ZEND_ATTRIBUTE_NONNULL_ARGS(1, 2) static void php_uri_query_params_delete_entries( + php_uri_query_params_object *query_params_object, zend_string *name +) { + const php_uri_query_param_lookup_entry *lookup_entry = zend_hash_find_ptr(&query_params_object->entry_lookup_table, name); + if (lookup_entry == NULL) { + return; + } + + uint32_t index = lookup_entry->first_index; + while (index != UINT32_MAX) { + php_uri_query_param_list_entry *list_entry = zend_hash_index_find_ptr(&query_params_object->entry_list, index); + ZEND_ASSERT(list_entry != NULL); + + const uint32_t next = list_entry->next_same_key; + php_uri_query_params_tombstone_entry(query_params_object, list_entry); + index = next; + } + + zend_hash_del(&query_params_object->entry_lookup_table, name); + + php_uri_query_params_cleanup_tombstones(query_params_object); +} + +ZEND_ATTRIBUTE_NONNULL_ARGS(1,2) static void php_uri_query_params_delete_entries_with_value( + php_uri_query_params_object *query_params_object, zend_string *name, const zend_string *value +) { + php_uri_query_param_lookup_entry *lookup_entry = zend_hash_find_ptr(&query_params_object->entry_lookup_table, name); + if (lookup_entry == NULL) { + return; + } + + uint32_t index = lookup_entry->first_index; + uint32_t new_first = UINT32_MAX; + uint32_t new_last = UINT32_MAX; + php_uri_query_param_list_entry *prev_list_entry = NULL; + + while (index != UINT32_MAX) { + php_uri_query_param_list_entry *list_entry = zend_hash_index_find_ptr(&query_params_object->entry_list, index); + ZEND_ASSERT(list_entry != NULL); + + const uint32_t next = list_entry->next_same_key; + + const bool should_delete = (value == NULL && list_entry->value == NULL) || + (value != NULL && list_entry->value != NULL && zend_string_equal_content(value, list_entry->value)); + + if (should_delete) { + php_uri_query_params_tombstone_entry(query_params_object, list_entry); + + /* Rebuild the same key list */ + if (prev_list_entry != NULL) { + prev_list_entry->next_same_key = next; + } + } else { + if (new_first == UINT32_MAX) { + new_first = index; + } + new_last = index; + prev_list_entry = list_entry; + } + + index = next; + } + + /* Either delete the lookup entry if no list entries remain, or update it with the new data. */ + if (new_first == UINT32_MAX) { + zend_hash_del(&query_params_object->entry_lookup_table, name); + } else { + php_uri_query_param_list_entry *last_entry = zend_hash_index_find_ptr(&query_params_object->entry_list, new_last); + ZEND_ASSERT(last_entry != NULL); + last_entry->next_same_key = UINT32_MAX; + + lookup_entry->first_index = new_first; + lookup_entry->last_index = new_last; + } +} + +ZEND_ATTRIBUTE_NONNULL_ARGS(1, 2) static zend_result php_uri_query_params_set_entry( + php_uri_query_params_object *query_params_object, zend_string *name, zend_string *value +) { + php_uri_query_param_lookup_entry *lookup_entry = zend_hash_find_ptr(&query_params_object->entry_lookup_table, name); + if (lookup_entry == NULL) { + return php_uri_query_params_append_entry(query_params_object, name, value); + } + + /* Overwrite first occurrence */ + php_uri_query_param_list_entry *first_entry = zend_hash_index_find_ptr(&query_params_object->entry_list, lookup_entry->first_index); + ZEND_ASSERT(first_entry != NULL); + + if (first_entry->value != NULL) { + zend_string_release(first_entry->value); + } + first_entry->value = value != NULL ? zend_string_copy(value) : NULL; + + /* Tombstone the rest of the occurrences */ + uint32_t index = first_entry->next_same_key; + first_entry->next_same_key = UINT32_MAX; + + while (index != UINT32_MAX) { + php_uri_query_param_list_entry *list_entry = zend_hash_index_find_ptr(&query_params_object->entry_list, index); + ZEND_ASSERT(list_entry != NULL); + + const uint32_t next = list_entry->next_same_key; + php_uri_query_params_tombstone_entry(query_params_object, list_entry); + index = next; + } + + /* Update the lookup entry */ + lookup_entry->last_index = lookup_entry->first_index; + + php_uri_query_params_cleanup_tombstones(query_params_object); + + return SUCCESS; +} + +ZEND_ATTRIBUTE_NONNULL zend_object *php_uri_object_query_params_create_object(zend_class_entry *ce) +{ + php_uri_query_params_object *uri_query_params_object = zend_object_alloc(sizeof(*uri_query_params_object), ce); + + zend_object_std_init(&uri_query_params_object->std, ce); + object_properties_init(&uri_query_params_object->std, ce); + + uri_query_params_object->is_initialized = false; + uri_query_params_object->tombstone_count = 0; + + return &uri_query_params_object->std; +} + +ZEND_ATTRIBUTE_NONNULL void php_uri_query_params_object_free(php_uri_query_params_object *uri_query_params_object) +{ + if (uri_query_params_object->is_initialized) { + zend_hash_destroy(&uri_query_params_object->entry_list); + zend_hash_destroy(&uri_query_params_object->entry_lookup_table); + uri_query_params_object->tombstone_count = 0; + } +} + +ZEND_ATTRIBUTE_NONNULL void php_uri_query_params_object_handler_free(zend_object *object) +{ + php_uri_query_params_object *uri_query_params_object = php_uri_query_params_object_from_obj(object); + + php_uri_query_params_object_free(uri_query_params_object); + + zend_object_std_dtor(&uri_query_params_object->std); +} + +ZEND_ATTRIBUTE_NONNULL zend_object *php_uri_query_params_object_handler_clone(zend_object *object) +{ + const php_uri_query_params_object *uri_query_params_object = php_uri_query_params_object_from_obj(object); + + php_uri_query_params_object *new_uri_query_params_object = php_uri_query_params_object_from_obj( + object->ce->create_object(object->ce) + ); + + zend_hash_copy(&new_uri_query_params_object->entry_list, &uri_query_params_object->entry_list, NULL); + zend_hash_copy(&new_uri_query_params_object->entry_lookup_table, &uri_query_params_object->entry_lookup_table, NULL); + new_uri_query_params_object->tombstone_count = uri_query_params_object->tombstone_count; + new_uri_query_params_object->is_initialized = uri_query_params_object->is_initialized; + zend_objects_clone_members(&new_uri_query_params_object->std, &uri_query_params_object->std); + + return &new_uri_query_params_object->std; +} + +typedef struct php_uri_query_params_iterator { + zend_object_iterator intern; + php_uri_query_params_object *query_params_object; + uint32_t current_index; + zval current_value; +} php_uri_query_params_iterator; + +static void php_uri_query_params_iterator_dtor(zend_object_iterator *iter) +{ + php_uri_query_params_iterator *iterator = (php_uri_query_params_iterator *) iter; + zval_ptr_dtor(&iterator->current_value); + zval_ptr_dtor(&iterator->intern.data); +} + +static zend_result php_uri_query_params_iterator_valid(zend_object_iterator *iter) +{ + php_uri_query_params_iterator *iterator = (php_uri_query_params_iterator *) iter; + php_uri_query_params_object *obj = iterator->query_params_object; + + if (!obj->is_initialized) { + return FAILURE; + } + + while (iterator->current_index < zend_hash_num_elements(&obj->entry_list)) { + php_uri_query_param_list_entry *entry = zend_hash_index_find_ptr( + &obj->entry_list, iterator->current_index); + if (entry != NULL && entry->key != NULL) { + return SUCCESS; + } + iterator->current_index++; + } + + return FAILURE; +} + +static zval *php_uri_query_params_iterator_current_data(zend_object_iterator *iter) +{ + php_uri_query_params_iterator *iterator = (php_uri_query_params_iterator *) iter; + php_uri_query_params_object *obj = iterator->query_params_object; + + php_uri_query_param_list_entry *entry = zend_hash_index_find_ptr( + &obj->entry_list, iterator->current_index); + ZEND_ASSERT(entry != NULL && entry->key != NULL); + + zval_ptr_dtor(&iterator->current_value); + array_init_size(&iterator->current_value, 2); + + zval key_zv, value_zv; + ZVAL_STR_COPY(&key_zv, entry->key); + if (entry->value != NULL) { + ZVAL_STR_COPY(&value_zv, entry->value); + } else { + ZVAL_NULL(&value_zv); + } + zend_hash_next_index_insert(Z_ARRVAL(iterator->current_value), &key_zv); + zend_hash_next_index_insert(Z_ARRVAL(iterator->current_value), &value_zv); + + return &iterator->current_value; +} + +static void php_uri_query_params_iterator_current_key( + zend_object_iterator *iter, zval *key) +{ + php_uri_query_params_iterator *iterator = (php_uri_query_params_iterator *) iter; + ZVAL_LONG(key, (zend_long) iterator->current_index); +} + +static void php_uri_query_params_iterator_move_forward(zend_object_iterator *iter) +{ + php_uri_query_params_iterator *iterator = (php_uri_query_params_iterator *) iter; + iterator->current_index++; +} + +static void php_uri_query_params_iterator_rewind(zend_object_iterator *iter) +{ + php_uri_query_params_iterator *iterator = (php_uri_query_params_iterator *) iter; + iterator->current_index = 0; +} + +static const zend_object_iterator_funcs php_uri_query_params_iterator_funcs = { + .dtor = php_uri_query_params_iterator_dtor, + .valid = php_uri_query_params_iterator_valid, + .get_current_data = php_uri_query_params_iterator_current_data, + .get_current_key = php_uri_query_params_iterator_current_key, + .move_forward = php_uri_query_params_iterator_move_forward, + .rewind = php_uri_query_params_iterator_rewind, + .invalidate_current = NULL, + .get_gc = NULL, +}; + +ZEND_ATTRIBUTE_NONNULL zend_object_iterator *php_uri_object_query_params_get_iterator( + zend_class_entry *ce, zval *object, const int by_ref) +{ + if (by_ref) { + zend_throw_error(NULL, "An iterator cannot be used with foreach by reference"); + return NULL; + } + + php_uri_query_params_iterator *iterator = emalloc(sizeof(php_uri_query_params_iterator)); + + zend_iterator_init(&iterator->intern); + iterator->intern.funcs = &php_uri_query_params_iterator_funcs; + + ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object)); + iterator->query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(object); + iterator->current_index = 0; + ZVAL_UNDEF(&iterator->current_value); + + return &iterator->intern; +} + +static void php_uri_query_param_list_entry_dtor(zval *zv) +{ + php_uri_query_param_list_entry *list_entry = Z_PTR_P(zv); + + if (list_entry->key != NULL) { + zend_string_release(list_entry->key); + } + + if (list_entry->value != NULL) { + zend_string_release(list_entry->value); + } + + efree(list_entry); +} + +static void php_uri_query_param_lookup_entry_dtor(zval *zv) +{ + efree(Z_PTR_P(zv)); +} + +ZEND_ATTRIBUTE_NONNULL_ARGS(1) static php_uri_query_param_options_object *php_uri_query_params_object_init( + php_uri_query_params_object *uri_query_params_object, zval *options +) { + if (!uri_query_params_object->is_initialized) { + zend_hash_init(&uri_query_params_object->entry_list, 8, NULL, php_uri_query_param_list_entry_dtor, 0); + zend_hash_init(&uri_query_params_object->entry_lookup_table, 8, NULL, php_uri_query_param_lookup_entry_dtor, 0); + uri_query_params_object->is_initialized = true; + } + + if (options == NULL) { + zval default_options; + object_init_ex(&default_options, php_uri_ce_query_param_options); + + if (php_uri_query_param_options_update_properties( + Z_OBJ(default_options), PARSING_MAX_QUERY_STRING_LENGTH_DEFAULT, + PARSING_MAX_QUERY_PARAM_COUNT_DEFAULT, NULL, NULL, USE_NULL_AS_EMPTY_STRING_DEFAULT + )) { + return NULL; + } + + /* There is no need to update the properties as the default values are already set by the create handler */ + zend_update_property(uri_query_params_object->std.ce, &uri_query_params_object->std, ZEND_STRL("options"), &default_options); + if (EG(exception)) { + zval_ptr_dtor(&default_options); + return NULL; + } + + return Z_URI_QUERY_PARAM_OPTIONS_OBJECT_P(&default_options); + } + + zend_update_property(uri_query_params_object->std.ce, &uri_query_params_object->std, ZEND_STRL("options"), options); + if (EG(exception)) { + return NULL; + } + GC_ADDREF(&uri_query_params_object->std); + + return Z_URI_QUERY_PARAM_OPTIONS_OBJECT_P(options); +} + +ZEND_ATTRIBUTE_NONNULL void php_uri_query_param_options_object_free(php_uri_query_param_options_object *uri_query_param_options_object) +{ + if (uri_query_param_options_object->is_initialized) { + if (uri_query_param_options_object->true_value != NULL) { + zend_string_release(uri_query_param_options_object->true_value); + uri_query_param_options_object->true_value = NULL; + } + if (uri_query_param_options_object->false_value != NULL) { + zend_string_release(uri_query_param_options_object->false_value); + uri_query_param_options_object->false_value = NULL; + } + } +} + +ZEND_ATTRIBUTE_NONNULL zend_object *php_uri_object_create_query_param_options(zend_class_entry *ce) +{ + php_uri_query_param_options_object *uri_query_param_options_object = zend_object_alloc(sizeof(*uri_query_param_options_object), ce); + + zend_object_std_init(&uri_query_param_options_object->std, ce); + object_properties_init(&uri_query_param_options_object->std, ce); + + uri_query_param_options_object->is_initialized = false; + + return &uri_query_param_options_object->std; +} + +ZEND_ATTRIBUTE_NONNULL void php_uri_query_param_options_object_handler_free(zend_object *object) +{ + php_uri_query_param_options_object *uri_query_param_options_object = php_uri_query_param_options_object_from_obj(object); + + php_uri_query_param_options_object_free(uri_query_param_options_object); + + zend_object_std_dtor(&uri_query_param_options_object->std); +} + +ZEND_ATTRIBUTE_NONNULL zend_object *php_uri_query_param_options_object_handler_clone(zend_object *object) +{ + const php_uri_query_param_options_object *uri_query_param_options_object = php_uri_query_param_options_object_from_obj(object); + + php_uri_query_param_options_object *new_uri_query_param_options_object = php_uri_query_param_options_object_from_obj( + object->ce->create_object(object->ce) + ); + + new_uri_query_param_options_object->parsing_max_query_string_length = uri_query_param_options_object->parsing_max_query_string_length; + new_uri_query_param_options_object->parsing_max_param_count = uri_query_param_options_object->parsing_max_param_count; + if (uri_query_param_options_object->true_value != NULL) { + new_uri_query_param_options_object->true_value = zend_string_copy(uri_query_param_options_object->true_value); + } + if (uri_query_param_options_object->false_value != NULL) { + new_uri_query_param_options_object->false_value = zend_string_copy(uri_query_param_options_object->false_value); + } + new_uri_query_param_options_object->use_null_as_string = uri_query_param_options_object->use_null_as_string; + new_uri_query_param_options_object->is_initialized = uri_query_param_options_object->is_initialized; + zend_objects_clone_members(&new_uri_query_param_options_object->std, &uri_query_param_options_object->std); + + return &new_uri_query_param_options_object->std; +} + +ZEND_ATTRIBUTE_NONNULL int php_uri_query_param_options_object_handler_has_property(zend_object *obj, zend_string *name, int check_empty, void **cache_slot) +{ + if (check_empty == ZEND_PROPERTY_NOT_EMPTY) { + const php_uri_query_param_options_object *options = php_uri_query_param_options_object_from_obj(obj); + + if (zend_string_equals_literal(name, "parsingMaxQueryStringLength")) { + return options->parsing_max_query_string_length != 0; + } + } + + return zend_string_equals_literal(name, "trueValue") || zend_string_equals_literal(name, "falseValue"); +} + +ZEND_ATTRIBUTE_NONNULL zval *php_uri_query_param_options_object_handler_read_property(zend_object *obj, zend_string *name, int type, void **cache_slot, zval *rv) +{ + const php_uri_query_param_options_object *options = php_uri_query_param_options_object_from_obj(obj); + + if (zend_string_equals_literal(name, "parsingMaxQueryStringLength")) { + ZVAL_LONG(rv, options->parsing_max_query_string_length); + return rv; + } + + if (zend_string_equals_literal(name, "parsingMaxParamCount")) { + ZVAL_LONG(rv, options->parsing_max_param_count); + return rv; + } + + if (zend_string_equals_literal(name, "trueValue")) { + ZVAL_STR_COPY(rv, options->true_value); + return rv; + } + + if (zend_string_equals_literal(name, "falseValue")) { + ZVAL_STR_COPY(rv, options->false_value); + return rv; + } + + if (zend_string_equals_literal(name, "useNullAsEmptyString")) { + ZVAL_BOOL(rv, options->use_null_as_string); + return rv; + } + + ZEND_UNREACHABLE(); +} + +ZEND_ATTRIBUTE_NONNULL zval *php_uri_query_param_options_object_handler_write_property(zend_object *obj, zend_string *name, zval *value, void **cache_slot) +{ + zend_readonly_property_modification_error_ex(ZSTR_VAL(obj->ce->name), ZSTR_VAL(name)); + return &EG(error_zval); +} + +ZEND_ATTRIBUTE_NONNULL void php_uri_query_param_options_object_handler_unset_property(zend_object *obj, zend_string *name, void **cache_slot) +{ + zend_throw_error(NULL, "Cannot unset readonly property %s::$%s", ZSTR_VAL(obj->ce->name), ZSTR_VAL(name)); +} + +ZEND_ATTRIBUTE_NONNULL zval *php_uri_query_param_options_object_handler_get_property_ptr_ptr(zend_object *object, zend_string *member, int type, void **cache_slot) +{ + /* Must always go through read property because all properties are virtual, and no dynamic properties are allowed. */ + return NULL; +} + +ZEND_ATTRIBUTE_NONNULL_ARGS(1, 3) static zend_result php_uri_query_params_parse( + const char *query_str_first, const size_t query_str_length, php_uri_query_params_object *query_params_object, + const php_uri_query_params_spec spec, const bool silent, zval *options +) { + const php_uri_query_param_options_object *query_param_options_object = php_uri_query_params_object_init(query_params_object, options); + if (query_param_options_object == NULL) { + return FAILURE; + } + + if (UNEXPECTED(query_str_length > query_param_options_object->parsing_max_query_string_length)) { + if (!silent) { + zend_throw_exception(php_uri_ce_exception, "The specified query string is too long", 0); + } + return FAILURE; + } + + const char *p = query_str_first; + const char *end = query_str_first + query_str_length; + + if (spec == PHP_URI_QUERY_PARAMS_SPEC_WHATWG && *p == '?') { // TODO check other skippable characters ("/t" "/n" etc.) + p++; + } + + while (p < end) { + const char *ampersand = memchr(p, '&', (size_t)(end - p)); + const char *pair_end = ampersand != NULL ? ampersand : end; + const size_t pair_len = (size_t)(pair_end - p); + + if (pair_len == 0) { + p = pair_end + 1; + continue; + } + + const char *eq = memchr(p, '=', pair_len); + + const char *name_start; + size_t name_len; + const char *value_start; + size_t value_len; + + if (eq == NULL) { + name_start = p; + name_len = pair_len; + value_start = NULL; + value_len = 0; + } else { + name_start = p; + name_len = (size_t)(eq - p); + value_start = eq + 1; + value_len = (size_t) (pair_end - value_start); + } + + zend_string *name; + zend_string *value; + + switch (spec) { + case PHP_URI_QUERY_PARAMS_SPEC_RFC1866: + name = php_uri_parser_rfc3986_query_component_percent_decode(name_start, name_len, true); + value = php_uri_parser_rfc3986_query_component_percent_decode(value_start, value_len, true); + break; + case PHP_URI_QUERY_PARAMS_SPEC_RFC3986: + name = php_uri_parser_rfc3986_query_component_percent_decode(name_start, name_len, false); + value = php_uri_parser_rfc3986_query_component_percent_decode(value_start, value_len, false); + break; + case PHP_URI_QUERY_PARAMS_SPEC_WHATWG: + name = php_uri_parser_whatwg_query_component_percent_decode(name_start, name_len); + value = php_uri_parser_whatwg_query_component_percent_decode(value_start, value_len); + break; + default: ZEND_UNREACHABLE(); + } + + const zend_result result = php_uri_query_params_append_entry(query_params_object, name, value); + + zend_string_release(name); + if (value != NULL) { + zend_string_release(value); + } + + if (UNEXPECTED(result == FAILURE)) { + if (!silent) { + zend_throw_exception(php_uri_ce_exception, "Failed to parse the specified query string", 0); + } + + return FAILURE; + } + + if (UNEXPECTED(zend_array_count(&query_params_object->entry_list) > query_param_options_object->parsing_max_param_count)) { + if (!silent) { + zend_throw_exception(php_uri_ce_exception, "The specified query string contains too many parameters", 0); + } + return FAILURE; + } + + p = pair_end + 1; + } + + return SUCCESS; +} + +ZEND_ATTRIBUTE_NONNULL static void throw_cannot_recompose_uri_to_string(const zend_object *object) +{ + zend_throw_exception_ex(php_uri_ce_error, 0, "Cannot recompose %s to a string", ZSTR_VAL(object->ce->name)); +} + +ZEND_ATTRIBUTE_NONNULL static zend_string *php_uri_query_params_to_rfc1866_string(const php_uri_query_params_object *uri_query_params_object) +{ + return php_uri_parser_rfc3986_query_params_to_string(uri_query_params_object, true); +} + +ZEND_ATTRIBUTE_NONNULL static zend_string *php_uri_query_params_to_rfc3986_string(const php_uri_query_params_object *uri_query_params_object) +{ + return php_uri_parser_rfc3986_query_params_to_string(uri_query_params_object, false); +} + +ZEND_ATTRIBUTE_NONNULL static zend_string *php_uri_query_params_to_whatwg_string(const php_uri_query_params_object *uri_query_params_object) +{ + return php_uri_parser_rfc3986_query_params_to_string(uri_query_params_object, false); +} + +ZEND_ATTRIBUTE_NONNULL static HashTable *php_uri_query_params_get_debug_properties(php_uri_query_params_object *object) +{ + const HashTable *std_properties = zend_std_get_properties(&object->std); + HashTable *result = zend_array_dup(std_properties); + + zval params; + php_uri_query_params_list_entries(object, ¶ms); + + zend_hash_str_add(result, ZEND_STRL("params"), ¶ms); + + return result; +} + +PHP_METHOD(Uri_QueryParams, parseRfc1866) +{ + zend_string *query_str; + zval *options; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(query_str) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS_OR_NULL(options, php_uri_ce_query_param_options) + ZEND_PARSE_PARAMETERS_END(); + + object_init_ex(return_value, Z_CE_P(ZEND_THIS)); + php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(return_value); + + if (php_uri_query_params_parse(ZSTR_VAL(query_str), ZSTR_LEN(query_str), uri_query_params_object, + PHP_URI_QUERY_PARAMS_SPEC_RFC1866, false, options + ) == FAILURE) { + php_uri_query_params_object_free(Z_URI_QUERY_PARAMS_OBJECT_P(return_value)); + RETURN_NULL(); + } +} + +PHP_METHOD(Uri_QueryParams, parseRfc3986) +{ + zend_string *query_str; + zval *options; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(query_str) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS_OR_NULL(options, php_uri_ce_query_param_options) + ZEND_PARSE_PARAMETERS_END(); + + object_init_ex(return_value, Z_CE_P(ZEND_THIS)); + php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(return_value); + + if (php_uri_query_params_parse(ZSTR_VAL(query_str), ZSTR_LEN(query_str), uri_query_params_object, + PHP_URI_QUERY_PARAMS_SPEC_RFC3986, false, options + ) == FAILURE) { + php_uri_query_params_object_free(Z_URI_QUERY_PARAMS_OBJECT_P(return_value)); + RETURN_NULL(); + } +} + +PHP_METHOD(Uri_QueryParams, parseWhatWg) +{ + zend_string *query_str; + zval *options; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(query_str) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS_OR_NULL(options, php_uri_ce_query_param_options) + ZEND_PARSE_PARAMETERS_END(); + + object_init_ex(return_value, Z_CE_P(ZEND_THIS)); + php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(return_value); + + if (php_uri_query_params_parse(ZSTR_VAL(query_str), ZSTR_LEN(query_str), uri_query_params_object, + PHP_URI_QUERY_PARAMS_SPEC_WHATWG, false, options + ) == FAILURE) { + php_uri_query_params_object_free(Z_URI_QUERY_PARAMS_OBJECT_P(return_value)); + RETURN_NULL(); + } +} + +PHP_METHOD(Uri_QueryParams, fromArray) +{ + HashTable *query_params_arr; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY_HT(query_params_arr) + ZEND_PARSE_PARAMETERS_END(); + + zend_throw_exception(NULL, "Not implemented", 0); + + RETVAL_COPY(ZEND_THIS); +} + +PHP_METHOD(Uri_QueryParams, __construct) +{ + zval *options = NULL; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS_OR_NULL(options, php_uri_ce_query_param_options) + ZEND_PARSE_PARAMETERS_END(); + + php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + + if (php_uri_query_params_object_init(uri_query_params_object, options) == NULL) { + RETURN_THROWS(); + } +} + +PHP_METHOD(Uri_QueryParams, append) +{ + zend_string *name; + zval *value; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(name) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + + zend_string *value_str = php_uri_query_params_convert_value_to_str(uri_query_params_object, value); + if (EG(exception)) { + RETURN_THROWS(); + } + + php_uri_query_params_append_entry(uri_query_params_object, name, value_str); + + RETVAL_COPY(ZEND_THIS); +} + +PHP_METHOD(Uri_QueryParams, delete) +{ + zend_string *name; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(name) + ZEND_PARSE_PARAMETERS_END(); + + php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + + php_uri_query_params_delete_entries(uri_query_params_object, name); + + RETVAL_COPY(ZEND_THIS); +} + +PHP_METHOD(Uri_QueryParams, deleteValue) +{ + zend_string *name, *value; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(name) + Z_PARAM_STR_OR_NULL(value) + ZEND_PARSE_PARAMETERS_END(); + + php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + + php_uri_query_params_delete_entries_with_value(uri_query_params_object, name, value); + + RETVAL_COPY(ZEND_THIS); +} + +PHP_METHOD(Uri_QueryParams, has) +{ + zend_string *name; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(name) + ZEND_PARSE_PARAMETERS_END(); + + const php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + + RETVAL_BOOL(php_uri_query_params_has_entry(uri_query_params_object, name)); +} + +PHP_METHOD(Uri_QueryParams, hasValue) +{ + zend_string *name; + zval *value; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(name) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + const php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + + zend_string *value_str = php_uri_query_params_convert_value_to_str(uri_query_params_object, value); + if (EG(exception)) { + RETURN_THROWS(); + } + + RETVAL_BOOL(php_uri_query_params_has_entry_with_value(uri_query_params_object, name, value_str)); +} + +PHP_METHOD(Uri_QueryParams, getFirst) +{ + zend_string *name; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(name) + ZEND_PARSE_PARAMETERS_END(); + + const php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + + php_uri_query_params_find_first_value(uri_query_params_object, name, return_value); +} + +PHP_METHOD(Uri_QueryParams, getLast) +{ + zend_string *name; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(name) + ZEND_PARSE_PARAMETERS_END(); + + const php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + + php_uri_query_params_find_last_value(uri_query_params_object, name, return_value); +} + +PHP_METHOD(Uri_QueryParams, getAll) +{ + zend_string *name; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(name) + ZEND_PARSE_PARAMETERS_END(); + + const php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + + php_uri_query_params_find_all_values(uri_query_params_object, name, return_value); +} + +PHP_METHOD(Uri_QueryParams, list) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + + php_uri_query_params_list_entries(uri_query_params_object, return_value); +} + +PHP_METHOD(Uri_QueryParams, getIterator) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + zend_create_internal_iterator_zval(return_value, ZEND_THIS); +} + +PHP_METHOD(Uri_QueryParams, count) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + + RETVAL_LONG(zend_array_count(&uri_query_params_object->entry_list) - uri_query_params_object->tombstone_count); +} + +PHP_METHOD(Uri_QueryParams, set) +{ + zend_string *name; + zval *value; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(name) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + + zend_string *value_str = php_uri_query_params_convert_value_to_str(uri_query_params_object, value); + if (EG(exception)) { + RETURN_THROWS(); + } + + php_uri_query_params_set_entry(uri_query_params_object, name, value_str); + + RETVAL_COPY(ZEND_THIS); +} + +PHP_METHOD(Uri_QueryParams, sort) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + zend_throw_exception(NULL, "Not implemented", 0); + + RETVAL_COPY(ZEND_THIS); +} + +static void php_uri_query_params_navigate_and_insert(zval *current, const char *suffix, size_t suffix_len, zend_string *value) +{ + ZEND_ASSERT(Z_TYPE_P(current) == IS_ARRAY); + + if (suffix_len == 0) { + return; + } + + ZEND_ASSERT(suffix[0] == '['); + + const char *close = memchr(suffix + 1, ']', suffix_len - 1); + if (UNEXPECTED(close == NULL)) { + return; /* TODO malformed key */ + } + + const char *seg = suffix + 1; + size_t seg_len = (size_t) (close - seg); + const char *rest = close + 1; + size_t rest_len = suffix_len - (size_t) (rest - suffix); + + if (seg_len == 0) { + if (rest_len == 0) { + zval val; + ZVAL_STR_COPY(&val, value); + zend_hash_next_index_insert(Z_ARRVAL_P(current), &val); + } else { + zval new_arr; + array_init(&new_arr); + zval *inserted = zend_hash_next_index_insert(Z_ARRVAL_P(current), &new_arr); + if (EXPECTED(inserted != NULL)) { + php_uri_query_params_navigate_and_insert(inserted, rest, rest_len, value); + } + } + return; + } + + zend_long lval; + zval *nested; + + if (is_numeric_string(seg, seg_len, &lval, NULL, 0) == IS_LONG) { + /* Numeric key: foo[0] → integer index */ + nested = zend_hash_index_find(Z_ARRVAL_P(current), (zend_ulong) lval); + if (rest_len == 0) { + zval val; + ZVAL_STR_COPY(&val, value); + zend_hash_index_update(Z_ARRVAL_P(current), (zend_ulong) lval, &val); + } else { + if (nested == NULL || Z_TYPE_P(nested) != IS_ARRAY) { + zval new_arr; + array_init(&new_arr); + nested = zend_hash_index_update(Z_ARRVAL_P(current), (zend_ulong) lval, &new_arr); + } + if (EXPECTED(nested != NULL)) { + php_uri_query_params_navigate_and_insert(nested, rest, rest_len, value); + } + } + } else { + /* String key: foo[a] */ + zend_string *seg_str = zend_string_init(seg, seg_len, 0); + if (rest_len == 0) { + zval val; + ZVAL_STR_COPY(&val, value); + zend_hash_update(Z_ARRVAL_P(current), seg_str, &val); + } else { + nested = zend_hash_find(Z_ARRVAL_P(current), seg_str); + if (nested == NULL || Z_TYPE_P(nested) != IS_ARRAY) { + zval new_arr; + array_init(&new_arr); + nested = zend_hash_update(Z_ARRVAL_P(current), seg_str, &new_arr); + } + if (EXPECTED(nested != NULL)) { + php_uri_query_params_navigate_and_insert(nested, rest, rest_len, value); + } + } + zend_string_release(seg_str); + } +} + +static void php_uri_query_params_insert_entry( + zval *result, const php_uri_query_param_list_entry *list_entry, bool is_list +) { + const char *key = ZSTR_VAL(list_entry->key); + size_t key_len = ZSTR_LEN(list_entry->key); + + const char *bracket = memchr(key, '[', key_len); + size_t root_len = bracket != NULL ? (size_t) (bracket - key) : key_len; + const char *suffix = (bracket != NULL) ? bracket : NULL; + size_t suffix_len = (bracket != NULL) ? (key_len - root_len) : 0; + + if (suffix == NULL) { + /* Bare key: scalar or list */ + if (is_list) { + zval *existing = zend_hash_str_find(Z_ARRVAL_P(result), key, key_len); + if (existing == NULL) { + zval list; + array_init(&list); + zval val; + ZVAL_STR_COPY(&val, list_entry->value); + zend_hash_next_index_insert(Z_ARRVAL(list), &val); + zend_hash_str_add(Z_ARRVAL_P(result), key, key_len, &list); + } else { + ZEND_ASSERT(Z_TYPE_P(existing) == IS_ARRAY); + zval val; + ZVAL_STR_COPY(&val, list_entry->value); + zend_hash_next_index_insert(Z_ARRVAL_P(existing), &val); + } + } else { + zval val; + ZVAL_STR_COPY(&val, list_entry->value); + zend_hash_str_update(Z_ARRVAL_P(result), key, key_len, &val); + } + return; + } + + /* key with brackets: navigate */ + zval *container = zend_hash_str_find(Z_ARRVAL_P(result), key, root_len); + if (container == NULL || Z_TYPE_P(container) != IS_ARRAY) { + zval new_arr; + array_init(&new_arr); + container = zend_hash_str_update(Z_ARRVAL_P(result), key, root_len, &new_arr); + } + + if (EXPECTED(container != NULL)) { + php_uri_query_params_navigate_and_insert(container, suffix, suffix_len, list_entry->value); + } +} + +PHP_METHOD(Uri_QueryParams, toArray) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + const php_uri_query_params_object *obj = php_uri_query_params_object_from_obj(Z_OBJ_P(ZEND_THIS)); + + array_init(return_value); + + php_uri_query_param_list_entry *list_entry; + + ZEND_HASH_FOREACH_PTR(&obj->entry_list, list_entry) { + if (list_entry->key == NULL) { + continue; /* tombstone */ + } + + bool is_list = false; + if (memchr(ZSTR_VAL(list_entry->key), '[', ZSTR_LEN(list_entry->key)) == NULL) { + const php_uri_query_param_lookup_entry *lookup_entry = zend_hash_find_ptr(&obj->entry_lookup_table, list_entry->key); + is_list = lookup_entry != NULL && lookup_entry->first_index != lookup_entry->last_index; + } + + php_uri_query_params_insert_entry(return_value, list_entry, is_list); + + } ZEND_HASH_FOREACH_END(); +} + +PHP_METHOD(Uri_QueryParams, toRfc1866String) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + const php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + + zend_string *result = php_uri_query_params_to_rfc1866_string(uri_query_params_object); + if (result == NULL) { + throw_cannot_recompose_uri_to_string(Z_OBJ_P(ZEND_THIS)); + RETURN_THROWS(); + } + + ZVAL_STR(return_value, result); +} + +PHP_METHOD(Uri_QueryParams, toRfc3986String) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + const php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + + zend_string *result = php_uri_query_params_to_rfc3986_string(uri_query_params_object); + if (result == NULL) { + throw_cannot_recompose_uri_to_string(Z_OBJ_P(ZEND_THIS)); + RETURN_THROWS(); + } + + ZVAL_STR(return_value, result); +} + +PHP_METHOD(Uri_QueryParams, toWhatWgString) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + const php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + + zend_string *result = php_uri_query_params_to_whatwg_string(uri_query_params_object); + if (result == NULL) { + throw_cannot_recompose_uri_to_string(Z_OBJ_P(ZEND_THIS)); + RETURN_THROWS(); + } + + ZVAL_STR(return_value, result); +} + +PHP_METHOD(Uri_QueryParams, __serialize) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + + /* Serialize state: "query" key in the first array */ + zend_string *uri_query_param_str = php_uri_query_params_to_rfc3986_string(uri_query_params_object); + if (uri_query_param_str == NULL) { + throw_cannot_recompose_uri_to_string(Z_OBJ_P(ZEND_THIS)); + RETURN_THROWS(); + } + zval tmp; + ZVAL_STR(&tmp, uri_query_param_str); + + array_init(return_value); + + zval arr; + array_init(&arr); + zend_hash_str_add_new(Z_ARRVAL(arr), PHP_URI_SERIALIZE_URI_QUERY_PARAM_FIELD_NAME, sizeof(PHP_URI_SERIALIZE_URI_QUERY_PARAM_FIELD_NAME) - 1, &tmp); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &arr); + + /* Serialize regular properties: second array */ + ZVAL_ARR(&arr, uri_query_params_object->std.handlers->get_properties(&uri_query_params_object->std)); + Z_TRY_ADDREF(arr); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &arr); +} + +static void uri_query_params_unserialize(INTERNAL_FUNCTION_PARAMETERS) +{ + HashTable *data; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY_HT(data) + ZEND_PARSE_PARAMETERS_END(); + + php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + if (uri_query_params_object->is_initialized) { + /* Intentionally throw two exceptions for proper chaining. */ + zend_throw_error(NULL, "Cannot modify readonly object of class %s", ZSTR_VAL(uri_query_params_object->std.ce->name)); + zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_query_params_object->std.ce->name)); + RETURN_THROWS(); + } + + /* Verify the expected number of elements, this implicitly ensures that no additional elements are present. */ + if (zend_hash_num_elements(data) != 2) { + zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_query_params_object->std.ce->name)); + RETURN_THROWS(); + } + + /* Unserialize state: "query" key in the first array */ + const zval *arr = zend_hash_index_find(data, 0); + if (arr == NULL || Z_TYPE_P(arr) != IS_ARRAY) { + zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_query_params_object->std.ce->name)); + RETURN_THROWS(); + } + + /* Verify the expected number of elements inside the first array, this implicitly ensures that no additional elements are present. */ + if (zend_hash_num_elements(Z_ARRVAL_P(arr)) != 1) { + zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_query_params_object->std.ce->name)); + RETURN_THROWS(); + } + + const zval *uri_query_param_zv = zend_hash_str_find_ind(Z_ARRVAL_P(arr), ZEND_STRL(PHP_URI_SERIALIZE_URI_QUERY_PARAM_FIELD_NAME)); + if (uri_query_param_zv == NULL || Z_TYPE_P(uri_query_param_zv) != IS_STRING) { + zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_query_params_object->std.ce->name)); + RETURN_THROWS(); + } + + const zend_result result = SUCCESS; // TODO + + if (result == FAILURE) { + RETURN_THROWS(); + } + + uri_query_params_object->is_initialized = true; + + /* Unserialize regular properties: second array */ + arr = zend_hash_index_find(data, 1); + if (arr == NULL || Z_TYPE_P(arr) != IS_ARRAY) { + zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_query_params_object->std.ce->name)); + RETURN_THROWS(); + } + + /* Verify that there is no regular property in the second array, because the query param classes have no properties and they are final. */ + if (zend_hash_num_elements(Z_ARRVAL_P(arr)) > 0) { + zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(uri_query_params_object->std.ce->name)); + RETURN_THROWS(); + } +} + +PHP_METHOD(Uri_QueryParams, __unserialize) +{ + uri_query_params_unserialize(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} + +PHP_METHOD(Uri_QueryParams, __debugInfo) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + php_uri_query_params_object *uri_query_params_object = Z_URI_QUERY_PARAMS_OBJECT_P(ZEND_THIS); + + RETURN_ARR(php_uri_query_params_get_debug_properties(uri_query_params_object)); +} + +PHP_METHOD(Uri_QueryParamOptions, __construct) +{ + zend_long parsing_max_query_string_length = PARSING_MAX_QUERY_STRING_LENGTH_DEFAULT; + zend_long parsing_max_param_count = PARSING_MAX_QUERY_PARAM_COUNT_DEFAULT; + zend_string *true_value = NULL; + zend_string *false_value = NULL; + bool use_null_as_empty_string = USE_NULL_AS_EMPTY_STRING_DEFAULT; + + ZEND_PARSE_PARAMETERS_START(0, 5) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(parsing_max_query_string_length) + Z_PARAM_LONG(parsing_max_param_count) + Z_PARAM_STR(true_value) + Z_PARAM_STR(false_value) + Z_PARAM_BOOL(use_null_as_empty_string) + ZEND_PARSE_PARAMETERS_END(); + + if (parsing_max_query_string_length < 0) { + zend_argument_value_error(1, "must be greater than or equal to 0"); + RETURN_THROWS(); + } + + if (parsing_max_param_count < 0) { + zend_argument_value_error(2, "must be greater than or equal to 0"); + RETURN_THROWS(); + } + + if (php_uri_query_param_options_update_properties( + Z_OBJ_P(ZEND_THIS), parsing_max_query_string_length, parsing_max_param_count, true_value, false_value, + use_null_as_empty_string + ) == FAILURE) { + RETURN_THROWS(); + } +} + +PHP_METHOD(Uri_QueryParamOptions, __debugInfo) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + const php_uri_query_param_options_object *object = Z_URI_QUERY_PARAM_OPTIONS_OBJECT_P(ZEND_THIS); + + array_init(return_value); + + add_assoc_long(return_value, "parsingMaxQueryStringLength", object->parsing_max_query_string_length); + add_assoc_long(return_value, "parsingMaxParamCount", object->parsing_max_param_count); + add_assoc_str(return_value, "trueValue", zend_string_copy(object->true_value)); + add_assoc_str(return_value, "falseValue", zend_string_copy(object->false_value)); + add_assoc_bool(return_value, "useNullAsLong", object->use_null_as_string); +} diff --git a/ext/uri/php_uri_query.h b/ext/uri/php_uri_query.h new file mode 100644 index 000000000000..358bc4d31378 --- /dev/null +++ b/ext/uri/php_uri_query.h @@ -0,0 +1,42 @@ +/* + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Authors: Máté Kocsis | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_URI_QUERY_H +#define PHP_URI_QUERY_H + +#define php_uri_query_params_object_from_obj(object) ZEND_CONTAINER_OF(object, php_uri_query_params_object, std) + +#define Z_URI_QUERY_PARAMS_OBJECT_P(zv) php_uri_query_params_object_from_obj(Z_OBJ_P((zv))) + +#define php_uri_query_param_options_object_from_obj(object) ZEND_CONTAINER_OF(object, php_uri_query_param_options_object, std) + +#define Z_URI_QUERY_PARAM_OPTIONS_OBJECT_P(zv) php_uri_query_param_options_object_from_obj(Z_OBJ_P((zv))) + +ZEND_ATTRIBUTE_NONNULL zend_object *php_uri_object_query_params_create_object(zend_class_entry *ce); +ZEND_ATTRIBUTE_NONNULL void php_uri_query_params_object_free(php_uri_query_params_object *uri_query_params_object); +ZEND_ATTRIBUTE_NONNULL void php_uri_query_params_object_handler_free(zend_object *object); +ZEND_ATTRIBUTE_NONNULL zend_object *php_uri_query_params_object_handler_clone(zend_object *object); +ZEND_ATTRIBUTE_NONNULL zend_object_iterator *php_uri_object_query_params_get_iterator(zend_class_entry *ce, zval *object, int by_ref); + +ZEND_ATTRIBUTE_NONNULL zend_object *php_uri_object_create_query_param_options(zend_class_entry *ce); +ZEND_ATTRIBUTE_NONNULL void php_uri_query_param_options_object_free(php_uri_query_param_options_object *uri_query_param_options_object); +ZEND_ATTRIBUTE_NONNULL void php_uri_query_param_options_object_handler_free(zend_object *object); +ZEND_ATTRIBUTE_NONNULL zend_object *php_uri_query_param_options_object_handler_clone(zend_object *object); +ZEND_ATTRIBUTE_NONNULL int php_uri_query_param_options_object_handler_has_property(zend_object *obj, zend_string *name, int check_empty, void **cache_slot); +ZEND_ATTRIBUTE_NONNULL zval *php_uri_query_param_options_object_handler_read_property(zend_object *obj, zend_string *name, int type, void **cache_slot, zval *rv); +ZEND_ATTRIBUTE_NONNULL zval *php_uri_query_param_options_object_handler_write_property(zend_object *obj, zend_string *name, zval *value, void **cache_slot); +ZEND_ATTRIBUTE_NONNULL zval *php_uri_query_param_options_object_handler_get_property_ptr_ptr(zend_object *object, zend_string *member, int type, void **cache_slot); +ZEND_ATTRIBUTE_NONNULL void php_uri_query_param_options_object_handler_unset_property(zend_object *obj, zend_string *name, void **cache_slot); + +#endif diff --git a/ext/uri/tests/query_params/append_error_enum_unbacked.phpt b/ext/uri/tests/query_params/append_error_enum_unbacked.phpt new file mode 100644 index 000000000000..b8fc66758473 --- /dev/null +++ b/ext/uri/tests/query_params/append_error_enum_unbacked.phpt @@ -0,0 +1,20 @@ +--TEST-- +Test Uri\QueryParams::append() - error - unbacked enum +--FILE-- +append("foo", TestEnum::Bar); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +ValueError: Uri\QueryParams::append(): Argument #2 ($value) must not contain an object diff --git a/ext/uri/tests/query_params/append_error_object.phpt b/ext/uri/tests/query_params/append_error_object.phpt new file mode 100644 index 000000000000..624f95bd43b4 --- /dev/null +++ b/ext/uri/tests/query_params/append_error_object.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\QueryParams::append() - error - object +--FILE-- +append("foo", new stdClass()); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +ValueError: Uri\QueryParams::append(): Argument #2 ($value) must not contain an object diff --git a/ext/uri/tests/query_params/append_error_resource.phpt b/ext/uri/tests/query_params/append_error_resource.phpt new file mode 100644 index 000000000000..e5b82df313da --- /dev/null +++ b/ext/uri/tests/query_params/append_error_resource.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\QueryParams::append() - error - resource +--FILE-- +append("foo", fopen("php://memory", "r")); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +ValueError: Uri\QueryParams::append(): Argument #2 ($value) must not contain a resource diff --git a/ext/uri/tests/query_params/append_success_array.phpt b/ext/uri/tests/query_params/append_success_array.phpt new file mode 100644 index 000000000000..bd5e35727955 --- /dev/null +++ b/ext/uri/tests/query_params/append_success_array.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test Uri\QueryParams::append() - success - array value +--FILE-- +append("baz", [null, false, "key2" => true, 123, "", ["bla"]]); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECTF-- +object(Uri\QueryParams)#%d (%d) { + ["baz[0]"]=> + NULL + ["baz[1]"]=> + string(1) "0" + ["baz[key2]"]=> + string(1) "1" + ["baz[2]"]=> + string(3) "123" + ["baz[3]"]=> + string(0) "" + ["baz[4][]"]=> + string(3) "bla" +} +string(87) "baz%5B0%5D&baz%5B1%5D=0&baz%5Bkey2%5D=1&baz%5B2%5D=123&baz%5B3%5D=&baz%5B4%5D%5B%5D=bla" diff --git a/ext/uri/tests/query_params/append_success_array_empty.phpt b/ext/uri/tests/query_params/append_success_array_empty.phpt new file mode 100644 index 000000000000..8cabc0e0668f --- /dev/null +++ b/ext/uri/tests/query_params/append_success_array_empty.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test Uri\QueryParams::append() - success - empty array value +--FILE-- +append("baz", []); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECTF-- +object(Uri\QueryParams)#%d (%d) { + ["params"]=> + array(2) { + [0]=> + array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "bar" + } + [1]=> + array(2) { + [0]=> + string(3) "baz" + [1]=> + NULL + } + } +} +string(11) "foo=bar&baz" diff --git a/ext/uri/tests/query_params/append_success_array_multi_level_list.phpt b/ext/uri/tests/query_params/append_success_array_multi_level_list.phpt new file mode 100644 index 000000000000..3c970e5d9626 --- /dev/null +++ b/ext/uri/tests/query_params/append_success_array_multi_level_list.phpt @@ -0,0 +1,20 @@ +--TEST-- +Test Uri\QueryParams::append() - success - multi-level array value +--FILE-- +append("foo", [[0 => "bar"], [123]]); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECTF-- +object(Uri\QueryParams)#%d (%d) { + ["foo[][]"]=> + string(3) "bar" + ["foo[][]"]=> + string(3) "123" +} +string(39) "foo%5B%5D%5B%5D=bar&foo%5B%5D%5B%5D=123" diff --git a/ext/uri/tests/query_params/append_success_array_multi_level_map.phpt b/ext/uri/tests/query_params/append_success_array_multi_level_map.phpt new file mode 100644 index 000000000000..96b2ff530e86 --- /dev/null +++ b/ext/uri/tests/query_params/append_success_array_multi_level_map.phpt @@ -0,0 +1,61 @@ +--TEST-- +Test Uri\QueryParams::append() - success - multi-level array value +--FILE-- +append("foo", ["bar" => [0 => "baz", 2 => 123, 3 => [], 4 => [1, 2, 3]]]); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECTF-- +object(Uri\QueryParams)#%d (%d) { + ["params"]=> + array(6) { + [0]=> + array(2) { + [0]=> + string(1) "0" + [1]=> + string(3) "baz" + } + [1]=> + array(2) { + [0]=> + string(11) "foo[bar][2]" + [1]=> + string(3) "123" + } + [2]=> + array(2) { + [0]=> + string(11) "foo[bar][3]" + [1]=> + NULL + } + [3]=> + array(2) { + [0]=> + string(13) "foo[bar][4][]" + [1]=> + string(1) "1" + } + [4]=> + array(2) { + [0]=> + string(13) "foo[bar][4][]" + [1]=> + string(1) "2" + } + [5]=> + array(2) { + [0]=> + string(13) "foo[bar][4][]" + [1]=> + string(1) "3" + } + } +} +string(133) "0=baz&foo%5Bbar%5D%5B2%5D=123&foo%5Bbar%5D%5B3%5D&foo%5Bbar%5D%5B4%5D%5B%5D=1&foo%5Bbar%5D%5B4%5D%5B%5D=2&foo%5Bbar%5D%5B4%5D%5B%5D=3" diff --git a/ext/uri/tests/query_params/append_success_basic.phpt b/ext/uri/tests/query_params/append_success_basic.phpt new file mode 100644 index 000000000000..a2d70d8a0a28 --- /dev/null +++ b/ext/uri/tests/query_params/append_success_basic.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\QueryParams::append() - success - basic +--FILE-- +append("baz", "qux"); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(3) { + [0]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "123" + } + [1]=> + array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "bar" + } + [2]=> + array(2) { + [0]=> + string(3) "baz" + [1]=> + string(3) "qux" + } +} +string(23) "abc=123&foo=bar&baz=qux" diff --git a/ext/uri/tests/query_params/append_success_bool.phpt b/ext/uri/tests/query_params/append_success_bool.phpt new file mode 100644 index 000000000000..1c91adf3d1e9 --- /dev/null +++ b/ext/uri/tests/query_params/append_success_bool.phpt @@ -0,0 +1,45 @@ +--TEST-- +Test Uri\QueryParams::append() - success - bool value +--FILE-- +append("baz", false); +$params->append("qux", true); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(4) { + [0]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "123" + } + [1]=> + array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "bar" + } + [2]=> + array(2) { + [0]=> + string(3) "baz" + [1]=> + string(1) "0" + } + [3]=> + array(2) { + [0]=> + string(3) "qux" + [1]=> + string(1) "1" + } +} +string(27) "abc=123&foo=bar&baz=0&qux=1" diff --git a/ext/uri/tests/query_params/append_success_enum_backed.phpt b/ext/uri/tests/query_params/append_success_enum_backed.phpt new file mode 100644 index 000000000000..9b56cc8092d7 --- /dev/null +++ b/ext/uri/tests/query_params/append_success_enum_backed.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test Uri\QueryParams::append() - success - backed enum +--FILE-- +append("foo", StringEnum::Bar); +$params->append("baz", IntEnum::Baz); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(2) { + [0]=> + array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "bar" + } + [1]=> + array(2) { + [0]=> + string(3) "baz" + [1]=> + string(3) "123" + } +} +string(15) "foo=bar&baz=123" diff --git a/ext/uri/tests/query_params/append_success_existing.phpt b/ext/uri/tests/query_params/append_success_existing.phpt new file mode 100644 index 000000000000..3f46db148f0d --- /dev/null +++ b/ext/uri/tests/query_params/append_success_existing.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\QueryParams::append() - success - existing parameter +--FILE-- +append("foo", "bar"); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(3) { + [0]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "123" + } + [1]=> + array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "bar" + } + [2]=> + array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "bar" + } +} +string(23) "abc=123&foo=bar&foo=bar" diff --git a/ext/uri/tests/query_params/append_success_int.phpt b/ext/uri/tests/query_params/append_success_int.phpt new file mode 100644 index 000000000000..772c1c3aa8a8 --- /dev/null +++ b/ext/uri/tests/query_params/append_success_int.phpt @@ -0,0 +1,38 @@ +--TEST-- +Test Uri\QueryParams::append() - success - integer value + +--FILE-- +append("baz", 0); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(3) { + [0]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "123" + } + [1]=> + array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "bar" + } + [2]=> + array(2) { + [0]=> + string(3) "baz" + [1]=> + string(1) "0" + } +} +string(21) "abc=123&foo=bar&baz=0" diff --git a/ext/uri/tests/query_params/append_success_list.phpt b/ext/uri/tests/query_params/append_success_list.phpt new file mode 100644 index 000000000000..9b2b052c3034 --- /dev/null +++ b/ext/uri/tests/query_params/append_success_list.phpt @@ -0,0 +1,47 @@ +--TEST-- +Test Uri\QueryParams::append() - success - list +--FILE-- +append("foo", [1, 2, 3]); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECTF-- +object(Uri\QueryParams)#%d (%d) { + ["params"]=> + array(4) { + [0]=> + array(2) { + [0]=> + string(3) "foo" + [1]=> + string(1) "0" + } + [1]=> + array(2) { + [0]=> + string(5) "foo[]" + [1]=> + string(1) "1" + } + [2]=> + array(2) { + [0]=> + string(5) "foo[]" + [1]=> + string(1) "2" + } + [3]=> + array(2) { + [0]=> + string(5) "foo[]" + [1]=> + string(1) "3" + } + } +} +string(41) "foo=0&foo%5B%5D=1&foo%5B%5D=2&foo%5B%5D=3" diff --git a/ext/uri/tests/query_params/append_success_null.phpt b/ext/uri/tests/query_params/append_success_null.phpt new file mode 100644 index 000000000000..b2f48a161ce1 --- /dev/null +++ b/ext/uri/tests/query_params/append_success_null.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\QueryParams::append() - success - null value +--FILE-- +append("baz", null); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(3) { + [0]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "123" + } + [1]=> + array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "bar" + } + [2]=> + array(2) { + [0]=> + string(3) "baz" + [1]=> + NULL + } +} +string(19) "abc=123&foo=bar&baz" diff --git a/ext/uri/tests/query_params/construct_success_basic.phpt b/ext/uri/tests/query_params/construct_success_basic.phpt new file mode 100644 index 000000000000..a6cb468cd2e7 --- /dev/null +++ b/ext/uri/tests/query_params/construct_success_basic.phpt @@ -0,0 +1,31 @@ +--TEST-- +Test Uri\QueryParams::__construct() - success - basic +--FILE-- +toRfc3986String()); + +?> +--EXPECTF-- +object(Uri\QueryParams)#%d (%d) { + ["options":"Uri\QueryParams":private]=> + object(Uri\QueryParamOptions)#%d (%d) { + ["parsingMaxQueryStringLength"]=> + int(10000) + ["parsingMaxParamCount"]=> + int(1000) + ["trueValue"]=> + string(1) "1" + ["falseValue"]=> + string(1) "0" + ["useNullAsLong"]=> + bool(false) + } + ["params"]=> + array(0) { + } +} +string(0) "" diff --git a/ext/uri/tests/query_params/construct_success_options.phpt b/ext/uri/tests/query_params/construct_success_options.phpt new file mode 100644 index 000000000000..b348a25ab476 --- /dev/null +++ b/ext/uri/tests/query_params/construct_success_options.phpt @@ -0,0 +1,31 @@ +--TEST-- +Test Uri\QueryParams::__construct() - success - with options set +--FILE-- +toRfc3986String()); + +?> +--EXPECTF-- +object(Uri\QueryParams)#%d (%d) { + ["options":"Uri\QueryParams":private]=> + object(Uri\QueryParamOptions)#%d (%d) { + ["parsingMaxQueryStringLength"]=> + int(100) + ["parsingMaxParamCount"]=> + int(10) + ["trueValue"]=> + string(4) "true" + ["falseValue"]=> + string(5) "false" + ["useNullAsLong"]=> + bool(true) + } + ["params"]=> + array(0) { + } +} +string(0) "" diff --git a/ext/uri/tests/query_params/delete_success_existing.phpt b/ext/uri/tests/query_params/delete_success_existing.phpt new file mode 100644 index 000000000000..1851910ea492 --- /dev/null +++ b/ext/uri/tests/query_params/delete_success_existing.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test Uri\QueryParams::delete() - success - basic +--FILE-- +delete("abc"); +$params->delete("foo"); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(0) { +} +string(0) "" diff --git a/ext/uri/tests/query_params/delete_success_multiple.phpt b/ext/uri/tests/query_params/delete_success_multiple.phpt new file mode 100644 index 000000000000..83bc5fc3799f --- /dev/null +++ b/ext/uri/tests/query_params/delete_success_multiple.phpt @@ -0,0 +1,23 @@ +--TEST-- +Test Uri\QueryParams::delete() - success - multiple values +--FILE-- +delete("abc"); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(1) { + [0]=> + array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "bar" + } +} +string(7) "foo=bar" diff --git a/ext/uri/tests/query_params/delete_success_non_existent.phpt b/ext/uri/tests/query_params/delete_success_non_existent.phpt new file mode 100644 index 000000000000..4d90c35ce8b4 --- /dev/null +++ b/ext/uri/tests/query_params/delete_success_non_existent.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test Uri\QueryParams::delete() - success - non-existent key +--FILE-- +delete("xyz"); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(2) { + [0]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "123" + } + [1]=> + array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "bar" + } +} +string(15) "abc=123&foo=bar" diff --git a/ext/uri/tests/query_params/delete_value_success_existing_key_missing_value.phpt b/ext/uri/tests/query_params/delete_value_success_existing_key_missing_value.phpt new file mode 100644 index 000000000000..872d19ac613b --- /dev/null +++ b/ext/uri/tests/query_params/delete_value_success_existing_key_missing_value.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\QueryParams::deleteValue() - success - existing key, missing value +--FILE-- +deleteValue("abc", ""); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(3) { + [0]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "123" + } + [1]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "456" + } + [2]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + NULL + } +} +string(19) "abc=123&abc=456&abc" diff --git a/ext/uri/tests/query_params/delete_value_success_existing_key_value.phpt b/ext/uri/tests/query_params/delete_value_success_existing_key_value.phpt new file mode 100644 index 000000000000..97ecba7a0afd --- /dev/null +++ b/ext/uri/tests/query_params/delete_value_success_existing_key_value.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test Uri\QueryParams::deleteValue() - success - existing key, existing value +--FILE-- +deleteValue("abc", 456); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(2) { + [0]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "123" + } + [1]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "789" + } +} +string(15) "abc=123&abc=789" diff --git a/ext/uri/tests/query_params/delete_value_success_int.phpt b/ext/uri/tests/query_params/delete_value_success_int.phpt new file mode 100644 index 000000000000..de266c89fad8 --- /dev/null +++ b/ext/uri/tests/query_params/delete_value_success_int.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test Uri\QueryParams::deleteValue() - success - int value +--FILE-- +deleteValue("abc", 123); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(2) { + [0]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "456" + } + [1]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "foo" + } +} +string(15) "abc=456&abc=foo" diff --git a/ext/uri/tests/query_params/delete_value_success_missing_key.phpt b/ext/uri/tests/query_params/delete_value_success_missing_key.phpt new file mode 100644 index 000000000000..c4e03a96b810 --- /dev/null +++ b/ext/uri/tests/query_params/delete_value_success_missing_key.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test Uri\QueryParams::deleteValue() - success - missing key +--FILE-- +deleteValue("def", 456); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(3) { + [0]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "123" + } + [1]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "456" + } + [2]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "789" + } +} +string(23) "abc=123&abc=456&abc=789" diff --git a/ext/uri/tests/query_params/delete_value_success_null.phpt b/ext/uri/tests/query_params/delete_value_success_null.phpt new file mode 100644 index 000000000000..9e346a30b4c5 --- /dev/null +++ b/ext/uri/tests/query_params/delete_value_success_null.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test Uri\QueryParams::deleteValue() - success - null value +--FILE-- +deleteValue("abc", null); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(2) { + [0]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "123" + } + [1]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "456" + } +} +string(15) "abc=123&abc=456" diff --git a/ext/uri/tests/query_params/get_iterator_basic.phpt b/ext/uri/tests/query_params/get_iterator_basic.phpt new file mode 100644 index 000000000000..b5f0456c4f84 --- /dev/null +++ b/ext/uri/tests/query_params/get_iterator_basic.phpt @@ -0,0 +1,31 @@ +--TEST-- +Test Uri\QueryParams::getIterator() - success - basic +--FILE-- + +--EXPECT-- +array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "123" +} +array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "456" +} +array(2) { + [0]=> + string(3) "bar" + [1]=> + string(3) "789" +} diff --git a/ext/uri/tests/query_params/has_success_existing.phpt b/ext/uri/tests/query_params/has_success_existing.phpt new file mode 100644 index 000000000000..d419158fe23b --- /dev/null +++ b/ext/uri/tests/query_params/has_success_existing.phpt @@ -0,0 +1,12 @@ +--TEST-- +Test Uri\QueryParams::has() - success - existing +--FILE-- +has("abc")); + +?> +--EXPECT-- +bool(true) diff --git a/ext/uri/tests/query_params/has_success_non_existent.phpt b/ext/uri/tests/query_params/has_success_non_existent.phpt new file mode 100644 index 000000000000..d3d2a3f866f1 --- /dev/null +++ b/ext/uri/tests/query_params/has_success_non_existent.phpt @@ -0,0 +1,12 @@ +--TEST-- +Test Uri\QueryParams::has() - success - non-existent +--FILE-- +has("bar")); + +?> +--EXPECT-- +bool(false) diff --git a/ext/uri/tests/query_params/has_value_success_existing_key_missing_value.phpt b/ext/uri/tests/query_params/has_value_success_existing_key_missing_value.phpt new file mode 100644 index 000000000000..8515b9a34e15 --- /dev/null +++ b/ext/uri/tests/query_params/has_value_success_existing_key_missing_value.phpt @@ -0,0 +1,12 @@ +--TEST-- +Test Uri\QueryParams::hasValue() - success - existing key, missing value +--FILE-- +hasValue("foo", 123)); + +?> +--EXPECT-- +bool(false) diff --git a/ext/uri/tests/query_params/has_value_success_existing_key_value.phpt b/ext/uri/tests/query_params/has_value_success_existing_key_value.phpt new file mode 100644 index 000000000000..a5e4a53d43d5 --- /dev/null +++ b/ext/uri/tests/query_params/has_value_success_existing_key_value.phpt @@ -0,0 +1,12 @@ +--TEST-- +Test Uri\QueryParams::hasValue() - success - existing key, existing value +--FILE-- +hasValue("foo", "bar")); + +?> +--EXPECT-- +bool(true) diff --git a/ext/uri/tests/query_params/has_value_success_int.phpt b/ext/uri/tests/query_params/has_value_success_int.phpt new file mode 100644 index 000000000000..260ac3c1c118 --- /dev/null +++ b/ext/uri/tests/query_params/has_value_success_int.phpt @@ -0,0 +1,12 @@ +--TEST-- +Test Uri\QueryParams::hasValue() - success - int value +--FILE-- +hasValue("abc", "123")); + +?> +--EXPECT-- +bool(true) diff --git a/ext/uri/tests/query_params/has_value_success_missing_key.phpt b/ext/uri/tests/query_params/has_value_success_missing_key.phpt new file mode 100644 index 000000000000..89627230ea88 --- /dev/null +++ b/ext/uri/tests/query_params/has_value_success_missing_key.phpt @@ -0,0 +1,12 @@ +--TEST-- +Test Uri\QueryParams::hasValue() - success - missing key +--FILE-- +hasValue("baz", "abc")); + +?> +--EXPECT-- +bool(false) diff --git a/ext/uri/tests/query_params/parse_rfc3986_success_basic.phpt b/ext/uri/tests/query_params/parse_rfc3986_success_basic.phpt new file mode 100644 index 000000000000..41eaf6e674cd --- /dev/null +++ b/ext/uri/tests/query_params/parse_rfc3986_success_basic.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test Uri\QueryParams::parseRfc3986() - success - basic +--FILE-- +list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(2) { + [0]=> + array(2) { + [0]=> + string(3) "abc" + [1]=> + string(3) "123" + } + [1]=> + array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "bar" + } +} +string(15) "abc=123&foo=bar" diff --git a/ext/uri/tests/query_params/parse_rfc3986_success_empty.phpt b/ext/uri/tests/query_params/parse_rfc3986_success_empty.phpt new file mode 100644 index 000000000000..cb1c04f51886 --- /dev/null +++ b/ext/uri/tests/query_params/parse_rfc3986_success_empty.phpt @@ -0,0 +1,15 @@ +--TEST-- +Test Uri\QueryParams::parseRfc3986() - success - empty string +--FILE-- +list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(0) { +} +string(0) "" diff --git a/ext/uri/tests/query_params/parse_rfc3986_success_empty_key.phpt b/ext/uri/tests/query_params/parse_rfc3986_success_empty_key.phpt new file mode 100644 index 000000000000..281c069840a1 --- /dev/null +++ b/ext/uri/tests/query_params/parse_rfc3986_success_empty_key.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test Uri\QueryParams::parseRfc3986() - success - empty query param pair +--FILE-- +list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(2) { + [0]=> + array(2) { + [0]=> + string(0) "" + [1]=> + string(1) "1" + } + [1]=> + array(2) { + [0]=> + string(0) "" + [1]=> + string(1) "2" + } +} +string(5) "=1&=2" diff --git a/ext/uri/tests/query_params/parse_rfc3986_success_empty_pair.phpt b/ext/uri/tests/query_params/parse_rfc3986_success_empty_pair.phpt new file mode 100644 index 000000000000..dc5020c0b595 --- /dev/null +++ b/ext/uri/tests/query_params/parse_rfc3986_success_empty_pair.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test Uri\QueryParams::parseRfc3986() - success - empty query param pair +--FILE-- +list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(2) { + [0]=> + array(2) { + [0]=> + string(1) "a" + [1]=> + string(1) "1" + } + [1]=> + array(2) { + [0]=> + string(1) "b" + [1]=> + string(1) "1" + } +} +string(7) "a=1&b=1" diff --git a/ext/uri/tests/query_params/parse_rfc3986_success_empty_value.phpt b/ext/uri/tests/query_params/parse_rfc3986_success_empty_value.phpt new file mode 100644 index 000000000000..160b3ba83774 --- /dev/null +++ b/ext/uri/tests/query_params/parse_rfc3986_success_empty_value.phpt @@ -0,0 +1,22 @@ +--TEST-- +Test Uri\QueryParams::parseRfc3986() - success - empty query param pair +--FILE-- +list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(1) { + [0]=> + array(2) { + [0]=> + string(1) "a" + [1]=> + string(0) "" + } +} +string(2) "a=" diff --git a/ext/uri/tests/query_params/parse_rfc3986_success_null.phpt b/ext/uri/tests/query_params/parse_rfc3986_success_null.phpt new file mode 100644 index 000000000000..c96ea048b610 --- /dev/null +++ b/ext/uri/tests/query_params/parse_rfc3986_success_null.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test Uri\QueryParams::parseRfc3986() - success - null value +--FILE-- +list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(2) { + [0]=> + array(2) { + [0]=> + string(1) "a" + [1]=> + NULL + } + [1]=> + array(2) { + [0]=> + string(1) "b" + [1]=> + string(1) "c" + } +} +string(5) "a&b=c" diff --git a/ext/uri/tests/query_params/parse_rfc3986_success_reserved.phpt b/ext/uri/tests/query_params/parse_rfc3986_success_reserved.phpt new file mode 100644 index 000000000000..d23ea53b7fd2 --- /dev/null +++ b/ext/uri/tests/query_params/parse_rfc3986_success_reserved.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test Uri\QueryParams::parseRfc3986() - success - reserved characters are not validated +--FILE-- +=b@r"); + +var_dump($params->list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(2) { + [0]=> + array(2) { + [0]=> + string(4) "ab:c" + [1]=> + string(4) "#123" + } + [1]=> + array(2) { + [0]=> + string(5) "" + [1]=> + string(3) "b@r" + } +} +string(25) "ab:c=%23123&%3Cfoo%3E=b@r" diff --git a/ext/uri/tests/query_params/parse_rfc3986_success_space.phpt b/ext/uri/tests/query_params/parse_rfc3986_success_space.phpt new file mode 100644 index 000000000000..37b4d1ff09f7 --- /dev/null +++ b/ext/uri/tests/query_params/parse_rfc3986_success_space.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test Uri\QueryParams::parseRfc3986() - success - space character +--FILE-- +list()); +var_dump($params->toRfc3986String()); + +?> +--EXPECT-- +array(2) { + [0]=> + array(2) { + [0]=> + string(3) "a c" + [1]=> + string(3) "123" + } + [1]=> + array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "b r" + } +} +string(19) "a%20c=123&foo=b%20r" diff --git a/ext/uri/tests/query_params/to_array_success_multi_dimension.phpt b/ext/uri/tests/query_params/to_array_success_multi_dimension.phpt new file mode 100644 index 000000000000..9d2bf9dc0e55 --- /dev/null +++ b/ext/uri/tests/query_params/to_array_success_multi_dimension.phpt @@ -0,0 +1,23 @@ +--TEST-- +Test Uri\QueryParams::toArray() - success - multi-dimension array +--FILE-- +toArray()); + +?> +--EXPECT-- +array(1) { + ["foo"]=> + array(2) { + ["bar"]=> + array(1) { + ["baz"]=> + string(3) "123" + } + ["qux"]=> + string(4) "quux" + } +} diff --git a/ext/uri/tests/query_params/to_array_success_multiple_keys_with_brackets.phpt b/ext/uri/tests/query_params/to_array_success_multiple_keys_with_brackets.phpt new file mode 100644 index 000000000000..4ef744e92a75 --- /dev/null +++ b/ext/uri/tests/query_params/to_array_success_multiple_keys_with_brackets.phpt @@ -0,0 +1,20 @@ +--TEST-- +Test Uri\QueryParams::toArray() - success - multiple keys, using brackets +--FILE-- +toArray()); + +?> +--EXPECT-- +array(1) { + ["abc"]=> + array(2) { + [0]=> + string(3) "123" + [1]=> + string(3) "456" + } +} diff --git a/ext/uri/tests/query_params/to_array_success_multiple_keys_without_brackets.phpt b/ext/uri/tests/query_params/to_array_success_multiple_keys_without_brackets.phpt new file mode 100644 index 000000000000..c65e35d89358 --- /dev/null +++ b/ext/uri/tests/query_params/to_array_success_multiple_keys_without_brackets.phpt @@ -0,0 +1,20 @@ +--TEST-- +Test Uri\QueryParams::toArray() - success - multiple keys, without brackets +--FILE-- +toArray()); + +?> +--EXPECT-- +array(1) { + ["abc"]=> + array(2) { + [0]=> + string(3) "123" + [1]=> + string(3) "456" + } +} diff --git a/ext/uri/tests/query_params/to_array_success_one_key_with_brackets.phpt b/ext/uri/tests/query_params/to_array_success_one_key_with_brackets.phpt new file mode 100644 index 000000000000..72a55f71cff3 --- /dev/null +++ b/ext/uri/tests/query_params/to_array_success_one_key_with_brackets.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test Uri\QueryParams::toArray() - success - one key, using the bracket syntax +--FILE-- +toArray()); + +?> +--EXPECT-- +array(1) { + ["abc"]=> + array(1) { + [0]=> + string(3) "123" + } +} diff --git a/ext/uri/tests/query_params/to_array_success_one_key_without_brackets.phpt b/ext/uri/tests/query_params/to_array_success_one_key_without_brackets.phpt new file mode 100644 index 000000000000..fe105deeadaf --- /dev/null +++ b/ext/uri/tests/query_params/to_array_success_one_key_without_brackets.phpt @@ -0,0 +1,15 @@ +--TEST-- +Test Uri\QueryParams::toArray() - success - one key, without brackets +--FILE-- +toArray()); + +?> +--EXPECT-- +array(1) { + ["abc"]=> + string(3) "123" +} diff --git a/ext/uri/uri_parser_rfc3986.c b/ext/uri/uri_parser_rfc3986.c index 88201914d593..0014f5b1b103 100644 --- a/ext/uri/uri_parser_rfc3986.c +++ b/ext/uri/uri_parser_rfc3986.c @@ -679,6 +679,90 @@ ZEND_ATTRIBUTE_NONNULL static zend_string *php_uri_parser_rfc3986_to_string(void return uri_string; } +zend_string *php_uri_parser_rfc3986_query_component_percent_decode(const char *str, const size_t str_length, const bool plus_to_space) +{ + if (UNEXPECTED(str == NULL)) { + return NULL; + } + + if (UNEXPECTED(str_length == 0)) { + return zend_empty_string; + } + + const bool needs_percent_decoding = memchr(str, '%', strlen(str)); + if (EXPECTED(!needs_percent_decoding)) { + return zend_string_init(str, str_length, false); + } + + zend_string *result = zend_string_alloc(str_length, false); + + memcpy(ZSTR_VAL(result), str, str_length); + ZSTR_VAL(result)[str_length] = '\0'; + + const char *end = uriUnescapeInPlaceExA(ZSTR_VAL(result), plus_to_space, URI_BR_DONT_TOUCH); + + ZSTR_LEN(result) = (size_t)(end - ZSTR_VAL(result)); + + return result; +} + +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_rfc3986_query_component_percent_encode(const char *str, const size_t str_length, const bool space_to_plus) +{ + zend_string *result = zend_string_alloc(str_length * 3, false); + + const char *end; + + if (space_to_plus) { + end = uriEscapeExA(str, str + str_length, ZSTR_VAL(result), space_to_plus, URI_BR_DONT_TOUCH); + } else { + end = uriEscapeQueryRfc3986ExA(str, str + str_length, ZSTR_VAL(result), URI_BR_DONT_TOUCH); + } + + ZSTR_LEN(result) = (size_t)(end - ZSTR_VAL(result)); + + return result; +} + +ZEND_ATTRIBUTE_NONNULL static void php_uri_parser_rfc3986_query_component_append_to_string( + const zend_string *component, const bool space_to_plus, smart_str *buf +) { + const bool needs_percent_encoding = true; // TODO + if (UNEXPECTED(needs_percent_encoding)) { + smart_str_append(buf, php_uri_parser_rfc3986_query_component_percent_encode(ZSTR_VAL(component), ZSTR_LEN(component), space_to_plus)); + } else { + smart_str_appendl(buf, ZSTR_VAL(component), ZSTR_LEN(component)); + } +} + +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_rfc3986_query_params_to_string( + const php_uri_query_params_object *query_params_object, const bool space_to_plus +) { + smart_str buf = {0}; + bool is_first = true; + + ZEND_HASH_FOREACH_PTR(&query_params_object->entry_list, php_uri_query_param_list_entry *entry) { + const zend_string *name = entry->key; + const zend_string *value = entry->value; + + if (name == NULL) { + continue; + } + + if (!is_first) { + smart_str_appendc(&buf, '&'); + } + is_first = false; + + php_uri_parser_rfc3986_query_component_append_to_string(name, space_to_plus, &buf); + if (value != NULL) { + smart_str_appendc(&buf, '='); + php_uri_parser_rfc3986_query_component_append_to_string(value, space_to_plus, &buf); + } + } ZEND_HASH_FOREACH_END(); + + return buf.s != NULL ? smart_str_extract(&buf) : ZSTR_EMPTY_ALLOC(); +} + static void php_uri_parser_rfc3986_destroy(void *uri) { php_uri_parser_rfc3986_uris *uriparser_uris = uri; diff --git a/ext/uri/uri_parser_rfc3986.h b/ext/uri/uri_parser_rfc3986.h index 9a9f0b38a0c4..c71422b3af86 100644 --- a/ext/uri/uri_parser_rfc3986.h +++ b/ext/uri/uri_parser_rfc3986.h @@ -43,4 +43,7 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(2,3,4,5,6,7,8) php_uri_parser_rfc3986_uris *php_uri_ const zval *path, const zval *query, const zval *fragment ); +zend_string *php_uri_parser_rfc3986_query_component_percent_decode(const char *str, size_t str_length, bool plus_to_space); +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_rfc3986_query_params_to_string(const php_uri_query_params_object *query_params_object, bool space_to_plus); + #endif diff --git a/ext/uri/uri_parser_whatwg.c b/ext/uri/uri_parser_whatwg.c index 6d46feb51aae..a39d367adc6b 100644 --- a/ext/uri/uri_parser_whatwg.c +++ b/ext/uri/uri_parser_whatwg.c @@ -559,6 +559,22 @@ static zend_result php_uri_parser_whatwg_fragment_write(void *uri, const zval *v return SUCCESS; } +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_query_component_percent_decode(const char *str, const size_t str_length) +{ + lexbor_str_t result = {0}; + + const lxb_status_t status = lxb_url_percent_decode_plus( + (const lxb_char_t *) str, (const lxb_char_t *) (str + str_length), + &result, &lexbor_mraw + ); + + if (status != LXB_STATUS_OK) { + return NULL; + } + + return zend_string_init((const char *) result.data, result.length, 0); +} + PHP_RINIT_FUNCTION(uri_parser_whatwg) { lxb_status_t status; diff --git a/ext/uri/uri_parser_whatwg.h b/ext/uri/uri_parser_whatwg.h index f714ee483680..12e66143ba01 100644 --- a/ext/uri/uri_parser_whatwg.h +++ b/ext/uri/uri_parser_whatwg.h @@ -25,6 +25,8 @@ ZEND_ATTRIBUTE_NONNULL void php_uri_parser_whatwg_host_type_read(const lxb_url_t lxb_url_t *php_uri_parser_whatwg_parse_ex(const char *uri_str, size_t uri_str_len, const lxb_url_t *lexbor_base_url, zval *errors, bool silent); +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_query_component_percent_decode(const char *str, size_t str_length); + PHP_RINIT_FUNCTION(uri_parser_whatwg); ZEND_MODULE_POST_ZEND_DEACTIVATE_D(uri_parser_whatwg); diff --git a/ext/uri/uriparser/include/uriparser/Uri.h b/ext/uri/uriparser/include/uriparser/Uri.h index ddbbd26f9e44..f8e6b5f5100f 100644 --- a/ext/uri/uriparser/include/uriparser/Uri.h +++ b/ext/uri/uriparser/include/uriparser/Uri.h @@ -444,6 +444,66 @@ URI_PUBLIC URI_CHAR * URI_FUNC(EscapeEx)(const URI_CHAR * inFirst, URI_PUBLIC URI_CHAR * URI_FUNC(Escape)(const URI_CHAR * in, URI_CHAR * out, UriBool spaceToPlus, UriBool normalizeBreaks); +/** + * Percent-encodes all but unreserved characters from the input string and + * writes the encoded version to the output string. + * + * NOTE: Be sure to allocate 3 times the space of the input buffer for + * the output buffer for normalizeBreaks == URI_FALSE and 6 times + * the space for normalizeBreaks == URI_TRUE + * (since e.g. "\x0d" becomes "%0D%0A" in that case). + * + * NOTE: The implementation treats (both char and) wchar_t units + * as code point integers, which works well for code points U+0001 to U+00ff + * in host-native endianness but nothing more; + * in particular, using uriEscapeQueryRfc3986ExW with arbitrary Unicode input will + * not produce healthy results. + * Passing UTF-8 input to uriEscapeQueryRfc3986ExA may be useful in some scenarios. + * Keep in mind that uriparser is about %URI (RFC 3986) not %IRI (RFC 3987). + * + * @param inFirst IN: Pointer to first character of the input text + * @param inAfterLast IN: Pointer after the last character of the input text + * @param out OUT: Encoded text destination + * @param normalizeBreaks IN: Whether to convert CR and LF to CR-LF or not. + * @return Position of terminator in output string + * + * @see uriEscapeQueryRfc3986A + * @see uriUnescapeInPlaceExA + * @since 0.5.2 + */ +URI_PUBLIC URI_CHAR * URI_FUNC(EscapeQueryRfc3986Ex)(const URI_CHAR * inFirst, + const URI_CHAR * inAfterLast, URI_CHAR * out, + UriBool normalizeBreaks); + +/** + * Percent-encodes all but unreserved characters from the input string and + * writes the encoded version to the output string. + * + * NOTE: Be sure to allocate 3 times the space of the input buffer for + * the output buffer for normalizeBreaks == URI_FALSE and 6 times + * the space for normalizeBreaks == URI_TRUE + * (since e.g. "\x0d" becomes "%0D%0A" in that case). + * + * NOTE: The implementation treats (both char and) wchar_t units + * as code point integers, which works well for code points U+0001 to U+00ff + * in host-native endianness but nothing more; + * in particular, using uriEscapeQueryRfc3986W with arbitrary Unicode input will + * not produce healthy results. + * Passing UTF-8 input to uriEEscapeQueryRfc3986A may be useful in some scenarios. + * Keep in mind that uriparser is about %URI (RFC 3986) not %IRI (RFC 3987). + * + * @param in IN: Text source + * @param out OUT: Encoded text destination + * @param normalizeBreaks IN: Whether to convert CR and LF to CR-LF or not. + * @return Position of terminator in output string + * + * @see uriEscapeQueryRfc3986ExA + * @see uriUnescapeInPlaceA + * @since 0.5.0 + */ +URI_PUBLIC URI_CHAR * URI_FUNC(EscapeQueryRfc3986)(const URI_CHAR * in, URI_CHAR * out, + UriBool normalizeBreaks); + /** * Unescapes percent-encoded groups in a given string. * E.g. "%20" will become " ". Unescaping is done in place. diff --git a/ext/uri/uriparser/src/UriEscape.c b/ext/uri/uriparser/src/UriEscape.c index a1763f97153c..17406bb05afc 100644 --- a/ext/uri/uriparser/src/UriEscape.c +++ b/ext/uri/uriparser/src/UriEscape.c @@ -65,14 +65,9 @@ # include "UriSets.h" # endif -URI_CHAR * URI_FUNC(Escape)(const URI_CHAR * in, URI_CHAR * out, UriBool spaceToPlus, - UriBool normalizeBreaks) { - return URI_FUNC(EscapeEx)(in, NULL, out, spaceToPlus, normalizeBreaks); -} - -URI_CHAR * URI_FUNC(EscapeEx)(const URI_CHAR * inFirst, const URI_CHAR * inAfterLast, - URI_CHAR * out, UriBool spaceToPlus, - UriBool normalizeBreaks) { +URI_CHAR * URI_FUNC(EscapeQueryEngine)(const URI_CHAR * inFirst, const URI_CHAR * inAfterLast, + URI_CHAR * out, UriBool spaceToPlus, + UriBool encodeReserved, UriBool normalizeBreaks) { const URI_CHAR * read = inFirst; URI_CHAR * write = out; UriBool prevWasCr = URI_FALSE; @@ -155,6 +150,26 @@ URI_CHAR * URI_FUNC(EscapeEx)(const URI_CHAR * inFirst, const URI_CHAR * inAfter prevWasCr = URI_TRUE; break; + case URI_SET_SUB_DELIMS(_UT): + /* intentional fallback */ + case _UT(':'): + /* intentional fallback */ + case _UT('@'): + /* intentional fallback */ + case _UT('/'): + /* intentional fallback */ + case _UT('?'): + if (!encodeReserved) { + /* Copy unmodified */ + write[0] = read[0]; + write++; + + prevWasCr = URI_FALSE; + break; + } + + /* intentional fallback */ + default: /* Percent encode */ { @@ -175,6 +190,27 @@ URI_CHAR * URI_FUNC(EscapeEx)(const URI_CHAR * inFirst, const URI_CHAR * inAfter } } +URI_CHAR * URI_FUNC(Escape)(const URI_CHAR * in, URI_CHAR * out, UriBool spaceToPlus, + UriBool normalizeBreaks) { + return URI_FUNC(EscapeEx)(in, NULL, out, spaceToPlus, normalizeBreaks); +} + +URI_CHAR * URI_FUNC(EscapeEx)(const URI_CHAR * inFirst, const URI_CHAR * inAfterLast, + URI_CHAR * out, UriBool spaceToPlus, + UriBool normalizeBreaks) { + return URI_FUNC(EscapeQueryEngine)(inFirst, inAfterLast, out, spaceToPlus, URI_TRUE, normalizeBreaks); +} + +URI_CHAR * URI_FUNC(EscapeQueryRfc3986)(const URI_CHAR * in, URI_CHAR * out, + UriBool normalizeBreaks) { + return URI_FUNC(EscapeQueryRfc3986Ex)(in, NULL, out, normalizeBreaks); +} + +URI_CHAR * URI_FUNC(EscapeQueryRfc3986Ex)(const URI_CHAR * inFirst, const URI_CHAR * inAfterLast, + URI_CHAR * out, UriBool normalizeBreaks) { + return URI_FUNC(EscapeQueryEngine)(inFirst, inAfterLast, out, URI_FALSE, URI_FALSE, normalizeBreaks); +} + const URI_CHAR * URI_FUNC(UnescapeInPlace)(URI_CHAR * inout) { return URI_FUNC(UnescapeInPlaceEx)(inout, URI_FALSE, URI_BR_DONT_TOUCH); }