Skip to content

Commit 07a63c7

Browse files
committed
Implement "Followup improvements for ext/uri" RFC - URL building with base URL
RFC: https://wiki.php.net/rfc/uri_followup#uri_building Add support for passing a non-null $baseUrl parameter for Uri\WhatWg\UrlBuilder::build().
1 parent 1273587 commit 07a63c7

8 files changed

Lines changed: 259 additions & 22 deletions

File tree

ext/lexbor/lexbor/url/url.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -909,7 +909,7 @@ lxb_url_scheme_copy_special(const lxb_url_scheme_data_t *src,
909909
return lxb_url_str_copy(&src->name, &dst->name, dst_mraw);
910910
}
911911

912-
static void
912+
void
913913
lxb_url_path_set_null(lxb_url_t *url)
914914
{
915915
if (url->path.str.data == NULL) {
@@ -1133,7 +1133,7 @@ lxb_url_host_destroy(lxb_url_host_t *host, lexbor_mraw_t *mraw)
11331133
}
11341134
}
11351135

1136-
static void
1136+
void
11371137
lxb_url_host_set_empty(lxb_url_host_t *host, lexbor_mraw_t *mraw)
11381138
{
11391139
lxb_url_host_destroy(host, mraw);

ext/lexbor/lexbor/url/url.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,12 @@ lxb_url_search_params_serialize(lxb_url_search_params_t *search_params,
894894
LXB_API bool
895895
lxb_url_is_special(const lxb_url_t *url);
896896

897+
LXB_API void
898+
lxb_url_path_set_null(lxb_url_t *url);
899+
900+
LXB_API void
901+
lxb_url_host_set_empty(lxb_url_host_t *host, lexbor_mraw_t *mraw);
902+
897903
/*
898904
* Inline functions.
899905
*/

ext/uri/php_uri.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,8 +1425,6 @@ PHP_METHOD(Uri_WhatWg_UrlBuilder, build)
14251425

14261426
lxb_url_t *base_url = NULL;
14271427
if (base_url_zv != NULL) {
1428-
zend_argument_error(NULL, 1, "is not supported yet, and therefore, null must be passed");
1429-
RETURN_THROWS();
14301428
base_url = Z_URI_OBJECT_P(base_url_zv)->uri;
14311429
}
14321430

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Test Uri\WhatWg\UrlBuilder basic - error - with base URL containing opaque path
3+
--FILE--
4+
<?php
5+
6+
$builder = new Uri\WhatWg\UrlBuilder();
7+
$builder->setPath("/foo/bar/baz");
8+
9+
try {
10+
$builder->build(new Uri\WhatWg\Url("scheme:opaque-path"));
11+
} catch (Throwable $e) {
12+
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
13+
}
14+
15+
?>
16+
--EXPECT--
17+
Uri\WhatWg\InvalidUrlException: The specified path is malformed (MissingSchemeNonRelativeUrl)

ext/uri/tests/whatwg/builder/basic_success_with_base.phpt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
--TEST--
22
Test Uri\WhatWg\UrlBuilder basic - success - with base URL
3-
--XFAIL--
4-
Support for passing $baseUrl to Uri\WhatWg\UrlBuilder::build() is not implemented yet.
53
--FILE--
64
<?php
75

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
Test Uri\WhatWg\UrlBuilder basic - success - with scheme relative URL
3+
--FILE--
4+
<?php
5+
6+
$builder = new Uri\WhatWg\UrlBuilder();
7+
$builder->setHost("example.net");
8+
$builder->setPath("/foo/bar/baz");
9+
$builder->setPort(124);
10+
$url = $builder->build(new Uri\WhatWg\Url("https://user:pass@example.com:123/foo/bar?query#hash"));
11+
12+
var_dump($url->toAsciiString());
13+
var_dump($url);
14+
var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
15+
16+
?>
17+
--EXPECTF--
18+
string(45) "https://user:pass@example.net:124/foo/bar/baz"
19+
object(Uri\WhatWg\Url)#%d (%d) {
20+
["scheme"]=>
21+
string(5) "https"
22+
["username"]=>
23+
string(4) "user"
24+
["password"]=>
25+
string(4) "pass"
26+
["host"]=>
27+
string(11) "example.net"
28+
["port"]=>
29+
int(124)
30+
["path"]=>
31+
string(12) "/foo/bar/baz"
32+
["query"]=>
33+
NULL
34+
["fragment"]=>
35+
NULL
36+
}
37+
bool(true)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
Test Uri\WhatWg\UrlBuilder::setFragment() - success - with base URL with opaque path
3+
--FILE--
4+
<?php
5+
6+
$builder = new Uri\WhatWg\UrlBuilder();
7+
$builder->setFragment("foo");
8+
$url = $builder->build(new Uri\WhatWg\Url("scheme:opaque-path"));
9+
10+
var_dump($url->toAsciiString());
11+
var_dump($url);
12+
var_dump($url->equals(new Uri\WhatWg\Url($url->toAsciiString())));
13+
14+
?>
15+
--EXPECTF--
16+
string(22) "scheme:opaque-path#foo"
17+
object(Uri\WhatWg\Url)#%d (%d) {
18+
["scheme"]=>
19+
string(6) "scheme"
20+
["username"]=>
21+
NULL
22+
["password"]=>
23+
NULL
24+
["host"]=>
25+
NULL
26+
["port"]=>
27+
NULL
28+
["path"]=>
29+
string(11) "opaque-path"
30+
["query"]=>
31+
NULL
32+
["fragment"]=>
33+
string(3) "foo"
34+
}
35+
bool(true)

ext/uri/uri_parser_whatwg.c

Lines changed: 162 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,8 @@ ZEND_ATTRIBUTE_NONNULL static const char *fill_errors(zval *errors)
202202
return fill_errors_inner(Z_ARRVAL_P(errors));
203203
}
204204

205-
static void throw_invalid_url_exception_during_write(zval *errors, const char *component)
205+
static void throw_invalid_url_exception_with_reason(zval *errors, const char *component, const char *reason, zval *err)
206206
{
207-
zval err;
208-
const char *reason = fill_errors(&err);
209207
zend_object *exception = zend_throw_exception_ex(
210208
php_uri_ce_whatwg_invalid_url_exception,
211209
0,
@@ -215,15 +213,23 @@ static void throw_invalid_url_exception_during_write(zval *errors, const char *c
215213
reason ? reason : "",
216214
reason ? ")" : ""
217215
);
218-
zend_update_property(exception->ce, exception, ZEND_STRL("errors"), &err);
216+
zend_update_property(exception->ce, exception, ZEND_STRL("errors"), err);
219217
if (errors) {
220218
zval_ptr_dtor(errors);
221-
ZVAL_COPY_VALUE(errors, &err);
219+
ZVAL_COPY_VALUE(errors, err);
222220
} else {
223-
zval_ptr_dtor(&err);
221+
zval_ptr_dtor(err);
224222
}
225223
}
226224

225+
static void throw_invalid_url_exception_during_write(zval *errors, const char *component)
226+
{
227+
zval err;
228+
const char *reason = fill_errors(&err);
229+
230+
throw_invalid_url_exception_with_reason(errors, component, reason, &err);
231+
}
232+
227233
static lxb_status_t serialize_to_smart_str_callback(const lxb_char_t *data, const size_t length, void *ctx)
228234
{
229235
smart_str *uri_str = ctx;
@@ -948,27 +954,174 @@ ZEND_ATTRIBUTE_NONNULL static lxb_url_scheme_type_t php_uri_parser_whatwg_get_sp
948954
return LXB_URL_SCHEMEL_TYPE__UNDEF;
949955
}
950956

951-
ZEND_ATTRIBUTE_NONNULL static void php_uri_parser_whatwg_build_errors(zval *errors)
957+
ZEND_ATTRIBUTE_NONNULL const char *php_uri_parser_whatwg_build_errors(zval *errors)
952958
{
953959
size_t log_len;
954960

955961
if (lexbor_parser.log == NULL || (log_len = lexbor_plog_length(lexbor_parser.log)) == 0) {
956-
return;
962+
return NULL;
957963
}
958964

959965
if (Z_TYPE_P(errors) != IS_ARRAY) {
960966
zval_ptr_dtor(errors);
961967
array_init_size(errors, log_len);
962968
}
963969

964-
fill_errors_inner(Z_ARRVAL_P(errors));
970+
return fill_errors_inner(Z_ARRVAL_P(errors));
971+
}
972+
973+
ZEND_ATTRIBUTE_NONNULL static void php_uri_parser_whatwg_build_errors_and_throw(const lxb_status_t status, const char *component, zval *errors)
974+
{
975+
zval err;
976+
ZVAL_UNDEF(&err);
977+
978+
const char *reason = php_uri_parser_whatwg_build_errors(&err);
979+
980+
if (status != LXB_STATUS_OK) {
981+
throw_invalid_url_exception_with_reason(errors, component, reason, &err);
982+
}
983+
}
984+
985+
ZEND_ATTRIBUTE_NONNULL_ARGS(1, 2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_whatwg_resolve_reference_from_zval(
986+
lxb_url_t *lexbor_base_url, const zval *scheme, const zval *username, const zval *password,
987+
const zval *host, const zval *port, const zval *path, const zval *query, const zval *fragment,
988+
zval *errors_zv
989+
) {
990+
lxb_status_t status;
991+
zval errors;
992+
ZVAL_UNDEF(&errors);
993+
994+
lxb_url_t *lexbor_url = php_uri_parser_whatwg_clone(lexbor_base_url);
995+
if (Z_TYPE_P(username) == IS_STRING) {
996+
status = lxb_url_parse_basic(&lexbor_parser, lexbor_url, lexbor_base_url,
997+
(lxb_char_t *) Z_STRVAL_P(username), Z_STRLEN_P(username),
998+
LXB_URL_STATE_AUTHORITY_STATE, LXB_ENCODING_AUTO
999+
);
1000+
php_uri_parser_whatwg_build_errors_and_throw(status, "username", &errors);
1001+
if (status != LXB_STATUS_OK) {
1002+
goto failure;
1003+
}
1004+
}
1005+
1006+
if (Z_TYPE_P(password) == IS_STRING) {
1007+
status = lxb_url_parse_basic(&lexbor_parser, lexbor_url, lexbor_base_url,
1008+
(lxb_char_t *) Z_STRVAL_P(password), Z_STRLEN_P(password),
1009+
LXB_URL_STATE_AUTHORITY_STATE, LXB_ENCODING_AUTO
1010+
);
1011+
php_uri_parser_whatwg_build_errors_and_throw(status, "password", &errors);
1012+
if (status != LXB_STATUS_OK) {
1013+
goto failure;
1014+
}
1015+
}
1016+
1017+
if (Z_TYPE_P(host) == IS_STRING) {
1018+
lxb_url_host_set_empty(&lexbor_url->host, &lexbor_mraw);
1019+
status = lxb_url_parse_basic(&lexbor_parser, lexbor_url, lexbor_base_url,
1020+
(lxb_char_t *) Z_STRVAL_P(host), Z_STRLEN_P(host),
1021+
LXB_URL_STATE_HOST_STATE, LXB_ENCODING_AUTO
1022+
);
1023+
php_uri_parser_whatwg_build_errors_and_throw(status, "host", &errors);
1024+
if (status != LXB_STATUS_OK) {
1025+
goto failure;
1026+
}
1027+
}
1028+
1029+
if (Z_TYPE_P(port) == IS_LONG) {
1030+
lexbor_str_t port_str = {0};
1031+
zval_long_or_null_to_lexbor_str(port, &port_str);
1032+
1033+
status = lxb_url_parse_basic(&lexbor_parser, lexbor_url, lexbor_base_url,
1034+
port_str.data, port_str.length, LXB_URL_STATE_PORT_STATE, LXB_ENCODING_AUTO
1035+
);
1036+
php_uri_parser_whatwg_build_errors_and_throw(status, "port", &errors);
1037+
if (status != LXB_STATUS_OK) {
1038+
goto failure;
1039+
}
1040+
}
1041+
1042+
if (Z_TYPE_P(path) == IS_STRING && Z_STRLEN_P(path) > 0) {
1043+
lxb_url_path_set_null(lexbor_url);
1044+
status = lxb_url_parse_basic(&lexbor_parser, lexbor_url, lexbor_base_url,
1045+
(lxb_char_t *) Z_STRVAL_P(path), Z_STRLEN_P(path),
1046+
lexbor_base_url->path.opaque ? LXB_URL_STATE_NO_SCHEME_STATE : LXB_URL_STATE_PATH_START_STATE, LXB_ENCODING_AUTO
1047+
);
1048+
php_uri_parser_whatwg_build_errors_and_throw(status, "path", &errors);
1049+
if (status != LXB_STATUS_OK) {
1050+
goto failure;
1051+
}
1052+
} else if (lexbor_base_url->path.str.data != NULL) {
1053+
zval zv;
1054+
ZVAL_NULL(&zv);
1055+
const zend_result result = php_uri_parser_whatwg_query_write(lexbor_url, &zv, NULL);
1056+
php_uri_parser_whatwg_build_errors(&errors);
1057+
if (result == FAILURE) {
1058+
goto failure;
1059+
}
1060+
}
1061+
1062+
if (Z_TYPE_P(query) == IS_STRING) {
1063+
status = lxb_url_parse_basic(&lexbor_parser, lexbor_url, lexbor_base_url,
1064+
(lxb_char_t *) Z_STRVAL_P(query), Z_STRLEN_P(query),
1065+
LXB_URL_STATE_QUERY_STATE, LXB_ENCODING_AUTO
1066+
);
1067+
php_uri_parser_whatwg_build_errors_and_throw(status, "query", &errors);
1068+
if (status != LXB_STATUS_OK) {
1069+
goto failure;
1070+
}
1071+
} else if (lexbor_base_url->query.data != NULL) {
1072+
zval zv;
1073+
ZVAL_NULL(&zv);
1074+
const zend_result result = php_uri_parser_whatwg_query_write(lexbor_url, &zv, NULL);
1075+
php_uri_parser_whatwg_build_errors(&errors);
1076+
if (result == FAILURE) {
1077+
goto failure;
1078+
}
1079+
}
1080+
1081+
if (Z_TYPE_P(fragment) == IS_STRING) {
1082+
status = lxb_url_parse_basic(&lexbor_parser, lexbor_url, lexbor_base_url,
1083+
(lxb_char_t *) Z_STRVAL_P(fragment), Z_STRLEN_P(fragment),
1084+
LXB_URL_STATE_FRAGMENT_STATE, LXB_ENCODING_AUTO
1085+
);
1086+
php_uri_parser_whatwg_build_errors_and_throw(status, "fragment", &errors);
1087+
if (status != LXB_STATUS_OK) {
1088+
goto failure;
1089+
}
1090+
} else if (lexbor_base_url->fragment.data != NULL) {
1091+
zval zv;
1092+
ZVAL_NULL(&zv);
1093+
const zend_result result = php_uri_parser_whatwg_fragment_write(lexbor_url, &zv, NULL);
1094+
php_uri_parser_whatwg_build_errors(&errors);
1095+
if (result == FAILURE) {
1096+
goto failure;
1097+
}
1098+
}
1099+
1100+
if (php_uri_pass_errors_by_ref_and_free(errors_zv, &errors) == FAILURE) {
1101+
goto failure;
1102+
}
1103+
1104+
return lexbor_url;
1105+
1106+
failure:
1107+
zval_ptr_dtor(&errors);
1108+
lxb_url_destroy(lexbor_url);
1109+
return NULL;
9651110
}
9661111

9671112
ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_whatwg_build_from_zval(
9681113
lxb_url_t *lexbor_base_url, const zval *scheme, const zval *username, const zval *password,
9691114
const zval *host, const zval *port, const zval *path, const zval *query, const zval *fragment,
9701115
zval *errors_zv
9711116
) {
1117+
lxb_url_parser_clean(&lexbor_parser);
1118+
1119+
if (lexbor_base_url != NULL && Z_TYPE_P(scheme) == IS_STRING && Z_STRLEN_P(scheme) == 0) {
1120+
return php_uri_parser_whatwg_resolve_reference_from_zval(
1121+
lexbor_base_url, scheme, username, password, host, port, path, query, fragment, errors_zv
1122+
);
1123+
}
1124+
9721125
if (Z_TYPE_P(host) == IS_NULL ||
9731126
Z_STRLEN_P(host) == 0 ||
9741127
php_uri_parser_whatwg_get_special_scheme(Z_STR_P(scheme)) == LXB_URL_SCHEMEL_TYPE_FILE
@@ -989,8 +1142,6 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_wh
9891142
}
9901143
}
9911144

992-
lxb_url_parser_clean(&lexbor_parser);
993-
9941145
lxb_url_t *lexbor_url = lexbor_mraw_calloc(lexbor_parser.mraw, sizeof(*lexbor_url));
9951146
if (lexbor_url == NULL) {
9961147
zend_throw_exception(php_uri_ce_whatwg_invalid_url_exception, "Memory allocation error", 0);
@@ -1061,16 +1212,11 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_wh
10611212
goto failure;
10621213
}
10631214

1064-
if (lexbor_base_url != NULL) {
1065-
/* TODO */
1066-
}
1067-
10681215
if (php_uri_pass_errors_by_ref_and_free(errors_zv, &errors) == FAILURE) {
10691216
goto failure;
10701217
}
10711218

10721219
return lexbor_url;
1073-
10741220
failure:
10751221
zval_ptr_dtor(&errors);
10761222
lxb_url_destroy(lexbor_url);

0 commit comments

Comments
 (0)