• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

neomutt / neomutt / 17366932035

31 Aug 2025 12:06PM UTC coverage: 50.099% (+0.05%) from 50.049%
17366932035

push

github

web-flow
tweak observer event types (#4023)

9132 of 18228 relevant lines covered (50.1%)

272.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

2.72
/email/rfc2231.c
1
/**
2
 * @file
3
 * RFC2231 MIME Charset routines
4
 *
5
 * @authors
6
 * Copyright (C) 2017-2023 Richard Russon <rich@flatcap.org>
7
 * Copyright (C) 2018 Federico Kircheis <federico.kircheis@gmail.com>
8
 * Copyright (C) 2018-2021 Pietro Cerutti <gahr@gahr.ch>
9
 *
10
 * @copyright
11
 * This program is free software: you can redistribute it and/or modify it under
12
 * the terms of the GNU General Public License as published by the Free Software
13
 * Foundation, either version 2 of the License, or (at your option) any later
14
 * version.
15
 *
16
 * This program is distributed in the hope that it will be useful, but WITHOUT
17
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
19
 * details.
20
 *
21
 * You should have received a copy of the GNU General Public License along with
22
 * this program.  If not, see <http://www.gnu.org/licenses/>.
23
 */
24

25
/**
26
 * @page email_rfc2231 RFC2231 MIME Charset functions
27
 *
28
 * RFC2231 MIME Charset routines
29
 *
30
 * Yet another MIME encoding for header data.  This time, it's parameters,
31
 * specified in RFC2231, and modelled after the encoding used in URLs.
32
 *
33
 * Additionally, continuations and encoding are mixed in an, errrm, interesting
34
 * manner.
35
 */
36

37
#include "config.h"
38
#include <limits.h>
39
#include <stdbool.h>
40
#include <string.h>
41
#include "mutt/lib.h"
42
#include "config/lib.h"
43
#include "core/lib.h"
44
#include "rfc2231.h"
45
#include "mime.h"
46
#include "parameter.h"
47
#include "rfc2047.h"
48

49
/**
50
 * struct Rfc2231Parameter - MIME section parameter
51
 */
52
struct Rfc2231Parameter
53
{
54
  char *attribute;               ///< Attribute name
55
  char *value;                   ///< Attribute value
56
  int index;                     ///< Index number in the list
57
  bool encoded;                  ///< Is the value encoded?
58
  struct Rfc2231Parameter *next; ///< Linked list
59
};
60

61
/**
62
 * purge_empty_parameters - Remove any ill-formed Parameters from a list
63
 * @param pl Parameter List to check
64
 */
65
static void purge_empty_parameters(struct ParameterList *pl)
×
66
{
67
  struct Parameter *np = NULL, *tmp = NULL;
68
  TAILQ_FOREACH_SAFE(np, pl, entries, tmp)
×
69
  {
70
    if (!np->attribute || !np->value)
×
71
    {
72
      TAILQ_REMOVE(pl, np, entries);
×
73
      mutt_param_free_one(&np);
×
74
    }
75
  }
76
}
×
77

78
/**
79
 * get_charset - Get the charset from an RFC2231 header
80
 * @param value   Header string
81
 * @param charset Buffer for the result
82
 * @param chslen  Length of buffer
83
 * @retval ptr First character after charset
84
 */
85
static char *get_charset(char *value, char *charset, size_t chslen)
×
86
{
87
  char *t = strchr(value, '\'');
×
88
  if (!t)
×
89
  {
90
    charset[0] = '\0';
×
91
    return value;
×
92
  }
93

94
  *t = '\0';
×
95
  mutt_str_copy(charset, value, chslen);
×
96

97
  char *u = strchr(t + 1, '\'');
×
98
  if (u)
×
99
    return u + 1;
×
100
  return t + 1;
101
}
102

103
/**
104
 * decode_one - Decode one percent-encoded character
105
 * @param[out] dest Where to save the result
106
 * @param[in]  src  Source string
107
 */
108
static void decode_one(char *dest, char *src)
×
109
{
110
  char *d = NULL;
111

112
  for (d = dest; *src; src++)
×
113
  {
114
    if ((src[0] == '%') && mutt_isxdigit(src[1]) &&
×
115
        mutt_isxdigit(src[2]))
×
116
    {
117
      *d++ = (hexval(src[1]) << 4) | hexval(src[2]);
×
118
      src += 2;
×
119
    }
120
    else
121
    {
122
      *d++ = *src;
×
123
    }
124
  }
125

126
  *d = '\0';
×
127
}
×
128

129
/**
130
 * parameter_new - Create a new Rfc2231Parameter
131
 * @retval ptr Newly allocated Rfc2231Parameter
132
 */
133
static struct Rfc2231Parameter *parameter_new(void)
134
{
135
  return MUTT_MEM_CALLOC(1, struct Rfc2231Parameter);
×
136
}
137

138
/**
139
 * list_insert - Insert parameter into an ordered list
140
 * @param[out] list List to insert into
141
 * @param[in]  par  Parameter to insert
142
 *
143
 * Primary sorting key: attribute
144
 * Secondary sorting key: index
145
 */
146
static void list_insert(struct Rfc2231Parameter **list, struct Rfc2231Parameter *par)
×
147
{
148
  struct Rfc2231Parameter **last = list;
149
  struct Rfc2231Parameter *p = *list;
×
150

151
  while (p)
×
152
  {
153
    const int c = mutt_str_cmp(par->attribute, p->attribute);
×
154
    if ((c < 0) || ((c == 0) && (par->index <= p->index)))
×
155
      break;
156

157
    last = &p->next;
×
158
    p = p->next;
×
159
  }
160

161
  par->next = p;
×
162
  *last = par;
×
163
}
×
164

165
/**
166
 * parameter_free - Free an Rfc2231Parameter
167
 * @param[out] ptr Rfc2231Parameter to free
168
 */
169
static void parameter_free(struct Rfc2231Parameter **ptr)
×
170
{
171
  if (!ptr || !*ptr)
×
172
    return;
173

174
  struct Rfc2231Parameter *p = *ptr;
175
  FREE(&p->attribute);
×
176
  FREE(&p->value);
×
177

178
  FREE(ptr);
×
179
}
180

181
/**
182
 * join_continuations - Process continuation parameters
183
 * @param pl  Parameter List for the results
184
 * @param par Continuation Parameter
185
 */
186
static void join_continuations(struct ParameterList *pl, struct Rfc2231Parameter *par)
×
187
{
188
  char attribute[256] = { 0 };
×
189
  char charset[256] = { 0 };
×
190

191
  const char *const c_charset = cc_charset();
×
192
  while (par)
×
193
  {
194
    char *value = NULL;
×
195
    size_t l = 0;
196

197
    mutt_str_copy(attribute, par->attribute, sizeof(attribute));
×
198

199
    const bool encoded = par->encoded;
×
200
    char *valp = NULL;
201
    if (encoded)
×
202
      valp = get_charset(par->value, charset, sizeof(charset));
×
203
    else
204
      valp = par->value;
×
205

206
    do
207
    {
208
      if (encoded && par->encoded)
×
209
        decode_one(par->value, valp);
×
210

211
      const size_t vl = strlen(par->value);
×
212

213
      MUTT_MEM_REALLOC(&value, l + vl + 1, char);
×
214
      strcpy(value + l, par->value);
×
215
      l += vl;
216

217
      struct Rfc2231Parameter *q = par->next;
×
218
      parameter_free(&par);
×
219
      par = q;
×
220
      if (par)
×
221
        valp = par->value;
×
222
    } while (par && (mutt_str_equal(par->attribute, attribute)));
×
223

224
    if (encoded)
×
225
    {
226
      mutt_ch_convert_string(&value, charset, c_charset, MUTT_ICONV_HOOK_FROM);
×
227
      mutt_mb_filter_unprintable(&value);
×
228
    }
229

230
    struct Parameter *np = mutt_param_new();
×
231
    np->attribute = mutt_str_dup(attribute);
×
232
    np->value = value;
×
233
    TAILQ_INSERT_HEAD(pl, np, entries);
×
234
  }
235
}
×
236

237
/**
238
 * rfc2231_decode_parameters - Decode a Parameter list
239
 * @param pl List to decode
240
 */
241
void rfc2231_decode_parameters(struct ParameterList *pl)
1✔
242
{
243
  if (!pl)
1✔
244
    return;
1✔
245

246
  struct Rfc2231Parameter *conthead = NULL;
×
247
  struct Rfc2231Parameter *conttmp = NULL;
248

249
  char *s = NULL, *t = NULL;
250
  char charset[256] = { 0 };
×
251

252
  bool encoded;
253
  int index;
254
  bool dirty = false; /* set when we may have created empty parameters. */
255

256
  purge_empty_parameters(pl);
×
257

258
  struct Parameter *np = NULL, *tmp = NULL;
×
259
  const bool c_rfc2047_parameters = cs_subset_bool(NeoMutt->sub, "rfc2047_parameters");
×
260
  const struct Slist *c_assumed_charset = cc_assumed_charset();
×
261
  const char *c_charset = cc_charset();
×
262

263
  TAILQ_FOREACH_SAFE(np, pl, entries, tmp)
×
264
  {
265
    s = strchr(np->attribute, '*');
×
266
    if (!s)
×
267
    {
268
      /* Single value, non encoded:
269
       *   attr=value
270
       */
271
      /* Using RFC2047 encoding in MIME parameters is explicitly
272
       * forbidden by that document.  Nevertheless, it's being
273
       * generated by some software, including certain Lotus Notes to
274
       * Internet Gateways.  So we actually decode it.  */
275

276
      if (c_rfc2047_parameters && np->value && strstr(np->value, "=?"))
×
277
      {
278
        rfc2047_decode(&np->value);
×
279
      }
280
      else if (!slist_is_empty(c_assumed_charset))
×
281
      {
282
        mutt_ch_convert_nonmime_string(c_assumed_charset, c_charset, &np->value);
×
283
      }
284
    }
285
    else if (s[1] == '\0')
×
286
    {
287
      /* Single value with encoding:
288
       *   attr*=us-ascii''the%20value
289
       */
290
      s[0] = '\0';
×
291

292
      s = get_charset(np->value, charset, sizeof(charset));
×
293
      decode_one(np->value, s);
×
294
      mutt_ch_convert_string(&np->value, charset, c_charset, MUTT_ICONV_HOOK_FROM);
×
295
      mutt_mb_filter_unprintable(&np->value);
×
296
      dirty = true;
297
    }
298
    else
299
    {
300
      /* A parameter continuation, which may or may not be encoded:
301
       *   attr*0=value
302
       *     -or-
303
       *   attr*0*=us-ascii''the%20value
304
       */
305
      s[0] = '\0';
×
306
      s++; /* let s point to the first character of index. */
×
307
      for (t = s; (t[0] != '\0') && mutt_isdigit(t[0]); t++)
×
308
        ; // do nothing
309

310
      encoded = (t[0] == '*');
×
311
      t[0] = '\0';
×
312

313
      /* RFC2231 says that the index starts at 0 and increments by 1,
314
       * thus an overflow should never occur in a valid message, thus
315
       * the value INT_MAX in case of overflow does not really matter
316
       * (the goal is just to avoid undefined behaviour). */
317
      if (!mutt_str_atoi_full(s, &index))
×
318
        index = INT_MAX;
×
319

320
      conttmp = parameter_new();
321
      conttmp->attribute = np->attribute;
×
322
      conttmp->value = np->value;
×
323
      conttmp->encoded = encoded;
×
324
      conttmp->index = index;
×
325

326
      np->attribute = NULL;
×
327
      np->value = NULL;
×
328
      TAILQ_REMOVE(pl, np, entries);
×
329
      FREE(&np);
×
330

331
      list_insert(&conthead, conttmp);
×
332
    }
333
  }
334

335
  if (conthead)
×
336
  {
337
    join_continuations(pl, conthead);
×
338
    dirty = true;
339
  }
340

341
  if (dirty)
×
342
    purge_empty_parameters(pl);
×
343
}
344

345
/**
346
 * rfc2231_encode_string - Encode a string to be suitable for an RFC2231 header
347
 * @param head      String encoded as a ParameterList, empty on error
348
 * @param attribute Name of attribute to encode
349
 * @param value     Value of attribute to encode
350
 * @retval num Number of Parameters in the List
351
 *
352
 * If the value is large, the list will contain continuation lines.
353
 */
354
size_t rfc2231_encode_string(struct ParameterList *head, const char *attribute, char *value)
2✔
355
{
356
  if (!attribute || !value)
2✔
357
    return 0;
358

359
  size_t count = 0;
360
  bool encode = false;
361
  bool add_quotes = false;
362
  bool free_src_value = false;
363
  bool split = false;
364
  int continuation_number = 0;
365
  size_t dest_value_len = 0, max_value_len = 0, cur_value_len = 0;
366
  char *cur = NULL, *charset = NULL, *src_value = NULL;
×
367
  struct Parameter *current = NULL;
368

369
  struct Buffer *cur_attribute = buf_pool_get();
×
370
  struct Buffer *cur_value = buf_pool_get();
×
371

372
  // Perform charset conversion
373
  for (cur = value; *cur; cur++)
×
374
  {
375
    if ((*cur < 0x20) || (*cur >= 0x7f))
×
376
    {
377
      encode = true;
378
      break;
379
    }
380
  }
381

382
  if (encode)
×
383
  {
384
    const struct Slist *const c_send_charset = cs_subset_slist(NeoMutt->sub, "send_charset");
×
385
    const char *c_charset = cc_charset();
×
386
    if (c_charset && c_send_charset)
×
387
    {
388
      charset = mutt_ch_choose(c_charset, c_send_charset, value,
×
389
                               mutt_str_len(value), &src_value, NULL);
390
    }
391
    if (src_value)
×
392
      free_src_value = true;
393
    if (!charset)
×
394
      charset = mutt_str_dup(c_charset ? c_charset : "unknown-8bit");
×
395
  }
396
  if (!src_value)
×
397
    src_value = value;
×
398

399
  // Count the size the resultant value will need in total
400
  if (encode)
×
401
    dest_value_len = mutt_str_len(charset) + 2; /* charset'' prefix */
×
402

403
  for (cur = src_value; *cur; cur++)
×
404
  {
405
    dest_value_len++;
×
406

407
    if (encode)
×
408
    {
409
      /* These get converted to %xx so need a total of three chars */
410
      if ((*cur < 0x20) || (*cur >= 0x7f) || strchr(MimeSpecials, *cur) ||
×
411
          strchr("*'%", *cur))
×
412
      {
413
        dest_value_len += 2;
×
414
      }
415
    }
416
    else
417
    {
418
      /* rfc822_cat() will add outer quotes if it finds MimeSpecials. */
419
      if (!add_quotes && strchr(MimeSpecials, *cur))
×
420
        add_quotes = true;
421
      /* rfc822_cat() will add a backslash if it finds '\' or '"'. */
422
      if ((*cur == '\\') || (*cur == '"'))
×
423
        dest_value_len++;
×
424
    }
425
  }
426

427
  // Determine if need to split into parameter value continuations
428
  max_value_len = 78 -                      // rfc suggested line length
×
429
                  1 -                       // Leading tab on continuation line
430
                  mutt_str_len(attribute) - // attribute
×
431
                  (encode ? 1 : 0) -        // '*' encoding marker
×
432
                  1 -                       // '='
×
433
                  (add_quotes ? 2 : 0) -    // "...."
×
434
                  1;                        // ';'
435

436
  if (max_value_len < 30)
×
437
    max_value_len = 30;
438

439
  if (dest_value_len > max_value_len)
×
440
  {
441
    split = true;
442
    max_value_len -= 4; /* '*n' continuation number and extra encoding
×
443
                         * space to keep loop below simpler */
444
  }
445

446
  // Generate list of parameter continuations
447
  cur = src_value;
×
448
  if (encode)
×
449
  {
450
    buf_printf(cur_value, "%s''", charset);
×
451
    cur_value_len = buf_len(cur_value);
×
452
  }
453

454
  while (*cur)
×
455
  {
456
    current = mutt_param_new();
×
457
    TAILQ_INSERT_TAIL(head, current, entries);
×
458
    count++;
×
459

460
    buf_strcpy(cur_attribute, attribute);
×
461
    if (split)
×
462
      buf_add_printf(cur_attribute, "*%d", continuation_number++);
×
463
    if (encode)
×
464
      buf_addch(cur_attribute, '*');
×
465

466
    while (*cur && (!split || (cur_value_len < max_value_len)))
×
467
    {
468
      if (encode)
×
469
      {
470
        if ((*cur < 0x20) || (*cur >= 0x7f) || strchr(MimeSpecials, *cur) ||
×
471
            strchr("*'%", *cur))
×
472
        {
473
          buf_add_printf(cur_value, "%%%02X", (unsigned char) *cur);
×
474
          cur_value_len += 3;
×
475
        }
476
        else
477
        {
478
          buf_addch(cur_value, *cur);
×
479
          cur_value_len++;
×
480
        }
481
      }
482
      else
483
      {
484
        buf_addch(cur_value, *cur);
×
485
        cur_value_len++;
×
486
        if ((*cur == '\\') || (*cur == '"'))
×
487
          cur_value_len++;
×
488
      }
489

490
      cur++;
×
491
    }
492

493
    current->attribute = buf_strdup(cur_attribute);
×
494
    current->value = buf_strdup(cur_value);
×
495

496
    buf_reset(cur_value);
×
497
    cur_value_len = 0;
498
  }
499

500
  buf_pool_release(&cur_attribute);
×
501
  buf_pool_release(&cur_value);
×
502

503
  FREE(&charset);
×
504
  if (free_src_value)
×
505
    FREE(&src_value);
×
506

507
  return count;
508
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc