From 589b96c14bd8ea7a907db37634984c690f73b558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Sat, 27 Jun 2026 23:11:59 +0200 Subject: [PATCH 1/2] Apply lexbor fix for empty hosts (#22485) Fixed behavior of Uri\WhatWg\Url wither methods with regards to empty opaque hosts (e.g. "scheme://") by applying the https://github.com/lexbor/lexbor/commit/cf07699ca0f5fa4e1f7fd05c2135fd38e6d196c2 upstream fix for lexbor. --- NEWS | 4 + ext/lexbor/lexbor/url/url.c | 35 +++- .../patches/0007-Fix-empty-port-setter.patch | 191 ++++++++++++++++++ .../password_success_empty_host.phpt | 17 ++ .../modification/port_success_empty_host.phpt | 17 ++ .../username_success_empty_host.phpt | 17 ++ 6 files changed, 276 insertions(+), 5 deletions(-) create mode 100644 ext/lexbor/patches/0007-Fix-empty-port-setter.patch create mode 100644 ext/uri/tests/whatwg/modification/password_success_empty_host.phpt create mode 100644 ext/uri/tests/whatwg/modification/port_success_empty_host.phpt create mode 100644 ext/uri/tests/whatwg/modification/username_success_empty_host.phpt diff --git a/NEWS b/NEWS index 4022869c0b06..9522c2028834 100644 --- a/NEWS +++ b/NEWS @@ -48,6 +48,10 @@ PHP NEWS . Fixed bug GH-22395 (base_convert() outputs at most 64 characters). (Weilin Du) +- URI: + . Fixed behavior of Uri\WhatWg\Url wither methods with regards to empty + opaque hosts. (kocsismate) + 02 Jul 2026, PHP 8.5.8 - Core: diff --git a/ext/lexbor/lexbor/url/url.c b/ext/lexbor/lexbor/url/url.c index 5a1143469d1a..86bcf8f35027 100644 --- a/ext/lexbor/lexbor/url/url.c +++ b/ext/lexbor/lexbor/url/url.c @@ -1115,11 +1115,13 @@ lxb_url_host_copy(const lxb_url_host_t *src, lxb_url_host_t *dst, dst->type = src->type; - if (src->type <= LXB_URL_HOST_TYPE_OPAQUE) { - if (src->type == LXB_URL_HOST_TYPE__UNDEF) { - return LXB_STATUS_OK; - } + if (src->type == LXB_URL_HOST_TYPE__UNDEF + || src->type == LXB_URL_HOST_TYPE_EMPTY) + { + return LXB_STATUS_OK; + } + if (src->type <= LXB_URL_HOST_TYPE_OPAQUE) { return lxb_url_str_copy(&src->u.domain, &dst->u.domain, dst_mraw); } @@ -1152,6 +1154,24 @@ lxb_url_host_set_empty(lxb_url_host_t *host, lexbor_mraw_t *mraw) host->type = LXB_URL_HOST_TYPE_EMPTY; } +lxb_inline bool +lxb_url_host_is_empty(const lxb_url_host_t *host) +{ + if (host->type == LXB_URL_HOST_TYPE_EMPTY) { + return true; + } + + if (host->type == LXB_URL_HOST_TYPE_DOMAIN) { + return host->u.domain.length == 0; + } + + if (host->type == LXB_URL_HOST_TYPE_OPAQUE) { + return host->u.opaque.length == 0; + } + + return false; +} + static bool lxb_url_host_eq(lxb_url_host_t *host, const lxb_char_t *data, size_t length) { @@ -1251,7 +1271,7 @@ lxb_url_normalized_windows_drive_letter(const lxb_char_t *data, static bool lxb_url_cannot_have_user_pass_port(lxb_url_t *url) { - return url->host.type == LXB_URL_HOST_TYPE_EMPTY + return lxb_url_host_is_empty(&url->host) || url->host.type == LXB_URL_HOST_TYPE__UNDEF || url->scheme.type == LXB_URL_SCHEMEL_TYPE_FILE; } @@ -3978,6 +3998,11 @@ lxb_url_opaque_host_parse(lxb_url_parser_t *parser, const lxb_char_t *data, lxb_status_t status; const lxb_char_t *p; + if (data == end) { + lxb_url_host_set_empty(host, mraw); + return LXB_STATUS_OK; + } + p = data; while (p < end) { diff --git a/ext/lexbor/patches/0007-Fix-empty-port-setter.patch b/ext/lexbor/patches/0007-Fix-empty-port-setter.patch new file mode 100644 index 000000000000..75ceaab0c63f --- /dev/null +++ b/ext/lexbor/patches/0007-Fix-empty-port-setter.patch @@ -0,0 +1,191 @@ +From cf07699ca0f5fa4e1f7fd05c2135fd38e6d196c2 Mon Sep 17 00:00:00 2001 +From: Alexander Borisov +Date: Fri, 26 Jun 2026 18:55:56 +0300 +Subject: [PATCH] URL: fixed setters for empty hosts. +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Empty non-special hosts were represented as empty opaque hosts, so +lxb_url_cannot_have_user_pass_port() allowed username, password, and port +setters to modify scheme://. + +For fixed this store empty opaque-host input as LXB_URL_HOST_TYPE_EMPTY. + +Per report from Máté Kocsis (@kocsismate). + +This relates to #387 issue on GitHub. +--- + source/lexbor/url/url.c | 35 ++++++++++++++++++--- + test/files/lexbor/url/changes.ton | 52 +++++++++++++++++++++++++++++-- + test/files/lexbor/url/url.ton | 8 ++++- + 3 files changed, 86 insertions(+), 9 deletions(-) + +diff --git a/source/lexbor/url/url.c b/source/lexbor/url/url.c +index ced4462b..e1da2c38 100644 +--- a/source/lexbor/url/url.c ++++ b/source/lexbor/url/url.c +@@ -1116,11 +1116,13 @@ lxb_url_host_copy(const lxb_url_host_t *src, lxb_url_host_t *dst, + + dst->type = src->type; + +- if (src->type <= LXB_URL_HOST_TYPE_OPAQUE) { +- if (src->type == LXB_URL_HOST_TYPE__UNDEF) { +- return LXB_STATUS_OK; +- } ++ if (src->type == LXB_URL_HOST_TYPE__UNDEF ++ || src->type == LXB_URL_HOST_TYPE_EMPTY) ++ { ++ return LXB_STATUS_OK; ++ } + ++ if (src->type <= LXB_URL_HOST_TYPE_OPAQUE) { + return lxb_url_str_copy(&src->u.domain, + &dst->u.domain, dst_mraw); + } +@@ -1153,6 +1155,24 @@ lxb_url_host_set_empty(lxb_url_host_t *host, lexbor_mraw_t *mraw) + host->type = LXB_URL_HOST_TYPE_EMPTY; + } + ++lxb_inline bool ++lxb_url_host_is_empty(const lxb_url_host_t *host) ++{ ++ if (host->type == LXB_URL_HOST_TYPE_EMPTY) { ++ return true; ++ } ++ ++ if (host->type == LXB_URL_HOST_TYPE_DOMAIN) { ++ return host->u.domain.length == 0; ++ } ++ ++ if (host->type == LXB_URL_HOST_TYPE_OPAQUE) { ++ return host->u.opaque.length == 0; ++ } ++ ++ return false; ++} ++ + static bool + lxb_url_host_eq(lxb_url_host_t *host, const lxb_char_t *data, size_t length) + { +@@ -1252,7 +1272,7 @@ lxb_url_normalized_windows_drive_letter(const lxb_char_t *data, + static bool + lxb_url_cannot_have_user_pass_port(lxb_url_t *url) + { +- return url->host.type == LXB_URL_HOST_TYPE_EMPTY ++ return lxb_url_host_is_empty(&url->host) + || url->host.type == LXB_URL_HOST_TYPE__UNDEF + || url->scheme.type == LXB_URL_SCHEMEL_TYPE_FILE; + } +@@ -3979,6 +3999,11 @@ lxb_url_opaque_host_parse(lxb_url_parser_t *parser, const lxb_char_t *data, + lxb_status_t status; + const lxb_char_t *p; + ++ if (data == end) { ++ lxb_url_host_set_empty(host, mraw); ++ return LXB_STATUS_OK; ++ } ++ + p = data; + + while (p < end) { +diff --git a/test/files/lexbor/url/changes.ton b/test/files/lexbor/url/changes.ton +index 07bc9449..1a0b6e35 100644 +--- a/test/files/lexbor/url/changes.ton ++++ b/test/files/lexbor/url/changes.ton +@@ -1,5 +1,5 @@ + [ +- /* Test count: 1 */ ++ /* Test count: 47 */ + /* 1 */ + { + "url": "https://user:pass@lexbor.com/docs/html/path?x=y&a=b#best-fragment", +@@ -982,9 +982,53 @@ + "failed": false + }, + /* 45 */ ++ { ++ "url": "scheme://", ++ "done": "scheme://", ++ "change": { ++ "href": null, ++ "protocol": null, ++ "username": "user", ++ "password": "pass", ++ "host": null, ++ "hostname": null, ++ "port": "433", ++ "pathname": null, ++ "search": null, ++ "hash": null ++ }, ++ "scheme": "scheme", ++ "host": "", ++ "path": "", ++ "failed": false ++ }, ++ /* 46 */ ++ { ++ "url": "scheme://host", ++ "done": "scheme://host:433", ++ "change": { ++ "href": null, ++ "protocol": null, ++ "username": null, ++ "password": null, ++ "host": null, ++ "hostname": null, ++ "port": "433", ++ "pathname": null, ++ "search": null, ++ "hash": null ++ }, ++ "scheme": "scheme", ++ "host": "host", ++ "port": 433, ++ "has_port": true, ++ "path": "", ++ "failed": false ++ }, ++ /* 47 */ + { + "url": "https://example.com:432", +- "done": "https://example.com:432", ++ "done": "https://example.com:432/", + "change": { + "href": null, + "protocol": null, +@@ -999,7 +1043,9 @@ + }, + "scheme": "https", + "host": "example.com", +- "port": "432", ++ "port": 432, ++ "has_port": true, ++ "path": "/", + "failed": true + } + ] +diff --git a/test/files/lexbor/url/url.ton b/test/files/lexbor/url/url.ton +index 2baa4bc2..85794c5b 100644 +--- a/test/files/lexbor/url/url.ton ++++ b/test/files/lexbor/url/url.ton +@@ -1,5 +1,5 @@ + [ +- /* Test count: 7 */ ++ /* Test count: 8 */ + /* 1 */ + { + "url": "https://user:pass@lexbor.com:450/docs/lexbor/?search=lxb_status_t#version", +@@ -74,5 +74,11 @@ + "path": "", + "failed": false, + "encoding": "utf-8" ++ }, ++ /* 8 */ ++ { ++ "url": "scheme://:433", ++ "failed": true, ++ "encoding": "utf-8" + } + ] diff --git a/ext/uri/tests/whatwg/modification/password_success_empty_host.phpt b/ext/uri/tests/whatwg/modification/password_success_empty_host.phpt new file mode 100644 index 000000000000..217e8f174d8b --- /dev/null +++ b/ext/uri/tests/whatwg/modification/password_success_empty_host.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test Uri\WhatWg\Url component modification - password - empty host +--FILE-- +withPassword("password"); + +var_dump($url1->getPassword()); +var_dump($url2->getPassword()); +var_dump($url2->toAsciiString()); + +?> +--EXPECT-- +NULL +NULL +string(9) "scheme://" diff --git a/ext/uri/tests/whatwg/modification/port_success_empty_host.phpt b/ext/uri/tests/whatwg/modification/port_success_empty_host.phpt new file mode 100644 index 000000000000..df392ab4b744 --- /dev/null +++ b/ext/uri/tests/whatwg/modification/port_success_empty_host.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test Uri\WhatWg\Url component modification - port - empty host +--FILE-- +withPort(433); + +var_dump($url1->getPort()); +var_dump($url2->getPort()); +var_dump($url2->toAsciiString()); + +?> +--EXPECT-- +NULL +NULL +string(9) "scheme://" diff --git a/ext/uri/tests/whatwg/modification/username_success_empty_host.phpt b/ext/uri/tests/whatwg/modification/username_success_empty_host.phpt new file mode 100644 index 000000000000..13357d269b49 --- /dev/null +++ b/ext/uri/tests/whatwg/modification/username_success_empty_host.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test Uri\WhatWg\Url component modification - username - empty host +--FILE-- +withUsername("user"); + +var_dump($url1->getUsername()); +var_dump($url2->getUsername()); +var_dump($url2->toAsciiString()); + +?> +--EXPECT-- +NULL +NULL +string(9) "scheme://" From 5cdd42452a218e17c570cbdb753a5fe63024acb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Sat, 27 Jun 2026 23:14:51 +0200 Subject: [PATCH 2/2] Fix lexbor patch name for master There is an extra patch on master compared to the PHP-8.5 branch. --- ...x-empty-port-setter.patch => 0008-Fix-empty-port-setter.patch} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ext/lexbor/patches/{0007-Fix-empty-port-setter.patch => 0008-Fix-empty-port-setter.patch} (100%) diff --git a/ext/lexbor/patches/0007-Fix-empty-port-setter.patch b/ext/lexbor/patches/0008-Fix-empty-port-setter.patch similarity index 100% rename from ext/lexbor/patches/0007-Fix-empty-port-setter.patch rename to ext/lexbor/patches/0008-Fix-empty-port-setter.patch