Skip to content

Commit 04b4e47

Browse files
fzipiclaude
andcommitted
refactor: extract the UTF-8 sequence decoder from utf8_unicode_inplace_ex
The two-, three- and four-byte branches repeated the same pattern: check the byte count, check each continuation byte for binary 10, shift in six bits. Move that into utf8_decode_sequence(), which derives the sequence length from the lead byte and loops over the continuation bytes. The NUL special case and the surrogate, range and overlong checks stay in the caller, so the output is unchanged. SonarCloud reported a cognitive complexity of 51 for the function on PR owasp-modsecurity#3685 (accepted there); it is now 17, with 10 for the helper. Verified output-identical to the previous version with a standalone harness under ASan and UBSan: every input of 1 to 3 bytes, every 4-byte input with a high lead byte and a third of the fourth-byte values, and 20 million random 4 to 15 byte inputs biased to high bytes and NUL: 758,263,296 cases, 0 mismatches. tests/tfn/utf8toUnicode.t passes 11/11. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tz1sULBvqAwnRreKh5eC9h
1 parent dc8afb4 commit 04b4e47

1 file changed

Lines changed: 46 additions & 44 deletions

File tree

‎apache2/msc_util.c‎

Lines changed: 46 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,49 @@ int swap_int32(int x) {
103103
*
104104
* \retval rval On Success
105105
*/
106+
/**
107+
* Decodes the multibyte UTF-8 sequence that starts with the lead byte utf[0]
108+
* (a byte with the high bit set). Returns the sequence length (2 to 4) and
109+
* stores the character number in *d, or a negative UNICODE_ERROR_* value.
110+
* Overlong and restricted-range checks are left to the caller.
111+
*/
112+
static int utf8_decode_sequence(const unsigned char *utf, unsigned int bytes_left, unsigned int *d) {
113+
unsigned char c = utf[0];
114+
unsigned int seq_len, k, cp;
115+
116+
/* If first byte begins with binary 110 it is two byte encoding */
117+
if ((c & 0xE0) == 0xC0) {
118+
seq_len = 2;
119+
cp = c & 0x1F;
120+
}
121+
/* If first byte begins with binary 1110 it is three byte encoding */
122+
else if ((c & 0xF0) == 0xE0) {
123+
seq_len = 3;
124+
cp = c & 0x0F;
125+
}
126+
/* If first byte begins with binary 11110 it is four byte encoding */
127+
else if ((c & 0xF8) == 0xF0) {
128+
/* restrict characters to UTF-8 range (U+0000 - U+10FFFF) */
129+
if (c >= 0xF5) return UNICODE_ERROR_RESTRICTED_CHARACTER;
130+
seq_len = 4;
131+
cp = c & 0x07;
132+
}
133+
/* any other first byte is invalid (RFC 3629) */
134+
else return UNICODE_ERROR_INVALID_ENCODING;
135+
136+
/* check we have enough bytes */
137+
if (bytes_left < seq_len) return UNICODE_ERROR_CHARACTERS_MISSING;
138+
139+
/* check the continuation bytes start with binary 10 and compute character number */
140+
for (k = 1; k < seq_len; k++) {
141+
if ((utf[k] & 0xC0) != 0x80) return UNICODE_ERROR_INVALID_ENCODING;
142+
cp = (cp << 6) | (utf[k] & 0x3F);
143+
}
144+
145+
*d = cp;
146+
return seq_len;
147+
}
148+
106149
char *utf8_unicode_inplace_ex(apr_pool_t *mp, unsigned char *input, long int input_len, int *changed) {
107150
int unicode_len = 0;
108151
unsigned int d = 0;
@@ -133,49 +176,8 @@ char *utf8_unicode_inplace_ex(apr_pool_t *mp, unsigned char *input, long int inp
133176
d = (bytes_left >= 2) ? utf[1] : 0;
134177
}
135178
}
136-
/* If first byte begins with binary 110 it is two byte encoding*/
137-
else if ((c & 0xE0) == 0xC0) {
138-
/* check we have at least two bytes */
139-
if (bytes_left < 2) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING;
140-
/* check second byte starts with binary 10 */
141-
else if ((utf[1] & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
142-
else {
143-
unicode_len = 2;
144-
/* compute character number */
145-
d = ((c & 0x1F) << 6) | (utf[1] & 0x3F);
146-
}
147-
}
148-
/* If first byte begins with binary 1110 it is three byte encoding */
149-
else if ((c & 0xF0) == 0xE0) {
150-
/* check we have at least three bytes */
151-
if (bytes_left < 3) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING;
152-
/* check second byte starts with binary 10 */
153-
else if ((utf[1] & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
154-
/* check third byte starts with binary 10 */
155-
else if (((*(utf + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
156-
else {
157-
unicode_len = 3;
158-
/* compute character number */
159-
d = ((c & 0x0F) << 12) | ((utf[1] & 0x3F) << 6) | (*(utf + 2) & 0x3F);
160-
}
161-
}
162-
/* If first byte begins with binary 11110 it is four byte encoding */
163-
else if ((c & 0xF8) == 0xF0) {
164-
/* restrict characters to UTF-8 range (U+0000 - U+10FFFF)*/
165-
if (c >= 0xF5) unicode_len = UNICODE_ERROR_RESTRICTED_CHARACTER;
166-
/* check we have at least four bytes */
167-
else if (bytes_left < 4) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING;
168-
/* check second byte starts with binary 10 */
169-
else if ((utf[1] & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
170-
/* check third byte starts with binary 10 */
171-
else if (((*(utf + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
172-
/* check forth byte starts with binary 10 */
173-
else if (((*(utf + 3)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
174-
else {
175-
unicode_len = 4;
176-
/* compute character number */
177-
d = ((c & 0x07) << 18) | ((utf[1] & 0x3F) << 12) | ((*(utf + 2) & 0x3F) << 6) | (*(utf + 3) & 0x3F);
178-
}
179+
else {
180+
unicode_len = utf8_decode_sequence(utf, bytes_left, &d);
179181
}
180182
/* invalid UTF-8 character number range (RFC 3629) */
181183
if ((d >= 0xD800) && (d <= 0xDFFF)) unicode_len = UNICODE_ERROR_RESTRICTED_CHARACTER;
@@ -195,7 +197,7 @@ char *utf8_unicode_inplace_ex(apr_pool_t *mp, unsigned char *input, long int inp
195197
*changed = 1;
196198
}
197199
else {
198-
/* any other first byte is invalid (RFC 3629), so assume it's an ASCII character */
200+
/* not a valid UTF-8 sequence, so pass the byte through unchanged */
199201
*data++ = c;
200202
i++;
201203
}

0 commit comments

Comments
 (0)