Skip to content

Commit 153070b

Browse files
fzipiclaude
andcommitted
fix: t:utf8toUnicode drops the last hex digit of code points above U+FFFF
Since the refactor in #3284 (2.9.9) the output pointer always advanced by six bytes after sprintf("%%u%04x"), so a code point that needs five hex digits (anything above U+FFFF) lost its last digit to the next write or to the terminating NUL: U+1F600 came out as "%u1f60". Advance by the number of bytes sprintf actually wrote. While there, fix two adjacent checks in the same function: bytes_left was never decremented, so the "enough bytes left" guards compared the total length instead of the remaining length and a truncated sequence at the end of the value was examined past input_len; and an 0xF4 lead byte with a second byte >= 0x90 (U+110000 and above, outside Unicode) was accepted as a valid four-byte sequence. Add four-byte, above-U+10FFFF and truncated-sequence cases to the utf8toUnicode unit tests, which had no four-byte input at all. Fixes #3684 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cq9GqRpB4wzTFnEUvQaUJg
1 parent 151074a commit 153070b

2 files changed

Lines changed: 39 additions & 4 deletions

File tree

‎apache2/msc_util.c‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ char *utf8_unicode_inplace_ex(apr_pool_t *mp, unsigned char *input, long int inp
120120
data = rval = apr_palloc(mp, len);
121121
if (rval == NULL) return NULL;
122122

123-
for (i = 0; i < bytes_left;) {
123+
for (i = 0; i < input_len;) {
124+
bytes_left = input_len - i;
124125
unicode_len = 0; d = 0;
125126
utf = (unsigned char *)&input[i];
126127
c = *utf;
@@ -129,7 +130,7 @@ char *utf8_unicode_inplace_ex(apr_pool_t *mp, unsigned char *input, long int inp
129130
if ((c & 0x80) == 0) {
130131
if (c == 0) {
131132
unicode_len = 2;
132-
d = utf[1];
133+
d = (bytes_left >= 2) ? utf[1] : 0;
133134
}
134135
}
135136
/* If first byte begins with binary 110 it is two byte encoding*/
@@ -178,6 +179,8 @@ char *utf8_unicode_inplace_ex(apr_pool_t *mp, unsigned char *input, long int inp
178179
}
179180
/* invalid UTF-8 character number range (RFC 3629) */
180181
if ((d >= 0xD800) && (d <= 0xDFFF)) unicode_len = UNICODE_ERROR_RESTRICTED_CHARACTER;
182+
/* restrict characters to UTF-8 range (U+0000 - U+10FFFF) */
183+
if ((unicode_len == 4) && (d > 0x10FFFF)) unicode_len = UNICODE_ERROR_RESTRICTED_CHARACTER;
181184
/* check for overlong */
182185
if ((unicode_len == 4) && (d < 0x010000)) unicode_len = UNICODE_ERROR_OVERLONG_CHARACTER;
183186
/* three byte could be represented with less bytes */
@@ -187,8 +190,8 @@ char *utf8_unicode_inplace_ex(apr_pool_t *mp, unsigned char *input, long int inp
187190

188191
if (unicode_len > 0) {
189192
i += unicode_len;
190-
sprintf(data, "%%u%04x", d);
191-
data += 6;
193+
/* code points above U+FFFF need five hex digits */
194+
data += sprintf(data, "%%u%04x", d);
192195
*changed = 1;
193196
}
194197
else {

‎tests/tfn/utf8toUnicode.t‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,35 @@
5454
output => " %u27f0 %u27f1 %u27f2 %u27f3 %u27f4 %u27f5 %u27f6 %u27f7 %u27f8 %u27f9 %u27fa %u27fb %u27fc %u27fd %u27fe %u27ff",
5555
ret => 1,
5656
},
57+
# Supplementary planes (four-byte sequences, five hex digits)
58+
{
59+
type => "tfn",
60+
name => "utf8toUnicode",
61+
input => "😀 𝒜 𠀀 􏿿",
62+
output => "%u1f600 %u1d49c %u20000 %u10ffff",
63+
ret => 1,
64+
},
65+
# Four-byte sequence followed by ASCII must not lose its last digit
66+
{
67+
type => "tfn",
68+
name => "utf8toUnicode",
69+
input => "😀A",
70+
output => "%u1f600A",
71+
ret => 1,
72+
},
73+
# Code point above U+10FFFF is not valid UTF-8 and is passed through
74+
{
75+
type => "tfn",
76+
name => "utf8toUnicode",
77+
input => "\xf4\x90\x80\x80",
78+
output => "\xf4\x90\x80\x80",
79+
ret => 0,
80+
},
81+
# Truncated sequence at the end of the input is passed through
82+
{
83+
type => "tfn",
84+
name => "utf8toUnicode",
85+
input => "a\xe2\x82",
86+
output => "a\xe2\x82",
87+
ret => 0,
88+
},

0 commit comments

Comments
 (0)