Coverage Report

Created: 2025-07-18 06:24

/src/dovecot/src/lib/strescape.c
Line
Count
Source (jump to first uncovered line)
1
/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "str.h"
5
#include "strescape.h"
6
7
const char *str_nescape(const void *str, size_t len)
8
0
{
9
0
  string_t *dest = t_str_new(len*2);
10
0
  str_append_escaped(dest, str, len);
11
0
  return str_c(dest);
12
0
}
13
14
void str_append_escaped(string_t *dest, const void *src, size_t src_size)
15
0
{
16
0
  const unsigned char *pstart = src, *p = src, *pend = pstart + src_size;
17
  /* see if we need to quote it */
18
0
  for (; p < pend; p++) {
19
0
    if (IS_ESCAPED_CHAR(*p))
20
0
      break;
21
0
  }
22
23
  /* quote */
24
0
  str_append_data(dest, pstart, (size_t)(p - pstart));
25
26
0
  for (; p < pend; p++) {
27
0
    if (IS_ESCAPED_CHAR(*p))
28
0
      str_append_c(dest, '\\');
29
0
    str_append_data(dest, p, 1);
30
0
  }
31
0
}
32
33
void str_append_unescaped(string_t *dest, const void *src, size_t src_size)
34
0
{
35
0
  const unsigned char *src_c = src;
36
0
  size_t start = 0, i = 0;
37
38
0
  while (i < src_size) {
39
0
    for (; i < src_size; i++) {
40
0
      if (src_c[i] == '\\')
41
0
        break;
42
0
    }
43
44
0
    str_append_data(dest, src_c + start, i-start);
45
46
0
    if (i < src_size) {
47
0
      if (++i == src_size)
48
0
        break;
49
0
      str_append_c(dest, src_c[i++]);
50
0
    }
51
0
    start = i;
52
0
  }
53
0
}
54
55
char *str_unescape(char *str)
56
9.20k
{
57
  /* @UNSAFE */
58
9.20k
  char *dest, *start = str;
59
60
5.89M
  while (*str != '\\') {
61
5.88M
    if (*str == '\0')
62
0
      return start;
63
5.88M
    str++;
64
5.88M
  }
65
66
15.8M
  for (dest = str; *str != '\0'; str++) {
67
15.8M
    if (*str == '\\') {
68
262k
      str++;
69
262k
      if (*str == '\0')
70
0
        break;
71
262k
    }
72
73
15.8M
    *dest++ = *str;
74
15.8M
  }
75
76
9.20k
  *dest = '\0';
77
9.20k
  return start;
78
9.20k
}
79
80
int str_unescape_next(const char **str, const char **unescaped_r)
81
0
{
82
0
  const char *p;
83
0
  char *escaped;
84
0
  bool esc_found = FALSE;
85
86
0
  for (p = *str; *p != '\0'; p++) {
87
0
    if (*p == '"')
88
0
      break;
89
0
    else if (*p == '\\') {
90
0
      if (p[1] == '\0')
91
0
        return -1;
92
0
      esc_found = TRUE;
93
0
      p++;
94
0
    }
95
0
  }
96
0
  if (*p != '"')
97
0
    return -1;
98
0
  escaped = p_strdup_until(unsafe_data_stack_pool, *str, p);
99
0
  *str = p+1;
100
0
  *unescaped_r = !esc_found ? escaped : str_unescape(escaped);
101
0
  return 0;
102
0
}
103
104
void str_append_tabescaped_n(string_t *dest, const unsigned char *src, size_t src_size)
105
0
{
106
0
  size_t prev_pos = 0;
107
0
  char esc[2] = { '\001', '\0' };
108
109
0
  for (size_t i = 0; i < src_size; i++) {
110
0
    switch (src[i]) {
111
0
    case '\000':
112
0
      esc[1] = '0';
113
0
      break;
114
0
    case '\001':
115
0
      esc[1] = '1';
116
0
      break;
117
0
    case '\t':
118
0
      esc[1] = 't';
119
0
      break;
120
0
    case '\r':
121
0
      esc[1] = 'r';
122
0
      break;
123
0
    case '\n':
124
0
      esc[1] = 'n';
125
0
      break;
126
0
    default:
127
0
      continue;
128
0
    }
129
0
    str_append_data(dest, src + prev_pos, i - prev_pos);
130
0
    str_append_data(dest, esc, 2);
131
0
    prev_pos = i + 1;
132
0
  }
133
0
  str_append_data(dest, src + prev_pos, src_size - prev_pos);
134
0
}
135
136
void str_append_tabescaped(string_t *dest, const char *src)
137
0
{
138
0
  size_t pos, prev_pos = 0;
139
0
  char esc[2] = { '\001', '\0' };
140
141
0
  for (;;) {
142
0
    pos = prev_pos + strcspn(src + prev_pos, "\001\t\r\n");
143
0
    str_append_data(dest, src + prev_pos, pos - prev_pos);
144
0
    prev_pos = pos + 1;
145
146
0
    switch (src[pos]) {
147
0
    case '\000':
148
      /* end of src string reached */
149
0
      return;
150
0
    case '\001':
151
0
      esc[1] = '1';
152
0
      break;
153
0
    case '\t':
154
0
      esc[1] = 't';
155
0
      break;
156
0
    case '\r':
157
0
      esc[1] = 'r';
158
0
      break;
159
0
    case '\n':
160
0
      esc[1] = 'n';
161
0
      break;
162
0
    default:
163
0
      i_unreached();
164
0
    }
165
0
    str_append_data(dest, esc, 2);
166
0
  }
167
0
}
168
169
170
const char *str_tabescape(const char *str)
171
0
{
172
0
  string_t *tmp;
173
0
  const char *p;
174
175
0
  if ((p = strpbrk(str, "\001\t\r\n")) != NULL) {
176
0
    tmp = t_str_new(128);
177
0
    str_append_data(tmp, str, p-str);
178
0
    str_append_tabescaped(tmp, p);
179
0
    return str_c(tmp);
180
0
  }
181
0
  return str;
182
0
}
183
184
void str_append_tabunescaped(string_t *dest, const void *src, size_t src_size)
185
0
{
186
0
  const unsigned char *src_c = src;
187
0
  size_t start = 0, i = 0;
188
189
0
  while (i < src_size) {
190
0
    for (; i < src_size; i++) {
191
0
      if (src_c[i] == '\001')
192
0
        break;
193
0
    }
194
195
0
    str_append_data(dest, src_c + start, i-start);
196
197
0
    if (i < src_size) {
198
0
      i++;
199
0
      if (i < src_size) {
200
0
        switch (src_c[i]) {
201
0
        case '0':
202
0
          str_append_c(dest, '\000');
203
0
          break;
204
0
        case '1':
205
0
          str_append_c(dest, '\001');
206
0
          break;
207
0
        case 't':
208
0
          str_append_c(dest, '\t');
209
0
          break;
210
0
        case 'r':
211
0
          str_append_c(dest, '\r');
212
0
          break;
213
0
        case 'n':
214
0
          str_append_c(dest, '\n');
215
0
          break;
216
0
        default:
217
0
          str_append_c(dest, src_c[i]);
218
0
          break;
219
0
        }
220
0
        i++;
221
0
      }
222
0
    }
223
0
    start = i;
224
0
  }
225
0
}
226
227
static char *str_tabunescape_from(char *str, char *src)
228
0
{
229
  /* @UNSAFE */
230
0
  char *dest, *p;
231
232
0
  dest = src;
233
0
  for (;;) {
234
0
    switch (src[1]) {
235
0
    case '\0':
236
      /* truncated input */
237
0
      *dest = '\0';
238
0
      return str;
239
0
    case '0':
240
0
      *dest++ = '\000';
241
0
      break;
242
0
    case '1':
243
0
      *dest++ = '\001';
244
0
      break;
245
0
    case 't':
246
0
      *dest++ = '\t';
247
0
      break;
248
0
    case 'r':
249
0
      *dest++ = '\r';
250
0
      break;
251
0
    case 'n':
252
0
      *dest++ = '\n';
253
0
      break;
254
0
    default:
255
0
      *dest++ = src[1];
256
0
      break;
257
0
    }
258
0
    src += 2;
259
260
0
    p = strchr(src, '\001');
261
0
    if (p == NULL) {
262
0
      memmove(dest, src, strlen(src)+1);
263
0
      break;
264
0
    }
265
266
0
    size_t copy_len = p - src;
267
0
    memmove(dest, src, copy_len);
268
0
    dest += copy_len;
269
0
    src = p;
270
0
  }
271
0
  return str;
272
0
}
273
274
char *str_tabunescape(char *str)
275
0
{
276
0
  char *src = strchr(str, '\001');
277
0
  if (src == NULL) {
278
    /* no unescaping needed */
279
0
    return str;
280
0
  }
281
0
  return str_tabunescape_from(str, src);
282
0
}
283
284
const char *t_str_tabunescape(const char *str)
285
0
{
286
0
  const char *p;
287
288
0
  p = strchr(str, '\001');
289
0
  if (p == NULL)
290
0
    return str;
291
292
0
  char *dest = t_strdup_noconst(str);
293
0
  return str_tabunescape_from(dest, dest + (p - str));
294
0
}
295
296
static char **p_strsplit_tabescaped_inplace(pool_t pool, char *data)
297
0
{
298
  /* @UNSAFE */
299
0
  char **array;
300
0
  unsigned int count, new_alloc_count, alloc_count;
301
302
0
  if (*data == '\0')
303
0
    return p_new(pool, char *, 1);
304
305
0
  alloc_count = 32;
306
0
  array = pool == unsafe_data_stack_pool ?
307
0
    t_malloc_no0(sizeof(char *) * alloc_count) :
308
0
    p_malloc(pool, sizeof(char *) * alloc_count);
309
310
0
  array[0] = data; count = 1;
311
0
  char *need_unescape = NULL;
312
0
  while ((data = strpbrk(data, "\t\001")) != NULL) {
313
    /* separator or escape char found */
314
0
    if (*data == '\001') {
315
0
      if (need_unescape == NULL)
316
0
        need_unescape = data;
317
0
      data++;
318
0
      continue;
319
0
    }
320
0
    if (count+1 >= alloc_count) {
321
0
      new_alloc_count = nearest_power(alloc_count+1);
322
0
      array = p_realloc(pool, array,
323
0
            sizeof(char *) * alloc_count,
324
0
            sizeof(char *) *
325
0
            new_alloc_count);
326
0
      alloc_count = new_alloc_count;
327
0
    }
328
0
    *data++ = '\0';
329
0
    if (need_unescape != NULL) {
330
0
      str_tabunescape_from(array[count-1], need_unescape);
331
0
      need_unescape = NULL;
332
0
    }
333
0
    array[count++] = data;
334
0
  }
335
0
  if (need_unescape != NULL)
336
0
    str_tabunescape_from(array[count-1], need_unescape);
337
0
  i_assert(count < alloc_count);
338
0
  array[count] = NULL;
339
340
0
  return array;
341
0
}
342
343
const char *const *t_strsplit_tabescaped_inplace(char *data)
344
0
{
345
0
  char *const *escaped =
346
0
    p_strsplit_tabescaped_inplace(unsafe_data_stack_pool, data);
347
0
  return (const char *const *)escaped;
348
0
}
349
350
char **p_strsplit_tabescaped(pool_t pool, const char *str)
351
0
{
352
0
  return p_strsplit_tabescaped_inplace(pool, p_strdup(pool, str));
353
0
}
354
355
const char *const *t_strsplit_tabescaped(const char *str)
356
0
{
357
0
  return t_strsplit_tabescaped_inplace(t_strdup_noconst(str));
358
0
}