[php-src] PHP-8.5.8: Apply lexbor fix for empty hosts (#22485)

Máté Kocsis via Daniel Scherzer <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Máté Kocsis (kocsismate)
Committer: Daniel Scherzer (DanielEScherzer)
Date: 2026-06-30T19:52:55-07:00

Commit: https://github.com/php/php-src/commit/5b650f37c5dbdf3422238dc51f2bb4db5eddd21c
Raw diff: https://github.com/php/php-src/commit/5b650f37c5dbdf3422238dc51f2bb4db5eddd21c.diff

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.

(cherry picked from commit 589b96c14bd8ea7a907db37634984c690f73b558)

Changed paths:
  A  ext/lexbor/patches/0007-Fix-empty-port-setter.patch
  A  ext/uri/tests/whatwg/modification/password_success_empty_host.phpt
  A  ext/uri/tests/whatwg/modification/port_success_empty_host.phpt
  A  ext/uri/tests/whatwg/modification/username_success_empty_host.phpt
  M  ext/lexbor/lexbor/url/url.c


Diff:

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 <[email protected]>
+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:[email protected]/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:[email protected]: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--
+<?php
+
+$url1 = new Uri\WhatWg\Url("scheme://");
+$url2 = $url1->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--
+<?php
+
+$url1 = new Uri\WhatWg\Url("scheme://");
+$url2 = $url1->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--
+<?php
+
+$url1 = new Uri\WhatWg\Url("scheme://");
+$url2 = $url1->withUsername("user");
+
+var_dump($url1->getUsername());
+var_dump($url2->getUsername());
+var_dump($url2->toAsciiString());
+
+?>
+--EXPECT--
+NULL
+NULL
+string(9) "scheme://"
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.