Coverage Report

Created: 2026-10-28 07:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-mail/rfc2231-parser.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "array.h"
5
#include "str.h"
6
#include "rfc822-parser.h"
7
#include "strfuncs.h"
8
#include "strnum.h"
9
#include "rfc2231-parser.h"
10
#include "charset-utf8.h"
11
12
#include <limits.h>
13
14
/* https://www.rfc-editor.org/rfc/rfc2231
15
   https://www.rfc-editor.org/rfc/rfc822
16
17
   RFC 2231 parameters use key*<n>[*]=value pairs. This means the following
18
   examples are valid and will produce the same pair key=value0value1value2 (or
19
   key*=value0value1value2):
20
21
    - key*0=value0; key*1=value1; key*2=value2
22
    - key*2=value2; key*1=value1; key*0=value0
23
    - key*2*=value2; key*1=value1; key*0*=value0
24
    - key*=value0value1value2
25
26
   In addition, non-extended and non-continued key=value parameters need to
27
   continue being supported.
28
29
   The RFC2231 document calls these indexed parameters "continued
30
   parameters". We call them indexed here.
31
32
   This implementation tries to avoid some pitfalls by putting pairs in two
33
   groups: 1) the results list and 2) a list of indexed pairs. The former will
34
   initially contain unextended pairs and extended but unindexed pairs. The
35
   latter will contain only extended indexed pairs. After merging the extended
36
   indexed pairs, they will be inserted into the results list if a pair with the
37
   same key does not already exist in there. This results in some input pairs
38
   being rejected if the key is a duplicate of one already in the results
39
   list. In short, this implementation will prefer unindexed parameters over
40
   indexed ones. Example:
41
42
     - key*0=value0; key*=value; key*1=value1   -> key=value
43
     - key=value; key*=value                    -> key=value
44
     - key=value; key*0=value0; key*1=value1    -> key=value
45
46
   The RFC does not mention much regarding invalid inputs or how to handle
47
   them. So this implementation does a best-effort "garbage-in garbage-out" sort
48
   of thing. */
49
50
static inline int decode_hex_digit(char digit)
51
9.48k
{
52
9.48k
  if (digit >= '0' && digit <= '9')
53
1.63k
    return digit - '0';
54
7.84k
  else if (digit >= 'a' && digit <= 'f')
55
1.65k
    return digit - 'a' + 0x0a;
56
6.19k
  else if (digit >= 'A' && digit <= 'F')
57
2.19k
    return digit - 'A' + 0x0A;
58
4.00k
  return -1;
59
9.48k
}
60
61
static inline bool decode_hex_byte(const char *digits, char *result_r)
62
6.23k
{
63
6.23k
  int higher = decode_hex_digit(digits[0]);
64
6.23k
  if (higher < 0)
65
2.97k
    return FALSE;
66
67
3.25k
  int lower = decode_hex_digit(digits[1]);
68
3.25k
  if (lower < 0)
69
1.02k
    return FALSE;
70
71
2.22k
  *result_r = (char)((higher << 4) + lower);
72
2.22k
  return TRUE;
73
3.25k
}
74
75
static string_t *rfc2231_decode_value(const char *value)
76
30.1k
{
77
30.1k
  string_t *str = t_str_new(64);
78
30.1k
  const char *plast = value;
79
30.1k
  const char *p;
80
32.4k
  while ((p = strchr(plast, '%')) != NULL) {
81
    /* Append whatever we've seen so far. */
82
6.23k
    str_append_data(str, plast, (p - plast));
83
6.23k
    char ch;
84
6.23k
    if (!decode_hex_byte(p+1, &ch))
85
4.00k
      return NULL;
86
2.22k
    plast = p + 3;
87
2.22k
    str_append_data(str, &ch, 1);
88
2.22k
  }
89
  /* Append whatever remains. */
90
26.1k
  str_append(str, plast);
91
26.1k
  return str;
92
30.1k
}
93
94
struct rfc2231_parameter {
95
  const char *key, *value;
96
  unsigned int idx;
97
  bool extended;
98
};
99
100
static int rfc2231_parameter_cmp(const struct rfc2231_parameter *r1,
101
         const struct rfc2231_parameter *r2)
102
98.4k
{
103
98.4k
  int ret = strcmp(r1->key, r2->key);
104
98.4k
  if (ret != 0)
105
20.0k
    return ret;
106
78.4k
  if (r1->idx < r2->idx)
107
12.2k
    return -1;
108
66.2k
  else if (r1->idx > r2->idx)
109
34.6k
    return 1;
110
31.5k
  return 0;
111
78.4k
}
112
113
static bool result_contains(const ARRAY_TYPE(const_string) *result,
114
          const char *key)
115
240k
{
116
240k
  unsigned int count;
117
240k
  const char *const *p = array_get(result, &count);
118
240k
  i_assert((count % 2) == 0);
119
1.66M
  for (unsigned int i = 0; i < count; i += 2) {
120
1.51M
    if (strcasecmp(key, p[i]) == 0)
121
94.2k
      return TRUE;
122
1.51M
  }
123
145k
  return FALSE;
124
240k
}
125
126
static void result_append(ARRAY_TYPE(const_string) *result,
127
        const char *key, const char *value)
128
240k
{
129
240k
  if (!result_contains(result, key)) {
130
145k
    array_push_back(result, &key);
131
145k
    array_push_back(result, &value);
132
145k
  }
133
240k
}
134
135
static const char *reconstruct_rfc2231_key(const struct rfc2231_parameter *param,
136
             const bool with_extended)
137
24.0k
{
138
24.0k
  return t_strdup_printf(
139
24.0k
    with_extended && param->extended ? "%s*%i*" : "%s*%i",
140
24.0k
    param->key,
141
24.0k
    param->idx);
142
24.0k
}
143
144
static const char *find_charset(const char *value, const char **end_r)
145
141k
{
146
  /* Note that it is perfectly permissible to leave either the character
147
     set or language field blank.  Note also that the single quote
148
     delimiters MUST be present even when one of the field values is
149
     omitted.  This is done when either character set, language, or both
150
     are not relevant to the parameter value at hand. */
151
141k
  const char *end = strchr(value, '\'');
152
141k
  if (end == NULL)
153
109k
    return NULL;
154
32.4k
  const char *const charset_r = t_strdup_until(value, end);
155
156
  /* We don't do anything with the language info but the format has to be
157
     valid, otherwise even the character set - which we have already
158
     parsed - is invalid. */
159
32.4k
  end = strchr(end + 1, '\'');
160
32.4k
  if (end == NULL)
161
2.01k
    return NULL;
162
163
30.4k
  *end_r = end + 1;
164
30.4k
  return charset_r;
165
32.4k
}
166
167
static const char *value_to_utf8(const char *charset,
168
         const char *value)
169
30.1k
{
170
30.1k
  string_t *utf8_r = t_str_new(128);
171
30.1k
  enum charset_result result;
172
30.1k
  if (charset_to_utf8_str(charset, NULL, value, utf8_r, &result) < 0 ||
173
18.4k
      result != CHARSET_RET_OK)
174
21.3k
    return NULL;
175
8.86k
  return str_c(utf8_r);
176
30.1k
}
177
178
static const char *rfc2231_decode_charset_value(const char *value,
179
            const char **charset_r)
180
143k
{
181
143k
  if (*charset_r == NULL)
182
141k
    *charset_r = find_charset(value, &value);
183
184
143k
  if (*charset_r != NULL && strcmp(*charset_r, "") != 0) {
185
30.1k
    string_t *tmp = rfc2231_decode_value(value);
186
30.1k
    const char *decoded = tmp == NULL ? value : str_c(tmp);
187
188
    /* Charset is valid and not empty. */
189
30.1k
    const char *const utf8 = value_to_utf8(*charset_r, decoded);
190
30.1k
    if (utf8 != NULL)
191
8.86k
      value = utf8;
192
30.1k
  }
193
143k
  return value;
194
143k
}
195
196
int rfc2231_parse(struct rfc822_parser_context *ctx,
197
      const char *const **result_r)
198
71.3k
{
199
71.3k
  ARRAY_TYPE(const_string) result;
200
71.3k
  ARRAY(struct rfc2231_parameter) rfc2231_params_arr;
201
71.3k
  struct rfc2231_parameter rfc2231_param;
202
71.3k
  const char *key, *p, *p2;
203
71.3k
  string_t *str;
204
71.3k
  unsigned int i, j, count, next, next_idx, params_count = 0;
205
71.3k
  bool ok, broken = FALSE;
206
71.3k
  const char *prev_replacement_str;
207
71.3k
  int ret;
208
209
  /* Temporarily replace the nul_replacement_char while we're parsing
210
     the content-params. It'll be restored before we return. */
211
71.3k
  prev_replacement_str = ctx->nul_replacement_str;
212
71.3k
  ctx->nul_replacement_str = RFC822_NUL_REPLACEMENT_STR;
213
214
  /* Get a list of all parameters. RFC 2231 uses key*<n>[*]=value pairs,
215
     which we want to merge to a key[*]=value pair. Save them to a
216
     separate array. */
217
71.3k
  i_zero(&rfc2231_param);
218
71.3k
  t_array_init(&result, 8);
219
71.3k
  t_array_init(&rfc2231_params_arr, 8);
220
71.3k
  str = t_str_new(64);
221
750k
  while ((ret = rfc822_parse_content_param(ctx, &key, str)) != 0) {
222
694k
    if (++params_count > RFC2231_MAX_PARAMS)
223
2.08k
      break;
224
692k
    if (ret < 0) {
225
      /* try to continue anyway.. */
226
447k
      broken = TRUE;
227
447k
      if (ctx->data >= ctx->end)
228
12.4k
        break;
229
434k
      ctx->data++;
230
434k
      continue;
231
447k
    }
232
244k
    p = strchr(key, '*');
233
244k
    if (p != NULL) {
234
180k
      p2 = p;
235
180k
      bool is_indexed = str_parse_uint(
236
180k
        p + 1, &rfc2231_param.idx, &p) != -1;
237
238
180k
      if (*p == '*') {
239
148k
        rfc2231_param.extended = TRUE;
240
148k
        p++;
241
148k
      } else {
242
32.3k
        rfc2231_param.extended = FALSE;
243
32.3k
      }
244
180k
      if (*p == '\0') {
245
174k
        const char *tmp_key = t_strdup_until(key, p2);
246
174k
        const char *tmp_val = t_strdup(str_c(str));
247
248
174k
        if (is_indexed) {
249
37.3k
          rfc2231_param.key = tmp_key;
250
37.3k
          rfc2231_param.value = tmp_val;
251
37.3k
          array_push_back(&rfc2231_params_arr,
252
37.3k
              &rfc2231_param);
253
136k
        } else {
254
136k
          if (rfc2231_param.extended) {
255
135k
            const char *charset = NULL;
256
135k
            tmp_val = rfc2231_decode_charset_value(
257
135k
              tmp_val, &charset);
258
135k
          }
259
136k
          result_append(&result, tmp_key, tmp_val);
260
136k
        }
261
174k
      } else {
262
6.44k
        p = NULL;
263
6.44k
      }
264
180k
    }
265
244k
    if (p == NULL)
266
70.5k
      result_append(&result, key, t_strdup(str_c(str)));
267
244k
  }
268
71.3k
  ctx->nul_replacement_str = prev_replacement_str;
269
270
71.3k
  if (array_count(&rfc2231_params_arr) == 0) {
271
    /* No RFC 2231 parameters */
272
63.5k
    array_append_zero(&result); /* NULL-terminate */
273
63.5k
    *result_r = array_front(&result);
274
63.5k
    return broken ? -1 : 0;
275
63.5k
  }
276
277
  /* Sort keys primarily by their name and secondarily by their index. */
278
7.74k
  array_sort(&rfc2231_params_arr, rfc2231_parameter_cmp);
279
7.74k
  const struct rfc2231_parameter *rfc2231_params =
280
7.74k
    array_get(&rfc2231_params_arr, &count);
281
282
  /* Merge the RFC 2231 parameters. If any indexes are missing, fallback
283
     to assuming these aren't RFC 2231 encoded parameters. */
284
21.6k
  for (i = 0; i < count; i = next) {
285
13.9k
    ok = TRUE;
286
13.9k
    next_idx = 0;
287
51.2k
    for (j = i; j < count; j++) {
288
43.5k
      if (strcasecmp(rfc2231_params[i].key,
289
43.5k
               rfc2231_params[j].key) != 0)
290
6.16k
        break;
291
37.3k
      if (rfc2231_params[j].idx != next_idx) {
292
        /* missing indexes */
293
22.1k
        ok = FALSE;
294
22.1k
      }
295
37.3k
      next_idx++;
296
37.3k
    }
297
13.9k
    next = j;
298
299
13.9k
    if (!ok) {
300
      /* Some indexes are missing so we can assume the keys
301
         are unindexed but might be extended. */
302
29.2k
      for (j = i; j < next; j++) {
303
24.0k
        const char *val = rfc2231_params[j].value;
304
24.0k
        bool with_extended = TRUE;
305
24.0k
        if (rfc2231_params[j].extended) {
306
          /* Since these values are now assumed to
307
             be unrelated - due to their indexing
308
             being invalid, we don't really care
309
             about the charset being set, or being
310
             available only in the first
311
             segment. We treat them as unrelated
312
             and independent parameters. */
313
1.92k
          const char *charset = NULL;
314
1.92k
          val = rfc2231_decode_charset_value(val, &charset);
315
1.92k
          with_extended = FALSE;
316
1.92k
        }
317
24.0k
        result_append(
318
24.0k
          &result,
319
24.0k
          reconstruct_rfc2231_key(
320
24.0k
            &rfc2231_params[j],
321
24.0k
            with_extended),
322
24.0k
          val);
323
24.0k
      }
324
5.24k
      continue;
325
5.24k
    }
326
327
    /* we have valid indexing */
328
8.67k
    str_truncate(str, 0);
329
330
8.67k
    const char *charset = NULL;
331
22.0k
    for (j = i; j < next; j++) {
332
13.3k
      const char *val = rfc2231_params[j].value;
333
      /* We expect rfc2231_params[j]'s value to contain the
334
         charset and language info:
335
336
         (4) The first segment of a continuation MUST be
337
         encoded if language and character set information are
338
         given.
339
340
         (5) If the first segment of a continued parameter
341
         value is encoded the language and character set field
342
         delimiters MUST be present even when the fields are
343
         left blank. */
344
13.3k
      if (rfc2231_params[j].extended)
345
5.46k
        val = rfc2231_decode_charset_value(val, &charset);
346
13.3k
      str_append(str, val);
347
13.3k
    }
348
8.67k
    key = rfc2231_params[i].key;
349
8.67k
    result_append(&result, key, t_strdup(str_c(str)));
350
8.67k
  }
351
7.74k
  array_append_zero(&result); /* NULL-terminate */
352
7.74k
  *result_r = array_front(&result);
353
7.74k
  return broken ? -1 : 0;
354
71.3k
}