From ab2bf4681d902e8a5962822395f8894083c1c42b Mon Sep 17 00:00:00 2001 From: Juliette <663378+jrfnl@users.noreply.github.com> Date: Sat, 15 Aug 2026 11:38:49 +0200 Subject: [PATCH 1/5] PHP 8.5 | UPGRADING: add missing finfo function (#23263) The entry about the new function parameter for finfo, is missing the OO method. See: https://github.com/php/php-src/commit/ba21ab4ea012f35091694b8b03a79dcab1de6742 Co-authored-by: jrfnl --- UPGRADING | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UPGRADING b/UPGRADING index 39400b5ea6d6..50754b279439 100644 --- a/UPGRADING +++ b/UPGRADING @@ -433,8 +433,8 @@ PHP 8.5 UPGRADE NOTES . The finfo_close() function has been deprecated. As finfo objects are freed automatically. RFC: https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_finfo_close - . The $context parameter of the finfo_buffer() function has been deprecated - as it is ignored. + . The $context parameter of the finfo_buffer() and finfo::buffer() functions + has been deprecated as it is ignored. RFC: https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_the_context_parameter_for_finfo_buffer - GD: From a0a11676e3b7987ba9d196e5525d734a3ff13b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Sat, 15 Aug 2026 13:02:11 +0200 Subject: [PATCH 2/5] lexbor: Add new URL patches (#23274) - URL: added public IPv6 parser. (https://github.com/lexbor/lexbor/commit/a7dbebbe) - URL: added public percent-encoder API. (https://github.com/lexbor/lexbor/commit/2b24b565) Both of which commits are needed for https://wiki.php.net/rfc/uri_followup --- ext/lexbor/lexbor/url/url.c | 135 ++-- ext/lexbor/lexbor/url/url.h | 122 ++++ ...nd-column-information-for-use-in-PHP.patch | 2 +- ...d-added-nodes-for-options-use-in-PHP.patch | 2 +- ...and-data-structure-to-be-able-to-gen.patch | 2 +- ...ve-unused-upper-case-tag-static-data.patch | 2 +- ...nk-size-of-static-binary-search-tree.patch | 2 +- ...0006-Patch-out-unused-CSS-style-code.patch | 2 +- ...url_is_special-to-the-public-API-362.patch | 2 +- ...08-URL-fixed-setters-for-empty-hosts.patch | 2 +- ...ialized-memory-in-the-path-buffer-gr.patch | 2 +- ...URL-containing-empty-host-and-userin.patch | 2 +- ...Percent-encode-the-caret-in-the-path.patch | 2 +- .../0012-URL-added-public-IPv6-parser.patch | 320 +++++++++ ...URL-added-public-percent-encoder-API.patch | 626 ++++++++++++++++++ 15 files changed, 1177 insertions(+), 48 deletions(-) create mode 100644 ext/lexbor/patches/0012-URL-added-public-IPv6-parser.patch create mode 100644 ext/lexbor/patches/0013-URL-added-public-percent-encoder-API.patch diff --git a/ext/lexbor/lexbor/url/url.c b/ext/lexbor/lexbor/url/url.c index 8099c12089bb..69d91969a6a1 100644 --- a/ext/lexbor/lexbor/url/url.c +++ b/ext/lexbor/lexbor/url/url.c @@ -27,20 +27,6 @@ #define LXB_URL_BUFFER_NUM_SIZE 128 -typedef enum { - LXB_URL_MAP_UNDEF = 0x00, - LXB_URL_MAP_C0 = 0x01, - LXB_URL_MAP_FRAGMENT = 0x02, - LXB_URL_MAP_QUERY = 0x04, - LXB_URL_MAP_SPECIAL_QUERY = 0x08, - LXB_URL_MAP_PATH = 0x10, - LXB_URL_MAP_USERINFO = 0x20, - LXB_URL_MAP_COMPONENT = 0x40, - LXB_URL_MAP_X_WWW_FORM = 0x80, - LXB_URL_MAP_ALL = 0xff -} -lxb_url_map_type_t; - typedef enum { LXB_URL_HOST_OPT_UNDEF = 0 << 0, LXB_URL_HOST_OPT_NOT_SPECIAL = 1 << 0, @@ -563,7 +549,7 @@ lxb_url_path_fix_windows_drive(lxb_url_t *url, lxb_char_t *sbuf, static lxb_status_t lxb_url_percent_encode_after_encoding(const lxb_char_t *data, const lxb_char_t *end, lexbor_str_t *str, - lexbor_mraw_t *mraw, + lexbor_mraw_t *mraw, const uint8_t *url_map, const lxb_encoding_data_t *encoding, lxb_url_map_type_t enmap, bool space_as_plus); @@ -571,7 +557,7 @@ lxb_url_percent_encode_after_encoding(const lxb_char_t *data, static lxb_status_t lxb_url_percent_encode_after_utf_8(const lxb_char_t *data, const lxb_char_t *end, lexbor_str_t *str, - lexbor_mraw_t *mraw, + lexbor_mraw_t *mraw, const uint8_t *url_map, lxb_url_map_type_t enmap, bool space_as_plus); @@ -1757,9 +1743,9 @@ lxb_url_parse_basic_h(lxb_url_parser_t *parser, lxb_url_t *url, tmp = (pswd != NULL) ? pswd - 1 : p; if (tmp > begin) { - status = lxb_url_percent_encode_after_utf_8(begin, tmp, - &url->username, url->mraw, - LXB_URL_MAP_USERINFO, false); + status = lxb_url_percent_encode_after_utf_8(begin, + tmp, &url->username, url->mraw, lxb_url_map, + LXB_URL_MAP_USERINFO, false); if (status != LXB_STATUS_OK) { lxb_url_parse_return(orig_data, buf, status); } @@ -1768,8 +1754,8 @@ lxb_url_parse_basic_h(lxb_url_parser_t *parser, lxb_url_t *url, if (pswd != NULL && p > pswd) { status = lxb_url_percent_encode_after_utf_8(pswd, p, - &url->password, url->mraw, - LXB_URL_MAP_USERINFO, false); + &url->password, url->mraw, lxb_url_map, + LXB_URL_MAP_USERINFO, false); if (status != LXB_STATUS_OK) { lxb_url_parse_return(orig_data, buf, status); } @@ -2319,8 +2305,8 @@ lxb_url_parse_basic_h(lxb_url_parser_t *parser, lxb_url_t *url, if (p >= end) { tmp_str.data = NULL; - status = lxb_url_percent_encode_after_utf_8(begin, p, - &tmp_str, url->mraw, + status = lxb_url_percent_encode_after_utf_8(begin, p, &tmp_str, + url->mraw, lxb_url_map, LXB_URL_MAP_C0, false); if (status != LXB_STATUS_OK) { lxb_url_parse_return(orig_data, buf, status); @@ -2336,8 +2322,8 @@ lxb_url_parse_basic_h(lxb_url_parser_t *parser, lxb_url_t *url, if (c == '#' || c == '?') { tmp_str.data = NULL; - status = lxb_url_percent_encode_after_utf_8(begin, p, - &tmp_str, url->mraw, + status = lxb_url_percent_encode_after_utf_8(begin, p, &tmp_str, + url->mraw, lxb_url_map, LXB_URL_MAP_C0, false); if (status != LXB_STATUS_OK) { lxb_url_parse_return(orig_data, buf, status); @@ -2407,7 +2393,8 @@ lxb_url_parse_basic_h(lxb_url_parser_t *parser, lxb_url_t *url, status = lxb_url_percent_encode_after_encoding(begin, p, &url->query, - url->mraw, enc, + url->mraw, + lxb_url_map, enc, map_type, false); if (status != LXB_STATUS_OK) { lxb_url_parse_return(orig_data, buf, status); @@ -2461,7 +2448,7 @@ lxb_url_parse_basic_h(lxb_url_parser_t *parser, lxb_url_t *url, } status = lxb_url_percent_encode_after_utf_8(begin, p, &url->fragment, - url->mraw, + url->mraw, lxb_url_map, LXB_URL_MAP_FRAGMENT, false); lxb_url_parse_return(orig_data, buf, status); @@ -3161,10 +3148,23 @@ lxb_url_scheme_find(const lxb_char_t *data, size_t length) return &lxb_url_scheme_res[LXB_URL_SCHEMEL_TYPE__UNKNOWN]; } +lxb_status_t +lxb_url_percent_encode_encoding(const lxb_char_t *data, size_t length, + lexbor_str_t *str, lexbor_mraw_t *mraw, + const uint8_t *url_map, + const lxb_encoding_data_t *encoding, + lxb_url_map_type_t enmap, + bool space_as_plus) +{ + return lxb_url_percent_encode_after_encoding(data, data + length, str, mraw, + url_map, encoding, enmap, + space_as_plus); +} + static lxb_status_t lxb_url_percent_encode_after_encoding(const lxb_char_t *data, const lxb_char_t *end, lexbor_str_t *str, - lexbor_mraw_t *mraw, + lexbor_mraw_t *mraw, const uint8_t *url_map, const lxb_encoding_data_t *encoding, lxb_url_map_type_t enmap, bool space_as_plus) @@ -3182,7 +3182,8 @@ lxb_url_percent_encode_after_encoding(const lxb_char_t *data, if (encoding->encoding == LXB_ENCODING_UTF_8) { return lxb_url_percent_encode_after_utf_8(data, end, str, mraw, - enmap, space_as_plus); + url_map, enmap, + space_as_plus); } lxb_url_encoding_init(encoding, &encode); @@ -3193,7 +3194,7 @@ lxb_url_percent_encode_after_encoding(const lxb_char_t *data, /* Only valid for UTF-8. */ while (p < end) { - if (lxb_url_map[*p++] & enmap) { + if (url_map[*p++] & enmap) { length += 2; } } @@ -3249,7 +3250,7 @@ lxb_url_percent_encode_after_encoding(const lxb_char_t *data, return LXB_STATUS_ERROR_MEMORY_ALLOCATION; } } - else if (lxb_url_map[c] & enmap) { + else if (url_map[c] & enmap) { percent[1] = lexbor_str_res_char_to_two_hex_value[c][0]; percent[2] = lexbor_str_res_char_to_two_hex_value[c][1]; @@ -3280,10 +3281,20 @@ lxb_url_percent_encode_after_encoding(const lxb_char_t *data, return LXB_STATUS_OK; } +lxb_status_t +lxb_url_percent_encode_utf_8(const lxb_char_t *data, size_t length, + lexbor_str_t *str, lexbor_mraw_t *mraw, + const uint8_t *url_map, lxb_url_map_type_t enmap, + bool space_as_plus) +{ + return lxb_url_percent_encode_after_utf_8(data, data + length, str, mraw, + url_map, enmap, space_as_plus); +} + static lxb_status_t lxb_url_percent_encode_after_utf_8(const lxb_char_t *data, const lxb_char_t *end, lexbor_str_t *str, - lexbor_mraw_t *mraw, + lexbor_mraw_t *mraw, const uint8_t *url_map, lxb_url_map_type_t enmap, bool space_as_plus) { @@ -3298,7 +3309,7 @@ lxb_url_percent_encode_after_utf_8(const lxb_char_t *data, /* Only valid for UTF-8. */ while (p < end) { - if (lxb_url_map[*p++] & enmap) { + if (url_map[*p++] & enmap) { length += 2; } } @@ -3317,7 +3328,7 @@ lxb_url_percent_encode_after_utf_8(const lxb_char_t *data, if (space_as_plus && c == ' ') { *pd++ = '+'; } - else if (lxb_url_map[c] & enmap) { + else if (url_map[c] & enmap) { *pd++ = '%'; *pd++ = lexbor_str_res_char_to_two_hex_value[c][0]; *pd++ = lexbor_str_res_char_to_two_hex_value[c][1]; @@ -3335,6 +3346,12 @@ lxb_url_percent_encode_after_utf_8(const lxb_char_t *data, return LXB_STATUS_OK; } +const uint8_t * +lxb_url_get_percent_encoding_map(void) +{ + return lxb_url_map; +} + static lxb_status_t lxb_url_host_parse(lxb_url_parser_t *parser, const lxb_char_t *data, const lxb_char_t *end, lxb_url_host_t *host, @@ -3752,6 +3769,46 @@ lxb_url_is_ipv4(lxb_url_parser_t *parser, const lxb_char_t *data, return status != LXB_STATUS_ERROR; } +lxb_status_t +lxb_url_parse_host_ipv6(lxb_url_parser_t *parser, const lxb_char_t *data, + size_t length, uint16_t *ipv6) +{ + lxb_status_t status; + lxb_url_parser_t self_parser; + + if (parser == NULL) { + parser = &self_parser; + + parser->log = NULL; + parser->idna = NULL; + parser->buffer = NULL; + } + + if (data < data + length && *data == '[') { + if (data[length - 1] != ']') { + (void) lxb_url_log_append(parser, &data[length - 1], + LXB_URL_ERROR_TYPE_IPV6_UNCLOSED); + + status = LXB_STATUS_ERROR_UNEXPECTED_DATA; + + goto done; + } + + data += 1; + length -= 2; + } + + status = lxb_url_ipv6_parse(parser, data, data + length, ipv6); + +done: + + if (parser == &self_parser) { + lxb_url_parser_destroy(parser, false); + } + + return status; +} + static lxb_status_t lxb_url_ipv6_parse(lxb_url_parser_t *parser, const lxb_char_t *data, const lxb_char_t *end, uint16_t *ipv6) @@ -3763,6 +3820,8 @@ lxb_url_ipv6_parse(lxb_url_parser_t *parser, const lxb_char_t *data, const lxb_char_t *p; lxb_url_error_type_t err_type; + memset(ipv6, 0x00, sizeof(uint16_t) * 8); + piece = ipv6; compress = NULL; p = data; @@ -4023,7 +4082,7 @@ lxb_url_opaque_host_parse(lxb_url_parser_t *parser, const lxb_char_t *data, host->type = LXB_URL_HOST_TYPE_OPAQUE; return lxb_url_percent_encode_after_utf_8(data, end, &host->u.opaque, mraw, - LXB_URL_MAP_C0, false); + lxb_url_map, LXB_URL_MAP_C0, false); } static lxb_status_t @@ -4302,7 +4361,8 @@ lxb_url_api_username_set(lxb_url_t *url, return lxb_url_percent_encode_after_utf_8(username, username + length, &url->username, url->mraw, - LXB_URL_MAP_USERINFO, false); + lxb_url_map, LXB_URL_MAP_USERINFO, + false); } lxb_status_t @@ -4322,7 +4382,8 @@ lxb_url_api_password_set(lxb_url_t *url, return lxb_url_percent_encode_after_utf_8(password, password + length, &url->password, url->mraw, - LXB_URL_MAP_USERINFO, false); + lxb_url_map, LXB_URL_MAP_USERINFO, + false); } lxb_status_t diff --git a/ext/lexbor/lexbor/url/url.h b/ext/lexbor/lexbor/url/url.h index 6cc6f1081c8a..d2c93080c922 100644 --- a/ext/lexbor/lexbor/url/url.h +++ b/ext/lexbor/lexbor/url/url.h @@ -81,6 +81,20 @@ typedef enum { } lxb_url_state_t; +typedef enum { + LXB_URL_MAP_UNDEF = 0x00, + LXB_URL_MAP_C0 = 0x01, + LXB_URL_MAP_FRAGMENT = 0x02, + LXB_URL_MAP_QUERY = 0x04, + LXB_URL_MAP_SPECIAL_QUERY = 0x08, + LXB_URL_MAP_PATH = 0x10, + LXB_URL_MAP_USERINFO = 0x20, + LXB_URL_MAP_COMPONENT = 0x40, + LXB_URL_MAP_X_WWW_FORM = 0x80, + LXB_URL_MAP_ALL = 0xff +} +lxb_url_map_type_t; + /* * New values can only be added downwards. * Before LXB_URL_SCHEMEL_TYPE__LAST_ENTRY. @@ -321,6 +335,114 @@ lxb_url_parse_basic(lxb_url_parser_t *parser, lxb_url_t *url, const lxb_char_t *data, size_t length, lxb_url_state_t override_state, lxb_encoding_t encoding); +/* + * IPv6 parser. + * + * This function is an implementation of IPv6 parsing according to the WHATWG + * specification. + * https://url.spec.whatwg.org/#concept-ipv6-parser + * + * The address can be passed both with and without the surrounding square + * brackets: "::1" and "[::1]" give the same result. If the opening bracket is + * present, the closing one is required. + * + * The output buffer is zeroed by the function, there is no need to prepare it. + * Use the lxb_url_serialize_host_ipv6() function to serialize the result. + * + * @param[in] lxb_url_parser_t *. Can be NULL. + * @param[in] Pointer to the beginning of the data. Not NULL. + * @param[in] Length of the data. Can be 0. + * @param[out] Buffer for eight (uint16_t[8]) IPv6 pieces. Not NULL. The value + * is meaningful only if LXB_STATUS_OK is returned. + * + * @return LXB_STATUS_OK if successful, otherwise an error status value. + */ +LXB_API lxb_status_t +lxb_url_parse_host_ipv6(lxb_url_parser_t *parser, const lxb_char_t *data, + size_t length, uint16_t *ipv6); + +/* + * UTF-8 percent-encoder. + * + * Percent-encodes bytes from data according to url_map and appends the result + * to str. A byte is encoded as "%HH" when the result of + * (url_map[byte] & enmap) is non-zero. Uppercase hexadecimal digits are used. + * If space_as_plus is true, U+0020 SPACE is encoded as '+' before the map is + * checked. + * + * The input is expected to be valid UTF-8; the function does not validate it. + * + * @param[in] Pointer to UTF-8 data. Not NULL. + * @param[in] Length of data. Can be 0. + * @param[in, out] Output string. Can be uninitialized (data = NULL). Encoded + * data is appended to any existing content. Not NULL. + * @param[in] Memory object used to allocate or resize the output string. Not + * NULL. + * @param[in] Table of 256 entries indexed by input byte, each entry is a bit + * mask of lxb_url_map_type_t values. Not NULL. + * @param[in] Mask selecting the bytes to percent-encode. + * @param[in] Replace U+0020 SPACE with '+' if true. + * + * @return LXB_STATUS_OK if successful, otherwise an error status value. + */ +LXB_API lxb_status_t +lxb_url_percent_encode_utf_8(const lxb_char_t *data, size_t length, + lexbor_str_t *str, lexbor_mraw_t *mraw, + const uint8_t *url_map, lxb_url_map_type_t enmap, + bool space_as_plus); + +/* + * Percent-encode after encoding. + * + * Converts valid UTF-8 data to the specified encoding and appends the + * percent-encoded result to str. Each encoded byte for which + * (url_map[byte] & enmap) is non-zero is written as "%HH" using uppercase + * hexadecimal digits. If a code point cannot be represented in the target + * encoding, its percent-encoded numeric character reference is appended. + * + * If encoding is UTF-8, no conversion is performed. If space_as_plus is true, + * an encoded U+0020 SPACE is replaced with '+' before the map is checked. + * The input is expected to be valid UTF-8; the function does not validate it. + * + * @param[in] Pointer to UTF-8 data. Not NULL. + * @param[in] Length of data. Can be 0. + * @param[in, out] Output string. Can be uninitialized (data = NULL). Encoded + * data is appended to any existing content. Not NULL. + * @param[in] Memory object used to allocate or resize the output string. Not + * NULL. + * @param[in] Table of 256 entries indexed by encoded byte, each entry is a bit + * mask of lxb_url_map_type_t values. Not NULL. + * @param[in] Target encoding. Not NULL. + * @param[in] Mask selecting the bytes to percent-encode. + * @param[in] Replace an encoded U+0020 SPACE with '+' if true. + * + * @return LXB_STATUS_OK if successful, otherwise an error status value. + */ +LXB_API lxb_status_t +lxb_url_percent_encode_encoding(const lxb_char_t *data, size_t length, + lexbor_str_t *str, lexbor_mraw_t *mraw, + const uint8_t *url_map, + const lxb_encoding_data_t *encoding, + lxb_url_map_type_t enmap, + bool space_as_plus); + +/* + * Get the URL percent-encoding map. + * + * Returns the built-in lookup table for the percent-encode sets defined by the + * URL specification. The table contains 256 entries indexed by byte value. + * Each entry is a bit mask of the lxb_url_map_type_t sets in which the byte + * must be percent-encoded. + * + * The returned map can be passed to lxb_url_percent_encode_utf_8() or + * lxb_url_percent_encode_encoding(). It has static storage duration and must + * not be modified or freed. + * + * @return Pointer to a read-only table of 256 entries. Never NULL. + */ +LXB_API const uint8_t * +lxb_url_get_percent_encoding_map(void); + /* * Erase URL. * diff --git a/ext/lexbor/patches/0001-Expose-line-and-column-information-for-use-in-PHP.patch b/ext/lexbor/patches/0001-Expose-line-and-column-information-for-use-in-PHP.patch index f23ec0f5034d..b9c4cda7a03a 100644 --- a/ext/lexbor/patches/0001-Expose-line-and-column-information-for-use-in-PHP.patch +++ b/ext/lexbor/patches/0001-Expose-line-and-column-information-for-use-in-PHP.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 26 Aug 2023 15:08:59 +0200 -Subject: [PATCH 01/11] Expose line and column information for use in PHP +Subject: [PATCH 01/13] Expose line and column information for use in PHP --- source/lexbor/dom/interfaces/node.h | 2 ++ diff --git a/ext/lexbor/patches/0002-Track-implied-added-nodes-for-options-use-in-PHP.patch b/ext/lexbor/patches/0002-Track-implied-added-nodes-for-options-use-in-PHP.patch index 8758c09a2e8f..84868e209e75 100644 --- a/ext/lexbor/patches/0002-Track-implied-added-nodes-for-options-use-in-PHP.patch +++ b/ext/lexbor/patches/0002-Track-implied-added-nodes-for-options-use-in-PHP.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 14 Aug 2023 20:18:51 +0200 -Subject: [PATCH 02/11] Track implied added nodes for options use in PHP +Subject: [PATCH 02/13] Track implied added nodes for options use in PHP --- source/lexbor/html/tree.h | 3 +++ diff --git a/ext/lexbor/patches/0003-Patch-utilities-and-data-structure-to-be-able-to-gen.patch b/ext/lexbor/patches/0003-Patch-utilities-and-data-structure-to-be-able-to-gen.patch index 56458a49deea..357504376679 100644 --- a/ext/lexbor/patches/0003-Patch-utilities-and-data-structure-to-be-able-to-gen.patch +++ b/ext/lexbor/patches/0003-Patch-utilities-and-data-structure-to-be-able-to-gen.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 24 Aug 2023 22:57:48 +0200 -Subject: [PATCH 03/11] Patch utilities and data structure to be able to +Subject: [PATCH 03/13] Patch utilities and data structure to be able to generate smaller lookup tables Changed the generation script to check if everything fits in 32-bits. diff --git a/ext/lexbor/patches/0004-Remove-unused-upper-case-tag-static-data.patch b/ext/lexbor/patches/0004-Remove-unused-upper-case-tag-static-data.patch index f4fe2050998d..029c0ff69335 100644 --- a/ext/lexbor/patches/0004-Remove-unused-upper-case-tag-static-data.patch +++ b/ext/lexbor/patches/0004-Remove-unused-upper-case-tag-static-data.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 29 Nov 2023 21:26:47 +0100 -Subject: [PATCH 04/11] Remove unused upper case tag static data +Subject: [PATCH 04/13] Remove unused upper case tag static data --- source/lexbor/tag/res.h | 2 ++ diff --git a/ext/lexbor/patches/0005-Shrink-size-of-static-binary-search-tree.patch b/ext/lexbor/patches/0005-Shrink-size-of-static-binary-search-tree.patch index f36a2d758181..a8771beea0f2 100644 --- a/ext/lexbor/patches/0005-Shrink-size-of-static-binary-search-tree.patch +++ b/ext/lexbor/patches/0005-Shrink-size-of-static-binary-search-tree.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 29 Nov 2023 21:29:31 +0100 -Subject: [PATCH 05/11] Shrink size of static binary search tree +Subject: [PATCH 05/13] Shrink size of static binary search tree This also makes it more efficient on the data cache. --- diff --git a/ext/lexbor/patches/0006-Patch-out-unused-CSS-style-code.patch b/ext/lexbor/patches/0006-Patch-out-unused-CSS-style-code.patch index 7c6e1beebf46..a81d614575ca 100644 --- a/ext/lexbor/patches/0006-Patch-out-unused-CSS-style-code.patch +++ b/ext/lexbor/patches/0006-Patch-out-unused-CSS-style-code.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 7 Jan 2024 21:59:28 +0100 -Subject: [PATCH 06/11] Patch out unused CSS style code +Subject: [PATCH 06/13] Patch out unused CSS style code --- source/lexbor/css/rule.h | 2 ++ diff --git a/ext/lexbor/patches/0007-Add-lxb_url_is_special-to-the-public-API-362.patch b/ext/lexbor/patches/0007-Add-lxb_url_is_special-to-the-public-API-362.patch index 50bdc2397ba5..c49cf53628b2 100644 --- a/ext/lexbor/patches/0007-Add-lxb_url_is_special-to-the-public-API-362.patch +++ b/ext/lexbor/patches/0007-Add-lxb_url_is_special-to-the-public-API-362.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Sun, 17 May 2026 22:17:14 +0200 -Subject: [PATCH 07/11] Add lxb_url_is_special() to the public API (#362) +Subject: [PATCH 07/13] Add lxb_url_is_special() to the public API (#362) As https://wiki.php.net/rfc/uri_followup#uri_type_detection relies on this information. --- diff --git a/ext/lexbor/patches/0008-URL-fixed-setters-for-empty-hosts.patch b/ext/lexbor/patches/0008-URL-fixed-setters-for-empty-hosts.patch index 4218461c3bad..c8c8e93686d6 100644 --- a/ext/lexbor/patches/0008-URL-fixed-setters-for-empty-hosts.patch +++ b/ext/lexbor/patches/0008-URL-fixed-setters-for-empty-hosts.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Fri, 26 Jun 2026 18:55:56 +0300 -Subject: [PATCH 08/11] URL: fixed setters for empty hosts. +Subject: [PATCH 08/13] URL: fixed setters for empty hosts. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit diff --git a/ext/lexbor/patches/0009-URL-fixed-uninitialized-memory-in-the-path-buffer-gr.patch b/ext/lexbor/patches/0009-URL-fixed-uninitialized-memory-in-the-path-buffer-gr.patch index 91e78a899f44..d95503378bef 100644 --- a/ext/lexbor/patches/0009-URL-fixed-uninitialized-memory-in-the-path-buffer-gr.patch +++ b/ext/lexbor/patches/0009-URL-fixed-uninitialized-memory-in-the-path-buffer-gr.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Fri, 5 Jun 2026 22:13:32 +0300 -Subject: [PATCH 09/11] URL: fixed uninitialized memory in the path buffer +Subject: [PATCH 09/13] URL: fixed uninitialized memory in the path buffer growth. When a path was long enough to outgrow the on-stack buffer, the first diff --git a/ext/lexbor/patches/0010-Fix-parsing-for-URL-containing-empty-host-and-userin.patch b/ext/lexbor/patches/0010-Fix-parsing-for-URL-containing-empty-host-and-userin.patch index 9cbf3e0094ed..624943a49248 100644 --- a/ext/lexbor/patches/0010-Fix-parsing-for-URL-containing-empty-host-and-userin.patch +++ b/ext/lexbor/patches/0010-Fix-parsing-for-URL-containing-empty-host-and-userin.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Thu, 9 Jul 2026 21:51:05 +0200 -Subject: [PATCH 10/11] Fix parsing for URL containing empty host and userinfo +Subject: [PATCH 10/13] Fix parsing for URL containing empty host and userinfo The returned error code (LXB_URL_ERROR_TYPE_INVALID_CREDENTIALS) apparently contradicts the specification: diff --git a/ext/lexbor/patches/0011-Percent-encode-the-caret-in-the-path.patch b/ext/lexbor/patches/0011-Percent-encode-the-caret-in-the-path.patch index 2781fad5bcb9..f971dc39cff0 100644 --- a/ext/lexbor/patches/0011-Percent-encode-the-caret-in-the-path.patch +++ b/ext/lexbor/patches/0011-Percent-encode-the-caret-in-the-path.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Fri, 10 Jul 2026 22:31:16 +0200 -Subject: [PATCH 11/11] Percent-encode the caret in the path +Subject: [PATCH 11/13] Percent-encode the caret in the path The caret (^) is part of the path percent-encode set: diff --git a/ext/lexbor/patches/0012-URL-added-public-IPv6-parser.patch b/ext/lexbor/patches/0012-URL-added-public-IPv6-parser.patch new file mode 100644 index 000000000000..a005dd134919 --- /dev/null +++ b/ext/lexbor/patches/0012-URL-added-public-IPv6-parser.patch @@ -0,0 +1,320 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alexander Borisov +Date: Wed, 12 Aug 2026 23:29:20 +0300 +Subject: [PATCH 12/13] URL: added public IPv6 parser. +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Added lxb_url_parse_host_ipv6() — a public entry point to the IPv6 +parser from the WHATWG specification: +https://url.spec.whatwg.org/#concept-ipv6-parser + +The address is accepted both with and without the surrounding square +brackets: "::1" and "[::1]" give the same result. + +https://github.com/lexbor/lexbor/pull/402 + +The API was requested in #402 for use by php/php-src#22268. + +Suggested-by: Máté Kocsis (@kocsismate) +--- + source/lexbor/url/url.c | 42 +++++++ + source/lexbor/url/url.h | 26 ++++ + test/lexbor/url/parse_host_ipv6.c | 190 ++++++++++++++++++++++++++++++ + 3 files changed, 258 insertions(+) + create mode 100644 test/lexbor/url/parse_host_ipv6.c + +diff --git a/source/lexbor/url/url.c b/source/lexbor/url/url.c +index 8099c12..7487762 100644 +--- a/source/lexbor/url/url.c ++++ b/source/lexbor/url/url.c +@@ -3752,6 +3752,46 @@ lxb_url_is_ipv4(lxb_url_parser_t *parser, const lxb_char_t *data, + return status != LXB_STATUS_ERROR; + } + ++lxb_status_t ++lxb_url_parse_host_ipv6(lxb_url_parser_t *parser, const lxb_char_t *data, ++ size_t length, uint16_t *ipv6) ++{ ++ lxb_status_t status; ++ lxb_url_parser_t self_parser; ++ ++ if (parser == NULL) { ++ parser = &self_parser; ++ ++ parser->log = NULL; ++ parser->idna = NULL; ++ parser->buffer = NULL; ++ } ++ ++ if (data < data + length && *data == '[') { ++ if (data[length - 1] != ']') { ++ (void) lxb_url_log_append(parser, &data[length - 1], ++ LXB_URL_ERROR_TYPE_IPV6_UNCLOSED); ++ ++ status = LXB_STATUS_ERROR_UNEXPECTED_DATA; ++ ++ goto done; ++ } ++ ++ data += 1; ++ length -= 2; ++ } ++ ++ status = lxb_url_ipv6_parse(parser, data, data + length, ipv6); ++ ++done: ++ ++ if (parser == &self_parser) { ++ lxb_url_parser_destroy(parser, false); ++ } ++ ++ return status; ++} ++ + static lxb_status_t + lxb_url_ipv6_parse(lxb_url_parser_t *parser, const lxb_char_t *data, + const lxb_char_t *end, uint16_t *ipv6) +@@ -3763,6 +3803,8 @@ lxb_url_ipv6_parse(lxb_url_parser_t *parser, const lxb_char_t *data, + const lxb_char_t *p; + lxb_url_error_type_t err_type; + ++ memset(ipv6, 0x00, sizeof(uint16_t) * 8); ++ + piece = ipv6; + compress = NULL; + p = data; +diff --git a/source/lexbor/url/url.h b/source/lexbor/url/url.h +index 6cc6f10..aa50485 100644 +--- a/source/lexbor/url/url.h ++++ b/source/lexbor/url/url.h +@@ -321,6 +321,32 @@ lxb_url_parse_basic(lxb_url_parser_t *parser, lxb_url_t *url, + const lxb_char_t *data, size_t length, + lxb_url_state_t override_state, lxb_encoding_t encoding); + ++/* ++ * IPv6 parser. ++ * ++ * This function is an implementation of IPv6 parsing according to the WHATWG ++ * specification. ++ * https://url.spec.whatwg.org/#concept-ipv6-parser ++ * ++ * The address can be passed both with and without the surrounding square ++ * brackets: "::1" and "[::1]" give the same result. If the opening bracket is ++ * present, the closing one is required. ++ * ++ * The output buffer is zeroed by the function, there is no need to prepare it. ++ * Use the lxb_url_serialize_host_ipv6() function to serialize the result. ++ * ++ * @param[in] lxb_url_parser_t *. Can be NULL. ++ * @param[in] Pointer to the beginning of the data. Not NULL. ++ * @param[in] Length of the data. Can be 0. ++ * @param[out] Buffer for eight (uint16_t[8]) IPv6 pieces. Not NULL. The value ++ * is meaningful only if LXB_STATUS_OK is returned. ++ * ++ * @return LXB_STATUS_OK if successful, otherwise an error status value. ++ */ ++LXB_API lxb_status_t ++lxb_url_parse_host_ipv6(lxb_url_parser_t *parser, const lxb_char_t *data, ++ size_t length, uint16_t *ipv6); ++ + /* + * Erase URL. + * +diff --git a/test/lexbor/url/parse_host_ipv6.c b/test/lexbor/url/parse_host_ipv6.c +new file mode 100644 +index 0000000..bbf5293 +--- /dev/null ++++ b/test/lexbor/url/parse_host_ipv6.c +@@ -0,0 +1,190 @@ ++/* ++ * Copyright (C) 2026 Alexander Borisov ++ * ++ * Author: Alexander Borisov ++ */ ++ ++#include ++#include ++ ++ ++typedef struct { ++ const lxb_char_t *input; ++ size_t length; ++ uint16_t ipv6[8]; ++} ++ipv6_success_t; ++ ++typedef struct { ++ const lxb_char_t *input; ++ lxb_url_error_type_t error; ++} ++ipv6_failure_t; ++ ++ ++static const ipv6_success_t success_entries[] = { ++ { ++ (const lxb_char_t *) "::", ++ sizeof("::") - 1, ++ {0, 0, 0, 0, 0, 0, 0, 0} ++ }, ++ { ++ (const lxb_char_t *) "::1", ++ sizeof("::1") - 1, ++ {0, 0, 0, 0, 0, 0, 0, 1} ++ }, ++ { ++ (const lxb_char_t *) "[::1]", ++ sizeof("[::1]") - 1, ++ {0, 0, 0, 0, 0, 0, 0, 1} ++ }, ++ { ++ (const lxb_char_t *) "1:2:3:4:5:6:7:8", ++ sizeof("1:2:3:4:5:6:7:8") - 1, ++ {1, 2, 3, 4, 5, 6, 7, 8} ++ }, ++ { ++ (const lxb_char_t *) "2001:db8::ff00:42:8329", ++ sizeof("2001:db8::ff00:42:8329") - 1, ++ {0x2001, 0x0db8, 0, 0, 0, 0xff00, 0x0042, 0x8329} ++ }, ++ { ++ (const lxb_char_t *) "::ffff:192.0.2.1", ++ sizeof("::ffff:192.0.2.1") - 1, ++ {0, 0, 0, 0, 0, 0xffff, 0xc000, 0x0201} ++ }, ++ { ++ (const lxb_char_t *) "[::1]ignored", ++ 5, ++ {0, 0, 0, 0, 0, 0, 0, 1} ++ } ++}; ++ ++static const ipv6_failure_t failure_entries[] = { ++ { ++ (const lxb_char_t *) "", ++ LXB_URL_ERROR_TYPE_IPV6_TOO_FEW_PIECES ++ }, ++ { ++ (const lxb_char_t *) "[::1", ++ LXB_URL_ERROR_TYPE_IPV6_UNCLOSED ++ }, ++ { ++ (const lxb_char_t *) ":", ++ LXB_URL_ERROR_TYPE_IPV6_INVALID_COMPRESSION ++ }, ++ { ++ (const lxb_char_t *) "1::2::3", ++ LXB_URL_ERROR_TYPE_IPV6_MULTIPLE_COMPRESSION ++ }, ++ { ++ (const lxb_char_t *) "1:2:3:4:5:6:7:8:9", ++ LXB_URL_ERROR_TYPE_IPV6_TOO_MANY_PIECES ++ }, ++ { ++ (const lxb_char_t *) "1:2:3:4:5:6:7", ++ LXB_URL_ERROR_TYPE_IPV6_TOO_FEW_PIECES ++ }, ++ { ++ (const lxb_char_t *) "1:2:3:4:5:6:7:g", ++ LXB_URL_ERROR_TYPE_IPV6_INVALID_CODE_POINT ++ }, ++ { ++ (const lxb_char_t *) "1:2:3:4:5:6:7:1.2.3.4", ++ LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_MANY_PIECES ++ }, ++ { ++ (const lxb_char_t *) "::ffff:.1.2.3", ++ LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_INVALID_CODE_POINT ++ }, ++ { ++ (const lxb_char_t *) "::ffff:192.0.2.256", ++ LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_OUT_OF_RANGE_PART ++ }, ++ { ++ (const lxb_char_t *) "::ffff:192.0.2", ++ LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_FEW_PARTS ++ } ++}; ++ ++ ++TEST_BEGIN(parse_success) ++{ ++ size_t length; ++ lxb_status_t status; ++ uint16_t ipv6[8]; ++ ++ length = sizeof(success_entries) / sizeof(ipv6_success_t); ++ ++ for (size_t i = 0; i < length; i++) { ++ memset(ipv6, 0xff, sizeof(ipv6)); ++ ++ status = lxb_url_parse_host_ipv6(NULL, success_entries[i].input, ++ success_entries[i].length, ipv6); ++ test_eq(status, LXB_STATUS_OK); ++ ++ for (size_t j = 0; j < 8; j++) { ++ test_eq_u_short(ipv6[j], success_entries[i].ipv6[j]); ++ } ++ } ++} ++TEST_END ++ ++TEST_BEGIN(parse_failure) ++{ ++ size_t length; ++ lxb_status_t status; ++ lxb_url_parser_t parser; ++ lexbor_plog_entry_t *error; ++ ++ status = lxb_url_parser_init(&parser, NULL); ++ test_eq(status, LXB_STATUS_OK); ++ ++ length = sizeof(failure_entries) / sizeof(ipv6_failure_t); ++ ++ for (size_t i = 0; i < length; i++) { ++ status = lxb_url_parse_host_ipv6( ++ &parser, failure_entries[i].input, ++ strlen((const char *) failure_entries[i].input), ++ (uint16_t[8]) {0}); ++ ++ test_eq(status, LXB_STATUS_ERROR_UNEXPECTED_DATA); ++ test_ne(parser.log, NULL); ++ test_eq_size(lexbor_plog_length(parser.log), 1UL); ++ ++ error = lexbor_array_obj_get(&parser.log->list, 0); ++ test_ne(error, NULL); ++ test_eq(error->id, failure_entries[i].error); ++ ++ lxb_url_parser_clean(&parser); ++ } ++ ++ lxb_url_parser_memory_destroy(&parser); ++ lxb_url_parser_destroy(&parser, false); ++} ++TEST_END ++ ++TEST_BEGIN(parse_failure_without_parser) ++{ ++ lxb_status_t status; ++ uint16_t ipv6[8]; ++ ++ static const lexbor_str_t input = lexbor_str("::ffff:192.0.2.256"); ++ ++ status = lxb_url_parse_host_ipv6(NULL, input.data, input.length, ipv6); ++ test_eq(status, LXB_STATUS_ERROR_UNEXPECTED_DATA); ++} ++TEST_END ++ ++int ++main(int argc, const char *argv[]) ++{ ++ TEST_INIT(); ++ ++ TEST_ADD(parse_success); ++ TEST_ADD(parse_failure); ++ TEST_ADD(parse_failure_without_parser); ++ ++ TEST_RUN("lexbor/url/parse_host_ipv6"); ++ TEST_RELEASE(); ++} diff --git a/ext/lexbor/patches/0013-URL-added-public-percent-encoder-API.patch b/ext/lexbor/patches/0013-URL-added-public-percent-encoder-API.patch new file mode 100644 index 000000000000..c5d11c8f64f1 --- /dev/null +++ b/ext/lexbor/patches/0013-URL-added-public-percent-encoder-API.patch @@ -0,0 +1,626 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alexander Borisov +Date: Thu, 13 Aug 2026 23:29:16 +0300 +Subject: [PATCH 13/13] URL: added public percent-encoder API. +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Added public entry points to the percent-encoder from the WHATWG +specification: +https://url.spec.whatwg.org/#percent-encoded-bytes + + lxb_url_percent_encode_utf_8() + lxb_url_percent_encode_encoding() + lxb_url_get_percent_encoding_map() + +Both encoders take a caller-supplied table of 256 entries indexed by byte +value, where each entry is a bit mask of lxb_url_map_type_t values, so the +percent-encode sets can be adjusted without patching the library. +lxb_url_get_percent_encoding_map() returns the built-in table for callers +that need only the sets defined by the specification. + +https://github.com/lexbor/lexbor/pull/404 + +The API was requested in #404 for use by PHP: +https://wiki.php.net/rfc/uri_followup#percent-encoding_support + +Based-on-patch-by: Máté Kocsis (@kocsismate) +--- + source/lexbor/url/url.c | 93 ++++++++----- + source/lexbor/url/url.h | 96 +++++++++++++ + test/lexbor/url/percent_encode.c | 228 +++++++++++++++++++++++++++++++ + 3 files changed, 380 insertions(+), 37 deletions(-) + create mode 100644 test/lexbor/url/percent_encode.c + +diff --git a/source/lexbor/url/url.c b/source/lexbor/url/url.c +index 7487762..69d9196 100644 +--- a/source/lexbor/url/url.c ++++ b/source/lexbor/url/url.c +@@ -27,20 +27,6 @@ + #define LXB_URL_BUFFER_NUM_SIZE 128 + + +-typedef enum { +- LXB_URL_MAP_UNDEF = 0x00, +- LXB_URL_MAP_C0 = 0x01, +- LXB_URL_MAP_FRAGMENT = 0x02, +- LXB_URL_MAP_QUERY = 0x04, +- LXB_URL_MAP_SPECIAL_QUERY = 0x08, +- LXB_URL_MAP_PATH = 0x10, +- LXB_URL_MAP_USERINFO = 0x20, +- LXB_URL_MAP_COMPONENT = 0x40, +- LXB_URL_MAP_X_WWW_FORM = 0x80, +- LXB_URL_MAP_ALL = 0xff +-} +-lxb_url_map_type_t; +- + typedef enum { + LXB_URL_HOST_OPT_UNDEF = 0 << 0, + LXB_URL_HOST_OPT_NOT_SPECIAL = 1 << 0, +@@ -563,7 +549,7 @@ lxb_url_path_fix_windows_drive(lxb_url_t *url, lxb_char_t *sbuf, + static lxb_status_t + lxb_url_percent_encode_after_encoding(const lxb_char_t *data, + const lxb_char_t *end, lexbor_str_t *str, +- lexbor_mraw_t *mraw, ++ lexbor_mraw_t *mraw, const uint8_t *url_map, + const lxb_encoding_data_t *encoding, + lxb_url_map_type_t enmap, + bool space_as_plus); +@@ -571,7 +557,7 @@ lxb_url_percent_encode_after_encoding(const lxb_char_t *data, + static lxb_status_t + lxb_url_percent_encode_after_utf_8(const lxb_char_t *data, + const lxb_char_t *end, lexbor_str_t *str, +- lexbor_mraw_t *mraw, ++ lexbor_mraw_t *mraw, const uint8_t *url_map, + lxb_url_map_type_t enmap, + bool space_as_plus); + +@@ -1757,9 +1743,9 @@ again: + tmp = (pswd != NULL) ? pswd - 1 : p; + + if (tmp > begin) { +- status = lxb_url_percent_encode_after_utf_8(begin, tmp, +- &url->username, url->mraw, +- LXB_URL_MAP_USERINFO, false); ++ status = lxb_url_percent_encode_after_utf_8(begin, ++ tmp, &url->username, url->mraw, lxb_url_map, ++ LXB_URL_MAP_USERINFO, false); + if (status != LXB_STATUS_OK) { + lxb_url_parse_return(orig_data, buf, status); + } +@@ -1768,8 +1754,8 @@ again: + + if (pswd != NULL && p > pswd) { + status = lxb_url_percent_encode_after_utf_8(pswd, p, +- &url->password, url->mraw, +- LXB_URL_MAP_USERINFO, false); ++ &url->password, url->mraw, lxb_url_map, ++ LXB_URL_MAP_USERINFO, false); + if (status != LXB_STATUS_OK) { + lxb_url_parse_return(orig_data, buf, status); + } +@@ -2319,8 +2305,8 @@ again: + if (p >= end) { + tmp_str.data = NULL; + +- status = lxb_url_percent_encode_after_utf_8(begin, p, +- &tmp_str, url->mraw, ++ status = lxb_url_percent_encode_after_utf_8(begin, p, &tmp_str, ++ url->mraw, lxb_url_map, + LXB_URL_MAP_C0, false); + if (status != LXB_STATUS_OK) { + lxb_url_parse_return(orig_data, buf, status); +@@ -2336,8 +2322,8 @@ again: + if (c == '#' || c == '?') { + tmp_str.data = NULL; + +- status = lxb_url_percent_encode_after_utf_8(begin, p, +- &tmp_str, url->mraw, ++ status = lxb_url_percent_encode_after_utf_8(begin, p, &tmp_str, ++ url->mraw, lxb_url_map, + LXB_URL_MAP_C0, false); + if (status != LXB_STATUS_OK) { + lxb_url_parse_return(orig_data, buf, status); +@@ -2407,7 +2393,8 @@ again: + + status = lxb_url_percent_encode_after_encoding(begin, p, + &url->query, +- url->mraw, enc, ++ url->mraw, ++ lxb_url_map, enc, + map_type, false); + if (status != LXB_STATUS_OK) { + lxb_url_parse_return(orig_data, buf, status); +@@ -2461,7 +2448,7 @@ again: + } + + status = lxb_url_percent_encode_after_utf_8(begin, p, &url->fragment, +- url->mraw, ++ url->mraw, lxb_url_map, + LXB_URL_MAP_FRAGMENT, false); + lxb_url_parse_return(orig_data, buf, status); + +@@ -3161,10 +3148,23 @@ lxb_url_scheme_find(const lxb_char_t *data, size_t length) + return &lxb_url_scheme_res[LXB_URL_SCHEMEL_TYPE__UNKNOWN]; + } + ++lxb_status_t ++lxb_url_percent_encode_encoding(const lxb_char_t *data, size_t length, ++ lexbor_str_t *str, lexbor_mraw_t *mraw, ++ const uint8_t *url_map, ++ const lxb_encoding_data_t *encoding, ++ lxb_url_map_type_t enmap, ++ bool space_as_plus) ++{ ++ return lxb_url_percent_encode_after_encoding(data, data + length, str, mraw, ++ url_map, encoding, enmap, ++ space_as_plus); ++} ++ + static lxb_status_t + lxb_url_percent_encode_after_encoding(const lxb_char_t *data, + const lxb_char_t *end, lexbor_str_t *str, +- lexbor_mraw_t *mraw, ++ lexbor_mraw_t *mraw, const uint8_t *url_map, + const lxb_encoding_data_t *encoding, + lxb_url_map_type_t enmap, + bool space_as_plus) +@@ -3182,7 +3182,8 @@ lxb_url_percent_encode_after_encoding(const lxb_char_t *data, + + if (encoding->encoding == LXB_ENCODING_UTF_8) { + return lxb_url_percent_encode_after_utf_8(data, end, str, mraw, +- enmap, space_as_plus); ++ url_map, enmap, ++ space_as_plus); + } + + lxb_url_encoding_init(encoding, &encode); +@@ -3193,7 +3194,7 @@ lxb_url_percent_encode_after_encoding(const lxb_char_t *data, + /* Only valid for UTF-8. */ + + while (p < end) { +- if (lxb_url_map[*p++] & enmap) { ++ if (url_map[*p++] & enmap) { + length += 2; + } + } +@@ -3249,7 +3250,7 @@ lxb_url_percent_encode_after_encoding(const lxb_char_t *data, + return LXB_STATUS_ERROR_MEMORY_ALLOCATION; + } + } +- else if (lxb_url_map[c] & enmap) { ++ else if (url_map[c] & enmap) { + percent[1] = lexbor_str_res_char_to_two_hex_value[c][0]; + percent[2] = lexbor_str_res_char_to_two_hex_value[c][1]; + +@@ -3280,10 +3281,20 @@ lxb_url_percent_encode_after_encoding(const lxb_char_t *data, + return LXB_STATUS_OK; + } + ++lxb_status_t ++lxb_url_percent_encode_utf_8(const lxb_char_t *data, size_t length, ++ lexbor_str_t *str, lexbor_mraw_t *mraw, ++ const uint8_t *url_map, lxb_url_map_type_t enmap, ++ bool space_as_plus) ++{ ++ return lxb_url_percent_encode_after_utf_8(data, data + length, str, mraw, ++ url_map, enmap, space_as_plus); ++} ++ + static lxb_status_t + lxb_url_percent_encode_after_utf_8(const lxb_char_t *data, + const lxb_char_t *end, lexbor_str_t *str, +- lexbor_mraw_t *mraw, ++ lexbor_mraw_t *mraw, const uint8_t *url_map, + lxb_url_map_type_t enmap, + bool space_as_plus) + { +@@ -3298,7 +3309,7 @@ lxb_url_percent_encode_after_utf_8(const lxb_char_t *data, + /* Only valid for UTF-8. */ + + while (p < end) { +- if (lxb_url_map[*p++] & enmap) { ++ if (url_map[*p++] & enmap) { + length += 2; + } + } +@@ -3317,7 +3328,7 @@ lxb_url_percent_encode_after_utf_8(const lxb_char_t *data, + if (space_as_plus && c == ' ') { + *pd++ = '+'; + } +- else if (lxb_url_map[c] & enmap) { ++ else if (url_map[c] & enmap) { + *pd++ = '%'; + *pd++ = lexbor_str_res_char_to_two_hex_value[c][0]; + *pd++ = lexbor_str_res_char_to_two_hex_value[c][1]; +@@ -3335,6 +3346,12 @@ lxb_url_percent_encode_after_utf_8(const lxb_char_t *data, + return LXB_STATUS_OK; + } + ++const uint8_t * ++lxb_url_get_percent_encoding_map(void) ++{ ++ return lxb_url_map; ++} ++ + static lxb_status_t + lxb_url_host_parse(lxb_url_parser_t *parser, const lxb_char_t *data, + const lxb_char_t *end, lxb_url_host_t *host, +@@ -4065,7 +4082,7 @@ lxb_url_opaque_host_parse(lxb_url_parser_t *parser, const lxb_char_t *data, + host->type = LXB_URL_HOST_TYPE_OPAQUE; + + return lxb_url_percent_encode_after_utf_8(data, end, &host->u.opaque, mraw, +- LXB_URL_MAP_C0, false); ++ lxb_url_map, LXB_URL_MAP_C0, false); + } + + static lxb_status_t +@@ -4344,7 +4361,8 @@ lxb_url_api_username_set(lxb_url_t *url, + + return lxb_url_percent_encode_after_utf_8(username, username + length, + &url->username, url->mraw, +- LXB_URL_MAP_USERINFO, false); ++ lxb_url_map, LXB_URL_MAP_USERINFO, ++ false); + } + + lxb_status_t +@@ -4364,7 +4382,8 @@ lxb_url_api_password_set(lxb_url_t *url, + + return lxb_url_percent_encode_after_utf_8(password, password + length, + &url->password, url->mraw, +- LXB_URL_MAP_USERINFO, false); ++ lxb_url_map, LXB_URL_MAP_USERINFO, ++ false); + } + + lxb_status_t +diff --git a/source/lexbor/url/url.h b/source/lexbor/url/url.h +index aa50485..d2c9308 100644 +--- a/source/lexbor/url/url.h ++++ b/source/lexbor/url/url.h +@@ -81,6 +81,20 @@ typedef enum { + } + lxb_url_state_t; + ++typedef enum { ++ LXB_URL_MAP_UNDEF = 0x00, ++ LXB_URL_MAP_C0 = 0x01, ++ LXB_URL_MAP_FRAGMENT = 0x02, ++ LXB_URL_MAP_QUERY = 0x04, ++ LXB_URL_MAP_SPECIAL_QUERY = 0x08, ++ LXB_URL_MAP_PATH = 0x10, ++ LXB_URL_MAP_USERINFO = 0x20, ++ LXB_URL_MAP_COMPONENT = 0x40, ++ LXB_URL_MAP_X_WWW_FORM = 0x80, ++ LXB_URL_MAP_ALL = 0xff ++} ++lxb_url_map_type_t; ++ + /* + * New values can only be added downwards. + * Before LXB_URL_SCHEMEL_TYPE__LAST_ENTRY. +@@ -347,6 +361,88 @@ LXB_API lxb_status_t + lxb_url_parse_host_ipv6(lxb_url_parser_t *parser, const lxb_char_t *data, + size_t length, uint16_t *ipv6); + ++/* ++ * UTF-8 percent-encoder. ++ * ++ * Percent-encodes bytes from data according to url_map and appends the result ++ * to str. A byte is encoded as "%HH" when the result of ++ * (url_map[byte] & enmap) is non-zero. Uppercase hexadecimal digits are used. ++ * If space_as_plus is true, U+0020 SPACE is encoded as '+' before the map is ++ * checked. ++ * ++ * The input is expected to be valid UTF-8; the function does not validate it. ++ * ++ * @param[in] Pointer to UTF-8 data. Not NULL. ++ * @param[in] Length of data. Can be 0. ++ * @param[in, out] Output string. Can be uninitialized (data = NULL). Encoded ++ * data is appended to any existing content. Not NULL. ++ * @param[in] Memory object used to allocate or resize the output string. Not ++ * NULL. ++ * @param[in] Table of 256 entries indexed by input byte, each entry is a bit ++ * mask of lxb_url_map_type_t values. Not NULL. ++ * @param[in] Mask selecting the bytes to percent-encode. ++ * @param[in] Replace U+0020 SPACE with '+' if true. ++ * ++ * @return LXB_STATUS_OK if successful, otherwise an error status value. ++ */ ++LXB_API lxb_status_t ++lxb_url_percent_encode_utf_8(const lxb_char_t *data, size_t length, ++ lexbor_str_t *str, lexbor_mraw_t *mraw, ++ const uint8_t *url_map, lxb_url_map_type_t enmap, ++ bool space_as_plus); ++ ++/* ++ * Percent-encode after encoding. ++ * ++ * Converts valid UTF-8 data to the specified encoding and appends the ++ * percent-encoded result to str. Each encoded byte for which ++ * (url_map[byte] & enmap) is non-zero is written as "%HH" using uppercase ++ * hexadecimal digits. If a code point cannot be represented in the target ++ * encoding, its percent-encoded numeric character reference is appended. ++ * ++ * If encoding is UTF-8, no conversion is performed. If space_as_plus is true, ++ * an encoded U+0020 SPACE is replaced with '+' before the map is checked. ++ * The input is expected to be valid UTF-8; the function does not validate it. ++ * ++ * @param[in] Pointer to UTF-8 data. Not NULL. ++ * @param[in] Length of data. Can be 0. ++ * @param[in, out] Output string. Can be uninitialized (data = NULL). Encoded ++ * data is appended to any existing content. Not NULL. ++ * @param[in] Memory object used to allocate or resize the output string. Not ++ * NULL. ++ * @param[in] Table of 256 entries indexed by encoded byte, each entry is a bit ++ * mask of lxb_url_map_type_t values. Not NULL. ++ * @param[in] Target encoding. Not NULL. ++ * @param[in] Mask selecting the bytes to percent-encode. ++ * @param[in] Replace an encoded U+0020 SPACE with '+' if true. ++ * ++ * @return LXB_STATUS_OK if successful, otherwise an error status value. ++ */ ++LXB_API lxb_status_t ++lxb_url_percent_encode_encoding(const lxb_char_t *data, size_t length, ++ lexbor_str_t *str, lexbor_mraw_t *mraw, ++ const uint8_t *url_map, ++ const lxb_encoding_data_t *encoding, ++ lxb_url_map_type_t enmap, ++ bool space_as_plus); ++ ++/* ++ * Get the URL percent-encoding map. ++ * ++ * Returns the built-in lookup table for the percent-encode sets defined by the ++ * URL specification. The table contains 256 entries indexed by byte value. ++ * Each entry is a bit mask of the lxb_url_map_type_t sets in which the byte ++ * must be percent-encoded. ++ * ++ * The returned map can be passed to lxb_url_percent_encode_utf_8() or ++ * lxb_url_percent_encode_encoding(). It has static storage duration and must ++ * not be modified or freed. ++ * ++ * @return Pointer to a read-only table of 256 entries. Never NULL. ++ */ ++LXB_API const uint8_t * ++lxb_url_get_percent_encoding_map(void); ++ + /* + * Erase URL. + * +diff --git a/test/lexbor/url/percent_encode.c b/test/lexbor/url/percent_encode.c +new file mode 100644 +index 0000000..361e22e +--- /dev/null ++++ b/test/lexbor/url/percent_encode.c +@@ -0,0 +1,228 @@ ++/* ++ * Copyright (C) 2026 Alexander Borisov ++ * ++ * Author: Alexander Borisov ++ */ ++ ++#include ++#include ++ ++ ++typedef struct { ++ const lexbor_str_t input; ++ const lexbor_str_t output; ++ lxb_encoding_t encoding; ++ lxb_url_map_type_t enmap; ++ bool space_as_plus; ++ const uint8_t *url_map; ++ const lexbor_str_t initial; ++} ++percent_encode_entry_t; ++ ++ ++static const uint8_t custom_url_map[256] = { ++ ['a'] = LXB_URL_MAP_QUERY, ++ ['b'] = LXB_URL_MAP_PATH ++}; ++ ++static const percent_encode_entry_t entries[] = { ++ { ++ lexbor_str(""), ++ lexbor_str(""), ++ LXB_ENCODING_UTF_8, ++ LXB_URL_MAP_COMPONENT, ++ false, ++ NULL, ++ lexbor_str("") ++ }, ++ { ++ lexbor_str("AZaz09-._~!*'()"), ++ lexbor_str("AZaz09-._~!*'()"), ++ LXB_ENCODING_UTF_8, ++ LXB_URL_MAP_COMPONENT, ++ false, ++ NULL, ++ lexbor_str("") ++ }, ++ { ++ lexbor_str("\x00" "A /?\xC3\xA9"), ++ lexbor_str("%00A%20%2F%3F%C3%A9"), ++ LXB_ENCODING_UTF_8, ++ LXB_URL_MAP_COMPONENT, ++ false, ++ NULL, ++ lexbor_str("") ++ }, ++ { ++ lexbor_str("A b"), ++ lexbor_str("prefix:A+b"), ++ LXB_ENCODING_UTF_8, ++ LXB_URL_MAP_COMPONENT, ++ true, ++ NULL, ++ lexbor_str("prefix:") ++ }, ++ { ++ lexbor_str("\xE2\x89\xA1\xE2\x80\xBD"), ++ lexbor_str("%E2%89%A1%E2%80%BD"), ++ LXB_ENCODING_UTF_8, ++ LXB_URL_MAP_SPECIAL_QUERY, ++ false, ++ NULL, ++ lexbor_str("") ++ }, ++ { ++ lexbor_str("\xE2\x89\xA1"), ++ lexbor_str("%81%DF"), ++ LXB_ENCODING_SHIFT_JIS, ++ LXB_URL_MAP_SPECIAL_QUERY, ++ false, ++ NULL, ++ lexbor_str("") ++ }, ++ { ++ lexbor_str("\xE2\x80\xBD"), ++ lexbor_str("%26%238253%3B"), ++ LXB_ENCODING_SHIFT_JIS, ++ LXB_URL_MAP_SPECIAL_QUERY, ++ false, ++ NULL, ++ lexbor_str("") ++ }, ++ { ++ lexbor_str("1+1 \xE2\x89\xA1 2%20\xE2\x80\xBD"), ++ lexbor_str("1+1%20%81%DF%202%20%26%238253%3B"), ++ LXB_ENCODING_SHIFT_JIS, ++ LXB_URL_MAP_SPECIAL_QUERY, ++ false, ++ NULL, ++ lexbor_str("") ++ }, ++ { ++ lexbor_str("\xC2\xA5"), ++ lexbor_str("%1B(J\\%1B(B"), ++ LXB_ENCODING_ISO_2022_JP, ++ LXB_URL_MAP_SPECIAL_QUERY, ++ false, ++ NULL, ++ lexbor_str("") ++ }, ++ { ++ lexbor_str("caf\xC3\xA9"), ++ lexbor_str("caf%E9"), ++ LXB_ENCODING_WINDOWS_1252, ++ LXB_URL_MAP_COMPONENT, ++ false, ++ NULL, ++ lexbor_str("") ++ }, ++ { ++ lexbor_str("\xD0\xAF"), ++ lexbor_str("%DF"), ++ LXB_ENCODING_WINDOWS_1251, ++ LXB_URL_MAP_COMPONENT, ++ false, ++ NULL, ++ lexbor_str("") ++ }, ++ { ++ lexbor_str("\xE4\xB8\xAD\xE6\x96\x87"), ++ lexbor_str("%A4%A4%A4%E5"), ++ LXB_ENCODING_BIG5, ++ LXB_URL_MAP_COMPONENT, ++ false, ++ NULL, ++ lexbor_str("") ++ }, ++ { ++ lexbor_str("a b+c~"), ++ lexbor_str("a+b%2Bc%7E"), ++ LXB_ENCODING_UTF_8, ++ LXB_URL_MAP_X_WWW_FORM, ++ true, ++ NULL, ++ lexbor_str("") ++ }, ++ { ++ lexbor_str("abc"), ++ lexbor_str("%61bc"), ++ LXB_ENCODING_UTF_8, ++ LXB_URL_MAP_QUERY, ++ false, ++ custom_url_map, ++ lexbor_str("") ++ } ++}; ++ ++ ++TEST_BEGIN(percent_encode) ++{ ++ size_t length; ++ lxb_char_t *data; ++ lxb_status_t status; ++ lexbor_mraw_t mraw; ++ lexbor_str_t str; ++ const uint8_t *url_map, *default_url_map; ++ const lxb_encoding_data_t *encoding; ++ const percent_encode_entry_t *entry; ++ ++ status = lexbor_mraw_init(&mraw, 1024); ++ test_eq(status, LXB_STATUS_OK); ++ ++ default_url_map = lxb_url_get_percent_encoding_map(); ++ test_ne(default_url_map, NULL); ++ ++ length = sizeof(entries) / sizeof(percent_encode_entry_t); ++ ++ for (size_t i = 0; i < length; i++) { ++ entry = &entries[i]; ++ encoding = lxb_encoding_data(entry->encoding); ++ test_ne(encoding, NULL); ++ ++ str = (lexbor_str_t) {0}; ++ ++ if (entry->initial.length != 0) { ++ data = lexbor_str_init_append(&str, &mraw, entry->initial.data, ++ entry->initial.length); ++ test_ne(data, NULL); ++ } ++ ++ url_map = entry->url_map; ++ if (url_map == NULL) { ++ url_map = default_url_map; ++ } ++ ++ status = lxb_url_percent_encode_encoding(entry->input.data, ++ entry->input.length, ++ &str, &mraw, url_map, encoding, ++ entry->enmap, ++ entry->space_as_plus); ++ test_eq(status, LXB_STATUS_OK); ++ ++ if (str.length != entry->output.length ++ || memcmp(str.data, entry->output.data, str.length) != 0) ++ { ++ TEST_PRINTLN("Percent-encode entry %zu (%s)", i + 1, ++ encoding->name); ++ } ++ ++ test_eq_str_n(str.data, str.length, entry->output.data, ++ entry->output.length); ++ ++ lexbor_str_destroy(&str, &mraw, false); ++ } ++ ++ lexbor_mraw_destroy(&mraw, false); ++} ++TEST_END ++ ++int ++main(int argc, const char *argv[]) ++{ ++ TEST_INIT(); ++ ++ TEST_ADD(percent_encode); ++ ++ TEST_RUN("lexbor/url/percent_encode"); ++ TEST_RELEASE(); ++} From 1f0e6ef98a09641fca04ef286d1687c3fdaa3f85 Mon Sep 17 00:00:00 2001 From: Arshid Date: Sat, 15 Aug 2026 18:01:09 +0530 Subject: [PATCH 3/5] ext/spl: Narrow tentative return types for DirectoryIterator methods (#22642) --- UPGRADING | 2 ++ ext/phar/tests/phar_oo_004.phpt | 4 ++-- .../ReflectionMethod_tentative_return_type.phpt | 6 +++--- ext/spl/spl_directory.stub.php | 16 +++++----------- ext/spl/spl_directory_arginfo.h | 10 +++++----- 5 files changed, 17 insertions(+), 21 deletions(-) diff --git a/UPGRADING b/UPGRADING index 8f588bbf877c..f61e175a65b8 100644 --- a/UPGRADING +++ b/UPGRADING @@ -205,6 +205,8 @@ PHP 8.6 UPGRADE NOTES . SplFileObject::next() past EOF no longer increments key() without bound. SplFileObject::seek() past EOF now produces the same key() value as SplTempFileObject; the two previously returned different values. + . DirectoryIterator::key() now returns int|string, + and DirectoryIterator::current() returns string|SplFileInfo|static. - SimpleXML: . SimpleXMLElement::__construct() now raises a ValueError when the $data diff --git a/ext/phar/tests/phar_oo_004.phpt b/ext/phar/tests/phar_oo_004.phpt index 097af93bbd9d..09c941bd4b84 100644 --- a/ext/phar/tests/phar_oo_004.phpt +++ b/ext/phar/tests/phar_oo_004.phpt @@ -43,13 +43,13 @@ class MyDirectoryIterator extends DirectoryIterator return parent::valid(); } - function key(): mixed + function key(): int { echo __METHOD__ . "\n"; return parent::key(); } - function current(): mixed + function current(): MyDirectoryIterator { echo __METHOD__ . "\n"; return parent::current(); diff --git a/ext/reflection/tests/ReflectionMethod_tentative_return_type.phpt b/ext/reflection/tests/ReflectionMethod_tentative_return_type.phpt index 05d18084f210..8968c5e58b57 100644 --- a/ext/reflection/tests/ReflectionMethod_tentative_return_type.phpt +++ b/ext/reflection/tests/ReflectionMethod_tentative_return_type.phpt @@ -59,11 +59,11 @@ string(%d) "Method [ sta bool(false) bool(true) string(0) "" -string(37) "SplFileInfo|FilesystemIterator|string" -string(191) "Method [ public method current ] { +string(25) "SplFileInfo|static|string" +string(179) "Method [ public method current ] { - Parameters [0] { } - - Tentative return [ SplFileInfo|FilesystemIterator|string ] + - Tentative return [ SplFileInfo|static|string ] } " diff --git a/ext/spl/spl_directory.stub.php b/ext/spl/spl_directory.stub.php index 6194a8617b43..57407007c0a5 100644 --- a/ext/spl/spl_directory.stub.php +++ b/ext/spl/spl_directory.stub.php @@ -123,17 +123,11 @@ public function rewind(): void {} /** @tentative-return-type */ public function valid(): bool {} - /** - * @tentative-return-type - * @return int - */ - public function key(): mixed {} // TODO change return type to string + /** @tentative-return-type */ + public function key(): int|string {} - /** - * @tentative-return-type - * @return DirectoryIterator - */ - public function current(): mixed {} // TODO narrow return type + /** @tentative-return-type */ + public function current(): string|SplFileInfo|static {} /** @tentative-return-type */ public function next(): void {} @@ -181,7 +175,7 @@ public function rewind(): void {} public function key(): string {} /** @tentative-return-type */ - public function current(): string|SplFileInfo|FilesystemIterator {} + public function current(): string|SplFileInfo|static {} /** @tentative-return-type */ public function getFlags(): int {} diff --git a/ext/spl/spl_directory_arginfo.h b/ext/spl/spl_directory_arginfo.h index 16860be558d7..3a5bd37d49d5 100644 --- a/ext/spl/spl_directory_arginfo.h +++ b/ext/spl/spl_directory_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit spl_directory.stub.php instead. - * Stub hash: 802429d736404c2d66601f640942c827b6e6e94b */ + * Stub hash: de8ef9b284ceb46a1d55b22d4a5a3009d04bc2af */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SplFileInfo___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) @@ -103,10 +103,11 @@ ZEND_END_ARG_INFO() #define arginfo_class_DirectoryIterator_valid arginfo_class_SplFileInfo_isWritable -ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_DirectoryIterator_key, 0, 0, IS_MIXED, 0) +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_DirectoryIterator_key, 0, 0, MAY_BE_LONG|MAY_BE_STRING) ZEND_END_ARG_INFO() -#define arginfo_class_DirectoryIterator_current arginfo_class_DirectoryIterator_key +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_DirectoryIterator_current, 0, 0, SplFileInfo, MAY_BE_STRING|MAY_BE_STATIC) +ZEND_END_ARG_INFO() #define arginfo_class_DirectoryIterator_next arginfo_class_DirectoryIterator_rewind @@ -125,8 +126,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_FilesystemIterator_key arginfo_class_SplFileInfo_getPath -ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_FilesystemIterator_current, 0, 0, SplFileInfo|FilesystemIterator, MAY_BE_STRING) -ZEND_END_ARG_INFO() +#define arginfo_class_FilesystemIterator_current arginfo_class_DirectoryIterator_current ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_FilesystemIterator_getFlags, 0, 0, IS_LONG, 0) ZEND_END_ARG_INFO() From d7a5e2f2384c099d03a02a955f63478afba7f29b Mon Sep 17 00:00:00 2001 From: NickSdot <32384907+NickSdot@users.noreply.github.com> Date: Sat, 15 Aug 2026 19:32:41 +0700 Subject: [PATCH 4/5] ext/uri: applied fixers to improve test robustness (#23271) --- ext/uri/tests/004.phpt | 6 +- ext/uri/tests/007.phpt | 12 ++-- ext/uri/tests/015.phpt | 12 ++-- ext/uri/tests/023.phpt | 24 ++++---- ext/uri/tests/026.phpt | 50 ++++++++-------- ext/uri/tests/026_userinfo.phpt | 6 +- ext/uri/tests/027.phpt | 6 +- ext/uri/tests/028.phpt | 12 ++-- ext/uri/tests/029.phpt | 6 +- ext/uri/tests/030.phpt | 12 ++-- ext/uri/tests/031.phpt | 102 ++++++++++++++++---------------- ext/uri/tests/051.phpt | 12 ++-- ext/uri/tests/052.phpt | 6 +- ext/uri/tests/053.phpt | 24 ++++---- ext/uri/tests/054.phpt | 6 +- ext/uri/tests/055.phpt | 6 +- ext/uri/tests/101.phpt | 6 +- ext/uri/tests/gh19780.phpt | 12 ++-- 18 files changed, 160 insertions(+), 160 deletions(-) diff --git a/ext/uri/tests/004.phpt b/ext/uri/tests/004.phpt index a7c2e190683a..c6ad96ce99a7 100644 --- a/ext/uri/tests/004.phpt +++ b/ext/uri/tests/004.phpt @@ -8,8 +8,8 @@ var_dump(Uri\Rfc3986\Uri::parse("")); try { new Uri\WhatWg\Url(""); -} catch (Uri\WhatWg\InvalidUrlException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } var_dump(Uri\WhatWg\Url::parse("")); @@ -58,7 +58,7 @@ object(Uri\Rfc3986\Uri)#%d (%d) { ["fragment"]=> NULL } -The specified URI is malformed (MissingSchemeNonRelativeUrl) +Uri\WhatWg\InvalidUrlException: The specified URI is malformed (MissingSchemeNonRelativeUrl) NULL object(Uri\Rfc3986\Uri)#%d (%d) { ["scheme"]=> diff --git a/ext/uri/tests/007.phpt b/ext/uri/tests/007.phpt index db051e553fb1..9d2f389e2513 100644 --- a/ext/uri/tests/007.phpt +++ b/ext/uri/tests/007.phpt @@ -5,14 +5,14 @@ Test URI creation errors try { new Uri\Rfc3986\Uri("https://example.com:8080@username:password/path?q=r#fragment"); -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { new Uri\WhatWg\Url("https://example.com:8080@username:password/path?q=r#fragment"); -} catch (Uri\WhatWg\InvalidUrlException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; var_dump($e->errors); } @@ -23,8 +23,8 @@ var_dump($failures); ?> --EXPECTF-- -The specified URI is malformed -The specified URI is malformed (PortInvalid) +Uri\InvalidUriException: The specified URI is malformed +Uri\WhatWg\InvalidUrlException: The specified URI is malformed (PortInvalid) array(%d) { [0]=> object(Uri\WhatWg\UrlValidationError)#%d (%d) { diff --git a/ext/uri/tests/015.phpt b/ext/uri/tests/015.phpt index cf6c09703d2e..e3a846f5e314 100644 --- a/ext/uri/tests/015.phpt +++ b/ext/uri/tests/015.phpt @@ -9,18 +9,18 @@ uri try { $reflectionClass = new ReflectionClass(Uri\Rfc3986\Uri::class); $reflectionClass->newInstanceWithoutConstructor(); -} catch (ReflectionException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { $reflectionClass = new ReflectionClass(Uri\WhatWg\Url::class); $reflectionClass->newInstanceWithoutConstructor(); -} catch (ReflectionException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } ?> --EXPECT-- -Class Uri\Rfc3986\Uri is an internal class marked as final that cannot be instantiated without invoking its constructor -Class Uri\WhatWg\Url is an internal class marked as final that cannot be instantiated without invoking its constructor +ReflectionException: Class Uri\Rfc3986\Uri is an internal class marked as final that cannot be instantiated without invoking its constructor +ReflectionException: Class Uri\WhatWg\Url is an internal class marked as final that cannot be instantiated without invoking its constructor diff --git a/ext/uri/tests/023.phpt b/ext/uri/tests/023.phpt index d52fa3405322..8899abc16b6c 100644 --- a/ext/uri/tests/023.phpt +++ b/ext/uri/tests/023.phpt @@ -17,14 +17,14 @@ var_dump($uri3->getScheme()); try { $uri3->withScheme(""); -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { $uri3->withScheme("http%73"); -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } $url1 = Uri\WhatWg\Url::parse("https://example.com"); @@ -35,14 +35,14 @@ var_dump($url2->getScheme()); try { $url2->withScheme(""); -} catch (Uri\WhatWg\InvalidUrlException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { $url2->withScheme("http%73"); -} catch (Uri\WhatWg\InvalidUrlException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } ?> @@ -53,9 +53,9 @@ string(4) "http" string(4) "http" NULL NULL -The specified scheme is malformed -The specified scheme is malformed +Uri\InvalidUriException: The specified scheme is malformed +Uri\InvalidUriException: The specified scheme is malformed string(5) "https" string(4) "http" -The specified scheme is malformed -The specified scheme is malformed +Uri\WhatWg\InvalidUrlException: The specified scheme is malformed +Uri\WhatWg\InvalidUrlException: The specified scheme is malformed diff --git a/ext/uri/tests/026.phpt b/ext/uri/tests/026.phpt index e46c30055cb6..179b00d38572 100644 --- a/ext/uri/tests/026.phpt +++ b/ext/uri/tests/026.phpt @@ -29,26 +29,26 @@ var_dump($uri6->getHost()); try { $uri3->withHost("test.com:8080"); -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { $uri3->withHost("t%3As%2Ft.com"); // t:s/t.com -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { $uri3->withHost("t:s/t.com"); -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { $uri2->withHost(""); -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } $uri1 = Uri\Rfc3986\Uri::parse("ftp://user:pass@foo.com?query=abc#foo"); @@ -59,8 +59,8 @@ var_dump($uri2->getHost()); try { $uri1->withHost(null); -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } $url1 = Uri\WhatWg\Url::parse("https://example.com"); @@ -77,26 +77,26 @@ var_dump($url5->getAsciiHost()); try { $url3->withHost("test.com:8080"); -} catch (Uri\WhatWg\InvalidUrlException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { $url3->withHost("t%3As%2Ft.com"); // t:s/t.com -} catch (Uri\WhatWg\InvalidUrlException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { $url3->withHost("t:s/t.com"); // t:s/t.com -} catch (Uri\WhatWg\InvalidUrlException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { $url2->withHost(null); -} catch (Uri\WhatWg\InvalidUrlException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } $url1 = Uri\WhatWg\Url::parse("ftp://foo.com?query=abc#foo"); @@ -119,19 +119,19 @@ string(11) "192.168.0.1" string(11) "192.168.0.1" string(40) "[2001:db8:3333:4444:5555:6666:7777:8888]" string(40) "[2001:db8:3333:4444:5555:6666:7777:8888]" -The specified host is malformed -The specified host is malformed +Uri\InvalidUriException: The specified host is malformed +Uri\InvalidUriException: The specified host is malformed string(7) "foo.com" string(8) "test.com" -Cannot remove the host from a URI that has a userinfo +Uri\InvalidUriException: Cannot remove the host from a URI that has a userinfo string(11) "example.com" string(8) "test.com" string(8) "test.com" string(11) "192.168.0.1" string(40) "[2001:db8:3333:4444:5555:6666:7777:8888]" -The specified host is malformed -The specified host is malformed (DomainInvalidCodePoint) -The specified host is malformed -The specified host is malformed (HostMissing) +Uri\WhatWg\InvalidUrlException: The specified host is malformed +Uri\WhatWg\InvalidUrlException: The specified host is malformed (DomainInvalidCodePoint) +Uri\WhatWg\InvalidUrlException: The specified host is malformed +Uri\WhatWg\InvalidUrlException: The specified host is malformed (HostMissing) string(7) "foo.com" string(8) "test.com" diff --git a/ext/uri/tests/026_userinfo.phpt b/ext/uri/tests/026_userinfo.phpt index cff2665803a4..a29abddf9720 100644 --- a/ext/uri/tests/026_userinfo.phpt +++ b/ext/uri/tests/026_userinfo.phpt @@ -25,8 +25,8 @@ var_dump($uri2->getPort()); try { $uri4->withUserInfo("u:s/r"); -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } $uri5 = Uri\Rfc3986\Uri::parse("file:///foo/bar/"); @@ -46,6 +46,6 @@ NULL string(13) "%75s%2Fr:pass" string(11) "us%2Fr:pass" NULL -The specified userinfo is malformed +Uri\InvalidUriException: The specified userinfo is malformed NULL string(9) "user:pass" diff --git a/ext/uri/tests/027.phpt b/ext/uri/tests/027.phpt index 334d67aa0bb6..2309cace93d9 100644 --- a/ext/uri/tests/027.phpt +++ b/ext/uri/tests/027.phpt @@ -29,8 +29,8 @@ var_dump($uri2->getPort()); try { $uri1->withPort(1); -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } $url1 = Uri\WhatWg\Url::parse("https://example.com:8080"); @@ -63,7 +63,7 @@ int(8080) NULL int(80) NULL -Cannot set a port without having a host +Uri\InvalidUriException: Cannot set a port without having a host int(8080) int(22) NULL diff --git a/ext/uri/tests/028.phpt b/ext/uri/tests/028.phpt index cfc11b331c8e..708a50d4f2ec 100644 --- a/ext/uri/tests/028.phpt +++ b/ext/uri/tests/028.phpt @@ -25,14 +25,14 @@ var_dump($uri5->getPath()); try { $uri5->withPath("test"); -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { $uri5->withPath("/#"); -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } $uri1 = Uri\Rfc3986\Uri::parse("/foo"); @@ -73,8 +73,8 @@ string(10) "/foo%2Fbar" string(10) "/foo%2Fbar" string(0) "" string(0) "" -The specified path is malformed -The specified path is malformed +Uri\InvalidUriException: The specified path is malformed +Uri\InvalidUriException: The specified path is malformed string(4) "/foo" string(3) "bar" string(9) "/foo/bar/" diff --git a/ext/uri/tests/029.phpt b/ext/uri/tests/029.phpt index 4936cb60db87..c1ce0e10d94c 100644 --- a/ext/uri/tests/029.phpt +++ b/ext/uri/tests/029.phpt @@ -37,8 +37,8 @@ var_dump($uri5->getQuery()); try { $uri5->withQuery("#"); -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } $url1 = Uri\WhatWg\Url::parse("https://example.com?foo=bar"); @@ -81,7 +81,7 @@ string(6) "t%65st" string(4) "test" string(25) "foo=foo%26bar&baz=/qux%3D" string(25) "foo=foo%26bar&baz=/qux%3D" -The specified query is malformed +Uri\InvalidUriException: The specified query is malformed string(7) "foo=bar" string(7) "foo=baz" NULL diff --git a/ext/uri/tests/030.phpt b/ext/uri/tests/030.phpt index 03ed763f7795..e3df0f939980 100644 --- a/ext/uri/tests/030.phpt +++ b/ext/uri/tests/030.phpt @@ -17,14 +17,14 @@ var_dump($uri3->getFragment()); try { $uri3->withFragment(" "); -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { $uri1->withFragment("#fragment2"); -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } $uri1 = Uri\Rfc3986\Uri::parse("https://example.com?abc=def"); @@ -57,8 +57,8 @@ string(9) "fragment2" string(9) "fragment2" NULL NULL -The specified fragment is malformed -The specified fragment is malformed +Uri\InvalidUriException: The specified fragment is malformed +Uri\InvalidUriException: The specified fragment is malformed NULL string(8) "fragment" string(9) "fragment1" diff --git a/ext/uri/tests/031.phpt b/ext/uri/tests/031.phpt index d3cc926b82fa..d60190457c1e 100644 --- a/ext/uri/tests/031.phpt +++ b/ext/uri/tests/031.phpt @@ -12,50 +12,50 @@ var_dump($uri2); try { unserialize('O:15:"Uri\Rfc3986\Uri":1:{i:0;a:0:{}}'); // less than 2 items -} catch (Exception $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { unserialize('O:15:"Uri\Rfc3986\Uri":3:{i:0;a:0:{}i:1;a:0:{}i:2;a:0:{}}'); // more than 2 items -} catch (Exception $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { unserialize('O:15:"Uri\Rfc3986\Uri":2:{i:0;N;i:1;a:0:{}}'); // first item is not an array -} catch (Exception $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { unserialize('O:15:"Uri\Rfc3986\Uri":2:{i:0;a:0:{}i:1;a:0:{}}'); // first array is empty -} catch (Exception $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { unserialize('O:15:"Uri\Rfc3986\Uri":2:{i:0;a:1:{s:3:"uri";i:1;}i:1;a:0:{}}'); // "uri" key in first array is not a string -} catch (Exception $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { unserialize('O:15:"Uri\Rfc3986\Uri":2:{i:0;a:1:{s:3:"uri";s:2:"%1";}i:1;a:0:{}}'); // "uri" key in first array contains invalid URI -} catch (Exception $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { unserialize('O:15:"Uri\Rfc3986\Uri":2:{i:0;a:1:{s:3:"uri";s:4:"/uri";}i:1;s:0:"";}'); // second item in not an array -} catch (Exception $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { unserialize('O:15:"Uri\Rfc3986\Uri":2:{i:0;a:1:{s:3:"uri";s:4:"/uri";}i:1;a:1:{s:5:"prop1";i:123;}}'); // second array contains a property -} catch (Exception $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } $url1 = new Uri\WhatWg\Url("https://username:password@www.example.com:8080/pathname1/pathname2/pathname3?query=true#hash-exists"); @@ -67,56 +67,56 @@ var_dump($url2); try { unserialize('O:14:"Uri\WhatWg\Url":1:{i:0;a:0:{}}'); // less than 2 items -} catch (Exception $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { unserialize('O:14:"Uri\WhatWg\Url":3:{i:0;a:0:{}i:1;a:0:{}i:2;a:0:{}}'); // more than 2 items -} catch (Exception $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { unserialize('O:14:"Uri\WhatWg\Url":2:{i:0;N;i:1;a:0:{}}'); // first item is not an array -} catch (Exception $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { unserialize('O:14:"Uri\WhatWg\Url":2:{i:0;a:0:{}i:1;a:0:{}}'); // first array is empty -} catch (Exception $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { unserialize('O:14:"Uri\WhatWg\Url":2:{i:0;a:2:{s:3:"uri";s:19:"https://example.com";s:1:"a";i:1;}i:1;a:0:{}}'); // "uri" key in first array contains more than 1 item -} catch (Exception $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { unserialize('O:14:"Uri\WhatWg\Url":2:{i:0;a:1:{s:3:"uri";i:1;}i:1;a:0:{}}'); // "uri" key in first array is not a string -} catch (Exception $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { unserialize('O:14:"Uri\WhatWg\Url":2:{i:0;a:1:{s:3:"uri";s:11:"invalid-url";}i:1;a:0:{}}'); // "uri" key in first array contains invalid URL -} catch (Exception $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { unserialize('O:14:"Uri\WhatWg\Url":2:{i:0;a:1:{s:3:"uri";s:19:"https://example.com";}i:1;s:0:"";}'); // second item in not an array -} catch (Exception $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { unserialize('O:14:"Uri\WhatWg\Url":2:{i:0;a:1:{s:3:"uri";s:19:"https://example.com";}i:1;a:1:{s:5:"prop1";i:123;}}'); // second array contains property -} catch (Exception $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } ?> @@ -140,14 +140,14 @@ object(Uri\Rfc3986\Uri)#%d (%d) { ["fragment"]=> string(11) "hash-exists" } -Invalid serialization data for Uri\Rfc3986\Uri object -Invalid serialization data for Uri\Rfc3986\Uri object -Invalid serialization data for Uri\Rfc3986\Uri object -Invalid serialization data for Uri\Rfc3986\Uri object -Invalid serialization data for Uri\Rfc3986\Uri object -Invalid serialization data for Uri\Rfc3986\Uri object -Invalid serialization data for Uri\Rfc3986\Uri object -Invalid serialization data for Uri\Rfc3986\Uri object +Exception: Invalid serialization data for Uri\Rfc3986\Uri object +Exception: Invalid serialization data for Uri\Rfc3986\Uri object +Exception: Invalid serialization data for Uri\Rfc3986\Uri object +Exception: Invalid serialization data for Uri\Rfc3986\Uri object +Exception: Invalid serialization data for Uri\Rfc3986\Uri object +Exception: Invalid serialization data for Uri\Rfc3986\Uri object +Exception: Invalid serialization data for Uri\Rfc3986\Uri object +Exception: Invalid serialization data for Uri\Rfc3986\Uri object string(163) "O:14:"Uri\WhatWg\Url":2:{i:0;a:1:{s:3:"uri";s:99:"https://username:password@www.example.com:8080/pathname1/pathname2/pathname3?query=true#hash-exists";}i:1;a:0:{}}" object(Uri\WhatWg\Url)#%d (%d) { ["scheme"]=> @@ -167,12 +167,12 @@ object(Uri\WhatWg\Url)#%d (%d) { ["fragment"]=> string(11) "hash-exists" } -Invalid serialization data for Uri\WhatWg\Url object -Invalid serialization data for Uri\WhatWg\Url object -Invalid serialization data for Uri\WhatWg\Url object -Invalid serialization data for Uri\WhatWg\Url object -Invalid serialization data for Uri\WhatWg\Url object -Invalid serialization data for Uri\WhatWg\Url object -Invalid serialization data for Uri\WhatWg\Url object -Invalid serialization data for Uri\WhatWg\Url object -Invalid serialization data for Uri\WhatWg\Url object +Exception: Invalid serialization data for Uri\WhatWg\Url object +Exception: Invalid serialization data for Uri\WhatWg\Url object +Exception: Invalid serialization data for Uri\WhatWg\Url object +Exception: Invalid serialization data for Uri\WhatWg\Url object +Exception: Invalid serialization data for Uri\WhatWg\Url object +Exception: Invalid serialization data for Uri\WhatWg\Url object +Exception: Invalid serialization data for Uri\WhatWg\Url object +Exception: Invalid serialization data for Uri\WhatWg\Url object +Exception: Invalid serialization data for Uri\WhatWg\Url object diff --git a/ext/uri/tests/051.phpt b/ext/uri/tests/051.phpt index 0485356075a8..cb9c975d2385 100644 --- a/ext/uri/tests/051.phpt +++ b/ext/uri/tests/051.phpt @@ -7,16 +7,16 @@ $uri = new Uri\Rfc3986\Uri("https://example.com"); try { $uri->resolve("á"); -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } $url = new Uri\WhatWg\Url("https://example.com"); try { $url->resolve("https://1.2.3.4.5"); -} catch (Uri\WhatWg\InvalidUrlException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } $softErrors = []; @@ -26,8 +26,8 @@ var_dump($softErrors); ?> --EXPECTF-- -The specified URI is malformed -The specified URI is malformed (Ipv4TooManyParts) +Uri\InvalidUriException: The specified URI is malformed +Uri\WhatWg\InvalidUrlException: The specified URI is malformed (Ipv4TooManyParts) string(23) "https://example.com/foo" array(%d) { [0]=> diff --git a/ext/uri/tests/052.phpt b/ext/uri/tests/052.phpt index 3d8d2053f6e8..b0867b02bd5a 100644 --- a/ext/uri/tests/052.phpt +++ b/ext/uri/tests/052.phpt @@ -7,15 +7,15 @@ $r = new Uri\WhatWg\UrlValidationError('foo', Uri\WhatWg\UrlValidationErrorType: try { $r->__construct('bar', Uri\WhatWg\UrlValidationErrorType::HostMissing, false); -} catch (Error $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } var_dump($r); ?> --EXPECTF-- -Cannot modify readonly property Uri\WhatWg\UrlValidationError::$context +Error: Cannot modify readonly property Uri\WhatWg\UrlValidationError::$context object(Uri\WhatWg\UrlValidationError)#%d (%d) { ["context"]=> string(3) "foo" diff --git a/ext/uri/tests/053.phpt b/ext/uri/tests/053.phpt index c88f4d386f35..7f68772e5487 100644 --- a/ext/uri/tests/053.phpt +++ b/ext/uri/tests/053.phpt @@ -12,26 +12,26 @@ $r = new Uri\WhatWg\InvalidUrlException( try { $r->__construct("foo"); -} catch (Error $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { $r->__construct("bar", []); -} catch (Error $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { $r->__construct("baz", [], 0); -} catch (Error $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { $r->__construct("qax", [], 0, null); -} catch (Error $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } var_dump($r->getMessage()); @@ -41,10 +41,10 @@ var_dump($r->getPrevious()::class); ?> --EXPECTF-- -Cannot modify readonly property Uri\WhatWg\InvalidUrlException::$errors -Cannot modify readonly property Uri\WhatWg\InvalidUrlException::$errors -Cannot modify readonly property Uri\WhatWg\InvalidUrlException::$errors -Cannot modify readonly property Uri\WhatWg\InvalidUrlException::$errors +Error: Cannot modify readonly property Uri\WhatWg\InvalidUrlException::$errors +Error: Cannot modify readonly property Uri\WhatWg\InvalidUrlException::$errors +Error: Cannot modify readonly property Uri\WhatWg\InvalidUrlException::$errors +Error: Cannot modify readonly property Uri\WhatWg\InvalidUrlException::$errors string(3) "qax" array(%d) { [%d]=> diff --git a/ext/uri/tests/054.phpt b/ext/uri/tests/054.phpt index 757b21cc114a..386f88ae93e5 100644 --- a/ext/uri/tests/054.phpt +++ b/ext/uri/tests/054.phpt @@ -5,12 +5,12 @@ Test UrlValidationErrorType singleton try { new \Uri\WhatWg\Url('http://localhost:99999'); -} catch (Uri\WhatWg\InvalidUrlException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; var_dump($e->errors[0]->type === \Uri\WhatWg\UrlValidationErrorType::PortOutOfRange); } ?> --EXPECT-- -The specified URI is malformed (PortOutOfRange) +Uri\WhatWg\InvalidUrlException: The specified URI is malformed (PortOutOfRange) bool(true) diff --git a/ext/uri/tests/055.phpt b/ext/uri/tests/055.phpt index de240bb6e053..fddb313149df 100644 --- a/ext/uri/tests/055.phpt +++ b/ext/uri/tests/055.phpt @@ -5,9 +5,9 @@ Test InvalidUrlException constructor error handling try { var_dump(new Uri\Rfc3986\Uri('foo', new Uri\Rfc3986\Uri('bar'))); -} catch (Uri\InvalidUriException $e) { - echo $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } ?> --EXPECT-- -The specified base URI must be absolute +Uri\InvalidUriException: The specified base URI must be absolute diff --git a/ext/uri/tests/101.phpt b/ext/uri/tests/101.phpt index daa83e45c9f8..b00eeac8f804 100644 --- a/ext/uri/tests/101.phpt +++ b/ext/uri/tests/101.phpt @@ -7,14 +7,14 @@ zend_test try { var_dump(zend_test_uri_parser('invalid uri', "Uri\\WhatWg\\Url")); -} catch (\Uri\WhatWg\InvalidUrlException $e) { - echo $e->getMessage(), PHP_EOL; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; var_dump($e->errors); } ?> --EXPECTF-- -The specified URI is malformed (MissingSchemeNonRelativeUrl) +Uri\WhatWg\InvalidUrlException: The specified URI is malformed (MissingSchemeNonRelativeUrl) array(1) { [0]=> object(Uri\WhatWg\UrlValidationError)#%d (3) { diff --git a/ext/uri/tests/gh19780.phpt b/ext/uri/tests/gh19780.phpt index fffa25bc37d2..24a3fed9e2a6 100644 --- a/ext/uri/tests/gh19780.phpt +++ b/ext/uri/tests/gh19780.phpt @@ -9,19 +9,19 @@ use Uri\WhatWg\UrlValidationErrorType; try { new InvalidUrlException('message', ['foo']); -} catch (ValueError $e) { - echo $e->getMessage(), "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } try { new InvalidUrlException('message', [ 1 => new UrlValidationError('context', UrlValidationErrorType::HostMissing, true) ]); -} catch (ValueError $e) { - echo $e->getMessage(), "\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } ?> --EXPECT-- -Uri\WhatWg\InvalidUrlException::__construct(): Argument #2 ($errors) must be a list of Uri\WhatWg\UrlValidationError -Uri\WhatWg\InvalidUrlException::__construct(): Argument #2 ($errors) must be a list of Uri\WhatWg\UrlValidationError +ValueError: Uri\WhatWg\InvalidUrlException::__construct(): Argument #2 ($errors) must be a list of Uri\WhatWg\UrlValidationError +ValueError: Uri\WhatWg\InvalidUrlException::__construct(): Argument #2 ($errors) must be a list of Uri\WhatWg\UrlValidationError From 277c755fb41fadc38b5fe164c94274f33d6d8805 Mon Sep 17 00:00:00 2001 From: Juliette <663378+jrfnl@users.noreply.github.com> Date: Sat, 15 Aug 2026 15:11:15 +0200 Subject: [PATCH 5/5] [skip ci] UPGRADING: various formatting, grammar and spelling fixes (#23265) Based on an initial pass through the changelog. * Consistently use US English instead of UK English. * Order extensions alphabetically. * Improve consistency for extension headings. * Minor other fixes. Co-authored-by: jrfnl --- UPGRADING | 62 +++++++++++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/UPGRADING b/UPGRADING index f61e175a65b8..a5262caf3e0f 100644 --- a/UPGRADING +++ b/UPGRADING @@ -21,18 +21,18 @@ PHP 8.6 UPGRADE NOTES - Core: . ??/empty() on a magic property no longer call __get() when __isset() - has materialised the property by writing into the property table. + has materialized the property by writing into the property table. The freshly-written value is returned directly. isset() is unaffected. +- COM + . It is no longer possible to clone variant objects, this is because + the cloning behavior was ill defined. + - Curl: . The callback registered with CURLOPT_READFUNCTION now throws a ValueError when returning an integer other than 0, CURL_READFUNC_ABORT or CURL_READFUNC_PAUSE. -- COM - . It is no longer possible to clone variant objects, this is because - the cloning behaviour was ill defined. - - DOM: . Properties previously documented as @readonly (e.g. DOMNode::$nodeType, DOMDocument::$xmlEncoding, DOMEntity::$actualEncoding, ::$encoding, @@ -59,9 +59,9 @@ PHP 8.6 UPGRADE NOTES extension such as ".pharma". - GMP: - . GMP power and shift operators now throw ValueError when GMP right operands + . GMP power and shift operators now throw a ValueError when GMP right operands are outside the unsigned long range, instead of silently truncating them. - . GMP integer string parsing now throw ValueError strings containing NUL + . GMP integer string parsing now throws a ValueError for strings containing NUL bytes, instead of silently truncating them. - Intl: @@ -104,6 +104,13 @@ PHP 8.6 UPGRADE NOTES execution error occurs (e.g. malformed UTF-8 input with the /u modifier). This is consistent with other preg_* functions. +- PGSQL: + . pg_fetch_object() now reports the ValueError for a non-empty + $constructor_args on a class without a constructor on the + $constructor_args argument instead of $class. Errors raised when + the requested class is not instantiable (abstract, interface, enum) + now surface before the row is fetched. + - Phar: . Phar::mungServer() now raises a ValueError when an invalid argument value is passed instead of being silently ignored. @@ -113,13 +120,6 @@ PHP 8.6 UPGRADE NOTES . Phar::addEmptyDir() now treats non-magic names that merely share the ".phar" prefix as ordinary directories. -- PGSQL: - . pg_fetch_object() now reports the ValueError for a non-empty - $constructor_args on a class without a constructor on the - $constructor_args argument instead of $class. Errors raised when - the requested class is not instantiable (abstract, interface, enum) - now surface before the row is fetched. - - Posix: . posix_access() now raises a ValueError when an invalid $flags argument value is passed. @@ -170,6 +170,14 @@ PHP 8.6 UPGRADE NOTES platform's key_t range instead of passing a truncated key to the operating system. +- SimpleXML: + . SimpleXMLElement::__construct() now raises a ValueError when the $data + argument contains NUL bytes, matching simplexml_load_file(). With + $dataIsURL set it previously truncated the path at the first NUL byte. + Without it the string went to libxml, which at default options rejects a + NUL on current versions but accepts the truncated document on older ones + and under LIBXML_RECOVER. + - SOAP: . The "classmap" option of SoapClient and SoapServer now rejects arrays containing integer keys. Previously, sparse integer-keyed and mixed-keyed @@ -208,14 +216,6 @@ PHP 8.6 UPGRADE NOTES . DirectoryIterator::key() now returns int|string, and DirectoryIterator::current() returns string|SplFileInfo|static. -- SimpleXML: - . SimpleXMLElement::__construct() now raises a ValueError when the $data - argument contains NUL bytes, matching simplexml_load_file(). With - $dataIsURL set it previously truncated the path at the first NUL byte. - Without it the string went to libxml, which at default options rejects a - NUL on current versions but accepts the truncated document on older ones - and under LIBXML_RECOVER. - - Standard: . array_intersect() with at least two arrays now converts values to strings while scanning its inputs instead of during sort comparisons. This can @@ -428,7 +428,7 @@ PHP 8.6 UPGRADE NOTES sockets. A positive value enables lingering for that many seconds, zero or a negative value disables it. Values above 65535 are clamped as the linger time is limited to an unsigned short on some platforms. - . Allowed casting casting filtered streams as file descriptor for select. + . Allowed casting filtered streams as file descriptor for select. . Added the "write_seek_mode stream" filter parameter for the bz2, iconv, zlib, and string stream filters. This parameter must be set via an associative array where the key is "write_seek_mode stream" and the @@ -573,7 +573,7 @@ PHP 8.6 UPGRADE NOTES . gmp_powm() modulo-by-zero now raises a DivisionByZeroError whose message includes the function name and argument index ($modulus). -- mysqli: +- MySQLi: . The return structure of mysqli_get_charset() no longer contains the undocumented "comment" element. The value of "charsetnr" is now set to a constant 0 as this number was an implementation detail that should not have @@ -624,7 +624,7 @@ PHP 8.6 UPGRADE NOTES . SpoofChecker::getBidiSkeleton() . SpoofChecker::getSkeleton() -- mysqli: +- MySQLi: . Added mysqli::quote_string() and mysqli_quote_string(). RFC: https://wiki.php.net/rfc/mysqli_quote_string @@ -717,7 +717,7 @@ PHP 8.6 UPGRADE NOTES - Hash: . The bundled version of xxHash was upgraded to 0.8.2. -- mysqli +- MySQLi: . Added new constant MYSQLI_OPT_COMPRESS. - Opcache @@ -753,7 +753,7 @@ PHP 8.6 UPGRADE NOTES . EAI_SERVICE. . EAI_ADDRFAMILY. . EAI_SYSTEM. - . EAI_OVERFLOW + . EAI_OVERFLOW. . EAI_INPROGRESS. . EAI_CANCELED. . EAI_NOTCANCELED. @@ -763,9 +763,9 @@ PHP 8.6 UPGRADE NOTES - Standard . ARRAY_FILTER_USE_VALUE. - . STREAM_CRYPTO_STATUS_NONE - . STREAM_CRYPTO_STATUS_WANT_READ - . STREAM_CRYPTO_STATUS_WANT_WRITE + . STREAM_CRYPTO_STATUS_NONE. + . STREAM_CRYPTO_STATUS_WANT_READ. + . STREAM_CRYPTO_STATUS_WANT_WRITE. ======================================== 11. Changes to INI File Handling @@ -778,7 +778,7 @@ PHP 8.6 UPGRADE NOTES directive to NULL or an empty string at runtime now leaves the previously configured detection order unchanged. -- Mysqli: +- MySQLi: . mysqli.default_port now checks the validity of the value which should be between 0 and 65535 included.