/src/wireshark/wsutil/unicode-utils.c
Line | Count | Source |
1 | | /* unicode-utils.c |
2 | | * Unicode utility routines |
3 | | * |
4 | | * Wireshark - Network traffic analyzer |
5 | | * By Gerald Combs <gerald@wireshark.org> |
6 | | * Copyright 2006 Gerald Combs |
7 | | * |
8 | | * SPDX-License-Identifier: GPL-2.0-or-later |
9 | | */ |
10 | | |
11 | | #include "config.h" |
12 | | |
13 | | #include "unicode-utils.h" |
14 | | |
15 | | const int ws_utf8_seqlen[256] = { |
16 | | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0x00...0x0f */ |
17 | | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0x10...0x1f */ |
18 | | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0x20...0x2f */ |
19 | | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0x30...0x3f */ |
20 | | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0x40...0x4f */ |
21 | | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0x50...0x5f */ |
22 | | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0x60...0x6f */ |
23 | | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0x70...0x7f */ |
24 | | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x80...0x8f */ |
25 | | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x90...0x9f */ |
26 | | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xa0...0xaf */ |
27 | | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xb0...0xbf */ |
28 | | 0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* 0xc0...0xcf */ |
29 | | 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* 0xd0...0xdf */ |
30 | | 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, /* 0xe0...0xef */ |
31 | | 4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0, /* 0xf0...0xff */ |
32 | | }; |
33 | | |
34 | | /* Given a pointer and a length, validates a string of bytes as UTF-8. |
35 | | * Returns the number of valid bytes, and a pointer immediately past |
36 | | * the checked region. |
37 | | * |
38 | | * Differs from Glib's g_utf8_validate_len in that null bytes are |
39 | | * considered valid UTF-8, and that maximal subparts are replaced as |
40 | | * a unit. (I.e., given a sequence of 2 or 3 bytes which are a |
41 | | * truncated version of a 3 or 4 byte UTF-8 character, but the next |
42 | | * byte does not continue the character, the set of 2 or 3 bytes |
43 | | * are replaced with one REPLACEMENT CHARACTER.) |
44 | | */ |
45 | | static inline size_t |
46 | | utf_8_validate(const uint8_t *start, ssize_t length, const uint8_t **endp) |
47 | 313k | { |
48 | 313k | const uint8_t *ptr = start; |
49 | 313k | uint8_t ch; |
50 | 313k | size_t unichar_len, valid_bytes = 0; |
51 | 313k | const uint8_t *end = start + length; |
52 | | |
53 | 1.09M | while (ptr < end) { |
54 | | |
55 | 1.05M | ch = *ptr; |
56 | | |
57 | 1.05M | if (ch < 0x80) { |
58 | 764k | valid_bytes++; |
59 | 764k | ptr++; |
60 | 764k | continue; |
61 | 764k | } |
62 | | |
63 | 289k | ch = *ptr; |
64 | | |
65 | 289k | if (ch < 0xc2 || ch > 0xf4) { |
66 | 184k | ptr++; |
67 | 184k | *endp = ptr; |
68 | 184k | return valid_bytes; |
69 | 184k | } |
70 | | |
71 | 105k | if (ch < 0xe0) { /* 110xxxxx, 2 byte char */ |
72 | 39.8k | unichar_len = 2; |
73 | 65.8k | } else if (ch < 0xf0) { /* 1110xxxx, 3 byte char */ |
74 | 38.2k | unichar_len = 3; |
75 | 38.2k | ptr++; |
76 | 38.2k | if (ptr == end) { |
77 | 1.32k | *endp = ptr; |
78 | 1.32k | return valid_bytes; |
79 | 1.32k | } |
80 | 36.8k | switch (ch) { |
81 | 4.87k | case 0xe0: |
82 | 4.87k | if (*ptr < 0xa0 || *ptr > 0xbf) { |
83 | 4.27k | *endp = ptr; |
84 | 4.27k | return valid_bytes; |
85 | 4.27k | } |
86 | 607 | break; |
87 | 5.12k | case 0xed: |
88 | 5.12k | if (*ptr < 0x80 || *ptr > 0x9f) { |
89 | 4.55k | *endp = ptr; |
90 | 4.55k | return valid_bytes; |
91 | 4.55k | } |
92 | 574 | break; |
93 | 26.8k | default: |
94 | 26.8k | if (*ptr < 0x80 || *ptr > 0xbf) { |
95 | 20.9k | *endp = ptr; |
96 | 20.9k | return valid_bytes; |
97 | 20.9k | } |
98 | 36.8k | } |
99 | 36.8k | } else { /* 11110xxx, 4 byte char - > 0xf4 excluded above */ |
100 | 27.6k | unichar_len = 4; |
101 | 27.6k | ptr++; |
102 | 27.6k | if (ptr == end) { |
103 | 957 | *endp = ptr; |
104 | 957 | return valid_bytes; |
105 | 957 | } |
106 | 26.6k | switch (ch) { |
107 | 5.89k | case 0xf0: |
108 | 5.89k | if (*ptr < 0x90 || *ptr > 0xbf) { |
109 | 5.00k | *endp = ptr; |
110 | 5.00k | return valid_bytes; |
111 | 5.00k | } |
112 | 884 | break; |
113 | 10.1k | case 0xf4: |
114 | 10.1k | if (*ptr < 0x80 || *ptr > 0x8f) { |
115 | 8.52k | *endp = ptr; |
116 | 8.52k | return valid_bytes; |
117 | 8.52k | } |
118 | 1.64k | break; |
119 | 10.6k | default: |
120 | 10.6k | if (*ptr < 0x80 || *ptr > 0xbf) { |
121 | 7.56k | *endp = ptr; |
122 | 7.56k | return valid_bytes; |
123 | 7.56k | } |
124 | 26.6k | } |
125 | 5.58k | ptr++; |
126 | 5.58k | if (ptr == end) { |
127 | 156 | *endp = ptr; |
128 | 156 | return valid_bytes; |
129 | 156 | } |
130 | 5.42k | if (*ptr < 0x80 || *ptr > 0xbf) { |
131 | 2.73k | *endp = ptr; |
132 | 2.73k | return valid_bytes; |
133 | 2.73k | } |
134 | 5.42k | } |
135 | | |
136 | 49.6k | ptr++; |
137 | 49.6k | if (ptr == end) { |
138 | 1.17k | *endp = ptr; |
139 | 1.17k | return valid_bytes; |
140 | 1.17k | } |
141 | 48.5k | if (*ptr < 0x80 || *ptr > 0xbf) { |
142 | 36.5k | *endp = ptr; |
143 | 36.5k | return valid_bytes; |
144 | 36.5k | } else { |
145 | 12.0k | ptr++; |
146 | 12.0k | valid_bytes += unichar_len; |
147 | 12.0k | } |
148 | | |
149 | 48.5k | } |
150 | 35.9k | *endp = ptr; |
151 | 35.9k | return valid_bytes; |
152 | 313k | } |
153 | | |
154 | | /* |
155 | | * Given a wmem scope, a pointer, and a length, treat the string of bytes |
156 | | * referred to by the pointer and length as a UTF-8 string, and return a |
157 | | * pointer to a UTF-8 string, allocated using the wmem scope, with all |
158 | | * ill-formed sequences replaced with the Unicode REPLACEMENT CHARACTER |
159 | | * according to the recommended "best practices" given in the Unicode |
160 | | * Standard and specified by W3C/WHATWG. |
161 | | * |
162 | | * Note that in conformance with the Unicode Standard, this treats three |
163 | | * byte sequences corresponding to UTF-16 surrogate halves (paired or unpaired) |
164 | | * and two byte overlong encodings of 7-bit ASCII characters as invalid and |
165 | | * substitutes REPLACEMENT CHARACTER for them. Explicit support for nonstandard |
166 | | * derivative encoding formats (e.g. CESU-8, Java Modified UTF-8, WTF-8) could |
167 | | * be added later. |
168 | | * |
169 | | * Compared with g_utf8_make_valid(), this function does not consider |
170 | | * internal NUL bytes as invalid and replace them with replacement characters. |
171 | | * It also replaces maximal subparts as a unit; i.e., a sequence of 2 or 3 |
172 | | * bytes which are a truncated version of a valid 3 or 4 byte character (but |
173 | | * the next byte does not continue the character) are replaced with a single |
174 | | * REPLACEMENT CHARACTER, whereas the Glib function replaces each byte of the |
175 | | * sequence with its own (3 octet) REPLACEMENT CHARACTER. |
176 | | * |
177 | | * XXX: length should probably be a size_t instead of a int in all |
178 | | * these encoding functions |
179 | | * XXX: the buffer returned can be of different length than the input, |
180 | | * and can have internal NULs as well (so that strlen doesn't give its |
181 | | * length). As with the other encoding functions, we should return the |
182 | | * length of the output buffer (or a wmem_strbuf_t directly) and an |
183 | | * indication of whether there was an invalid character (i.e. |
184 | | * REPLACEMENT CHARACTER was used.) |
185 | | */ |
186 | | wmem_strbuf_t * |
187 | | ws_utf8_make_valid_strbuf(wmem_allocator_t *scope, const uint8_t *ptr, ssize_t length) |
188 | 57.5k | { |
189 | 57.5k | wmem_strbuf_t *str; |
190 | | |
191 | 57.5k | str = wmem_strbuf_new_sized(scope, length+1); |
192 | | |
193 | | /* See the Unicode Standard conformance chapter at |
194 | | * https://www.unicode.org/versions/Unicode15.0.0/ch03.pdf especially |
195 | | * Table 3-7 "Well-Formed UTF-8 Byte Sequences" and |
196 | | * U+FFFD Substitution of Maximal Subparts. */ |
197 | | |
198 | 371k | while (length > 0) { |
199 | 313k | const uint8_t *prev = ptr; |
200 | 313k | size_t valid_bytes = utf_8_validate(prev, length, &ptr); |
201 | | |
202 | 313k | if (valid_bytes) { |
203 | 111k | wmem_strbuf_append_len(str, (const char*)prev, valid_bytes); |
204 | 111k | } |
205 | 313k | length -= ptr - prev; |
206 | 313k | prev += valid_bytes; |
207 | 313k | if (ptr - prev) { |
208 | 277k | wmem_strbuf_append_unichar_repl(str); |
209 | 277k | } |
210 | 313k | } |
211 | | |
212 | 57.5k | return str; |
213 | 57.5k | } |
214 | | |
215 | | uint8_t * |
216 | | ws_utf8_make_valid(wmem_allocator_t *scope, const uint8_t *ptr, ssize_t length) |
217 | 57.5k | { |
218 | 57.5k | wmem_strbuf_t *str = ws_utf8_make_valid_strbuf(scope, ptr, length); |
219 | 57.5k | return (uint8_t*)wmem_strbuf_finalize(str); |
220 | 57.5k | } |
221 | | |
222 | | #ifdef _WIN32 |
223 | | |
224 | | #include <strsafe.h> |
225 | | |
226 | | /** @file |
227 | | * Unicode utilities (internal interface) |
228 | | * |
229 | | * We define UNICODE and _UNICODE under Windows. This means that |
230 | | * Windows SDK routines expect UTF-16 strings, in contrast to newer |
231 | | * versions of Glib and GTK+ which expect UTF-8. This module provides |
232 | | * convenience routines for converting between UTF-8 and UTF-16. |
233 | | */ |
234 | | |
235 | | #define INITIAL_UTFBUF_SIZE 128 |
236 | | |
237 | | /* |
238 | | * XXX - Should we use g_utf8_to_utf16() and g_utf16_to_utf8() |
239 | | * instead? The goal of the functions below was to provide simple |
240 | | * wrappers for UTF-8 <-> UTF-16 conversion without making the |
241 | | * caller worry about freeing up memory afterward. |
242 | | */ |
243 | | |
244 | | /* Convert from UTF-8 to UTF-16. */ |
245 | | const wchar_t * |
246 | | utf_8to16(const char *utf8str) |
247 | | { |
248 | | static wchar_t *utf16buf[3]; |
249 | | static int utf16buf_len[3]; |
250 | | static int idx; |
251 | | |
252 | | if (utf8str == NULL) |
253 | | return NULL; |
254 | | |
255 | | idx = (idx + 1) % 3; |
256 | | |
257 | | /* |
258 | | * Allocate the buffer if it's not already allocated. |
259 | | */ |
260 | | if (utf16buf[idx] == NULL) { |
261 | | utf16buf_len[idx] = INITIAL_UTFBUF_SIZE; |
262 | | utf16buf[idx] = g_malloc(utf16buf_len[idx] * sizeof(wchar_t)); |
263 | | } |
264 | | |
265 | | while (MultiByteToWideChar(CP_UTF8, 0, utf8str, -1, NULL, 0) >= utf16buf_len[idx]) { |
266 | | /* |
267 | | * Double the buffer's size if it's not big enough. |
268 | | * The size of the buffer starts at 128, so doubling its size |
269 | | * adds at least another 128 bytes, which is more than enough |
270 | | * for one more character plus a terminating '\0'. |
271 | | */ |
272 | | utf16buf_len[idx] *= 2; |
273 | | utf16buf[idx] = g_realloc(utf16buf[idx], utf16buf_len[idx] * sizeof(wchar_t)); |
274 | | } |
275 | | |
276 | | if (MultiByteToWideChar(CP_UTF8, 0, utf8str, -1, utf16buf[idx], utf16buf_len[idx]) == 0) |
277 | | return NULL; |
278 | | |
279 | | return utf16buf[idx]; |
280 | | } |
281 | | |
282 | | void |
283 | | utf_8to16_snprintf(TCHAR *utf16buf, int utf16buf_len, const char* fmt, ...) |
284 | | { |
285 | | va_list ap; |
286 | | char* dst; |
287 | | |
288 | | va_start(ap,fmt); |
289 | | dst = ws_strdup_vprintf(fmt, ap); |
290 | | va_end(ap); |
291 | | |
292 | | StringCchPrintf(utf16buf, utf16buf_len, _T("%s"), utf_8to16(dst)); |
293 | | |
294 | | g_free(dst); |
295 | | } |
296 | | |
297 | | /* Convert from UTF-16 to UTF-8. */ |
298 | | char * |
299 | | utf_16to8(const wchar_t *utf16str) |
300 | | { |
301 | | static char *utf8buf[3]; |
302 | | static int utf8buf_len[3]; |
303 | | static int idx; |
304 | | |
305 | | if (utf16str == NULL) |
306 | | return NULL; |
307 | | |
308 | | idx = (idx + 1) % 3; |
309 | | |
310 | | /* |
311 | | * Allocate the buffer if it's not already allocated. |
312 | | */ |
313 | | if (utf8buf[idx] == NULL) { |
314 | | utf8buf_len[idx] = INITIAL_UTFBUF_SIZE; |
315 | | utf8buf[idx] = g_malloc(utf8buf_len[idx]); |
316 | | } |
317 | | |
318 | | while (WideCharToMultiByte(CP_UTF8, 0, utf16str, -1, NULL, 0, NULL, NULL) >= utf8buf_len[idx]) { |
319 | | /* |
320 | | * Double the buffer's size if it's not big enough. |
321 | | * The size of the buffer starts at 128, so doubling its size |
322 | | * adds at least another 128 bytes, which is more than enough |
323 | | * for one more character plus a terminating '\0'. |
324 | | */ |
325 | | utf8buf_len[idx] *= 2; |
326 | | utf8buf[idx] = g_realloc(utf8buf[idx], utf8buf_len[idx]); |
327 | | } |
328 | | |
329 | | if (WideCharToMultiByte(CP_UTF8, 0, utf16str, -1, utf8buf[idx], utf8buf_len[idx], NULL, NULL) == 0) |
330 | | return NULL; |
331 | | |
332 | | return utf8buf[idx]; |
333 | | } |
334 | | |
335 | | /* Convert our argument list from UTF-16 to UTF-8. */ |
336 | | char ** |
337 | | arg_list_utf_16to8(int argc, wchar_t *wc_argv[]) { |
338 | | char **argv; |
339 | | int i; |
340 | | |
341 | | argv = (char **)g_malloc((argc + 1) * sizeof(char *)); |
342 | | for (i = 0; i < argc; i++) { |
343 | | argv[i] = g_utf16_to_utf8(wc_argv[i], -1, NULL, NULL, NULL); |
344 | | } |
345 | | argv[argc] = NULL; |
346 | | return argv; |
347 | | } |
348 | | |
349 | | #endif |
350 | | |
351 | | /* |
352 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
353 | | * |
354 | | * Local variables: |
355 | | * c-basic-offset: 4 |
356 | | * tab-width: 8 |
357 | | * indent-tabs-mode: nil |
358 | | * End: |
359 | | * |
360 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
361 | | * :indentSize=4:tabSize=8:noTabs=true: |
362 | | */ |