Skip to content

Commit 54eca7b

Browse files
committed
Charset: Limit _wp_scan_utf8() ASCII scan to remaining code points.
The ASCII fast-path in `_wp_scan_utf8()` uses `strspn()` to skip past ASCII bytes. When a code point limit was provided without a byte limit, the scan would include the rest of the input even when there was a code point limit. Because ASCII characters are single-byte code points, the fast-path scan length can be bounded by the number of remaining code points. This improves performance when working with some large documents. Developed in WordPress#12214. Follow-up to [60768]. Props jonsurrell, dmsnell, zieladam. Fixes #65483. See #63863. git-svn-id: https://develop.svn.wordpress.org/trunk@62523 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 2237e05 commit 54eca7b

2 files changed

Lines changed: 104 additions & 1 deletion

File tree

src/wp-includes/compat-utf8.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ function _wp_scan_utf8( string $bytes, int &$at, int &$invalid_length, ?int $max
6565
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" .
6666
" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f",
6767
$i,
68-
$end - $i
68+
min( $end - $i, $max_count - $count )
6969
);
7070

7171
if ( $count + $ascii_byte_count >= $max_count ) {
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
/**
3+
* Unit tests covering fallback UTF-8 code-point span detection.
4+
*
5+
* @package WordPress
6+
* @subpackage Charset
7+
*
8+
* @group compat
9+
*
10+
* @covers ::_wp_utf8_codepoint_span()
11+
*/
12+
class Tests_Compat_wpUtf8CodePointSpan extends WP_UnitTestCase {
13+
/**
14+
* Ensures that the span accounts for the requested number of code points.
15+
*
16+
* @dataProvider data_codepoint_spans
17+
*
18+
* @ticket 65483
19+
* @ticket 63863
20+
*
21+
* @param string $text
22+
* @param int $byte_offset
23+
* @param int $max_code_points
24+
* @param int $expected_span
25+
* @param int $expected_found
26+
*/
27+
public function test_finds_codepoint_spans( string $text, int $byte_offset, int $max_code_points, int $expected_span, int $expected_found ) {
28+
$found_code_points = null;
29+
30+
$this->assertSame(
31+
$expected_span,
32+
_wp_utf8_codepoint_span( $text, $byte_offset, $max_code_points, $found_code_points ),
33+
'Should have found the expected byte span.'
34+
);
35+
36+
$this->assertSame(
37+
$expected_found,
38+
$found_code_points,
39+
'Should have reported the expected number of code points.'
40+
);
41+
}
42+
43+
/**
44+
* Data provider.
45+
*
46+
* @return array<string, array{0: string, 1: int, 2: int, 3: int, 4: int}>
47+
*/
48+
public static function data_codepoint_spans() {
49+
$long_ascii_run = str_repeat( 'a', 1024 );
50+
51+
return array(
52+
'zero code point budget' => array(
53+
'abcdef',
54+
0,
55+
0,
56+
0,
57+
0,
58+
),
59+
'long ASCII run at start' => array(
60+
$long_ascii_run,
61+
0,
62+
5,
63+
5,
64+
5,
65+
),
66+
'long ASCII run from non-zero offset' => array(
67+
"zz{$long_ascii_run}",
68+
2,
69+
5,
70+
5,
71+
5,
72+
),
73+
'multibyte character before the boundary' => array(
74+
"ab\u{1F170}cd",
75+
0,
76+
2,
77+
2,
78+
2,
79+
),
80+
'multibyte character at the boundary' => array(
81+
"ab\u{1F170}cd",
82+
0,
83+
3,
84+
strlen( "ab\u{1F170}" ),
85+
3,
86+
),
87+
'invalid span after the boundary' => array(
88+
"ab\xF0\x9Fzz",
89+
0,
90+
2,
91+
2,
92+
2,
93+
),
94+
'invalid span at the boundary' => array(
95+
"ab\xF0\x9Fzz",
96+
0,
97+
3,
98+
4,
99+
3,
100+
),
101+
);
102+
}
103+
}

0 commit comments

Comments
 (0)