diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c index 58f34a370151..a760b537ff0b 100644 --- a/ext/uri/php_uri.c +++ b/ext/uri/php_uri.c @@ -30,6 +30,7 @@ #include "php_uri_arginfo.h" #include "uriparser/Uri.h" +zend_class_entry *php_uri_ce_rfc3986_uri_builder; zend_class_entry *php_uri_ce_rfc3986_uri; zend_class_entry *php_uri_ce_rfc3986_uri_type; zend_class_entry *php_uri_ce_rfc3986_uri_host_type; @@ -46,6 +47,9 @@ zend_class_entry *php_uri_ce_whatwg_url_validation_error; static zend_object_handlers object_handlers_rfc3986_uri; static zend_object_handlers object_handlers_whatwg_uri; +typedef bool (*php_uri_component_validator_string)(const zend_string *component); +typedef bool (*php_uri_component_validator_long)(zend_long component); + static const zend_module_dep uri_deps[] = { ZEND_MOD_REQUIRED("lexbor") ZEND_MOD_END @@ -53,6 +57,14 @@ static const zend_module_dep uri_deps[] = { static zend_array uri_parsers; +#define Z_RFC3986_URI_PROP_SCHEME_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 0) +#define Z_RFC3986_URI_PROP_USERINFO_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 1) +#define Z_RFC3986_URI_PROP_HOST_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 2) +#define Z_RFC3986_URI_PROP_PORT_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 3) +#define Z_RFC3986_URI_PROP_PATH_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 4) +#define Z_RFC3986_URI_PROP_QUERY_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 5) +#define Z_RFC3986_URI_PROP_FRAGMENT_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 6) + static HashTable *uri_get_debug_properties(php_uri_object *object) { const HashTable *std_properties = zend_std_get_properties(&object->std); @@ -1044,6 +1056,197 @@ PHP_METHOD(Uri_WhatWg_Url, __debugInfo) RETURN_ARR(uri_get_debug_properties(uri_object)); } +PHP_METHOD(Uri_Rfc3986_UriBuilder, __construct) +{ + ZEND_PARSE_PARAMETERS_NONE(); +} + +PHP_METHOD(Uri_Rfc3986_UriBuilder, reset) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + ZVAL_NULL(Z_RFC3986_URI_PROP_SCHEME_P(ZEND_THIS)); + ZVAL_NULL(Z_RFC3986_URI_PROP_USERINFO_P(ZEND_THIS)); + ZVAL_NULL(Z_RFC3986_URI_PROP_HOST_P(ZEND_THIS)); + ZVAL_NULL(Z_RFC3986_URI_PROP_PORT_P(ZEND_THIS)); + ZVAL_EMPTY_STRING(Z_RFC3986_URI_PROP_PATH_P(ZEND_THIS)); + ZVAL_NULL(Z_RFC3986_URI_PROP_QUERY_P(ZEND_THIS)); + ZVAL_NULL(Z_RFC3986_URI_PROP_FRAGMENT_P(ZEND_THIS)); + + RETVAL_COPY(ZEND_THIS); +} + +ZEND_ATTRIBUTE_NONNULL static void php_uri_builder_set_component_string( + INTERNAL_FUNCTION_PARAMETERS, const char *name, size_t name_length, + const php_uri_component_validator_string validator +) { + zend_string *component; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(component) + ZEND_PARSE_PARAMETERS_END(); + + if (!validator(component)) { + zend_throw_exception_ex(php_uri_ce_invalid_uri_exception, 0, "The specified %s is malformed", name); + RETURN_THROWS(); + } + + zend_update_property_str(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), name, name_length, component); + + RETVAL_COPY(ZEND_THIS); +} + +ZEND_ATTRIBUTE_NONNULL static void php_uri_builder_set_component_string_or_null( + INTERNAL_FUNCTION_PARAMETERS, const char *name, size_t name_length, + const php_uri_component_validator_string validator +) { + zend_string *component; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR_OR_NULL(component) + ZEND_PARSE_PARAMETERS_END(); + + if (component == NULL) { + zend_update_property_null(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), name, name_length); + } else { + if (!validator(component)) { + zend_throw_exception_ex(php_uri_ce_invalid_uri_exception, 0, "The specified %s is malformed", name); + RETURN_THROWS(); + } + + zend_update_property_str(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), name, name_length, component); + } + + RETVAL_COPY(ZEND_THIS); +} + +ZEND_ATTRIBUTE_NONNULL_ARGS(1) static void php_uri_builder_set_component_long_or_null( + INTERNAL_FUNCTION_PARAMETERS, const char *name, size_t name_length, + const php_uri_component_validator_long validator +) { + zend_long component; + bool component_is_null; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG_OR_NULL(component, component_is_null) + ZEND_PARSE_PARAMETERS_END(); + + if (component_is_null) { + zend_update_property_null(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), name, name_length); + } else { + if (!validator(component)) { + zend_throw_exception_ex(php_uri_ce_invalid_uri_exception, 0, "The specified %s is malformed", name); + RETURN_THROWS(); + } + + zend_update_property_long(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), name, name_length, component); + } + + RETVAL_COPY(ZEND_THIS); +} + +PHP_METHOD(Uri_Rfc3986_UriBuilder, setScheme) +{ + php_uri_builder_set_component_string_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("scheme"), + php_uri_parser_rfc3986_validate_scheme + ); +} + +PHP_METHOD(Uri_Rfc3986_UriBuilder, setUserInfo) +{ + php_uri_builder_set_component_string_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("userinfo"), + php_uri_parser_rfc3986_validate_userinfo + ); +} + +PHP_METHOD(Uri_Rfc3986_UriBuilder, setHost) +{ + php_uri_builder_set_component_string_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("host"), + php_uri_parser_rfc3986_validate_host + ); +} + +PHP_METHOD(Uri_Rfc3986_UriBuilder, setPort) +{ + php_uri_builder_set_component_long_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("port"), + php_uri_parser_rfc3986_validate_port + ); +} + +PHP_METHOD(Uri_Rfc3986_UriBuilder, setPath) +{ + php_uri_builder_set_component_string( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("path"), + php_uri_parser_rfc3986_validate_path + ); +} + +PHP_METHOD(Uri_Rfc3986_UriBuilder, setQuery) +{ + php_uri_builder_set_component_string_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("query"), + php_uri_parser_rfc3986_validate_query + ); +} + +PHP_METHOD(Uri_Rfc3986_UriBuilder, setFragment) +{ + php_uri_builder_set_component_string_or_null( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_STRL("fragment"), + php_uri_parser_rfc3986_validate_fragment + ); +} + +PHP_METHOD(Uri_Rfc3986_UriBuilder, build) +{ + zval *base_url = NULL; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_OBJECT_OF_CLASS_OR_NULL(base_url, php_uri_ce_rfc3986_uri) + ZEND_PARSE_PARAMETERS_END(); + + const zval *scheme = Z_RFC3986_URI_PROP_SCHEME_P(ZEND_THIS); + const zval *userinfo = Z_RFC3986_URI_PROP_USERINFO_P(ZEND_THIS); + const zval *host = Z_RFC3986_URI_PROP_HOST_P(ZEND_THIS); + const zval *port = Z_RFC3986_URI_PROP_PORT_P(ZEND_THIS); + const zval *path = Z_RFC3986_URI_PROP_PATH_P(ZEND_THIS); + const zval *query = Z_RFC3986_URI_PROP_QUERY_P(ZEND_THIS); + const zval *fragment = Z_RFC3986_URI_PROP_FRAGMENT_P(ZEND_THIS); + + zend_string *uri_str = php_uri_parser_rfc3986_recompose_from_zval(scheme, userinfo, host, port, path, query, fragment); + if (uri_str == NULL) { + RETURN_THROWS(); + } + + php_uri_parser_rfc3986_uris *base_uri = NULL; + if (base_url != NULL) { + base_uri = Z_URI_OBJECT_P(base_url)->uri; + } + + php_uri_parser_rfc3986_uris *uri = php_uri_parser_rfc3986_parse_ex(ZSTR_VAL(uri_str), ZSTR_LEN(uri_str), base_uri, false); + zend_string_release(uri_str); + if (uri == NULL) { + RETURN_THROWS(); + } + + object_init_ex(return_value, php_uri_ce_rfc3986_uri); + php_uri_object *uri_object = Z_URI_OBJECT_P(return_value); + uri_object->parser = &php_uri_parser_rfc3986; + uri_object->uri = uri; +} + PHPAPI php_uri_object *php_uri_object_create(zend_class_entry *class_type, const php_uri_parser *parser) { php_uri_object *uri_object = zend_object_alloc(sizeof(*uri_object), class_type); @@ -1113,6 +1316,8 @@ PHPAPI zend_result php_uri_parser_register(const php_uri_parser *uri_parser) static PHP_MINIT_FUNCTION(uri) { + php_uri_ce_rfc3986_uri_builder = register_class_Uri_Rfc3986_UriBuilder(); + php_uri_ce_rfc3986_uri = register_class_Uri_Rfc3986_Uri(); php_uri_ce_rfc3986_uri->create_object = php_uri_object_create_rfc3986; php_uri_ce_rfc3986_uri->default_object_handlers = &object_handlers_rfc3986_uri; diff --git a/ext/uri/php_uri.stub.php b/ext/uri/php_uri.stub.php index b0b83fcf83ec..6fe68c67c5c8 100644 --- a/ext/uri/php_uri.stub.php +++ b/ext/uri/php_uri.stub.php @@ -45,6 +45,37 @@ enum UriHostType case RegisteredName; } + final class UriBuilder + { + private ?string $scheme = null; + private ?string $userinfo = null; + private ?string $host = null; + private ?int $port = null; + private string $path = ""; + private ?string $query = null; + private ?string $fragment = null; + + public function __construct() {} + + public function reset(): static {} + + public function setScheme(?string $scheme): static {} + + public function setUserInfo(#[\SensitiveParameter] ?string $userInfo): static {} + + public function setHost(?string $host): static {} + + public function setPort(?int $port): static {} + + public function setPath(string $path): static {} + + public function setQuery(?string $query): static {} + + public function setFragment(?string $fragment): static {} + + public function build(?\Uri\Rfc3986\Uri $baseUrl = null): \Uri\Rfc3986\Uri {} + } + /** @strict-properties */ final readonly class Uri { diff --git a/ext/uri/php_uri_arginfo.h b/ext/uri/php_uri_arginfo.h index 0fb464ee74aa..492f8f39ced1 100644 --- a/ext/uri/php_uri_arginfo.h +++ b/ext/uri/php_uri_arginfo.h @@ -1,7 +1,45 @@ /* This is a generated file, edit php_uri.stub.php instead. - * Stub hash: a3b4696ac001d537cc34b818715c7eb382c17c5b + * Stub hash: a7e51d052ee5a78698ae4bc3b38d0241e7b47576 * Has decl header: yes */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder___construct, 0, 0, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_reset, 0, 0, IS_STATIC, 0) +ZEND_END_ARG_INFO() + +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() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_setUserInfo, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, userInfo, IS_STRING, 1) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_setHost, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, host, IS_STRING, 1) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_setPort, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, port, IS_LONG, 1) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_setPath, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_setQuery, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, query, IS_STRING, 1) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_setFragment, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, fragment, IS_STRING, 1) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Uri_Rfc3986_UriBuilder_build, 0, 0, Uri\\Rfc3986\\\125ri, 0) + ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, baseUrl, Uri\\Rfc3986\\\125ri, 1, "null") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_parse, 0, 1, IS_STATIC, 1) ZEND_ARG_TYPE_INFO(0, uri, IS_STRING, 0) ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, baseUrl, Uri\\Rfc3986\\\125ri, 1, "null") @@ -20,9 +58,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_Uri_Rfc3986_Uri_getRawScheme arginfo_class_Uri_Rfc3986_Uri_getScheme -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withScheme, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, scheme, IS_STRING, 1) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_Rfc3986_Uri_withScheme arginfo_class_Uri_Rfc3986_UriBuilder_setScheme #define arginfo_class_Uri_Rfc3986_Uri_getUserInfo arginfo_class_Uri_Rfc3986_Uri_getScheme @@ -47,41 +83,31 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_getHostType, 0, 0, Uri\\Rfc3986\\\125riHostType, 1) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withHost, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, host, IS_STRING, 1) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_Rfc3986_Uri_withHost arginfo_class_Uri_Rfc3986_UriBuilder_setHost ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_getPort, 0, 0, IS_LONG, 1) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withPort, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, port, IS_LONG, 1) -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_getRawPath arginfo_class_Uri_Rfc3986_Uri_getPath -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withPath, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_Rfc3986_Uri_withPath arginfo_class_Uri_Rfc3986_UriBuilder_setPath #define arginfo_class_Uri_Rfc3986_Uri_getQuery arginfo_class_Uri_Rfc3986_Uri_getScheme #define arginfo_class_Uri_Rfc3986_Uri_getRawQuery arginfo_class_Uri_Rfc3986_Uri_getScheme -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withQuery, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, query, IS_STRING, 1) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_Rfc3986_Uri_withQuery arginfo_class_Uri_Rfc3986_UriBuilder_setQuery #define arginfo_class_Uri_Rfc3986_Uri_getFragment arginfo_class_Uri_Rfc3986_Uri_getScheme #define arginfo_class_Uri_Rfc3986_Uri_getRawFragment arginfo_class_Uri_Rfc3986_Uri_getScheme -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_withFragment, 0, 1, IS_STATIC, 0) - ZEND_ARG_TYPE_INFO(0, fragment, IS_STRING, 1) -ZEND_END_ARG_INFO() +#define arginfo_class_Uri_Rfc3986_Uri_withFragment arginfo_class_Uri_Rfc3986_UriBuilder_setFragment ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_Rfc3986_Uri_equals, 0, 1, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, uri, Uri\\Rfc3986\\\125ri, 0) @@ -158,23 +184,23 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Uri_WhatWg_Url_getHostType, 0, 0, Uri\\WhatWg\\\125rlHostType, 1) ZEND_END_ARG_INFO() -#define arginfo_class_Uri_WhatWg_Url_withHost arginfo_class_Uri_Rfc3986_Uri_withHost +#define arginfo_class_Uri_WhatWg_Url_withHost arginfo_class_Uri_Rfc3986_UriBuilder_setHost #define arginfo_class_Uri_WhatWg_Url_getPort arginfo_class_Uri_Rfc3986_Uri_getPort -#define arginfo_class_Uri_WhatWg_Url_withPort arginfo_class_Uri_Rfc3986_Uri_withPort +#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_withPath arginfo_class_Uri_Rfc3986_Uri_withPath +#define arginfo_class_Uri_WhatWg_Url_withPath arginfo_class_Uri_Rfc3986_UriBuilder_setPath #define arginfo_class_Uri_WhatWg_Url_getQuery arginfo_class_Uri_Rfc3986_Uri_getScheme -#define arginfo_class_Uri_WhatWg_Url_withQuery arginfo_class_Uri_Rfc3986_Uri_withQuery +#define arginfo_class_Uri_WhatWg_Url_withQuery arginfo_class_Uri_Rfc3986_UriBuilder_setQuery #define arginfo_class_Uri_WhatWg_Url_getFragment arginfo_class_Uri_Rfc3986_Uri_getScheme -#define arginfo_class_Uri_WhatWg_Url_withFragment arginfo_class_Uri_Rfc3986_Uri_withFragment +#define arginfo_class_Uri_WhatWg_Url_withFragment arginfo_class_Uri_Rfc3986_UriBuilder_setFragment ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Uri_WhatWg_Url_equals, 0, 1, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, url, Uri\\WhatWg\\\125rl, 0) @@ -196,6 +222,16 @@ ZEND_END_ARG_INFO() #define arginfo_class_Uri_WhatWg_Url___debugInfo arginfo_class_Uri_Rfc3986_Uri___serialize +ZEND_METHOD(Uri_Rfc3986_UriBuilder, __construct); +ZEND_METHOD(Uri_Rfc3986_UriBuilder, reset); +ZEND_METHOD(Uri_Rfc3986_UriBuilder, setScheme); +ZEND_METHOD(Uri_Rfc3986_UriBuilder, setUserInfo); +ZEND_METHOD(Uri_Rfc3986_UriBuilder, setHost); +ZEND_METHOD(Uri_Rfc3986_UriBuilder, setPort); +ZEND_METHOD(Uri_Rfc3986_UriBuilder, setPath); +ZEND_METHOD(Uri_Rfc3986_UriBuilder, setQuery); +ZEND_METHOD(Uri_Rfc3986_UriBuilder, setFragment); +ZEND_METHOD(Uri_Rfc3986_UriBuilder, build); ZEND_METHOD(Uri_Rfc3986_Uri, parse); ZEND_METHOD(Uri_Rfc3986_Uri, __construct); ZEND_METHOD(Uri_Rfc3986_Uri, getUriType); @@ -251,6 +287,20 @@ 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_Rfc3986_UriBuilder_methods[] = { + ZEND_ME(Uri_Rfc3986_UriBuilder, __construct, arginfo_class_Uri_Rfc3986_UriBuilder___construct, ZEND_ACC_PUBLIC) + 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) + ZEND_ME(Uri_Rfc3986_UriBuilder, setUserInfo, arginfo_class_Uri_Rfc3986_UriBuilder_setUserInfo, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_UriBuilder, setHost, arginfo_class_Uri_Rfc3986_UriBuilder_setHost, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_UriBuilder, setPort, arginfo_class_Uri_Rfc3986_UriBuilder_setPort, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_UriBuilder, setPath, arginfo_class_Uri_Rfc3986_UriBuilder_setPath, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_UriBuilder, setQuery, arginfo_class_Uri_Rfc3986_UriBuilder_setQuery, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_UriBuilder, setFragment, arginfo_class_Uri_Rfc3986_UriBuilder_setFragment, ZEND_ACC_PUBLIC) + ZEND_ME(Uri_Rfc3986_UriBuilder, build, arginfo_class_Uri_Rfc3986_UriBuilder_build, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + static const zend_function_entry class_Uri_Rfc3986_Uri_methods[] = { ZEND_ME(Uri_Rfc3986_Uri, parse, arginfo_class_Uri_Rfc3986_Uri_parse, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(Uri_Rfc3986_Uri, __construct, arginfo_class_Uri_Rfc3986_Uri___construct, ZEND_ACC_PUBLIC) @@ -403,6 +453,49 @@ static zend_class_entry *register_class_Uri_Rfc3986_UriHostType(void) return class_entry; } +static zend_class_entry *register_class_Uri_Rfc3986_UriBuilder(void) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Uri\\Rfc3986", "UriBuilder", class_Uri_Rfc3986_UriBuilder_methods); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL); + + zval property_scheme_default_value; + ZVAL_NULL(&property_scheme_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_SCHEME), &property_scheme_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + + zval property_userinfo_default_value; + ZVAL_NULL(&property_userinfo_default_value); + zend_string *property_userinfo_name = zend_string_init("userinfo", sizeof("userinfo") - 1, true); + zend_declare_typed_property(class_entry, property_userinfo_name, &property_userinfo_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + zend_string_release_ex(property_userinfo_name, true); + + zval property_host_default_value; + ZVAL_NULL(&property_host_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_HOST), &property_host_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + + zval property_port_default_value; + ZVAL_NULL(&property_port_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_PORT), &property_port_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG|MAY_BE_NULL)); + + zval property_path_default_value; + ZVAL_EMPTY_STRING(&property_path_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_PATH), &property_path_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + + zval property_query_default_value; + ZVAL_NULL(&property_query_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_QUERY), &property_query_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + + zval property_fragment_default_value; + ZVAL_NULL(&property_fragment_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_FRAGMENT), &property_fragment_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + + + zend_add_parameter_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "setuserinfo", sizeof("setuserinfo") - 1), 0, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); + + return class_entry; +} + static zend_class_entry *register_class_Uri_Rfc3986_Uri(void) { zend_class_entry ce, *class_entry; diff --git a/ext/uri/php_uri_common.h b/ext/uri/php_uri_common.h index c9f5e81075fe..8b70ff4c77d7 100644 --- a/ext/uri/php_uri_common.h +++ b/ext/uri/php_uri_common.h @@ -17,6 +17,7 @@ #include "php_uri_decl.h" +extern zend_class_entry *php_uri_ce_rfc3986_uri_builder; extern zend_class_entry *php_uri_ce_rfc3986_uri; extern zend_class_entry *php_uri_ce_rfc3986_uri_type; extern zend_class_entry *php_uri_ce_rfc3986_uri_host_type; diff --git a/ext/uri/php_uri_decl.h b/ext/uri/php_uri_decl.h index d1fd58d04b2c..e606a13ca9a3 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: a3b4696ac001d537cc34b818715c7eb382c17c5b */ + * Stub hash: a7e51d052ee5a78698ae4bc3b38d0241e7b47576 */ -#ifndef ZEND_PHP_URI_DECL_a3b4696ac001d537cc34b818715c7eb382c17c5b_H -#define ZEND_PHP_URI_DECL_a3b4696ac001d537cc34b818715c7eb382c17c5b_H +#ifndef ZEND_PHP_URI_DECL_a7e51d052ee5a78698ae4bc3b38d0241e7b47576_H +#define ZEND_PHP_URI_DECL_a7e51d052ee5a78698ae4bc3b38d0241e7b47576_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_a3b4696ac001d537cc34b818715c7eb382c17c5b_H */ +#endif /* ZEND_PHP_URI_DECL_a7e51d052ee5a78698ae4bc3b38d0241e7b47576_H */ diff --git a/ext/uri/tests/rfc3986/builder/all_success_with_reset.phpt b/ext/uri/tests/rfc3986/builder/all_success_with_reset.phpt new file mode 100644 index 000000000000..96c46744bf17 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/all_success_with_reset.phpt @@ -0,0 +1,63 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder all components - success - calling reset() afterwards +--FILE-- +setScheme("https") + ->setUserInfo("user:info") + ->setHost("example.com") + ->setPort(443) + ->setPath("/foo/bar/baz") + ->setQuery("foo=1&bar=baz") + ->setFragment("fragment"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +$uri = $builder->reset()->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(68) "https://user:info@example.com:443/foo/bar/baz?foo=1&bar=baz#fragment" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + string(4) "user" + ["password"]=> + string(4) "info" + ["host"]=> + string(11) "example.com" + ["port"]=> + int(443) + ["path"]=> + string(12) "/foo/bar/baz" + ["query"]=> + string(13) "foo=1&bar=baz" + ["fragment"]=> + string(8) "fragment" +} +string(0) "" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} diff --git a/ext/uri/tests/rfc3986/builder/fragment_error_special_char.phpt b/ext/uri/tests/rfc3986/builder/fragment_error_special_char.phpt new file mode 100644 index 000000000000..8ed2a8938e9a --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/fragment_error_special_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setFragment() - error - contains invalid special character +--FILE-- +setFragment("#foo"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified fragment is malformed diff --git a/ext/uri/tests/rfc3986/builder/fragment_error_unicode_char.phpt b/ext/uri/tests/rfc3986/builder/fragment_error_unicode_char.phpt new file mode 100644 index 000000000000..2a15d5e85a82 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/fragment_error_unicode_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setFragment() - error - contains Unicode character +--FILE-- +setFragment("főő"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified fragment is malformed diff --git a/ext/uri/tests/rfc3986/builder/host_error_ipv6_closing_brace.phpt b/ext/uri/tests/rfc3986/builder/host_error_ipv6_closing_brace.phpt new file mode 100644 index 000000000000..45eef13faf71 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_error_ipv6_closing_brace.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - error - missing IPv6 closing brace +--FILE-- +setHost("[2001:%30db8:85a3:0000:0000:8a2e:0370:7334"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified host is malformed diff --git a/ext/uri/tests/rfc3986/builder/host_error_percent_encoding1.phpt b/ext/uri/tests/rfc3986/builder/host_error_percent_encoding1.phpt new file mode 100644 index 000000000000..ae06238a1f8f --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_error_percent_encoding1.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - error - invalid percent encoding octet in registered name +--FILE-- +setHost("ex%3mple.co"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified host is malformed diff --git a/ext/uri/tests/rfc3986/builder/host_error_percent_encoding2.phpt b/ext/uri/tests/rfc3986/builder/host_error_percent_encoding2.phpt new file mode 100644 index 000000000000..cc514ec69191 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_error_percent_encoding2.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - error - invalid percent encoded octet in IPv6 +--FILE-- +setHost("[2001:%308:85a3:0000:0000:8a2e:0370:7334]"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified host is malformed diff --git a/ext/uri/tests/rfc3986/builder/host_success_ip4_to_regname.phpt b/ext/uri/tests/rfc3986/builder/host_success_ip4_to_regname.phpt new file mode 100644 index 000000000000..6b0bc758fae5 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_success_ip4_to_regname.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - success - invalid IPv4 address falls back to a registered name +--FILE-- +setHost("192.168.%30.1"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(15) "//192.168.%30.1" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(13) "192.168.%30.1" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} diff --git a/ext/uri/tests/rfc3986/builder/host_success_ipv4.phpt b/ext/uri/tests/rfc3986/builder/host_success_ipv4.phpt new file mode 100644 index 000000000000..8ae65be833a6 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_success_ipv4.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - success - IPv4 address +--FILE-- +setHost("192.168.0.1"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(13) "//192.168.0.1" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "192.168.0.1" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} diff --git a/ext/uri/tests/rfc3986/builder/host_success_ipv6.phpt b/ext/uri/tests/rfc3986/builder/host_success_ipv6.phpt new file mode 100644 index 000000000000..73f73df17e25 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_success_ipv6.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - success - IPv6 address +--FILE-- +setHost("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(43) "//[2001:0db8:85a3:0000:0000:8a2e:0370:7334]" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(41) "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} diff --git a/ext/uri/tests/rfc3986/builder/host_success_ipvfuture.phpt b/ext/uri/tests/rfc3986/builder/host_success_ipvfuture.phpt new file mode 100644 index 000000000000..3a44bb20721a --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_success_ipvfuture.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - success - IPvFuture address +--FILE-- +setHost("[v1.2001:db8::1]"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(18) "//[v1.2001:db8::1]" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(16) "[v1.2001:db8::1]" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} diff --git a/ext/uri/tests/rfc3986/builder/host_success_null.phpt b/ext/uri/tests/rfc3986/builder/host_success_null.phpt new file mode 100644 index 000000000000..b3c79f5e7b0f --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_success_null.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - success - null +--FILE-- +setHost(null); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(0) "" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} diff --git a/ext/uri/tests/rfc3986/builder/host_success_regname.phpt b/ext/uri/tests/rfc3986/builder/host_success_regname.phpt new file mode 100644 index 000000000000..9bf4a4e97c7f --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/host_success_regname.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setHost() - success - Registered name +--FILE-- +setHost("www.example.com"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(17) "//www.example.com" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(15) "www.example.com" + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} diff --git a/ext/uri/tests/rfc3986/builder/path_error_first_segment_colon.phpt b/ext/uri/tests/rfc3986/builder/path_error_first_segment_colon.phpt new file mode 100644 index 000000000000..711d76187db2 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/path_error_first_segment_colon.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPath() - error - contains a colon in the first path segment when the URI doesn't contain a scheme +--FILE-- +setPath("fo:o/bar/baz"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The path must not begin with ":" when the URI doesn't contain a scheme diff --git a/ext/uri/tests/rfc3986/builder/path_error_leading_double_slash.phpt b/ext/uri/tests/rfc3986/builder/path_error_leading_double_slash.phpt new file mode 100644 index 000000000000..f32cedde136e --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/path_error_leading_double_slash.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPath() - error - contains leading double slash when the host is not present +--FILE-- +setPath("//foo/bar/baz"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The path must not begin with "//" when the URI doesn't contain a host diff --git a/ext/uri/tests/rfc3986/builder/path_error_missing_leading_slash.phpt b/ext/uri/tests/rfc3986/builder/path_error_missing_leading_slash.phpt new file mode 100644 index 000000000000..3f594f2853a9 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/path_error_missing_leading_slash.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPath() - error - missing a leading slash when the URI contains a host +--FILE-- +setPath("foo/bar/baz"); +$builder->setHost("example.com"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: A path must be either empty or begin with "/" when the URI contains a host diff --git a/ext/uri/tests/rfc3986/builder/path_error_special_char.phpt b/ext/uri/tests/rfc3986/builder/path_error_special_char.phpt new file mode 100644 index 000000000000..039b22770246 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/path_error_special_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPath() - error - contains invalid special character +--FILE-- +setPath("#foo"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified path is malformed diff --git a/ext/uri/tests/rfc3986/builder/path_success_empty_string.phpt b/ext/uri/tests/rfc3986/builder/path_success_empty_string.phpt new file mode 100644 index 000000000000..7c1dfdd60d13 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/path_success_empty_string.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPath() - success - empty string +--FILE-- +setPath(""); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(0) "" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} diff --git a/ext/uri/tests/rfc3986/builder/path_success_first_segment_colon.phpt b/ext/uri/tests/rfc3986/builder/path_success_first_segment_colon.phpt new file mode 100644 index 000000000000..b144a7dd3be0 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/path_success_first_segment_colon.phpt @@ -0,0 +1,34 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPath() - success - contains a colon in the first segment when the scheme is present +--FILE-- +setScheme("https"); +$builder->setPath(":foo/bar/baz"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(%d) "https::foo/bar/baz" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + string(5) "https" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(12) ":foo/bar/baz" + ["query"]=> + NULL + ["fragment"]=> + NULL +} diff --git a/ext/uri/tests/rfc3986/builder/path_success_leading_double_slash.phpt b/ext/uri/tests/rfc3986/builder/path_success_leading_double_slash.phpt new file mode 100644 index 000000000000..eaa6c38b086b --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/path_success_leading_double_slash.phpt @@ -0,0 +1,34 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPath() - success - begins with double slashes when the URI contains a host +--FILE-- +setHost("example.com"); +$builder->setPath("//foo/bar/baz"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(26) "//example.com//foo/bar/baz" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + NULL + ["path"]=> + string(13) "//foo/bar/baz" + ["query"]=> + NULL + ["fragment"]=> + NULL +} \ No newline at end of file diff --git a/ext/uri/tests/rfc3986/builder/port_error_missing_host.phpt b/ext/uri/tests/rfc3986/builder/port_error_missing_host.phpt new file mode 100644 index 000000000000..b4787559f5d0 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/port_error_missing_host.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPort() - error - missing host +--FILE-- +setPort(443); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The URI must contain a host if either the userinfo or the port component is present diff --git a/ext/uri/tests/rfc3986/builder/port_error_negative.phpt b/ext/uri/tests/rfc3986/builder/port_error_negative.phpt new file mode 100644 index 000000000000..4cbc0e1c69d7 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/port_error_negative.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPort() - error - negative number +--FILE-- +setPort(-1); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified port is malformed diff --git a/ext/uri/tests/rfc3986/builder/port_success_large.phpt b/ext/uri/tests/rfc3986/builder/port_success_large.phpt new file mode 100644 index 000000000000..010af1cf1ef8 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/port_success_large.phpt @@ -0,0 +1,34 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPort() - success - large number +--FILE-- +setPort(PHP_INT_MAX); +$builder->setHost("example.com"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(%d) "//example.com:%d" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(11) "example.com" + ["port"]=> + int(%d) + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} diff --git a/ext/uri/tests/rfc3986/builder/port_success_null.phpt b/ext/uri/tests/rfc3986/builder/port_success_null.phpt new file mode 100644 index 000000000000..b1fbdfee25ba --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/port_success_null.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setPort() - success - null +--FILE-- +setPort(null); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(0) "" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} diff --git a/ext/uri/tests/rfc3986/builder/query_error_special_char.phpt b/ext/uri/tests/rfc3986/builder/query_error_special_char.phpt new file mode 100644 index 000000000000..ecfced24a44c --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/query_error_special_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setQuery() - error - contains invalid special character +--FILE-- +setQuery("#foo"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified query is malformed diff --git a/ext/uri/tests/rfc3986/builder/query_error_unicode_char.phpt b/ext/uri/tests/rfc3986/builder/query_error_unicode_char.phpt new file mode 100644 index 000000000000..4c212cddd631 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/query_error_unicode_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setQuery() - error - contains Unicode character +--FILE-- +setQuery("főő"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified query is malformed diff --git a/ext/uri/tests/rfc3986/builder/query_success_basic.phpt b/ext/uri/tests/rfc3986/builder/query_success_basic.phpt new file mode 100644 index 000000000000..eec7c5cc38b1 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/query_success_basic.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setQuery() - success - basic +--FILE-- +setQuery("foo=1&bar=baz"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(14) "?foo=1&bar=baz" +object(Uri\Rfc3986\Uri)#2 (8) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + string(13) "foo=1&bar=baz" + ["fragment"]=> + NULL +} diff --git a/ext/uri/tests/rfc3986/builder/scheme_error_empty.phpt b/ext/uri/tests/rfc3986/builder/scheme_error_empty.phpt new file mode 100644 index 000000000000..13594c163363 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/scheme_error_empty.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setScheme() - error - empty +--FILE-- +setScheme(""); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified scheme is malformed diff --git a/ext/uri/tests/rfc3986/builder/scheme_error_first_char.phpt b/ext/uri/tests/rfc3986/builder/scheme_error_first_char.phpt new file mode 100644 index 000000000000..c178a0fa1f1e --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/scheme_error_first_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setScheme() - error - first character is not alpha +--FILE-- +setScheme("1"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified scheme is malformed diff --git a/ext/uri/tests/rfc3986/builder/scheme_error_special_char.phpt b/ext/uri/tests/rfc3986/builder/scheme_error_special_char.phpt new file mode 100644 index 000000000000..4941db44aa7d --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/scheme_error_special_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setScheme() - error - contains invalid special character +--FILE-- +setScheme(":"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified scheme is malformed diff --git a/ext/uri/tests/rfc3986/builder/scheme_success_basic.phpt b/ext/uri/tests/rfc3986/builder/scheme_success_basic.phpt new file mode 100644 index 000000000000..1b0f51db3c49 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/scheme_success_basic.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setScheme() - success - basic +--FILE-- +setScheme("scheme"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(7) "scheme:" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + string(6) "scheme" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} diff --git a/ext/uri/tests/rfc3986/builder/scheme_success_null.phpt b/ext/uri/tests/rfc3986/builder/scheme_success_null.phpt new file mode 100644 index 000000000000..d454f6da4f02 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/scheme_success_null.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setScheme() - success - null +--FILE-- +setScheme(null); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(0) "" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} diff --git a/ext/uri/tests/rfc3986/builder/scheme_success_special.phpt b/ext/uri/tests/rfc3986/builder/scheme_success_special.phpt new file mode 100644 index 000000000000..d7e6a2326a62 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/scheme_success_special.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setScheme() - success - contains digit & special characters +--FILE-- +setScheme("my-12+34.scheme"); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(16) "my-12+34.scheme:" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + string(15) "my-12+34.scheme" + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} diff --git a/ext/uri/tests/rfc3986/builder/userinfo_error_missing_host.phpt b/ext/uri/tests/rfc3986/builder/userinfo_error_missing_host.phpt new file mode 100644 index 000000000000..0beb7e077073 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/userinfo_error_missing_host.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setUserInfo() - error - missing host +--FILE-- +setUserInfo("user:pass"); + +try { + $builder->build(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The URI must contain a host if either the userinfo or the port component is present diff --git a/ext/uri/tests/rfc3986/builder/userinfo_error_percent_encoding.phpt b/ext/uri/tests/rfc3986/builder/userinfo_error_percent_encoding.phpt new file mode 100644 index 000000000000..0830a57864d7 --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/userinfo_error_percent_encoding.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setUserInfo() - error - invalid percent encoding +--FILE-- +setUserInfo("%3"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified userinfo is malformed diff --git a/ext/uri/tests/rfc3986/builder/userinfo_error_special_char.phpt b/ext/uri/tests/rfc3986/builder/userinfo_error_special_char.phpt new file mode 100644 index 000000000000..9d3fb2544daf --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/userinfo_error_special_char.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setUserInfo() - error - contains invalid special character +--FILE-- +setUserInfo("<>"); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Uri\InvalidUriException: The specified userinfo is malformed diff --git a/ext/uri/tests/rfc3986/builder/userinfo_success_null.phpt b/ext/uri/tests/rfc3986/builder/userinfo_success_null.phpt new file mode 100644 index 000000000000..d0735f291faa --- /dev/null +++ b/ext/uri/tests/rfc3986/builder/userinfo_success_null.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test Uri\Rfc3986\UriBuilder::setUserInfo() - success - null +--FILE-- +setUserInfo(null); +$uri = $builder->build(); + +var_dump($uri->toRawString()); +var_dump($uri); + +?> +--EXPECTF-- +string(0) "" +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + NULL + ["port"]=> + NULL + ["path"]=> + string(0) "" + ["query"]=> + NULL + ["fragment"]=> + NULL +} diff --git a/ext/uri/uri_parser_rfc3986.c b/ext/uri/uri_parser_rfc3986.c index ad47aa1946c7..07113340a500 100644 --- a/ext/uri/uri_parser_rfc3986.c +++ b/ext/uri/uri_parser_rfc3986.c @@ -673,6 +673,193 @@ static void php_uri_parser_rfc3986_destroy(void *uri) efree(uriparser_uris); } +ZEND_ATTRIBUTE_NONNULL bool php_uri_parser_rfc3986_validate_scheme(const zend_string *scheme) +{ + const char *p = ZSTR_VAL(scheme); + const size_t len = ZSTR_LEN(scheme); + + return uriIsWellFormedSchemeA(p, p + len); +} + +ZEND_ATTRIBUTE_NONNULL bool php_uri_parser_rfc3986_validate_userinfo(const zend_string *userinfo) +{ + const char *p = ZSTR_VAL(userinfo); + const size_t len = ZSTR_LEN(userinfo); + + return uriIsWellFormedUserInfoA(p, p + len); +} + +ZEND_ATTRIBUTE_NONNULL bool php_uri_parser_rfc3986_validate_host(const zend_string *host) +{ + const char *p = ZSTR_VAL(host); + const size_t len = ZSTR_LEN(host); + + if (len == 0) { + return true; + } + + if (p[0] == '[') { + if (p[len - 1] != ']') { + return false; + } + + if (len >= 2 && (p[1] == 'v' || p[1] == 'V')) { + return uriIsWellFormedHostIpFutureA(p + 1, p + len - 1) == URI_SUCCESS; + } + + return uriIsWellFormedHostIp6A(p + 1, p + len - 1) == URI_SUCCESS; + } + + if (uriIsWellFormedHostIp4A(p, p + len)) { + return true; + } + + return uriIsWellFormedHostRegNameA(p, p + len); +} + +ZEND_ATTRIBUTE_NONNULL bool php_uri_parser_rfc3986_validate_port(zend_long port) +{ + zend_string *tmp = zend_long_to_str(port); + const char *p = ZSTR_VAL(tmp); + const size_t len = ZSTR_LEN(tmp); + + bool result = uriIsWellFormedPortA(p, p + len); + zend_string_release(tmp); + + return result; +} + +ZEND_ATTRIBUTE_NONNULL bool php_uri_parser_rfc3986_validate_path(const zend_string *path) +{ + const char *p = ZSTR_VAL(path); + const size_t len = ZSTR_LEN(path); + + /* The build() method checks whether the path begins with a "/" when there's a host. + * In order to skip doing the same check, a false hasHost argument is passed to uriIsWellFormedPathA(). */ + return uriIsWellFormedPathA(p, p + len, /* hasHost */ false); +} + +ZEND_ATTRIBUTE_NONNULL bool php_uri_parser_rfc3986_validate_query(const zend_string *query) +{ + const char *p = ZSTR_VAL(query); + const size_t len = ZSTR_LEN(query); + + return uriIsWellFormedQueryA(p, p + len); +} + +ZEND_ATTRIBUTE_NONNULL bool php_uri_parser_rfc3986_validate_fragment(const zend_string *fragment) +{ + const char *p = ZSTR_VAL(fragment); + const size_t len = ZSTR_LEN(fragment); + + return uriIsWellFormedFragmentA(p, p + len); +} + +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_rfc3986_recompose_from_zval( + const zval *scheme, const zval *userinfo, const zval *host, const zval *port, + const zval *path, const zval *query, const zval *fragment +) { + if (Z_TYPE_P(host) == IS_NULL) { + /* The path must not begin with "//" if the URI doesn't contain a host */ + if (zend_string_starts_with_literal(Z_STR_P(path), "//")) { + zend_throw_exception(php_uri_ce_invalid_uri_exception, "The path must not begin with \"//\" when the URI doesn't contain a host", 0); + return NULL; + } + + /* The userinfo and port components must not be present if the URI doesn't contain a host */ + if (Z_TYPE_P(userinfo) != IS_NULL || Z_TYPE_P(port) != IS_NULL) { + zend_throw_exception(php_uri_ce_invalid_uri_exception, "The URI must contain a host if either the userinfo or the port component is present", 0); + return NULL; + } + } else { + /* The path must be either empty or begin with a "/" if the URI contains a host */ + if (Z_STRLEN_P(path) > 0 && Z_STRVAL_P(path)[0] != '/') { + zend_throw_exception(php_uri_ce_invalid_uri_exception, "A path must be either empty or begin with \"/\" when the URI contains a host", 0); + return NULL; + } + } + + /* The first segment of the path must not contain ":" if the URI doesn't contain a scheme */ + if (Z_TYPE_P(scheme) == IS_NULL) { + const char *p = Z_STRVAL_P(path); + while (*p != '\0' && *p != '/') { + if (*p == ':') { + zend_throw_exception(php_uri_ce_invalid_uri_exception, "The path must not begin with \":\" when the URI doesn't contain a scheme", 0); + return NULL; + } + + p++; + } + } + + /* result = "" */ + smart_str uri_str = {0}; + + /* + if defined(scheme) then + append scheme to result; + append ":" to result; + endif; + */ + if (Z_TYPE_P(scheme) != IS_NULL) { + smart_str_append(&uri_str, Z_STR_P(scheme)); + smart_str_appendc(&uri_str, ':'); + } + + /* + if defined(authority) then + append "//" to result; + append authority to result; + endif; + */ + /* Check if authority is defined */ + if (Z_TYPE_P(host) != IS_NULL) { + smart_str_appends(&uri_str, "//"); + + if (Z_TYPE_P(userinfo) != IS_NULL) { + smart_str_append(&uri_str, Z_STR_P(userinfo)); + smart_str_appendc(&uri_str, '@'); + } + + if (Z_TYPE_P(host) != IS_NULL) { + smart_str_append(&uri_str, Z_STR_P(host)); + } + + if (Z_TYPE_P(port) != IS_NULL) { + smart_str_appendc(&uri_str, ':'); + smart_str_append_long(&uri_str, Z_LVAL_P(port)); + } + } + + /* append path to result; */ + smart_str_append(&uri_str, Z_STR_P(path)); + + /* + if defined(query) then + append "?" to result; + append query to result; + endif; + */ + if (Z_TYPE_P(query) != IS_NULL) { + smart_str_appendc(&uri_str, '?'); + smart_str_append(&uri_str, Z_STR_P(query)); + } + + /* + if defined(fragment) then + append "#" to result; + append fragment to result; + endif; + */ + if (Z_TYPE_P(fragment) != IS_NULL) { + smart_str_appendc(&uri_str, '#'); + smart_str_append(&uri_str, Z_STR_P(fragment)); + } + + /* return result; */ + return smart_str_extract(&uri_str); +} + PHPAPI const php_uri_parser php_uri_parser_rfc3986 = { .name = PHP_URI_PARSER_RFC3986, .parse = php_uri_parser_rfc3986_parse, diff --git a/ext/uri/uri_parser_rfc3986.h b/ext/uri/uri_parser_rfc3986.h index 633dd72062f2..cd37ae84353c 100644 --- a/ext/uri/uri_parser_rfc3986.h +++ b/ext/uri/uri_parser_rfc3986.h @@ -29,4 +29,17 @@ zend_result php_uri_parser_rfc3986_userinfo_write(php_uri_parser_rfc3986_uris *u php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_parse_ex(const char *uri_str, size_t uri_str_len, const php_uri_parser_rfc3986_uris *uriparser_base_url, bool silent); +ZEND_ATTRIBUTE_NONNULL bool php_uri_parser_rfc3986_validate_scheme(const zend_string *scheme); +ZEND_ATTRIBUTE_NONNULL bool php_uri_parser_rfc3986_validate_userinfo(const zend_string *userinfo); +ZEND_ATTRIBUTE_NONNULL bool php_uri_parser_rfc3986_validate_host(const zend_string *host); +ZEND_ATTRIBUTE_NONNULL bool php_uri_parser_rfc3986_validate_port(zend_long port); +ZEND_ATTRIBUTE_NONNULL bool php_uri_parser_rfc3986_validate_path(const zend_string *path); +ZEND_ATTRIBUTE_NONNULL bool php_uri_parser_rfc3986_validate_query(const zend_string *query); +ZEND_ATTRIBUTE_NONNULL bool php_uri_parser_rfc3986_validate_fragment(const zend_string *fragment); + +ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_rfc3986_recompose_from_zval( + const zval *scheme, const zval *userinfo, const zval *host, const zval *port, + const zval *path, const zval *query, const zval *fragment +); + #endif