Coverage Report

Created: 2026-10-28 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/utf8.c
Line
Count
Source
1
/* $OpenBSD: utf8.c,v 1.76 2026/10/23 06:30:50 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include <sys/types.h>
20
21
#include <ctype.h>
22
#include <errno.h>
23
#include <stdlib.h>
24
#include <string.h>
25
#include <wchar.h>
26
27
#include "compat.h"
28
#include "tmux.h"
29
30
struct utf8_width_item {
31
  wchar_t       wc;
32
  u_int       width;
33
  int       allocated;
34
35
  RB_ENTRY(utf8_width_item) entry;
36
};
37
38
static int
39
utf8_width_cache_cmp(struct utf8_width_item *uw1, struct utf8_width_item *uw2)
40
0
{
41
0
  if (uw1->wc < uw2->wc)
42
0
    return (-1);
43
0
  if (uw1->wc > uw2->wc)
44
0
    return (1);
45
0
  return (0);
46
0
}
47
RB_HEAD(utf8_width_cache, utf8_width_item);
48
0
RB_GENERATE_STATIC(utf8_width_cache, utf8_width_item, entry,
Unexecuted instantiation: utf8.c:utf8_width_cache_RB_MINMAX
Unexecuted instantiation: utf8.c:utf8_width_cache_RB_REMOVE
Unexecuted instantiation: utf8.c:utf8_width_cache_RB_REMOVE_COLOR
Unexecuted instantiation: utf8.c:utf8_width_cache_RB_INSERT
Unexecuted instantiation: utf8.c:utf8_width_cache_RB_FIND
49
0
    utf8_width_cache_cmp);
50
0
static struct utf8_width_cache utf8_width_cache =
51
0
    RB_INITIALIZER(utf8_width_cache);
52
0
53
0
static struct utf8_width_item utf8_default_width_cache[] = {
54
0
  { .wc = 0x0270A, .width = 2 },
55
0
  { .wc = 0x0270B, .width = 2 },
56
0
  { .wc = 0x1F1E6, .width = 1 },
57
0
  { .wc = 0x1F1E7, .width = 1 },
58
0
  { .wc = 0x1F1E8, .width = 1 },
59
0
  { .wc = 0x1F1E9, .width = 1 },
60
0
  { .wc = 0x1F1EA, .width = 1 },
61
0
  { .wc = 0x1F1EB, .width = 1 },
62
0
  { .wc = 0x1F1EC, .width = 1 },
63
0
  { .wc = 0x1F1ED, .width = 1 },
64
0
  { .wc = 0x1F1EE, .width = 1 },
65
0
  { .wc = 0x1F1EF, .width = 1 },
66
0
  { .wc = 0x1F1F0, .width = 1 },
67
0
  { .wc = 0x1F1F1, .width = 1 },
68
0
  { .wc = 0x1F1F2, .width = 1 },
69
0
  { .wc = 0x1F1F3, .width = 1 },
70
0
  { .wc = 0x1F1F4, .width = 1 },
71
0
  { .wc = 0x1F1F5, .width = 1 },
72
0
  { .wc = 0x1F1F6, .width = 1 },
73
0
  { .wc = 0x1F1F7, .width = 1 },
74
0
  { .wc = 0x1F1F8, .width = 1 },
75
0
  { .wc = 0x1F1F9, .width = 1 },
76
0
  { .wc = 0x1F1FA, .width = 1 },
77
0
  { .wc = 0x1F1FB, .width = 1 },
78
0
  { .wc = 0x1F1FC, .width = 1 },
79
0
  { .wc = 0x1F1FD, .width = 1 },
80
0
  { .wc = 0x1F1FE, .width = 1 },
81
0
  { .wc = 0x1F1FF, .width = 1 },
82
0
  { .wc = 0x1F385, .width = 2 },
83
0
  { .wc = 0x1F3C2, .width = 2 },
84
0
  { .wc = 0x1F3C3, .width = 2 },
85
0
  { .wc = 0x1F3C4, .width = 2 },
86
0
  { .wc = 0x1F3C7, .width = 2 },
87
0
  { .wc = 0x1F3CA, .width = 2 },
88
0
  { .wc = 0x1F3CB, .width = 2 },
89
0
  { .wc = 0x1F3CC, .width = 2 },
90
0
  { .wc = 0x1F3FB, .width = 2 },
91
0
  { .wc = 0x1F3FC, .width = 2 },
92
0
  { .wc = 0x1F3FD, .width = 2 },
93
0
  { .wc = 0x1F3FE, .width = 2 },
94
0
  { .wc = 0x1F3FF, .width = 2 },
95
0
  { .wc = 0x1F442, .width = 2 },
96
0
  { .wc = 0x1F443, .width = 2 },
97
0
  { .wc = 0x1F446, .width = 2 },
98
0
  { .wc = 0x1F447, .width = 2 },
99
0
  { .wc = 0x1F448, .width = 2 },
100
0
  { .wc = 0x1F449, .width = 2 },
101
0
  { .wc = 0x1F44A, .width = 2 },
102
0
  { .wc = 0x1F44B, .width = 2 },
103
0
  { .wc = 0x1F44C, .width = 2 },
104
0
  { .wc = 0x1F44D, .width = 2 },
105
0
  { .wc = 0x1F44E, .width = 2 },
106
0
  { .wc = 0x1F44F, .width = 2 },
107
0
  { .wc = 0x1F450, .width = 2 },
108
0
  { .wc = 0x1F466, .width = 2 },
109
0
  { .wc = 0x1F467, .width = 2 },
110
0
  { .wc = 0x1F468, .width = 2 },
111
0
  { .wc = 0x1F469, .width = 2 },
112
0
  { .wc = 0x1F46B, .width = 2 },
113
0
  { .wc = 0x1F46C, .width = 2 },
114
0
  { .wc = 0x1F46D, .width = 2 },
115
0
  { .wc = 0x1F46E, .width = 2 },
116
0
  { .wc = 0x1F470, .width = 2 },
117
0
  { .wc = 0x1F471, .width = 2 },
118
0
  { .wc = 0x1F472, .width = 2 },
119
0
  { .wc = 0x1F473, .width = 2 },
120
0
  { .wc = 0x1F474, .width = 2 },
121
0
  { .wc = 0x1F475, .width = 2 },
122
0
  { .wc = 0x1F476, .width = 2 },
123
0
  { .wc = 0x1F477, .width = 2 },
124
0
  { .wc = 0x1F478, .width = 2 },
125
0
  { .wc = 0x1F47C, .width = 2 },
126
0
  { .wc = 0x1F481, .width = 2 },
127
0
  { .wc = 0x1F482, .width = 2 },
128
0
  { .wc = 0x1F483, .width = 2 },
129
0
  { .wc = 0x1F485, .width = 2 },
130
0
  { .wc = 0x1F486, .width = 2 },
131
0
  { .wc = 0x1F487, .width = 2 },
132
0
  { .wc = 0x1F48F, .width = 2 },
133
0
  { .wc = 0x1F491, .width = 2 },
134
0
  { .wc = 0x1F4AA, .width = 2 },
135
0
  { .wc = 0x1F574, .width = 2 },
136
0
  { .wc = 0x1F575, .width = 2 },
137
0
  { .wc = 0x1F57A, .width = 2 },
138
0
  { .wc = 0x1F590, .width = 2 },
139
0
  { .wc = 0x1F595, .width = 2 },
140
0
  { .wc = 0x1F596, .width = 2 },
141
0
  { .wc = 0x1F645, .width = 2 },
142
0
  { .wc = 0x1F646, .width = 2 },
143
0
  { .wc = 0x1F647, .width = 2 },
144
0
  { .wc = 0x1F64B, .width = 2 },
145
0
  { .wc = 0x1F64C, .width = 2 },
146
0
  { .wc = 0x1F64D, .width = 2 },
147
0
  { .wc = 0x1F64E, .width = 2 },
148
0
  { .wc = 0x1F64F, .width = 2 },
149
0
  { .wc = 0x1F6A3, .width = 2 },
150
0
  { .wc = 0x1F6B4, .width = 2 },
151
0
  { .wc = 0x1F6B5, .width = 2 },
152
0
  { .wc = 0x1F6B6, .width = 2 },
153
0
  { .wc = 0x1F6C0, .width = 2 },
154
0
  { .wc = 0x1F6CC, .width = 2 },
155
0
  { .wc = 0x1F90C, .width = 2 },
156
0
  { .wc = 0x1F90F, .width = 2 },
157
0
  { .wc = 0x1F918, .width = 2 },
158
0
  { .wc = 0x1F919, .width = 2 },
159
0
  { .wc = 0x1F91A, .width = 2 },
160
0
  { .wc = 0x1F91B, .width = 2 },
161
0
  { .wc = 0x1F91C, .width = 2 },
162
0
  { .wc = 0x1F91D, .width = 2 },
163
0
  { .wc = 0x1F91E, .width = 2 },
164
0
  { .wc = 0x1F91F, .width = 2 },
165
0
  { .wc = 0x1F926, .width = 2 },
166
0
  { .wc = 0x1F930, .width = 2 },
167
0
  { .wc = 0x1F931, .width = 2 },
168
0
  { .wc = 0x1F932, .width = 2 },
169
0
  { .wc = 0x1F933, .width = 2 },
170
0
  { .wc = 0x1F934, .width = 2 },
171
0
  { .wc = 0x1F935, .width = 2 },
172
0
  { .wc = 0x1F936, .width = 2 },
173
0
  { .wc = 0x1F937, .width = 2 },
174
0
  { .wc = 0x1F938, .width = 2 },
175
0
  { .wc = 0x1F939, .width = 2 },
176
0
  { .wc = 0x1F93D, .width = 2 },
177
0
  { .wc = 0x1F93E, .width = 2 },
178
0
  { .wc = 0x1F977, .width = 2 },
179
0
  { .wc = 0x1F9B5, .width = 2 },
180
0
  { .wc = 0x1F9B6, .width = 2 },
181
0
  { .wc = 0x1F9B8, .width = 2 },
182
0
  { .wc = 0x1F9B9, .width = 2 },
183
0
  { .wc = 0x1F9BB, .width = 2 },
184
0
  { .wc = 0x1F9CD, .width = 2 },
185
0
  { .wc = 0x1F9CE, .width = 2 },
186
0
  { .wc = 0x1F9CF, .width = 2 },
187
0
  { .wc = 0x1F9D1, .width = 2 },
188
0
  { .wc = 0x1F9D2, .width = 2 },
189
0
  { .wc = 0x1F9D3, .width = 2 },
190
0
  { .wc = 0x1F9D4, .width = 2 },
191
0
  { .wc = 0x1F9D5, .width = 2 },
192
0
  { .wc = 0x1F9D6, .width = 2 },
193
0
  { .wc = 0x1F9D7, .width = 2 },
194
0
  { .wc = 0x1F9D8, .width = 2 },
195
0
  { .wc = 0x1F9D9, .width = 2 },
196
0
  { .wc = 0x1F9DA, .width = 2 },
197
0
  { .wc = 0x1F9DB, .width = 2 },
198
0
  { .wc = 0x1F9DC, .width = 2 },
199
0
  { .wc = 0x1F9DD, .width = 2 },
200
0
  { .wc = 0x1FAC3, .width = 2 },
201
0
  { .wc = 0x1FAC4, .width = 2 },
202
0
  { .wc = 0x1FAC5, .width = 2 },
203
0
  { .wc = 0x1FAF0, .width = 2 },
204
0
  { .wc = 0x1FAF1, .width = 2 },
205
0
  { .wc = 0x1FAF2, .width = 2 },
206
0
  { .wc = 0x1FAF3, .width = 2 },
207
0
  { .wc = 0x1FAF4, .width = 2 },
208
0
  { .wc = 0x1FAF5, .width = 2 },
209
0
  { .wc = 0x1FAF6, .width = 2 },
210
0
  { .wc = 0x1FAF7, .width = 2 },
211
0
  { .wc = 0x1FAF8, .width = 2 }
212
0
};
213
0
214
0
struct utf8_item {
215
0
  RB_ENTRY(utf8_item) index_entry;
216
0
  u_int     index;
217
0
218
0
  RB_ENTRY(utf8_item) data_entry;
219
0
  char      data[UTF8_SIZE];
220
0
  u_char      size;
221
0
};
222
0
223
0
static int
224
0
utf8_data_cmp(struct utf8_item *ui1, struct utf8_item *ui2)
225
0
{
226
0
  if (ui1->size < ui2->size)
227
0
    return (-1);
228
0
  if (ui1->size > ui2->size)
229
0
    return (1);
230
0
  return (memcmp(ui1->data, ui2->data, ui1->size));
231
0
}
232
RB_HEAD(utf8_data_tree, utf8_item);
233
0
RB_GENERATE_STATIC(utf8_data_tree, utf8_item, data_entry, utf8_data_cmp);
Unexecuted instantiation: utf8.c:utf8_data_tree_RB_FIND
Unexecuted instantiation: utf8.c:utf8_data_tree_RB_INSERT
234
0
static struct utf8_data_tree utf8_data_tree = RB_INITIALIZER(utf8_data_tree);
235
0
236
0
static int
237
0
utf8_index_cmp(struct utf8_item *ui1, struct utf8_item *ui2)
238
0
{
239
0
  if (ui1->index < ui2->index)
240
0
    return (-1);
241
0
  if (ui1->index > ui2->index)
242
0
    return (1);
243
0
  return (0);
244
0
}
245
RB_HEAD(utf8_index_tree, utf8_item);
246
0
RB_GENERATE_STATIC(utf8_index_tree, utf8_item, index_entry, utf8_index_cmp);
Unexecuted instantiation: utf8.c:utf8_index_tree_RB_INSERT
Unexecuted instantiation: utf8.c:utf8_index_tree_RB_FIND
247
0
static struct utf8_index_tree utf8_index_tree = RB_INITIALIZER(utf8_index_tree);
248
0
249
0
static int  utf8_no_width;
250
0
static u_int  utf8_next_index;
251
0
252
58.3k
#define UTF8_GET_SIZE(uc) (((uc) >> 24) & 0x1f)
253
58.3k
#define UTF8_GET_WIDTH(uc) (((uc) >> 29) - 1)
254
255
102k
#define UTF8_SET_SIZE(size) (((utf8_char)(size)) << 24)
256
102k
#define UTF8_SET_WIDTH(width) ((((utf8_char)(width)) + 1) << 29)
257
258
/* Get a UTF-8 item from data. */
259
static struct utf8_item *
260
utf8_item_by_data(const u_char *data, size_t size)
261
0
{
262
0
  struct utf8_item  ui;
263
264
0
  memcpy(ui.data, data, size);
265
0
  ui.size = size;
266
267
0
  return (RB_FIND(utf8_data_tree, &utf8_data_tree, &ui));
268
0
}
269
270
/* Get a UTF-8 item from data. */
271
static struct utf8_item *
272
utf8_item_by_index(u_int index)
273
0
{
274
0
  struct utf8_item  ui;
275
276
0
  ui.index = index;
277
278
0
  return (RB_FIND(utf8_index_tree, &utf8_index_tree, &ui));
279
0
}
280
281
/* Find a codepoint in the cache. */
282
static struct utf8_width_item *
283
utf8_find_in_width_cache(wchar_t wc)
284
0
{
285
0
  struct utf8_width_item  uw;
286
287
0
  uw.wc = wc;
288
0
  return RB_FIND(utf8_width_cache, &utf8_width_cache, &uw);
289
0
}
290
291
/* Add to width cache. */
292
static void
293
utf8_insert_width_cache(wchar_t wc, u_int width)
294
0
{
295
0
  struct utf8_width_item  *uw, *old;
296
297
0
  log_debug("Unicode width cache: %08X=%u", (u_int)wc, width);
298
299
0
  uw = xcalloc(1, sizeof *uw);
300
0
  uw->wc = wc;
301
0
  uw->width = width;
302
0
  uw->allocated = 1;
303
304
0
  old = RB_INSERT(utf8_width_cache, &utf8_width_cache, uw);
305
0
  if (old != NULL) {
306
0
    RB_REMOVE(utf8_width_cache, &utf8_width_cache, old);
307
0
    if (old->allocated)
308
0
      free(old);
309
0
    RB_INSERT(utf8_width_cache, &utf8_width_cache, uw);
310
0
  }
311
0
}
312
313
/* Parse a single codepoint option. */
314
static void
315
utf8_add_to_width_cache(const char *s)
316
0
{
317
0
  char      *copy, *cp, *endptr;
318
0
  u_int      width;
319
0
  const char    *errstr;
320
0
  struct utf8_data  *ud;
321
0
  wchar_t      wc, wc_start, wc_end;
322
0
  unsigned long long   n;
323
324
0
  copy = xstrdup(s);
325
0
  if ((cp = strchr(copy, '=')) == NULL) {
326
0
    free(copy);
327
0
    return;
328
0
  }
329
0
  *cp++ = '\0';
330
331
0
  width = strtonum(cp, 0, 2, &errstr);
332
0
  if (errstr != NULL) {
333
0
    free(copy);
334
0
    return;
335
0
  }
336
337
0
  if (strncmp(copy, "U+", 2) == 0) {
338
0
    errno = 0;
339
0
    n = strtoull(copy + 2, &endptr, 16);
340
0
    if (copy[2] == '\0' ||
341
0
        n == 0 ||
342
0
        n > WCHAR_MAX ||
343
0
        (errno == ERANGE && n == ULLONG_MAX)) {
344
0
      free(copy);
345
0
      return;
346
0
    }
347
0
    wc_start = n;
348
0
    if (*endptr == '-') {
349
0
      endptr++;
350
0
      if (strncmp(endptr, "U+", 2) != 0) {
351
0
        free(copy);
352
0
        return;
353
0
      }
354
0
      errno = 0;
355
0
      n = strtoull(endptr + 2, &endptr, 16);
356
0
      if (*endptr != '\0' ||
357
0
          n == 0 ||
358
0
          n > WCHAR_MAX ||
359
0
          (errno == ERANGE && n == ULLONG_MAX) ||
360
0
          (wchar_t)n < wc_start) {
361
0
        free(copy);
362
0
        return;
363
0
      }
364
0
      wc_end = n;
365
0
    } else {
366
0
      if (*endptr != '\0') {
367
0
        free(copy);
368
0
        return;
369
0
      }
370
0
      wc_end = wc_start;
371
0
    }
372
373
0
    wc = wc_start;
374
0
    for (;;) {
375
0
      utf8_insert_width_cache(wc, width);
376
0
      if (wc == wc_end)
377
0
        break;
378
0
      wc++;
379
0
    }
380
0
  } else {
381
0
    utf8_no_width = 1;
382
0
    ud = utf8_fromcstr(copy);
383
0
    utf8_no_width = 0;
384
0
    if (ud[0].size == 0 || ud[1].size != 0) {
385
0
      free(ud);
386
0
      free(copy);
387
0
      return;
388
0
    }
389
#ifdef HAVE_UTF8PROC
390
    if (utf8proc_mbtowc(&wc, ud[0].data, ud[0].size) <= 0) {
391
#else
392
0
    if (mbtowc(&wc, ud[0].data, ud[0].size) <= 0) {
393
0
#endif
394
0
      free(ud);
395
0
      free(copy);
396
0
      return;
397
0
    }
398
0
    free(ud);
399
400
0
    utf8_insert_width_cache(wc, width);
401
0
  }
402
403
0
  free(copy);
404
0
}
405
406
/* Rebuild cache of widths. */
407
void
408
utf8_update_width_cache(void)
409
0
{
410
0
  struct utf8_width_item    *uw, *uw1;
411
0
  struct options_entry    *o;
412
0
  struct options_array_item *a;
413
0
  u_int        i;
414
415
0
  RB_FOREACH_SAFE (uw, utf8_width_cache, &utf8_width_cache, uw1) {
416
0
    RB_REMOVE(utf8_width_cache, &utf8_width_cache, uw);
417
0
    if (uw->allocated)
418
0
      free(uw);
419
0
  }
420
421
0
  for (i = 0; i < nitems(utf8_default_width_cache); i++) {
422
0
    RB_INSERT(utf8_width_cache, &utf8_width_cache,
423
0
        &utf8_default_width_cache[i]);
424
0
  }
425
426
0
  o = options_get(global_options, "codepoint-widths");
427
0
  a = options_array_first(o);
428
0
  while (a != NULL) {
429
0
    utf8_add_to_width_cache(options_array_item_value(a)->string);
430
0
    a = options_array_next(a);
431
0
  }
432
0
}
433
434
/* Add a UTF-8 item. */
435
static int
436
utf8_put_item(const u_char *data, size_t size, u_int *index)
437
0
{
438
0
  struct utf8_item  *ui;
439
440
0
  ui = utf8_item_by_data(data, size);
441
0
  if (ui != NULL) {
442
0
    *index = ui->index;
443
0
    log_debug("%s: found %.*s = %u", __func__, (int)size, data,
444
0
        *index);
445
0
    return (0);
446
0
  }
447
448
0
  if (utf8_next_index == 0xffffff + 1)
449
0
    return (-1);
450
451
0
  ui = xcalloc(1, sizeof *ui);
452
0
  ui->index = utf8_next_index++;
453
0
  RB_INSERT(utf8_index_tree, &utf8_index_tree, ui);
454
455
0
  memcpy(ui->data, data, size);
456
0
  ui->size = size;
457
0
  RB_INSERT(utf8_data_tree, &utf8_data_tree, ui);
458
459
0
  *index = ui->index;
460
0
  log_debug("%s: added %.*s = %u", __func__, (int)size, data, *index);
461
0
  return (0);
462
0
}
463
464
/* Get UTF-8 character from data. */
465
enum utf8_state
466
utf8_from_data(const struct utf8_data *ud, utf8_char *uc)
467
99.2k
{
468
99.2k
  u_int index;
469
470
99.2k
  if (ud->width > 2)
471
0
    fatalx("invalid UTF-8 width: %u", ud->width);
472
473
99.2k
  if (ud->size > UTF8_SIZE)
474
0
    goto fail;
475
99.2k
  if (ud->size <= 3) {
476
99.2k
    index = (((utf8_char)ud->data[2] << 16)|
477
99.2k
        ((utf8_char)ud->data[1] << 8)|
478
99.2k
        ((utf8_char)ud->data[0]));
479
99.2k
  } else if (utf8_put_item(ud->data, ud->size, &index) != 0)
480
0
    goto fail;
481
99.2k
  *uc = UTF8_SET_SIZE(ud->size)|UTF8_SET_WIDTH(ud->width)|index;
482
99.2k
  log_debug("%s: (%d %d %.*s) -> %08x", __func__, ud->width, ud->size,
483
99.2k
      (int)ud->size, ud->data, *uc);
484
99.2k
  return (UTF8_DONE);
485
486
0
fail:
487
0
  if (ud->width == 0)
488
0
    *uc = UTF8_SET_SIZE(0)|UTF8_SET_WIDTH(0);
489
0
  else if (ud->width == 1)
490
0
    *uc = UTF8_SET_SIZE(1)|UTF8_SET_WIDTH(1)|0x20;
491
0
  else
492
0
    *uc = UTF8_SET_SIZE(1)|UTF8_SET_WIDTH(1)|0x2020;
493
0
  return (UTF8_ERROR);
494
99.2k
}
495
496
/* Get UTF-8 data from character. */
497
void
498
utf8_to_data(utf8_char uc, struct utf8_data *ud)
499
58.3k
{
500
58.3k
  struct utf8_item  *ui;
501
58.3k
  u_int      index;
502
503
58.3k
  memset(ud, 0, sizeof *ud);
504
58.3k
  ud->size = ud->have = UTF8_GET_SIZE(uc);
505
58.3k
  ud->width = UTF8_GET_WIDTH(uc);
506
507
58.3k
  if (ud->size <= 3) {
508
58.3k
    ud->data[2] = (uc >> 16);
509
58.3k
    ud->data[1] = ((uc >> 8) & 0xff);
510
58.3k
    ud->data[0] = (uc & 0xff);
511
58.3k
  } else {
512
0
    index = (uc & 0xffffff);
513
0
    if ((ui = utf8_item_by_index(index)) == NULL)
514
0
      memset(ud->data, ' ', ud->size);
515
0
    else
516
0
      memcpy(ud->data, ui->data, ud->size);
517
0
  }
518
519
58.3k
  log_debug("%s: %08x -> (%d %d %.*s)", __func__, uc, ud->width, ud->size,
520
58.3k
      (int)ud->size, ud->data);
521
58.3k
}
522
523
/* Get UTF-8 character from a single ASCII character. */
524
u_int
525
utf8_build_one(u_char ch)
526
3.20k
{
527
3.20k
  return (UTF8_SET_SIZE(1)|UTF8_SET_WIDTH(1)|ch);
528
3.20k
}
529
530
/* Set a single character. */
531
void
532
utf8_set(struct utf8_data *ud, u_char ch)
533
164k
{
534
164k
  static const struct utf8_data empty = { { 0 }, 1, 1, 1 };
535
536
164k
  memcpy(ud, &empty, sizeof *ud);
537
164k
  *ud->data = ch;
538
164k
}
539
540
/* Copy UTF-8 character. */
541
void
542
utf8_copy(struct utf8_data *to, const struct utf8_data *from)
543
24.3k
{
544
24.3k
  u_int i;
545
546
24.3k
  memcpy(to, from, sizeof *to);
547
548
765k
  for (i = to->size; i < sizeof to->data; i++)
549
741k
    to->data[i] = '\0';
550
24.3k
}
551
552
/* Get width of Unicode character. */
553
static enum utf8_state
554
utf8_width(struct utf8_data *ud, int *width)
555
961
{
556
961
  struct utf8_width_item  *uw;
557
961
  wchar_t      wc;
558
559
961
  if (utf8_towc(ud, &wc) != UTF8_DONE)
560
961
    return (UTF8_ERROR);
561
0
  uw = utf8_find_in_width_cache(wc);
562
0
  if (uw != NULL) {
563
0
    *width = uw->width;
564
0
    log_debug("cached width for %08X is %d", (u_int)wc, *width);
565
0
    return (UTF8_DONE);
566
0
  }
567
#ifdef HAVE_UTF8PROC
568
  *width = utf8proc_wcwidth(wc);
569
  log_debug("utf8proc_wcwidth(%05X) returned %d", (u_int)wc, *width);
570
#else
571
0
  *width = wcwidth(wc);
572
0
  log_debug("wcwidth(%05X) returned %d", (u_int)wc, *width);
573
0
  if (*width < 0) {
574
    /*
575
     * C1 control characters are nonprintable, so they are always
576
     * zero width.
577
     */
578
0
    *width = (wc >= 0x80 && wc <= 0x9f) ? 0 : 1;
579
0
  }
580
0
#endif
581
0
  if (*width >= 0 && *width <= 0xff)
582
0
    return (UTF8_DONE);
583
0
  return (UTF8_ERROR);
584
0
}
585
586
/* Convert UTF-8 character to wide character. */
587
enum utf8_state
588
utf8_towc(const struct utf8_data *ud, wchar_t *wc)
589
9.88k
{
590
#ifdef HAVE_UTF8PROC
591
  switch (utf8proc_mbtowc(wc, ud->data, ud->size)) {
592
#else
593
9.88k
  switch (mbtowc(wc, ud->data, ud->size)) {
594
0
#endif
595
6.88k
  case -1:
596
6.88k
    log_debug("UTF-8 %.*s, mbtowc() %d", (int)ud->size, ud->data,
597
6.88k
        errno);
598
6.88k
    mbtowc(NULL, NULL, MB_CUR_MAX);
599
6.88k
    return (UTF8_ERROR);
600
0
  case 0:
601
0
    return (UTF8_ERROR);
602
9.88k
  }
603
3.00k
  log_debug("UTF-8 %.*s is U+%06X", (int)ud->size, ud->data, (u_int)*wc);
604
3.00k
  return (UTF8_DONE);
605
9.88k
}
606
607
/* Check for a Unicode whitespace character. */
608
int
609
utf8_has_whitespace(const struct utf8_data *ud)
610
0
{
611
0
  struct utf8_data tmp;
612
0
  wchar_t    wc;
613
0
  u_int    offset = 0, size;
614
0
  u_char     ch;
615
616
0
  while (offset < ud->size) {
617
0
    ch = ud->data[offset];
618
0
    if (ch < 0x80) {
619
0
      wc = ch;
620
0
      size = 1;
621
0
    } else {
622
0
      if (ch >= 0xc2 && ch <= 0xdf)
623
0
        size = 2;
624
0
      else if (ch >= 0xe0 && ch <= 0xef)
625
0
        size = 3;
626
0
      else if (ch >= 0xf0 && ch <= 0xf4)
627
0
        size = 4;
628
0
      else
629
0
        return (0);
630
0
      if (size > ud->size - offset)
631
0
        return (0);
632
633
0
      memset(&tmp, 0, sizeof tmp);
634
0
      memcpy(tmp.data, ud->data + offset, size);
635
0
      tmp.size = tmp.have = size;
636
0
      if (utf8_towc(&tmp, &wc) != UTF8_DONE)
637
0
        return (0);
638
0
    }
639
0
    offset += size;
640
641
0
    switch (wc) {
642
0
    case 0x0009:
643
0
    case 0x000A:
644
0
    case 0x000B:
645
0
    case 0x000C:
646
0
    case 0x000D:
647
0
    case 0x0020:
648
0
    case 0x0085:
649
0
    case 0x00A0:
650
0
    case 0x1680:
651
0
    case 0x2000:
652
0
    case 0x2001:
653
0
    case 0x2002:
654
0
    case 0x2003:
655
0
    case 0x2004:
656
0
    case 0x2005:
657
0
    case 0x2006:
658
0
    case 0x2007:
659
0
    case 0x2008:
660
0
    case 0x2009:
661
0
    case 0x200A:
662
0
    case 0x2028:
663
0
    case 0x2029:
664
0
    case 0x202F:
665
0
    case 0x205F:
666
0
    case 0x3000:
667
0
      return (1);
668
0
    }
669
0
  }
670
0
  return (0);
671
0
}
672
673
/* Convert wide character to UTF-8 character. */
674
enum utf8_state
675
utf8_fromwc(wchar_t wc, struct utf8_data *ud)
676
0
{
677
0
  int size, width;
678
679
0
  memset(ud, 0, sizeof *ud);
680
681
#ifdef HAVE_UTF8PROC
682
  size = utf8proc_wctomb(ud->data, wc);
683
#else
684
0
  size = wctomb(ud->data, wc);
685
0
#endif
686
0
  if (size < 0) {
687
0
    log_debug("UTF-8 %d, wctomb() %d", wc, errno);
688
0
    wctomb(NULL, 0);
689
0
    return (UTF8_ERROR);
690
0
  }
691
0
  if (size == 0)
692
0
    return (UTF8_ERROR);
693
0
  ud->size = ud->have = size;
694
0
  if (utf8_width(ud, &width) == UTF8_DONE) {
695
0
    ud->width = width;
696
0
    return (UTF8_DONE);
697
0
  }
698
0
  return (UTF8_ERROR);
699
0
}
700
701
/*
702
 * Open UTF-8 sequence.
703
 *
704
 * 11000010-11011111 C2-DF start of 2-byte sequence
705
 * 11100000-11101111 E0-EF start of 3-byte sequence
706
 * 11110000-11110100 F0-F4 start of 4-byte sequence
707
 */
708
enum utf8_state
709
utf8_open(struct utf8_data *ud, u_char ch)
710
349k
{
711
349k
  memset(ud, 0, sizeof *ud);
712
349k
  if (ch >= 0xc2 && ch <= 0xdf)
713
3.38k
    ud->size = 2;
714
345k
  else if (ch >= 0xe0 && ch <= 0xef)
715
1.31k
    ud->size = 3;
716
344k
  else if (ch >= 0xf0 && ch <= 0xf4)
717
1.59k
    ud->size = 4;
718
342k
  else
719
342k
    return (UTF8_ERROR);
720
6.29k
  utf8_append(ud, ch);
721
6.29k
  return (UTF8_MORE);
722
349k
}
723
724
/* Append character to UTF-8, closing if finished. */
725
enum utf8_state
726
utf8_append(struct utf8_data *ud, u_char ch)
727
12.4k
{
728
12.4k
  int width;
729
730
12.4k
  if (ud->have >= ud->size)
731
0
    fatalx("UTF-8 character overflow");
732
12.4k
  if (ud->size > sizeof ud->data)
733
0
    fatalx("UTF-8 character size too large");
734
735
12.4k
  if (ud->have != 0 && (ch & 0xc0) != 0x80)
736
4.38k
    ud->width = 0xff;
737
738
12.4k
  ud->data[ud->have++] = ch;
739
12.4k
  if (ud->have != ud->size)
740
9.07k
    return (UTF8_MORE);
741
742
3.35k
  if (!utf8_no_width) {
743
3.35k
    if (ud->width == 0xff)
744
2.39k
      return (UTF8_ERROR);
745
961
    if (utf8_width(ud, &width) != UTF8_DONE)
746
961
      return (UTF8_ERROR);
747
0
    ud->width = width;
748
0
  }
749
750
0
  return (UTF8_DONE);
751
3.35k
}
752
753
/*
754
 * Encode len characters from src into dst, which is guaranteed to have four
755
 * bytes available for each character from src (for \abc or UTF-8) plus space
756
 * for \0.
757
 */
758
size_t
759
utf8_strvis(char *dst, const char *src, size_t len, int flag)
760
29.7k
{
761
29.7k
  struct utf8_data   ud;
762
29.7k
  const char    *start = dst, *end = src + len;
763
29.7k
  enum utf8_state    more;
764
29.7k
  size_t       i;
765
766
204k
  while (src < end) {
767
174k
    if ((more = utf8_open(&ud, *src)) == UTF8_MORE) {
768
7.06k
      while (++src < end && more == UTF8_MORE)
769
3.81k
        more = utf8_append(&ud, *src);
770
3.24k
      if (more == UTF8_DONE) {
771
        /* UTF-8 character finished. */
772
0
        for (i = 0; i < ud.size; i++)
773
0
          *dst++ = ud.data[i];
774
0
        continue;
775
0
      }
776
      /* Not a complete, valid UTF-8 character. */
777
3.24k
      src -= ud.have;
778
3.24k
    }
779
174k
    if ((flag & VIS_DQ) && src[0] == '$' && src < end - 1) {
780
0
      if (isalpha((u_char)src[1]) ||
781
0
          src[1] == '_' ||
782
0
          src[1] == '{')
783
0
        *dst++ = '\\';
784
0
      *dst++ = '$';
785
174k
    } else if (src < end - 1)
786
151k
      dst = vis(dst, src[0], flag, src[1]);
787
23.5k
    else if (src < end)
788
23.5k
      dst = vis(dst, src[0], flag, '\0');
789
174k
    src++;
790
174k
  }
791
29.7k
  *dst = '\0';
792
29.7k
  return (dst - start);
793
29.7k
}
794
795
/* Same as utf8_strvis but allocate the buffer. */
796
size_t
797
utf8_stravis(char **dst, const char *src, int flag)
798
29.7k
{
799
29.7k
  char  *buf;
800
29.7k
  size_t   len;
801
802
29.7k
  buf = xreallocarray(NULL, 4, strlen(src) + 1);
803
29.7k
  len = utf8_strvis(buf, src, strlen(src), flag);
804
805
29.7k
  *dst = xrealloc(buf, len + 1);
806
29.7k
  return (len);
807
29.7k
}
808
809
/* Same as utf8_strvis but allocate the buffer. */
810
size_t
811
utf8_stravisx(char **dst, const char *src, size_t srclen, int flag)
812
0
{
813
0
  char  *buf;
814
0
  size_t   len;
815
816
0
  buf = xreallocarray(NULL, 4, srclen + 1);
817
0
  len = utf8_strvis(buf, src, srclen, flag);
818
819
0
  *dst = xrealloc(buf, len + 1);
820
0
  return (len);
821
0
}
822
823
/* Does this string contain anything that isn't valid UTF-8? */
824
int
825
utf8_isvalid(const char *s)
826
17.7k
{
827
17.7k
  struct utf8_data ud;
828
17.7k
  const char  *end;
829
17.7k
  enum utf8_state  more;
830
831
17.7k
  end = s + strlen(s);
832
184k
  while (s < end) {
833
167k
    if ((more = utf8_open(&ud, *s)) == UTF8_MORE) {
834
1.77k
      while (++s < end && more == UTF8_MORE)
835
991
        more = utf8_append(&ud, *s);
836
785
      if (more == UTF8_DONE)
837
0
        continue;
838
785
      return (0);
839
785
    }
840
166k
    if (*s != '\t' && *s != '\n' && (*s < 0x20 || *s > 0x7e))
841
427
      return (0);
842
166k
    s++;
843
166k
  }
844
16.5k
  return (1);
845
17.7k
}
846
847
/*
848
 * Sanitize a string, changing any UTF-8 characters to '_'. Caller should free
849
 * the returned string. Anything not valid printable ASCII or UTF-8 is
850
 * stripped.
851
 */
852
char *
853
utf8_sanitize(const char *src)
854
0
{
855
0
  char    *dst = NULL;
856
0
  size_t     n = 0;
857
0
  enum utf8_state  more;
858
0
  struct utf8_data ud;
859
0
  u_int    i;
860
861
0
  while (*src != '\0') {
862
0
    dst = xreallocarray(dst, n + 1, sizeof *dst);
863
0
    if ((more = utf8_open(&ud, *src)) == UTF8_MORE) {
864
0
      while (*++src != '\0' && more == UTF8_MORE)
865
0
        more = utf8_append(&ud, *src);
866
0
      if (more == UTF8_DONE) {
867
0
        dst = xreallocarray(dst, n + ud.width,
868
0
            sizeof *dst);
869
0
        for (i = 0; i < ud.width; i++)
870
0
          dst[n++] = '_';
871
0
        continue;
872
0
      }
873
0
      src -= ud.have;
874
0
    }
875
0
    if (*src > 0x1f && *src < 0x7f)
876
0
      dst[n++] = *src;
877
0
    else
878
0
      dst[n++] = '_';
879
0
    src++;
880
0
  }
881
0
  dst = xreallocarray(dst, n + 1, sizeof *dst);
882
0
  dst[n] = '\0';
883
0
  return (dst);
884
0
}
885
886
/* Get UTF-8 buffer length. */
887
size_t
888
utf8_strlen(const struct utf8_data *s)
889
0
{
890
0
  size_t  i;
891
892
0
  for (i = 0; s[i].size != 0; i++)
893
0
    /* nothing */;
894
0
  return (i);
895
0
}
896
897
/* Get UTF-8 string width. */
898
u_int
899
utf8_strwidth(const struct utf8_data *s, ssize_t n)
900
0
{
901
0
  ssize_t i;
902
0
  u_int width = 0;
903
904
0
  for (i = 0; s[i].size != 0; i++) {
905
0
    if (n != -1 && n == i)
906
0
      break;
907
0
    width += s[i].width;
908
0
  }
909
0
  return (width);
910
0
}
911
912
/*
913
 * Convert a string into a buffer of UTF-8 characters. Terminated by size == 0.
914
 * Caller frees.
915
 */
916
struct utf8_data *
917
utf8_fromcstr(const char *src)
918
0
{
919
0
  struct utf8_data  *dst = NULL;
920
0
  size_t       n = 0;
921
0
  enum utf8_state    more;
922
923
0
  while (*src != '\0') {
924
0
    dst = xreallocarray(dst, n + 1, sizeof *dst);
925
0
    if ((more = utf8_open(&dst[n], *src)) == UTF8_MORE) {
926
0
      while (*++src != '\0' && more == UTF8_MORE)
927
0
        more = utf8_append(&dst[n], *src);
928
0
      if (more == UTF8_DONE) {
929
0
        n++;
930
0
        continue;
931
0
      }
932
0
      src -= dst[n].have;
933
0
    }
934
0
    utf8_set(&dst[n], *src);
935
0
    n++;
936
0
    src++;
937
0
  }
938
0
  dst = xreallocarray(dst, n + 1, sizeof *dst);
939
0
  dst[n].size = 0;
940
0
  return (dst);
941
0
}
942
943
/* Convert from a buffer of UTF-8 characters into a string. Caller frees. */
944
char *
945
utf8_tocstr(struct utf8_data *src)
946
0
{
947
0
  char  *dst = NULL;
948
0
  size_t   n = 0;
949
950
0
  for(; src->size != 0; src++) {
951
0
    dst = xreallocarray(dst, n + src->size, 1);
952
0
    memcpy(dst + n, src->data, src->size);
953
0
    n += src->size;
954
0
  }
955
0
  dst = xreallocarray(dst, n + 1, 1);
956
0
  dst[n] = '\0';
957
0
  return (dst);
958
0
}
959
960
/* Get width of UTF-8 string. */
961
u_int
962
utf8_cstrwidth(const char *s)
963
0
{
964
0
  struct utf8_data  tmp;
965
0
  u_int     width;
966
0
  enum utf8_state   more;
967
968
0
  width = 0;
969
0
  while (*s != '\0') {
970
0
    if ((more = utf8_open(&tmp, *s)) == UTF8_MORE) {
971
0
      while (*++s != '\0' && more == UTF8_MORE)
972
0
        more = utf8_append(&tmp, *s);
973
0
      if (more == UTF8_DONE) {
974
0
        width += tmp.width;
975
0
        continue;
976
0
      }
977
0
      s -= tmp.have;
978
0
    }
979
0
    if (*s > 0x1f && *s != 0x7f)
980
0
      width++;
981
0
    s++;
982
0
  }
983
0
  return (width);
984
0
}
985
986
/* Pad UTF-8 string to width on the left. Caller frees. */
987
char *
988
utf8_padcstr(const char *s, u_int width)
989
0
{
990
0
  size_t   slen;
991
0
  char  *out;
992
0
  u_int  n, i;
993
994
0
  n = utf8_cstrwidth(s);
995
0
  if (n >= width)
996
0
    return (xstrdup(s));
997
998
0
  slen = strlen(s);
999
0
  out = xmalloc(slen + 1 + (width - n));
1000
0
  memcpy(out, s, slen);
1001
0
  for (i = n; i < width; i++)
1002
0
    out[slen++] = ' ';
1003
0
  out[slen] = '\0';
1004
0
  return (out);
1005
0
}
1006
1007
/* Pad UTF-8 string to width on the right. Caller frees. */
1008
char *
1009
utf8_rpadcstr(const char *s, u_int width)
1010
0
{
1011
0
  size_t   slen;
1012
0
  char  *out;
1013
0
  u_int  n, i;
1014
1015
0
  n = utf8_cstrwidth(s);
1016
0
  if (n >= width)
1017
0
    return (xstrdup(s));
1018
1019
0
  slen = strlen(s);
1020
0
  out = xmalloc(slen + 1 + (width - n));
1021
0
  for (i = 0; i < width - n; i++)
1022
0
    out[i] = ' ';
1023
0
  memcpy(out + i, s, slen);
1024
0
  out[i + slen] = '\0';
1025
0
  return (out);
1026
0
}
1027
1028
int
1029
utf8_cstrhas(const char *s, const struct utf8_data *ud)
1030
0
{
1031
0
  struct utf8_data  *copy, *loop;
1032
0
  int      found = 0;
1033
1034
0
  copy = utf8_fromcstr(s);
1035
0
  for (loop = copy; loop->size != 0; loop++) {
1036
0
    if (loop->size != ud->size)
1037
0
      continue;
1038
0
    if (memcmp(loop->data, ud->data, loop->size) == 0) {
1039
0
      found = 1;
1040
0
      break;
1041
0
    }
1042
0
  }
1043
0
  free(copy);
1044
1045
0
  return (found);
1046
0
}