Coverage Report

Created: 2026-08-14 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
 */