Skip to content

Commit 0d6ab53

Browse files
eyupcanakmanalexdowad
authored andcommitted
Fix GH-22779: mb_strrpos() wrong result in a non-UTF-8 encoding
mb_find_strpos() converts the haystack and needle to UTF-8 and does the length and offset math on the converted strings. The negative-offset branch computed the needle length from the raw needle instead of needle_u8. mb_fast_strlen_utf8() reads those bytes as UTF-8, so the count is wrong whenever they do not decode one to one. A byte in 0x80-0xBF (ISO-8859-1) undercounts, and a wider encoding such as an ASCII needle in UTF-16 overcounts, so the reverse-search window is shifted. Use needle_u8, the converted needle the rest of the branch already searches with.
1 parent 3221a07 commit 0d6ab53

3 files changed

Lines changed: 39 additions & 1 deletion

File tree

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ PHP NEWS
1212
attribute node that collides by local name with a namespaced
1313
attribute). (David Carlier)
1414

15+
- MBString:
16+
. Fixed bug GH-22779 (mb_strrpos() returns the wrong position for a negative
17+
offset in a non-UTF-8 encoding). (Eyüp Can Akman)
18+
1519
- Sockets:
1620
. Fixed various memory related issues in ext/sockets. (David Carlier)
1721

ext/mbstring/mbstring.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1931,7 +1931,7 @@ static size_t mb_find_strpos(zend_string *haystack, zend_string *needle, const m
19311931
} else if (offset >= 0) {
19321932
found_pos = zend_memnrstr((const char*)offset_pointer, ZSTR_VAL(needle_u8), ZSTR_LEN(needle_u8), ZSTR_VAL(haystack_u8) + ZSTR_LEN(haystack_u8));
19331933
} else {
1934-
size_t needle_len = pointer_to_offset_utf8((unsigned char*)ZSTR_VAL(needle), (unsigned char*)ZSTR_VAL(needle) + ZSTR_LEN(needle));
1934+
size_t needle_len = pointer_to_offset_utf8((unsigned char*)ZSTR_VAL(needle_u8), (unsigned char*)ZSTR_VAL(needle_u8) + ZSTR_LEN(needle_u8));
19351935
offset_pointer = offset_to_pointer_utf8(offset_pointer, (unsigned char*)ZSTR_VAL(haystack_u8) + ZSTR_LEN(haystack_u8), needle_len);
19361936
if (!offset_pointer) {
19371937
offset_pointer = (unsigned char*)ZSTR_VAL(haystack_u8) + ZSTR_LEN(haystack_u8);

ext/mbstring/tests/gh22779.phpt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
GH-22779: mb_strrpos() wrong result for a negative offset in a non-UTF-8 encoding
3+
--EXTENSIONS--
4+
mbstring
5+
--FILE--
6+
<?php
7+
/* mb_strrpos() with a negative offset must return the correct position in non-UTF-8 encodings. */
8+
$haystack = "\xA9\xA9X";
9+
$needle = "\xA9";
10+
foreach ([-1, -2, -3] as $offset) {
11+
var_dump(mb_strrpos($haystack, $needle, $offset, 'ISO-8859-1'));
12+
}
13+
var_dump(mb_strrpos("X\xA9", "\xA9", -1, 'ISO-8859-1'));
14+
var_dump(mb_strrpos("\x95Z\x95Z", "\x95", -1, 'Windows-1252'));
15+
var_dump(mb_strrpos("\x95Z\x95Z", "\x95", -2, 'Windows-1252'));
16+
// Two-byte needle in a single-byte encoding.
17+
var_dump(mb_strrpos("\xA9\xB0\xA9\xB0Z", "\xA9\xB0", -2, 'ISO-8859-1'));
18+
// Multibyte: "ああA" in Shift_JIS, needle "あ" (\x82\xA0).
19+
var_dump(mb_strrpos("\x82\xA0\x82\xA0A", "\x82\xA0", -2, 'SJIS'));
20+
var_dump(mb_strrpos("\x82\xA0\x82\xA0A", "\x82\xA0", -3, 'SJIS'));
21+
// UTF-16: an ASCII needle miscounts the other way.
22+
var_dump(mb_strrpos("\x00A\x00X\x00A", "\x00A", -2, 'UTF-16BE'));
23+
?>
24+
--EXPECT--
25+
int(1)
26+
int(1)
27+
int(0)
28+
int(1)
29+
int(2)
30+
int(2)
31+
int(2)
32+
int(1)
33+
int(0)
34+
int(0)

0 commit comments

Comments
 (0)