diff --git a/NEWS b/NEWS index a06ac7f852587..6929e7b2d2f14 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,12 @@ PHP NEWS . socket_set_option for multicast context throws a ValueError when the socket family is not of AF_INET/AF_INET6 family. (David Carlier) +- URI: + . Empty host handling is fixed. (Máté Kocsis) + . Error handling of Uri\WhatWg\Url::withHost() is fixed when the input + contains a port. Now, it triggers an exception; previously, the error + was silently swallowed. (Máté Kocsis) + 17 Jul 2025, PHP 8.5.0alpha2 - Core: diff --git a/UPGRADING b/UPGRADING index 87d08e1260150..2a82975517178 100644 --- a/UPGRADING +++ b/UPGRADING @@ -460,6 +460,13 @@ PHP 8.5 UPGRADE NOTES across multiple PHP requests. RFC: https://wiki.php.net/rfc/curl_share_persistence_improvement +- URI: + . Uri\UriException, Uri\InvalidUriException, Uri\UriComparisonMode, + Uri\Rfc3986\Uri, Uri\WhatWg\InvalidUrlException, + Uri\WhatWg\UrlValidationErrorType, Uri\WhatWg\UrlValidationError, + and Uri\WhatWg\Url are added. + RFC: https://wiki.php.net/rfc/url_parsing_api + ======================================== 8. Removed Extensions and SAPIs ======================================== @@ -483,6 +490,11 @@ PHP 8.5 UPGRADE NOTES library that was separated from ext/dom for being reused among other extensions. The new extension is not directly exposed to userland. +- URI: + . An always enabled uri extension is added that can be used for handling + URIs and URLs according to RFC 3986 and WHATWG URL. + RFC: https://wiki.php.net/rfc/url_parsing_api + - PCRE: . Upgraded to pcre2lib from 10.44 to 10.45. diff --git a/ext/hash/hash.c b/ext/hash/hash.c index 13c345de4dd1c..465bbf3cd0949 100644 --- a/ext/hash/hash.c +++ b/ext/hash/hash.c @@ -411,7 +411,7 @@ static void php_hash_do_hash( php_hash_bin2hex(ZSTR_VAL(hex_digest), (unsigned char *) ZSTR_VAL(digest), ops->digest_size); ZSTR_VAL(hex_digest)[2 * ops->digest_size] = 0; - zend_string_release_ex(digest, 0); + zend_string_efree(digest); RETURN_NEW_STR(hex_digest); } } @@ -542,7 +542,7 @@ static void php_hash_do_hash_hmac( if (n < 0) { efree(context); efree(K); - zend_string_release(digest); + zend_string_efree(digest); RETURN_FALSE; } @@ -568,7 +568,7 @@ static void php_hash_do_hash_hmac( php_hash_bin2hex(ZSTR_VAL(hex_digest), (unsigned char *) ZSTR_VAL(digest), ops->digest_size); ZSTR_VAL(hex_digest)[2 * ops->digest_size] = 0; - zend_string_release_ex(digest, 0); + zend_string_efree(digest); RETURN_NEW_STR(hex_digest); } } @@ -829,7 +829,7 @@ PHP_FUNCTION(hash_final) php_hash_bin2hex(ZSTR_VAL(hex_digest), (unsigned char *) ZSTR_VAL(digest), digest_len); ZSTR_VAL(hex_digest)[2 * digest_len] = 0; - zend_string_release_ex(digest, 0); + zend_string_efree(digest); RETURN_NEW_STR(hex_digest); } } diff --git a/ext/intl/collator/collator_sort.c b/ext/intl/collator/collator_sort.c index ee68a21c98960..215f150c4faf9 100644 --- a/ext/intl/collator/collator_sort.c +++ b/ext/intl/collator/collator_sort.c @@ -553,6 +553,7 @@ PHP_FUNCTION( collator_get_sort_key ) key_len = ucol_getSortKey(co->ucoll, ustr, ustr_len, (uint8_t*)ZSTR_VAL(key_str), key_len); efree( ustr ); if(!key_len) { + zend_string_efree(key_str); RETURN_FALSE; } ZSTR_LEN(key_str) = key_len - 1; diff --git a/ext/lexbor/lexbor/core/str.c b/ext/lexbor/lexbor/core/str.c index 0f04286bdea56..3b3c574f1d9a9 100644 --- a/ext/lexbor/lexbor/core/str.c +++ b/ext/lexbor/lexbor/core/str.c @@ -133,6 +133,10 @@ lexbor_str_append(lexbor_str_t *str, lexbor_mraw_t *mraw, { lxb_char_t *data_begin; + if (length == 0) { + return str->data; + } + lexbor_str_check_size_arg_m(str, lexbor_str_size(str), mraw, (length + 1), NULL); diff --git a/ext/lexbor/lexbor/url/url.c b/ext/lexbor/lexbor/url/url.c index bbb3b5bbd3cb7..ed3732a5ccab2 100644 --- a/ext/lexbor/lexbor/url/url.c +++ b/ext/lexbor/lexbor/url/url.c @@ -1818,7 +1818,7 @@ lxb_url_parse_basic_h(lxb_url_parser_t *parser, lxb_url_t *url, } if (override_state == LXB_URL_STATE_HOSTNAME_STATE) { - lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK); + lxb_url_parse_return(orig_data, buf, LXB_STATUS_ERROR); } status = lxb_url_host_parse(parser, begin, p, &url->host, diff --git a/ext/openssl/openssl_backend_common.c b/ext/openssl/openssl_backend_common.c index 184ea2e205cc9..272ebd6915a46 100644 --- a/ext/openssl/openssl_backend_common.c +++ b/ext/openssl/openssl_backend_common.c @@ -1671,7 +1671,7 @@ zend_result php_openssl_validate_iv(const char **piv, size_t *piv_len, size_t iv char *iv_new; if (mode->is_aead) { - if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_ivlen_flag, *piv_len, NULL) != 1) { + if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_ivlen_flag, *piv_len, NULL) <= 0) { php_error_docref(NULL, E_WARNING, "Setting of IV length for AEAD mode failed"); return FAILURE; } @@ -1742,7 +1742,7 @@ zend_result php_openssl_cipher_init(const EVP_CIPHER *cipher_type, return FAILURE; } if (mode->set_tag_length_always || (enc && mode->set_tag_length_when_encrypting)) { - if (!EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, NULL)) { + if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, NULL) <= 0) { php_error_docref(NULL, E_WARNING, "Setting tag length for AEAD cipher failed"); return FAILURE; } @@ -1750,7 +1750,7 @@ zend_result php_openssl_cipher_init(const EVP_CIPHER *cipher_type, if (!enc && tag && tag_len > 0) { if (!mode->is_aead) { php_error_docref(NULL, E_WARNING, "The tag cannot be used because the cipher algorithm does not support AEAD"); - } else if (!EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, (unsigned char *) tag)) { + } else if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, (unsigned char *) tag) <= 0) { php_error_docref(NULL, E_WARNING, "Setting tag for AEAD cipher decryption failed"); return FAILURE; } @@ -1886,7 +1886,7 @@ PHP_OPENSSL_API zend_string* php_openssl_encrypt( if (mode.is_aead && tag) { zend_string *tag_str = zend_string_alloc(tag_len, 0); - if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode.aead_get_tag_flag, tag_len, ZSTR_VAL(tag_str)) == 1) { + if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode.aead_get_tag_flag, tag_len, ZSTR_VAL(tag_str)) > 0) { ZSTR_VAL(tag_str)[tag_len] = '\0'; ZSTR_LEN(tag_str) = tag_len; ZEND_TRY_ASSIGN_REF_NEW_STR(tag, tag_str); diff --git a/ext/uri/php_lexbor.c b/ext/uri/php_lexbor.c index 39b0fb7d09ce3..5287bbcd9023d 100644 --- a/ext/uri/php_lexbor.c +++ b/ext/uri/php_lexbor.c @@ -372,7 +372,9 @@ static zend_result lexbor_read_host(const struct uri_internal_t *internal_uri, u smart_str_appendc(&host_str, ']'); ZVAL_NEW_STR(retval, smart_str_extract(&host_str)); - } else if (lexbor_uri->host.type != LXB_URL_HOST_TYPE_EMPTY && lexbor_uri->host.type != LXB_URL_HOST_TYPE__UNDEF) { + } else if (lexbor_uri->host.type == LXB_URL_HOST_TYPE_EMPTY) { + ZVAL_EMPTY_STRING(retval); + } else if (lexbor_uri->host.type != LXB_URL_HOST_TYPE__UNDEF) { switch (read_mode) { case URI_COMPONENT_READ_NORMALIZED_UNICODE: { smart_str host_str = {0}; diff --git a/ext/uri/php_uriparser.c b/ext/uri/php_uriparser.c index 875d72b85241a..ba6f1ce776cb6 100644 --- a/ext/uri/php_uriparser.c +++ b/ext/uri/php_uriparser.c @@ -174,7 +174,7 @@ ZEND_ATTRIBUTE_NONNULL static zend_result uriparser_read_host(const uri_internal UriUriA *uriparser_uri = uriparser_read_uri(internal_uri->uri, read_mode); ZEND_ASSERT(uriparser_uri != NULL); - if (uriparser_uri->hostText.first != NULL && uriparser_uri->hostText.afterLast != NULL && get_text_range_length(&uriparser_uri->hostText) > 0) { + if (uriparser_uri->hostText.first != NULL && uriparser_uri->hostText.afterLast != NULL) { if (uriparser_uri->hostData.ip6 != NULL || uriparser_uri->hostData.ipFuture.first != NULL) { /* the textual representation of the host is always accessible in the .hostText field no matter what the host is */ smart_str host_str = {0}; diff --git a/ext/uri/tests/003.phpt b/ext/uri/tests/003.phpt index be607fd6cacef..a1918f7a838b2 100644 --- a/ext/uri/tests/003.phpt +++ b/ext/uri/tests/003.phpt @@ -1,5 +1,5 @@ --TEST-- -Parse URL exotic URLs +Parse special URIs --EXTENSIONS-- uri --FILE-- @@ -8,6 +8,8 @@ uri var_dump(Uri\Rfc3986\Uri::parse("http://username:password@héééostname:9090/gah/../path?arg=vaéue#anchor")); var_dump(Uri\WhatWg\Url::parse("http://username:password@héééostname:9090/gah/../path?arg=vaéue#anchor")); +var_dump(Uri\Rfc3986\Uri::parse("//host123/")); +var_dump(Uri\Rfc3986\Uri::parse("///foo/")); var_dump(Uri\Rfc3986\Uri::parse("/page:1")); var_dump(Uri\WhatWg\Url::parse("/page:1")); @@ -32,6 +34,42 @@ object(Uri\WhatWg\Url)#%d (%d) { ["fragment"]=> string(6) "anchor" } +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(7) "host123" + ["port"]=> + NULL + ["path"]=> + string(1) "/" + ["query"]=> + NULL + ["fragment"]=> + NULL +} +object(Uri\Rfc3986\Uri)#%d (%d) { + ["scheme"]=> + NULL + ["username"]=> + NULL + ["password"]=> + NULL + ["host"]=> + string(0) "" + ["port"]=> + NULL + ["path"]=> + string(5) "/foo/" + ["query"]=> + NULL + ["fragment"]=> + NULL +} object(Uri\Rfc3986\Uri)#%d (%d) { ["scheme"]=> NULL diff --git a/ext/uri/tests/012.phpt b/ext/uri/tests/012.phpt index 7c14014fb3519..3eb343af535cb 100644 --- a/ext/uri/tests/012.phpt +++ b/ext/uri/tests/012.phpt @@ -57,7 +57,7 @@ object(Uri\Rfc3986\Uri)#%d (%d) { ["password"]=> NULL ["host"]=> - NULL + string(0) "" ["port"]=> NULL ["path"]=> @@ -75,7 +75,7 @@ object(Uri\WhatWg\Url)#%d (%d) { ["password"]=> NULL ["host"]=> - NULL + string(0) "" ["port"]=> NULL ["path"]=> diff --git a/ext/uri/tests/026.phpt b/ext/uri/tests/026.phpt index 4763ea9d4406c..47a8597fa2e7d 100644 --- a/ext/uri/tests/026.phpt +++ b/ext/uri/tests/026.phpt @@ -8,21 +8,27 @@ uri $url1 = Uri\WhatWg\Url::parse("https://example.com"); $url2 = $url1->withHost("test.com"); $url3 = $url2->withHost("t%65st.com"); // test.com -$url4 = $url3->withHost("test.com:8080"); +try { + $url3->withHost("test.com:8080"); +} catch (Uri\WhatWg\InvalidUrlException $e) { + echo $e->getMessage() . "\n"; +} var_dump($url1->getAsciiHost()); var_dump($url2->getAsciiHost()); var_dump($url3->getAsciiHost()); -var_dump($url4->getAsciiHost()); -var_dump($url4->getPort()); try { - $url4->withHost("t%3As%2Ft.com"); // t:s/t.com + $url3->withHost("t%3As%2Ft.com"); // t:s/t.com } catch (Uri\WhatWg\InvalidUrlException $e) { echo $e->getMessage() . "\n"; } -var_dump($url4->withHost("t:s/t.com")); +try { + $url3->withHost("t:s/t.com"); // t:s/t.com +} catch (Uri\WhatWg\InvalidUrlException $e) { + echo $e->getMessage() . "\n"; +} try { $url2->withHost(null); @@ -38,30 +44,12 @@ var_dump($url2->getAsciiHost()); ?> --EXPECTF-- +The specified host is malformed string(11) "example.com" string(8) "test.com" string(8) "test.com" -string(8) "test.com" -NULL The specified host is malformed (DomainInvalidCodePoint) -object(Uri\WhatWg\Url)#%d (%d) { - ["scheme"]=> - string(5) "https" - ["username"]=> - NULL - ["password"]=> - NULL - ["host"]=> - string(8) "test.com" - ["port"]=> - NULL - ["path"]=> - string(1) "/" - ["query"]=> - NULL - ["fragment"]=> - NULL -} +The specified host is malformed The specified host is malformed (HostMissing) string(7) "foo.com" string(8) "test.com"