Commit 872c6e19c42 for php
commit 872c6e19c42986e6488561930aefb01cd1dde6d6
Author: lazerg <lazerg2@gmail.com>
Date: Thu Oct 22 21:20:03 2026 +0500
Fix GH-24552: Use ASCII case folding in string comparisons (#24558)
PHP 8.2 made case-insensitive comparisons ASCII-only, but ksort() and
krsort() with SORT_STRING | SORT_FLAG_CASE and substr_compare() with
case_insensitive=true still used the locale-sensitive _l variants.
These call tolower() and depend on LC_CTYPE.
Use zend_binary_strcasecmp() and zend_binary_strncasecmp(), consistent
with sort() and strncasecmp(), so these comparisons also use ASCII case
folding regardless of the current locale.
Fixes #24552
Co-authored-by: Jon Surrell <sirreal@users.noreply.github.com>
Closes #24558
diff --git a/NEWS b/NEWS
index 01556b62951..4c27156901d 100644
--- a/NEWS
+++ b/NEWS
@@ -190,6 +190,8 @@ PHP NEWS
(Nicolas Grekas)
. Fixed bug GH-24373 (serialize() called from a destructor during var_hash
destruction reuses the outer var_hash). (David Carlier)
+ . Fixed bug GH-24552 (Case-insensitive substr_compare(), ksort() and
+ krsort() are still locale-dependent). (Lazizbek Ergashev, Jon Surrell)
. Fixed bug GH-24605 (var_export() crashes on deeply nested arrays and
objects). (Labish Bardiya)
diff --git a/ext/standard/array.c b/ext/standard/array.c
index 8454e9616c0..82c3f0a7aa6 100644
--- a/ext/standard/array.c
+++ b/ext/standard/array.c
@@ -185,7 +185,7 @@ static zend_always_inline int php_array_key_compare_string_case_unstable_i(Bucke
s2 = zend_print_long_to_buf(buf2 + sizeof(buf2) - 1, s->h);
l2 = buf2 + sizeof(buf2) - 1 - s2;
}
- return zend_binary_strcasecmp_l(s1, l1, s2, l2);
+ return zend_binary_strcasecmp(s1, l1, s2, l2);
}
/* }}} */
diff --git a/ext/standard/string.c b/ext/standard/string.c
index 9f8e45af329..8b0f8ec572b 100644
--- a/ext/standard/string.c
+++ b/ext/standard/string.c
@@ -6352,7 +6352,7 @@ PHP_FUNCTION(substr_compare)
if (!cs) {
RETURN_LONG(zend_binary_strncmp(ZSTR_VAL(s1) + offset, (ZSTR_LEN(s1) - offset), ZSTR_VAL(s2), ZSTR_LEN(s2), cmp_len));
} else {
- RETURN_LONG(zend_binary_strncasecmp_l(ZSTR_VAL(s1) + offset, (ZSTR_LEN(s1) - offset), ZSTR_VAL(s2), ZSTR_LEN(s2), cmp_len));
+ RETURN_LONG(zend_binary_strncasecmp(ZSTR_VAL(s1) + offset, (ZSTR_LEN(s1) - offset), ZSTR_VAL(s2), ZSTR_LEN(s2), cmp_len));
}
}
/* }}} */
diff --git a/ext/standard/tests/strings/gh24552.phpt b/ext/standard/tests/strings/gh24552.phpt
new file mode 100644
index 00000000000..97d350e2096
--- /dev/null
+++ b/ext/standard/tests/strings/gh24552.phpt
@@ -0,0 +1,38 @@
+--TEST--
+GH-24552 (Case-insensitive substr_compare, ksort, krsort are still locale-dependent)
+--SKIPIF--
+<?php
+if (setlocale(LC_CTYPE, "de_DE.ISO8859-1", "de_DE") === false) {
+ die("skip de_DE locale not available");
+}
+?>
+--FILE--
+<?php
+setlocale(LC_CTYPE, "de_DE.ISO8859-1", "de_DE");
+
+var_dump(substr_compare("\xC4", "\xE4", 0, 1, true));
+var_dump(strncasecmp("\xC4", "\xE4", 1));
+
+$keys = ["\xE4" => 0, "\xC4" => 0];
+ksort($keys, SORT_STRING | SORT_FLAG_CASE);
+var_dump(array_map('bin2hex', array_keys($keys)));
+
+$keys = ["\xC4" => 0, "\xE4" => 0];
+krsort($keys, SORT_STRING | SORT_FLAG_CASE);
+var_dump(array_map('bin2hex', array_keys($keys)));
+?>
+--EXPECT--
+int(-32)
+int(-32)
+array(2) {
+ [0]=>
+ string(2) "c4"
+ [1]=>
+ string(2) "e4"
+}
+array(2) {
+ [0]=>
+ string(2) "e4"
+ [1]=>
+ string(2) "c4"
+}