Coverage Report

Created: 2026-07-16 07:00

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
7.18k
{
52
7.18k
  if (digit >= '0' && digit <= '9')
53
638
    return digit - '0';
54
6.54k
  else if (digit >= 'a' && digit <= 'f')
55
669
    return digit - 'a' + 0x0a;
56
5.87k
  else if (digit >= 'A' && digit <= 'F')
57
2.52k
    return digit - 'A' + 0x0A;
58
3.35k
  return -1;
59
7.18k
}
60
61
static inline bool decode_hex_byte(const char *digits, char *result_r)
62
4.27k
{
63
4.27k
  int higher = decode_hex_digit(digits[0]);
64
4.27k
  if (higher < 0)
65
1.36k
    return FALSE;
66
67
2.90k
  int lower = decode_hex_digit(digits[1]);
68
2.90k
  if (lower < 0)
69
1.98k
    return FALSE;
70
71
920
  *result_r = (char)((higher << 4) + lower);
72
920
  return TRUE;
73
2.90k
}
74
75
static string_t *rfc2231_decode_value(const char *value)
76
19.6k
{
77
19.6k
  string_t *str = t_str_new(64);
78
19.6k
  const char *plast = value;
79
19.6k
  const char *p;
80
20.5k
  while ((p = strchr(plast, '%')) != NULL) {
81
    /* Append whatever we've seen so far. */
82
4.27k
    str_append_data(str, plast, (p - plast));
83
4.27k
    char ch;
84
4.27k
    if (!decode_hex_byte(p+1, &ch))
85
3.35k
      return NULL;
86
920
    plast = p + 3;
87
920
    str_append_data(str, &ch, 1);
88
920
  }
89
  /* Append whatever remains. */
90
16.2k
  str_append(str, plast);
91
16.2k
  return str;
92
19.6k
}
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
51.8k
{
103
51.8k
  int ret = strcmp(r1->key, r2->key);
104
51.8k
  if (ret != 0)
105
15.9k
    return ret;
106
35.8k
  if (r1->idx < r2->idx)
107
3.71k
    return -1;
108
32.1k
  else if (r1->idx > r2->idx)
109
14.8k
    return 1;
110
17.2k
  return 0;
111
35.8k
}
112
113
static bool result_contains(const ARRAY_TYPE(const_string) *result,
114
          const char *key)
115
245k
{
116
245k
  unsigned int count;
117
245k
  const char *const *p = array_get(result, &count);
118
245k
  i_assert((count % 2) == 0);
119
2.23M
  for (unsigned int i = 0; i < count; i += 2) {
120
2.08M
    if (strcasecmp(key, p[i]) == 0)
121
95.2k
      return TRUE;
122
2.08M
  }
123
150k
  return FALSE;
124
245k
}
125
126
static void result_append(ARRAY_TYPE(const_string) *result,
127
        const char *key, const char *value)
128
245k
{
129
245k
  if (!result_contains(result, key)) {
130
150k
    array_push_back(result, &key);
131
150k
    array_push_back(result, &value);
132
150k
  }
133
245k
}
134
135
static const char *reconstruct_rfc2231_key(const struct rfc2231_parameter *param,
136
             const bool with_extended)
137
13.6k
{
138
13.6k
  return t_strdup_printf(
139
13.6k
    with_extended && param->extended ? "%s*%i*" : "%s*%i",
140
13.6k
    param->key,
141
13.6k
    param->idx);
142
13.6k
}
143
144
static const char *find_charset(const char *value, const char **end_r)
145
175k
{
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
175k
  const char *end = strchr(value, '\'');
152
175k
  if (end == NULL)
153
154k
    return NULL;
154
21.1k
  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
21.1k
  end = strchr(end + 1, '\'');
160
21.1k
  if (end == NULL)
161
1.59k
    return NULL;
162
163
19.5k
  *end_r = end + 1;
164
19.5k
  return charset_r;
165
21.1k
}
166
167
static const char *value_to_utf8(const char *charset,
168
         const char *value)
169
19.6k
{
170
19.6k
  string_t *utf8_r = t_str_new(128);
171
19.6k
  enum charset_result result;
172
19.6k
  if (charset_to_utf8_str(charset, NULL, value, utf8_r, &result) < 0 ||
173
13.2k
      result != CHARSET_RET_OK)
174
14.4k
    return NULL;
175
5.19k
  return str_c(utf8_r);
176
19.6k
}
177
178
static const char *rfc2231_decode_charset_value(const char *value,
179
            const char **charset_r)
180
176k
{
181
176k
  if (*charset_r == NULL)
182
175k
    *charset_r = find_charset(value, &value);
183
184
176k
  if (*charset_r != NULL && strcmp(*charset_r, "") != 0) {
185
19.6k
    string_t *tmp = rfc2231_decode_value(value);
186
19.6k
    const char *decoded = tmp == NULL ? value : str_c(tmp);
187
188
    /* Charset is valid and not empty. */
189
19.6k
    const char *const utf8 = value_to_utf8(*charset_r, decoded);
190
19.6k
    if (utf8 != NULL)
191
5.19k
      value = utf8;
192
19.6k
  }
193
176k
  return value;
194
176k
}
195
196
int rfc2231_parse(struct rfc822_parser_context *ctx,
197
      const char *const **result_r)
198
35.8k
{
199
35.8k
  ARRAY_TYPE(const_string) result;
200
35.8k
  ARRAY(struct rfc2231_parameter) rfc2231_params_arr;
201
35.8k
  struct rfc2231_parameter rfc2231_param;
202
35.8k
  const char *key, *p, *p2;
203
35.8k
  string_t *str;
204
35.8k
  unsigned int i, j, count, next, next_idx, params_count = 0;
205
35.8k
  bool ok, broken = FALSE;
206
35.8k
  const char *prev_replacement_str;
207
35.8k
  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
35.8k
  prev_replacement_str = ctx->nul_replacement_str;
212
35.8k
  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
35.8k
  i_zero(&rfc2231_param);
218
35.8k
  t_array_init(&result, 8);
219
35.8k
  t_array_init(&rfc2231_params_arr, 8);
220
35.8k
  str = t_str_new(64);
221
631k
  while ((ret = rfc822_parse_content_param(ctx, &key, str)) != 0) {
222
605k
    if (++params_count > RFC2231_MAX_PARAMS)
223
1.69k
      break;
224
603k
    if (ret < 0) {
225
      /* try to continue anyway.. */
226
356k
      broken = TRUE;
227
356k
      if (ctx->data >= ctx->end)
228
7.77k
        break;
229
348k
      ctx->data++;
230
348k
      continue;
231
356k
    }
232
247k
    p = strchr(key, '*');
233
247k
    if (p != NULL) {
234
200k
      p2 = p;
235
200k
      bool is_indexed = str_parse_uint(
236
200k
        p + 1, &rfc2231_param.idx, &p) != -1;
237
238
200k
      if (*p == '*') {
239
181k
        rfc2231_param.extended = TRUE;
240
181k
        p++;
241
181k
      } else {
242
18.5k
        rfc2231_param.extended = FALSE;
243
18.5k
      }
244
200k
      if (*p == '\0') {
245
194k
        const char *tmp_key = t_strdup_until(key, p2);
246
194k
        const char *tmp_val = t_strdup(str_c(str));
247
248
194k
        if (is_indexed) {
249
22.3k
          rfc2231_param.key = tmp_key;
250
22.3k
          rfc2231_param.value = tmp_val;
251
22.3k
          array_push_back(&rfc2231_params_arr,
252
22.3k
              &rfc2231_param);
253
171k
        } else {
254
171k
          if (rfc2231_param.extended) {
255
170k
            const char *charset = NULL;
256
170k
            tmp_val = rfc2231_decode_charset_value(
257
170k
              tmp_val, &charset);
258
170k
          }
259
171k
          result_append(&result, tmp_key, tmp_val);
260
171k
        }
261
194k
      } else {
262
6.25k
        p = NULL;
263
6.25k
      }
264
200k
    }
265
247k
    if (p == NULL)
266
53.2k
      result_append(&result, key, t_strdup(str_c(str)));
267
247k
  }
268
35.8k
  ctx->nul_replacement_str = prev_replacement_str;
269
270
35.8k
  if (array_count(&rfc2231_params_arr) == 0) {
271
    /* No RFC 2231 parameters */
272
30.1k
    array_append_zero(&result); /* NULL-terminate */
273
30.1k
    *result_r = array_front(&result);
274
30.1k
    return broken ? -1 : 0;
275
30.1k
  }
276
277
  /* Sort keys primarily by their name and secondarily by their index. */
278
5.76k
  array_sort(&rfc2231_params_arr, rfc2231_parameter_cmp);
279
5.76k
  const struct rfc2231_parameter *rfc2231_params =
280
5.76k
    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
16.9k
  for (i = 0; i < count; i = next) {
285
11.1k
    ok = TRUE;
286
11.1k
    next_idx = 0;
287
33.4k
    for (j = i; j < count; j++) {
288
27.7k
      if (strcasecmp(rfc2231_params[i].key,
289
27.7k
               rfc2231_params[j].key) != 0)
290
5.38k
        break;
291
22.3k
      if (rfc2231_params[j].idx != next_idx) {
292
        /* missing indexes */
293
12.6k
        ok = FALSE;
294
12.6k
      }
295
22.3k
      next_idx++;
296
22.3k
    }
297
11.1k
    next = j;
298
299
11.1k
    if (!ok) {
300
      /* Some indexes are missing so we can assume the keys
301
         are unindexed but might be extended. */
302
17.9k
      for (j = i; j < next; j++) {
303
13.6k
        const char *val = rfc2231_params[j].value;
304
13.6k
        bool with_extended = TRUE;
305
13.6k
        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
2.13k
          const char *charset = NULL;
314
2.13k
          val = rfc2231_decode_charset_value(val, &charset);
315
2.13k
          with_extended = FALSE;
316
2.13k
        }
317
13.6k
        result_append(
318
13.6k
          &result,
319
13.6k
          reconstruct_rfc2231_key(
320
13.6k
            &rfc2231_params[j],
321
13.6k
            with_extended),
322
13.6k
          val);
323
13.6k
      }
324
4.27k
      continue;
325
4.27k
    }
326
327
    /* we have valid indexing */
328
6.87k
    str_truncate(str, 0);
329
330
6.87k
    const char *charset = NULL;
331
15.5k
    for (j = i; j < next; j++) {
332
8.69k
      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
8.69k
      if (rfc2231_params[j].extended)
345
3.48k
        val = rfc2231_decode_charset_value(val, &charset);
346
8.69k
      str_append(str, val);
347
8.69k
    }
348
6.87k
    key = rfc2231_params[i].key;
349
6.87k
    result_append(&result, key, t_strdup(str_c(str)));
350
6.87k
  }
351
5.76k
  array_append_zero(&result); /* NULL-terminate */
352
5.76k
  *result_r = array_front(&result);
353
5.76k
  return broken ? -1 : 0;
354
35.8k
}