Commit 4efca409534 for php.net
commit 4efca40953435efce4511e494d28c8496b51b712
Author: Weilin Du <108666168+LamentXU123@users.noreply.github.com>
Date: Sat Jun 20 13:07:19 2026 +0800
ext/intl: Fix Locale::lookup() fallback on invalid language tags (#22306)
Locale::lookup() and locale_lookup() should not return the fallback locale
when canonicalizing a language tag fails. Returning the fallback hid the
intl error raised by lookup_loc_range() for invalid language tags.
Return NULL in that error case instead, while preserving the exception path
when intl.use_exceptions is enabled.
Closes #22306
diff --git a/NEWS b/NEWS
index 32bb0b49f34..df93cb5b77b 100644
--- a/NEWS
+++ b/NEWS
@@ -48,6 +48,8 @@ PHP NEWS
for invalid display types. (Weilin Du)
. Fixed Spoofchecker restriction-level APIs to only be exposed with ICU 53
and later. (Graham Campbell)
+ . Fixed Locale::lookup() and locale_lookup() to return NULL instead of the
+ fallback locale when a language tag cannot be canonicalized. (Weilin Du)
- mysqli:
. Fix stmt->query leak in mysqli_execute_query() validation errors.
diff --git a/ext/intl/locale/locale_methods.c b/ext/intl/locale/locale_methods.c
index e7cc9d8364c..b5d48257338 100644
--- a/ext/intl/locale/locale_methods.c
+++ b/ext/intl/locale/locale_methods.c
@@ -1435,14 +1435,15 @@ static zend_string* lookup_loc_range(const char* loc_range, HashTable* hash_arr,
zend_argument_type_error(2, "must only contain string values");
LOOKUP_CLEAN_RETURN(NULL);
}
- cur_arr[cur_arr_len*2] = estrndup(Z_STRVAL_P(ele_value), Z_STRLEN_P(ele_value));
- result = strToMatch(Z_STRVAL_P(ele_value), cur_arr[cur_arr_len*2]);
+ i = cur_arr_len*2;
+ cur_arr[i] = estrndup(Z_STRVAL_P(ele_value), Z_STRLEN_P(ele_value));
+ cur_arr_len++;
+ result = strToMatch(Z_STRVAL_P(ele_value), cur_arr[i]);
if(result == 0) {
intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag", 0);
LOOKUP_CLEAN_RETURN(NULL);
}
- cur_arr[cur_arr_len*2+1] = Z_STRVAL_P(ele_value);
- cur_arr_len++ ;
+ cur_arr[i+1] = Z_STRVAL_P(ele_value);
} ZEND_HASH_FOREACH_END(); /* end of for */
/* Canonicalize array elements */
@@ -1562,6 +1563,15 @@ PHP_FUNCTION(locale_lookup)
}
result_str = lookup_loc_range(loc_range, hash_arr, boolCanonical);
+ if (EG(exception)) {
+ RETURN_THROWS();
+ }
+ if (U_FAILURE(intl_error_get_code(NULL))) {
+ if (result_str) {
+ zend_string_release_ex(result_str, 0);
+ }
+ RETURN_NULL();
+ }
if(result_str == NULL || ZSTR_VAL(result_str)[0] == '\0') {
if( fallback_loc_str ) {
result_str = zend_string_copy(fallback_loc_str);
diff --git a/ext/intl/tests/locale_lookup_invalid_language_tag.phpt b/ext/intl/tests/locale_lookup_invalid_language_tag.phpt
new file mode 100644
index 00000000000..6f7c7517f95
--- /dev/null
+++ b/ext/intl/tests/locale_lookup_invalid_language_tag.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Locale::lookup() returns null for invalid language tags
+--EXTENSIONS--
+intl
+--FILE--
+<?php
+
+var_dump(Locale::lookup([''], 'de-DE', false, 'en-US'));
+var_dump(intl_get_error_message());
+
+var_dump(locale_lookup([''], 'de-DE', false, 'en-US'));
+var_dump(intl_get_error_message());
+
+ini_set('intl.use_exceptions', '1');
+
+try {
+ Locale::lookup([''], 'de-DE', false, 'en-US');
+} catch (IntlException $e) {
+ echo $e->getMessage(), PHP_EOL;
+}
+
+try {
+ locale_lookup([''], 'de-DE', false, 'en-US');
+} catch (IntlException $e) {
+ echo $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+NULL
+string(75) "lookup_loc_range: unable to canonicalize lang_tag: U_ILLEGAL_ARGUMENT_ERROR"
+NULL
+string(75) "lookup_loc_range: unable to canonicalize lang_tag: U_ILLEGAL_ARGUMENT_ERROR"
+lookup_loc_range: unable to canonicalize lang_tag
+lookup_loc_range: unable to canonicalize lang_tag