From 2bbe62eb5677e4612eac880a27a3c5a9cba73128 Mon Sep 17 00:00:00 2001 From: Louis-Arnaud Date: Wed, 2 Sep 2026 11:59:45 +0200 Subject: [PATCH 1/3] ext/intl: report argument #3 ($variant) for an invalid IDNA variant (#23533) - php_intl_idn_handoff() takes domain, flags, variant, so the variant check must report argument 3; it hardcodes 2, which makes idn_to_ascii() and idn_to_utf8() blame $flags for a value carried by $variant. - The message concatenated PRId32 as a literal instead of using it as a conversion, so it read "must be less than d bytes" and INT32_MAX was never printed. --- ext/intl/idn/idn.cpp | 4 ++-- ext/intl/tests/idn_uts46_errors.phpt | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ext/intl/idn/idn.cpp b/ext/intl/idn/idn.cpp index 76cca2281296..c1db9a84ccd0 100644 --- a/ext/intl/idn/idn.cpp +++ b/ext/intl/idn/idn.cpp @@ -122,11 +122,11 @@ static void php_intl_idn_handoff(INTERNAL_FUNCTION_PARAMETERS, int mode) RETURN_THROWS(); } if (UNEXPECTED(ZSTR_LEN(domain) > INT32_MAX - 1)) { - zend_argument_value_error(1, "must be less than " PRId32 " bytes", INT32_MAX); + zend_argument_value_error(1, "must be less than %" PRId32 " bytes", INT32_MAX); RETURN_THROWS(); } if (variant != INTL_IDN_VARIANT_UTS46) { - zend_argument_value_error(2, "must be INTL_IDNA_VARIANT_UTS46"); + zend_argument_value_error(3, "must be INTL_IDNA_VARIANT_UTS46"); RETURN_THROWS(); } /* don't check options; it wasn't checked before */ diff --git a/ext/intl/tests/idn_uts46_errors.phpt b/ext/intl/tests/idn_uts46_errors.phpt index c1058c3a2439..be7121482b7f 100644 --- a/ext/intl/tests/idn_uts46_errors.phpt +++ b/ext/intl/tests/idn_uts46_errors.phpt @@ -18,6 +18,13 @@ try { echo $e::class, ': ', $e->getMessage(), PHP_EOL; } +echo "bad variant, named argument:", "\n"; +try { + var_dump(idn_to_utf8("xn--fuball-cta.com", variant: INTL_IDNA_VARIANT_UTS46 + 10)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + echo "empty domain:", "\n"; try { var_dump(idn_to_ascii("", 0, INTL_IDNA_VARIANT_UTS46)); @@ -45,7 +52,9 @@ var_dump($foo["errors"]==IDNA_ERROR_CONTEXTJ); --EXPECT-- => PHP level errors bad variant: -ValueError: idn_to_ascii(): Argument #2 ($flags) must be INTL_IDNA_VARIANT_UTS46 +ValueError: idn_to_ascii(): Argument #3 ($variant) must be INTL_IDNA_VARIANT_UTS46 +bad variant, named argument: +ValueError: idn_to_utf8(): Argument #3 ($variant) must be INTL_IDNA_VARIANT_UTS46 empty domain: ValueError: idn_to_ascii(): Argument #1 ($domain) must not be empty with error, but no details arg: From 78c33e071e2fb92f01695c520e0828a32a3561ae Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 2 Sep 2026 11:06:28 +0100 Subject: [PATCH 2/3] [skip ci] Add NEWS entries --- NEWS | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/NEWS b/NEWS index 17ec66961b3b..2fd0fa3a3ace 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,10 @@ PHP NEWS . Fixed stale getElementsByClassName() and other node list caches after className/classList writes and attribute removals. (Ilia Alshanetsky) +- Hash: + . Fixed hash_file() reporting argument #1 ($algo) instead of argument #2 + ($filename) when the filename contains null bytes. (lacatoire) + - Intl: . Fixed grapheme_strpos() and grapheme_strrpos() with an empty needle returning UTF-16 offsets instead of grapheme offsets. (Ilia Alshanetsky) @@ -26,12 +30,20 @@ PHP NEWS from compiled rules. (iliaal) . Fixed Spoofchecker methods not recording the ICU error code when an ICU call fails. (Ilia Alshanetsky) + . Fixed idn_to_ascii() and idn_to_utf8() reporting argument #2 ($flags) + instead of argument #3 ($variant) for an invalid IDNA variant, and the + domain length error message printing a literal "d" instead of the limit. + (lacatoire) - MBString: . Fixed mb_ereg_replace() emitting a NUL or out-of-bounds bytes in the replacement when a \k backref has no closing delimiter. (Ilia Alshanetsky) +- PCNTL: + . Fixed the declared signature of pcntl_signal(), whose $restart_syscalls + argument accepts null and defaults to it. (lacatoire) + - PDO_PGSQL: . Added Pdo\Pgsql::ATTR_CHUNK_SIZE to fetch a result set in chunks of the given number of rows. (KentarouTakeda) From 386a46757d510d551160abdf371341374517984e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= Date: Wed, 2 Sep 2026 15:24:19 +0200 Subject: [PATCH 3/3] ext/intl: fix build with older ICU (#23539) Covariant return types for clone() were only introduced in ICU 65; before that NumberFormat::clone() returns Format*, so cast the result explicitly in NumberFormatter_object_clone(). is a C++-only header, and older ICU does not wrap it in U_SHOW_CPLUSPLUS_API, so including it from php_intl.c breaks the C compile. Move it into the __cplusplus branch, next to the using declaration that needs it. --- ext/intl/formatter/formatter_class.cpp | 2 +- ext/intl/rangeformatter/rangeformatter_class.h | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ext/intl/formatter/formatter_class.cpp b/ext/intl/formatter/formatter_class.cpp index 5b82b53c6d62..29817132acac 100644 --- a/ext/intl/formatter/formatter_class.cpp +++ b/ext/intl/formatter/formatter_class.cpp @@ -71,7 +71,7 @@ U_CFUNC zend_object *NumberFormatter_object_clone(zend_object *object) /* clone formatter object. It may fail, the destruction code must handle this case */ if (FORMATTER_OBJECT(nfo) != nullptr) { - FORMATTER_OBJECT(new_nfo) = FORMATTER_OBJECT(nfo)->clone(); + FORMATTER_OBJECT(new_nfo) = static_cast(FORMATTER_OBJECT(nfo)->clone()); if (FORMATTER_OBJECT(new_nfo) == nullptr) { zend_throw_error(NULL, "Failed to clone NumberFormatter"); } diff --git a/ext/intl/rangeformatter/rangeformatter_class.h b/ext/intl/rangeformatter/rangeformatter_class.h index 494a6d09a05f..4b6f02dc93c9 100644 --- a/ext/intl/rangeformatter/rangeformatter_class.h +++ b/ext/intl/rangeformatter/rangeformatter_class.h @@ -15,9 +15,8 @@ #ifndef RANGEFORMATTER_CLASS_H #define RANGEFORMATTER_CLASS_H -#include - #ifdef __cplusplus +#include using icu::number::LocalizedNumberRangeFormatter; #else typedef void LocalizedNumberRangeFormatter;