• 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

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

27
/**
28
 * @page email_rfc2047 RFC2047 encoding / decoding
29
 *
30
 * RFC2047 MIME extensions encoding / decoding routines.
31
 */
32

33
#include "config.h"
34
#include <errno.h>
35
#include <iconv.h>
36
#include <stdbool.h>
37
#include <string.h>
38
#include "mutt/lib.h"
39
#include "address/lib.h"
40
#include "config/lib.h"
41
#include "core/lib.h"
42
#include "rfc2047.h"
43
#include "envelope.h"
44
#include "mime.h"
45

46
#define ENCWORD_LEN_MAX 75
47
#define ENCWORD_LEN_MIN 9 /* strlen ("=?.?.?.?=") */
48

49
#define HSPACE(ch) (((ch) == '\0') || ((ch) == ' ') || ((ch) == '\t'))
50

51
#define CONTINUATION_BYTE(ch) (((ch) & 0xc0) == 0x80)
52

53
/**
54
 * @defgroup encoder_api Mime Encoder API
55
 *
56
 * Prototype for an encoding function
57
 *
58
 * @param res    Buffer for the result
59
 * @param src    String to encode
60
 * @param srclen Length of string to encode
61
 * @param tocode Character encoding
62
 * @retval num Bytes written to buffer
63
 */
64
typedef size_t (*encoder_t)(char *res, const char *buf, size_t buflen, const char *tocode);
65

66
/**
67
 * b_encoder - Base64 Encode a string - Implements ::encoder_t - @ingroup encoder_api
68
 */
69
static size_t b_encoder(char *res, const char *src, size_t srclen, const char *tocode)
4✔
70
{
71
  char *s0 = res;
72

73
  memcpy(res, "=?", 2);
74
  res += 2;
4✔
75
  memcpy(res, tocode, strlen(tocode));
4✔
76
  res += strlen(tocode);
4✔
77
  memcpy(res, "?B?", 3);
78
  res += 3;
4✔
79

80
  while (srclen)
19✔
81
  {
82
    char encoded[11] = { 0 };
15✔
83
    size_t rc;
84
    size_t in_len = MIN(3, srclen);
15✔
85

86
    rc = mutt_b64_encode(src, in_len, encoded, sizeof(encoded));
15✔
87
    for (size_t i = 0; i < rc; i++)
75✔
88
      *res++ = encoded[i];
60✔
89

90
    srclen -= in_len;
15✔
91
    src += in_len;
15✔
92
  }
93

94
  memcpy(res, "?=", 2);
95
  res += 2;
4✔
96
  return res - s0;
4✔
97
}
98

99
/**
100
 * q_encoder - Quoted-printable Encode a string - Implements ::encoder_t - @ingroup encoder_api
101
 */
102
static size_t q_encoder(char *res, const char *src, size_t srclen, const char *tocode)
3✔
103
{
104
  static const char hex[] = "0123456789ABCDEF";
105
  char *s0 = res;
106

107
  memcpy(res, "=?", 2);
108
  res += 2;
3✔
109
  memcpy(res, tocode, strlen(tocode));
3✔
110
  res += strlen(tocode);
3✔
111
  memcpy(res, "?Q?", 3);
112
  res += 3;
3✔
113
  while (srclen--)
77✔
114
  {
115
    unsigned char c = *src++;
74✔
116
    if (c == ' ')
74✔
117
    {
118
      *res++ = '_';
5✔
119
    }
120
    else if ((c >= 0x7f) || (c < 0x20) || (c == '_') || strchr(MimeSpecials, c))
69✔
121
    {
122
      *res++ = '=';
12✔
123
      *res++ = hex[(c & 0xf0) >> 4];
12✔
124
      *res++ = hex[c & 0x0f];
12✔
125
    }
126
    else
127
    {
128
      *res++ = c;
57✔
129
    }
130
  }
131
  memcpy(res, "?=", 2);
132
  res += 2;
3✔
133
  return res - s0;
3✔
134
}
135

136
/**
137
 * parse_encoded_word - Parse a string and report RFC2047 elements
138
 * @param[in]  str        String to parse
139
 * @param[out] enc        Content encoding found in the first RFC2047 word
140
 * @param[out] charset    Charset found in the first RFC2047 word
141
 * @param[out] charsetlen Length of the charset string found
142
 * @param[out] text       Start of the first RFC2047 encoded text
143
 * @param[out] textlen    Length of the encoded text found
144
 * @retval ptr Start of the RFC2047 encoded word
145
 * @retval NULL None was found
146
 */
147
static char *parse_encoded_word(char *str, enum ContentEncoding *enc, char **charset,
26✔
148
                                size_t *charsetlen, char **text, size_t *textlen)
149
{
150
  regmatch_t *match = mutt_prex_capture(PREX_RFC2047_ENCODED_WORD, str);
26✔
151
  if (!match)
26✔
152
    return NULL;
153

154
  const regmatch_t *mfull = &match[PREX_RFC2047_ENCODED_WORD_MATCH_FULL];
155
  const regmatch_t *mcharset = &match[PREX_RFC2047_ENCODED_WORD_MATCH_CHARSET];
156
  const regmatch_t *mencoding = &match[PREX_RFC2047_ENCODED_WORD_MATCH_ENCODING];
157
  const regmatch_t *mtext = &match[PREX_RFC2047_ENCODED_WORD_MATCH_TEXT];
158

159
  /* Charset */
160
  *charset = str + mutt_regmatch_start(mcharset);
17✔
161
  *charsetlen = mutt_regmatch_len(mcharset);
17✔
162

163
  /* Encoding: either Q or B */
164
  *enc = (mutt_tolower(str[mutt_regmatch_start(mencoding)]) == 'q') ? ENC_QUOTED_PRINTABLE : ENC_BASE64;
23✔
165

166
  *text = str + mutt_regmatch_start(mtext);
17✔
167
  *textlen = mutt_regmatch_len(mtext);
17✔
168
  return str + mutt_regmatch_start(mfull);
17✔
169
}
170

171
/**
172
 * try_block - Attempt to convert a block of text
173
 * @param d        String to convert
174
 * @param dlen     Length of string
175
 * @param fromcode Original encoding
176
 * @param tocode   New encoding
177
 * @param encoder  Encoding function
178
 * @param wlen     Number of characters converted
179
 * @retval  0 Success, string converted
180
 * @retval >0 Error, number of bytes that could be converted
181
 *
182
 * If the data could be converted using encoder, then set *encoder and *wlen.
183
 * Otherwise return an upper bound on the maximum length of the data which
184
 * could be converted.
185
 *
186
 * The data is converted from fromcode (which must be stateless) to tocode,
187
 * unless fromcode is NULL, in which case the data is assumed to be already in
188
 * tocode, which should be 8-bit and stateless.
189
 */
190
static size_t try_block(const char *d, size_t dlen, const char *fromcode,
19✔
191
                        const char *tocode, encoder_t *encoder, size_t *wlen)
192
{
193
  char buf[ENCWORD_LEN_MAX - ENCWORD_LEN_MIN + 1];
194
  const char *ib = NULL;
19✔
195
  char *ob = NULL;
19✔
196
  size_t ibl, obl;
197
  int count, len, len_b, len_q;
198

199
  if (fromcode)
19✔
200
  {
201
    iconv_t cd = mutt_ch_iconv_open(tocode, fromcode, MUTT_ICONV_NO_FLAGS);
19✔
202
    ASSERT(iconv_t_valid(cd));
19✔
203
    ib = d;
19✔
204
    ibl = dlen;
19✔
205
    ob = buf;
19✔
206
    obl = sizeof(buf) - strlen(tocode);
19✔
207
    if ((iconv(cd, (ICONV_CONST char **) &ib, &ibl, &ob, &obl) == ICONV_ILLEGAL_SEQ) ||
38✔
208
        (iconv(cd, NULL, NULL, &ob, &obl) == ICONV_ILLEGAL_SEQ))
19✔
209
    {
210
      ASSERT(errno == E2BIG);
×
211
      ASSERT(ib > d);
×
212
      return ((ib - d) == dlen) ? dlen : ib - d + 1;
×
213
    }
214
  }
215
  else
216
  {
217
    if (dlen > (sizeof(buf) - strlen(tocode)))
×
218
      return sizeof(buf) - strlen(tocode) + 1;
×
219
    memcpy(buf, d, dlen);
220
    ob = buf + dlen;
×
221
  }
222

223
  count = 0;
224
  for (char *p = buf; p < ob; p++)
612✔
225
  {
226
    unsigned char c = *p;
593✔
227
    ASSERT(strchr(MimeSpecials, '?'));
593✔
228
    if ((c >= 0x7f) || (c < 0x20) || (*p == '_') ||
593✔
229
        ((c != ' ') && strchr(MimeSpecials, *p)))
407✔
230
    {
231
      count++;
132✔
232
    }
233
  }
234

235
  len = ENCWORD_LEN_MIN - 2 + strlen(tocode);
19✔
236
  len_b = len + (((ob - buf) + 2) / 3) * 4;
19✔
237
  len_q = len + (ob - buf) + 2 * count;
19✔
238

239
  /* Apparently RFC1468 says to use B encoding for iso-2022-jp. */
240
  if (mutt_istr_equal(tocode, "ISO-2022-JP"))
19✔
241
    len_q = ENCWORD_LEN_MAX + 1;
242

243
  if ((len_b < len_q) && (len_b <= ENCWORD_LEN_MAX))
19✔
244
  {
245
    *encoder = b_encoder;
6✔
246
    *wlen = len_b;
6✔
247
    return 0;
6✔
248
  }
249
  else if (len_q <= ENCWORD_LEN_MAX)
13✔
250
  {
251
    *encoder = q_encoder;
4✔
252
    *wlen = len_q;
4✔
253
    return 0;
4✔
254
  }
255
  else
256
  {
257
    return dlen;
258
  }
259
}
260

261
/**
262
 * encode_block - Encode a block of text using an encoder
263
 * @param str      String to convert
264
 * @param buf      Buffer for result
265
 * @param buflen   Buffer length
266
 * @param fromcode Original encoding
267
 * @param tocode   New encoding
268
 * @param encoder  Encoding function
269
 * @retval num Length of the encoded word
270
 *
271
 * Encode the data (buf, buflen) into str using the encoder.
272
 */
273
static size_t encode_block(char *str, char *buf, size_t buflen, const char *fromcode,
7✔
274
                           const char *tocode, encoder_t encoder)
275
{
276
  if (!fromcode)
7✔
277
  {
278
    return (*encoder)(str, buf, buflen, tocode);
×
279
  }
280

281
  const iconv_t cd = mutt_ch_iconv_open(tocode, fromcode, MUTT_ICONV_NO_FLAGS);
7✔
282
  ASSERT(iconv_t_valid(cd));
7✔
283
  const char *ib = buf;
7✔
284
  size_t ibl = buflen;
7✔
285
  char tmp[ENCWORD_LEN_MAX - ENCWORD_LEN_MIN + 1];
286
  char *ob = tmp;
7✔
287
  size_t obl = sizeof(tmp) - strlen(tocode);
7✔
288
  const size_t n1 = iconv(cd, (ICONV_CONST char **) &ib, &ibl, &ob, &obl);
7✔
289
  const size_t n2 = iconv(cd, NULL, NULL, &ob, &obl);
7✔
290
  ASSERT((n1 != ICONV_ILLEGAL_SEQ) && (n2 != ICONV_ILLEGAL_SEQ));
7✔
291
  return (*encoder)(str, tmp, ob - tmp, tocode);
7✔
292
}
293

294
/**
295
 * choose_block - Calculate how much data can be converted
296
 * @param d        String to convert
297
 * @param dlen     Length of string
298
 * @param col      Starting column to convert
299
 * @param fromcode Original encoding
300
 * @param tocode   New encoding
301
 * @param encoder  Encoding function
302
 * @param wlen     Number of characters converted
303
 * @retval num Bytes that can be converted
304
 *
305
 * Discover how much of the data (d, dlen) can be converted into a single
306
 * encoded word. Return how much data can be converted, and set the length
307
 * *wlen of the encoded word and *encoder.  We start in column col, which
308
 * limits the length of the word.
309
 */
310
static size_t choose_block(char *d, size_t dlen, int col, const char *fromcode,
7✔
311
                           const char *tocode, encoder_t *encoder, size_t *wlen)
312
{
313
  const bool utf8 = fromcode && mutt_istr_equal(fromcode, "utf-8");
7✔
314

315
  size_t n = dlen;
316
  while (true)
317
  {
318
    ASSERT(n > 0);
16✔
319
    const size_t nn = try_block(d, n, fromcode, tocode, encoder, wlen);
16✔
320
    if ((nn == 0) && (((col + *wlen) <= (ENCWORD_LEN_MAX + 1)) || (n <= 1)))
16✔
321
      break;
322
    n = ((nn != 0) ? nn : n) - 1;
9✔
323
    ASSERT(n > 0);
9✔
324
    if (utf8)
9✔
325
      while ((n > 1) && CONTINUATION_BYTE(d[n]))
10✔
326
        n--;
1✔
327
  }
328
  return n;
7✔
329
}
330

331
/**
332
 * finalize_chunk - Perform charset conversion and filtering
333
 * @param[out] res        Buffer where the resulting string is appended
334
 * @param[in]  buf        Buffer with the input string
335
 * @param[in]  charset    Charset to use for the conversion
336
 * @param[in]  charsetlen Length of the charset parameter
337
 *
338
 * The buffer buf is reinitialized at the end of this function.
339
 */
340
static void finalize_chunk(struct Buffer *res, struct Buffer *buf, char *charset, size_t charsetlen)
12✔
341
{
342
  if (!charset)
12✔
343
    return;
344
  char end = charset[charsetlen];
12✔
345
  charset[charsetlen] = '\0';
12✔
346
  mutt_ch_convert_string(&buf->data, charset, cc_charset(), MUTT_ICONV_HOOK_FROM);
12✔
347
  charset[charsetlen] = end;
12✔
348
  mutt_mb_filter_unprintable(&buf->data);
12✔
349
  buf_addstr(res, buf->data);
12✔
350
  FREE(&buf->data);
12✔
351
  buf_init(buf);
12✔
352
}
353

354
/**
355
 * decode_word - Decode an RFC2047-encoded string
356
 * @param s   String to decode
357
 * @param len Length of the string
358
 * @param enc Encoding type
359
 * @retval ptr Decoded string
360
 *
361
 * @note The input string must be NUL-terminated; the len parameter is
362
 *       an optimization. The caller must free the returned string.
363
 */
364
static char *decode_word(const char *s, size_t len, enum ContentEncoding enc)
15✔
365
{
366
  const char *it = s;
367
  const char *end = s + len;
15✔
368

369
  ASSERT(*end == '\0');
15✔
370

371
  if (enc == ENC_QUOTED_PRINTABLE)
15✔
372
  {
373
    struct Buffer *buf = buf_pool_get();
9✔
374
    for (; it < end; it++)
208✔
375
    {
376
      if (*it == '_')
199✔
377
      {
378
        buf_addch(buf, ' ');
9✔
379
      }
380
      else if ((it[0] == '=') && (!(it[1] & ~127) && (hexval(it[1]) != -1)) &&
190✔
381
               (!(it[2] & ~127) && (hexval(it[2]) != -1)))
40✔
382
      {
383
        buf_addch(buf, (hexval(it[1]) << 4) | hexval(it[2]));
40✔
384
        it += 2;
40✔
385
      }
386
      else
387
      {
388
        buf_addch(buf, *it);
150✔
389
      }
390
    }
391
    char *str = buf_strdup(buf);
9✔
392
    buf_pool_release(&buf);
9✔
393
    return str;
394
  }
395
  else if (enc == ENC_BASE64)
6✔
396
  {
397
    const int olen = 3 * len / 4 + 1;
6✔
398
    char *out = MUTT_MEM_MALLOC(olen, char);
6✔
399
    int dlen = mutt_b64_decode(it, out, olen);
6✔
400
    if (dlen == -1)
6✔
401
    {
402
      FREE(&out);
×
403
      return NULL;
×
404
    }
405
    out[dlen] = '\0';
6✔
406
    return out;
6✔
407
  }
408

409
  ASSERT(0); /* The enc parameter has an invalid value */
×
410
  return NULL;
411
}
412

413
/**
414
 * encode - RFC2047-encode a string
415
 * @param[in]  d        String to convert
416
 * @param[in]  dlen     Length of string
417
 * @param[in]  col      Starting column to convert
418
 * @param[in]  fromcode Original encoding
419
 * @param[in]  charsets List of allowable encodings (colon separated)
420
 * @param[out] e        Encoded string
421
 * @param[out] elen     Length of encoded string
422
 * @param[in]  specials Special characters to be encoded
423
 * @retval 0 Success
424
 */
425
static int encode(const char *d, size_t dlen, int col, const char *fromcode,
6✔
426
                  const struct Slist *charsets, char **e, size_t *elen, const char *specials)
427
{
428
  int rc = 0;
429
  char *buf = NULL;
6✔
430
  size_t bufpos, buflen;
431
  char *t0 = NULL, *t1 = NULL, *t = NULL;
432
  char *s0 = NULL, *s1 = NULL;
433
  size_t ulen, r, wlen = 0;
6✔
434
  encoder_t encoder = NULL;
6✔
435
  char *tocode1 = NULL;
6✔
436
  const char *tocode = NULL;
437
  const char *icode = "utf-8";
438

439
  /* Try to convert to UTF-8. */
440
  char *u = mutt_strn_dup(d, dlen);
6✔
441
  if (mutt_ch_convert_string(&u, fromcode, icode, MUTT_ICONV_NO_FLAGS) != 0)
6✔
442
  {
443
    rc = 1;
444
    icode = 0;
445
  }
446
  ulen = mutt_str_len(u);
6✔
447

448
  /* Find earliest and latest things we must encode. */
449
  s0 = 0;
450
  s1 = 0;
451
  t0 = 0;
452
  t1 = 0;
453
  for (t = u; t < (u + ulen); t++)
171✔
454
  {
455
    if ((*t & 0x80) || ((*t == '=') && (t[1] == '?') && ((t == u) || HSPACE(*(t - 1)))))
165✔
456
    {
457
      if (!t0)
50✔
458
        t0 = t;
459
      t1 = t;
460
    }
461
    else if (specials && *t && strchr(specials, *t))
115✔
462
    {
463
      if (!s0)
×
464
        s0 = t;
465
      s1 = t;
466
    }
467
  }
468

469
  /* If we have something to encode, include RFC822 specials */
470
  if (t0 && s0 && (s0 < t0))
6✔
471
    t0 = s0;
472
  if (t1 && s1 && (s1 > t1))
6✔
473
    t1 = s1;
474

475
  if (!t0)
6✔
476
  {
477
    /* No encoding is required. */
478
    *e = u;
×
479
    *elen = ulen;
×
480
    return rc;
×
481
  }
482

483
  /* Choose target charset. */
484
  tocode = fromcode;
485
  if (icode)
6✔
486
  {
487
    tocode1 = mutt_ch_choose(icode, charsets, u, ulen, 0, 0);
6✔
488
    if (tocode1)
6✔
489
    {
490
      tocode = tocode1;
491
    }
492
    else
493
    {
494
      rc = 2;
495
      icode = 0;
496
    }
497
  }
498

499
  /* Hack to avoid labelling 8-bit data as us-ascii. */
500
  if (!icode && mutt_ch_is_us_ascii(tocode))
6✔
501
    tocode = "unknown-8bit";
502

503
  /* Adjust t0 for maximum length of line. */
504
  t = u + (ENCWORD_LEN_MAX + 1) - col - ENCWORD_LEN_MIN;
6✔
505
  if (t < u)
6✔
506
    t = u;
507
  if (t < t0)
6✔
508
    t0 = t;
509

510
  /* Adjust t0 until we can encode a character after a space. */
511
  for (; t0 > u; t0--)
22✔
512
  {
513
    if (!HSPACE(*(t0 - 1)))
17✔
514
      continue;
16✔
515
    t = t0 + 1;
1✔
516
    if (icode)
1✔
517
      while ((t < (u + ulen)) && CONTINUATION_BYTE(*t))
3✔
518
        t++;
2✔
519
    if ((try_block(t0, t - t0, icode, tocode, &encoder, &wlen) == 0) &&
1✔
520
        ((col + (t0 - u) + wlen) <= (ENCWORD_LEN_MAX + 1)))
1✔
521
    {
522
      break;
523
    }
524
  }
525

526
  /* Adjust t1 until we can encode a character before a space. */
527
  for (; t1 < (u + ulen); t1++)
16✔
528
  {
529
    if (!HSPACE(*t1))
12✔
530
      continue;
10✔
531
    t = t1 - 1;
2✔
532
    if (icode)
2✔
533
      while (CONTINUATION_BYTE(*t))
4✔
534
        t--;
2✔
535
    if ((try_block(t, t1 - t, icode, tocode, &encoder, &wlen) == 0) &&
2✔
536
        ((1 + wlen + (u + ulen - t1)) <= (ENCWORD_LEN_MAX + 1)))
2✔
537
    {
538
      break;
539
    }
540
  }
541

542
  /* We shall encode the region [t0,t1). */
543

544
  /* Initialise the output buffer with the us-ascii prefix. */
545
  buflen = 2 * ulen;
6✔
546
  buf = MUTT_MEM_MALLOC(buflen, char);
6✔
547
  bufpos = t0 - u;
6✔
548
  memcpy(buf, u, t0 - u);
549

550
  col += t0 - u;
6✔
551

552
  t = t0;
553
  while (true)
554
  {
555
    /* Find how much we can encode. */
556
    size_t n = choose_block(t, t1 - t, col, icode, tocode, &encoder, &wlen);
7✔
557
    if (n == (t1 - t))
7✔
558
    {
559
      /* See if we can fit the us-ascii suffix, too. */
560
      if ((col + wlen + (u + ulen - t1)) <= (ENCWORD_LEN_MAX + 1))
6✔
561
        break;
562
      n = t1 - t - 1;
×
563
      if (icode)
×
564
        while (CONTINUATION_BYTE(t[n]))
×
565
          n--;
×
566
      if (n == 0)
×
567
      {
568
        /* This should only happen in the really stupid case where the
569
         * only word that needs encoding is one character long, but
570
         * there is too much us-ascii stuff after it to use a single
571
         * encoded word. We add the next word to the encoded region
572
         * and try again. */
573
        ASSERT(t1 < (u + ulen));
×
574
        for (t1++; (t1 < (u + ulen)) && !HSPACE(*t1); t1++)
×
575
          ; // do nothing
576

577
        continue;
×
578
      }
579
      n = choose_block(t, n, col, icode, tocode, &encoder, &wlen);
×
580
    }
581

582
    /* Add to output buffer. */
583
    const char *line_break = "\n\t";
584
    const int lb_len = 2; /* strlen(line_break) */
585

586
    if ((bufpos + wlen + lb_len) > buflen)
1✔
587
    {
588
      buflen = bufpos + wlen + lb_len;
589
      MUTT_MEM_REALLOC(&buf, buflen, char);
×
590
    }
591
    r = encode_block(buf + bufpos, t, n, icode, tocode, encoder);
1✔
592
    ASSERT(r == wlen);
1✔
593
    bufpos += wlen;
594
    memcpy(buf + bufpos, line_break, lb_len);
1✔
595
    bufpos += lb_len;
596

597
    col = 1;
598

599
    t += n;
1✔
600
  }
601

602
  /* Add last encoded word and us-ascii suffix to buffer. */
603
  buflen = bufpos + wlen + (u + ulen - t1);
6✔
604
  MUTT_MEM_REALLOC(&buf, buflen + 1, char);
6✔
605
  r = encode_block(buf + bufpos, t, t1 - t, icode, tocode, encoder);
6✔
606
  ASSERT(r == wlen);
6✔
607
  bufpos += wlen;
608
  memcpy(buf + bufpos, t1, u + ulen - t1);
6✔
609

610
  FREE(&tocode1);
6✔
611
  FREE(&u);
6✔
612

613
  buf[buflen] = '\0';
6✔
614

615
  *e = buf;
6✔
616
  *elen = buflen + 1;
6✔
617
  return rc;
6✔
618
}
619

620
/**
621
 * rfc2047_encode - RFC-2047-encode a string
622
 * @param[in,out] pd       String to be encoded, and resulting encoded string
623
 * @param[in]     specials Special characters to be encoded
624
 * @param[in]     col      Starting index in string
625
 * @param[in]     charsets List of charsets to choose from
626
 */
627
void rfc2047_encode(char **pd, const char *specials, int col, const struct Slist *charsets)
9✔
628
{
629
  if (!pd || !*pd)
9✔
630
    return;
3✔
631

632
  const char *const c_charset = cc_charset();
6✔
633
  if (!c_charset)
6✔
634
    return;
635

636
  struct Slist *fallback = NULL;
6✔
637
  if (!charsets)
6✔
638
  {
639
    fallback = slist_parse("utf-8", D_SLIST_SEP_COLON);
×
640
    charsets = fallback;
641
  }
642

643
  char *e = NULL;
6✔
644
  size_t elen = 0;
645
  encode(*pd, strlen(*pd), col, c_charset, charsets, &e, &elen, specials);
6✔
646

647
  slist_free(&fallback);
6✔
648
  FREE(pd);
6✔
649
  *pd = e;
6✔
650
}
651

652
/**
653
 * rfc2047_decode - Decode any RFC2047-encoded header fields
654
 * @param[in,out] pd  String to be decoded, and resulting decoded string
655
 *
656
 * Try to decode anything that looks like a valid RFC2047 encoded header field,
657
 * ignoring RFC822 parsing rules. If decoding fails, for example due to an
658
 * invalid base64 string, the original input is left untouched.
659
 */
660
void rfc2047_decode(char **pd)
22✔
661
{
662
  if (!pd || !*pd)
22✔
663
    return;
4✔
664

665
  struct Buffer *buf = buf_pool_get();  // Output buffer
18✔
666
  char *s = *pd;                        // Read pointer
18✔
667
  char *beg = NULL;                     // Begin of encoded word
668
  enum ContentEncoding enc = ENC_OTHER; // ENC_BASE64 or ENC_QUOTED_PRINTABLE
18✔
669
  char *charset = NULL;                 // Which charset
18✔
670
  size_t charsetlen;                    // Length of the charset
671
  char *text = NULL;                    // Encoded text
18✔
672
  size_t textlen = 0;                   // Length of encoded text
18✔
673

674
  /* Keep some state in case the next decoded word is using the same charset
675
   * and it happens to be split in the middle of a multibyte character.
676
   * See https://github.com/neomutt/neomutt/issues/1015 */
677
  struct Buffer *prev = buf_pool_get(); /* Previously decoded word  */
18✔
678
  char *prev_charset = NULL;  /* Previously used charset                */
679
  size_t prev_charsetlen = 0; /* Length of the previously used charset  */
680

681
  const struct Slist *c_assumed_charset = cc_assumed_charset();
18✔
682
  const char *c_charset = cc_charset();
18✔
683
  while (*s)
44✔
684
  {
685
    beg = parse_encoded_word(s, &enc, &charset, &charsetlen, &text, &textlen);
26✔
686
    if (beg != s)
26✔
687
    {
688
      /* Some non-encoded text was found */
689
      size_t holelen = beg ? beg - s : mutt_str_len(s);
12✔
690

691
      /* Ignore whitespace between encoded words */
692
      if (beg && (mutt_str_lws_len(s, holelen) == holelen))
12✔
693
      {
694
        s = beg;
695
        continue;
2✔
696
      }
697

698
      /* If we have some previously decoded text, add it now */
699
      if (!buf_is_empty(prev))
10✔
700
      {
701
        finalize_chunk(buf, prev, prev_charset, prev_charsetlen);
3✔
702
      }
703

704
      /* Add non-encoded part */
705
      if (slist_is_empty(c_assumed_charset))
10✔
706
      {
707
        buf_addstr_n(buf, s, holelen);
10✔
708
      }
709
      else
710
      {
711
        char *conv = mutt_strn_dup(s, holelen);
×
712
        mutt_ch_convert_nonmime_string(c_assumed_charset, c_charset, &conv);
×
713
        buf_addstr(buf, conv);
×
714
        FREE(&conv);
×
715
      }
716
      s += holelen;
10✔
717
    }
718
    if (beg)
24✔
719
    {
720
      /* Some encoded text was found */
721
      text[textlen] = '\0';
15✔
722
      char *decoded = decode_word(text, textlen, enc);
15✔
723
      if (!decoded)
15✔
724
      {
725
        goto done;
×
726
      }
727
      if (!buf_is_empty(prev) && ((prev_charsetlen != charsetlen) ||
18✔
728
                                  !mutt_strn_equal(prev_charset, charset, charsetlen)))
3✔
729
      {
730
        /* Different charset, convert the previous chunk and add it to the
731
         * final result */
732
        finalize_chunk(buf, prev, prev_charset, prev_charsetlen);
×
733
      }
734

735
      buf_addstr(prev, decoded);
15✔
736
      FREE(&decoded);
15✔
737
      prev_charset = charset;
15✔
738
      prev_charsetlen = charsetlen;
15✔
739
      s = text + textlen + 2; /* Skip final ?= */
15✔
740
    }
741
  }
742

743
  /* Save the last chunk */
744
  if (!buf_is_empty(prev))
18✔
745
  {
746
    finalize_chunk(buf, prev, prev_charset, prev_charsetlen);
9✔
747
  }
748

749
  FREE(pd);
18✔
750
  *pd = buf_strdup(buf);
18✔
751

752
done:
18✔
753
  buf_pool_release(&buf);
18✔
754
  buf_pool_release(&prev);
18✔
755
}
756

757
/**
758
 * rfc2047_encode_addrlist - Encode any RFC2047 headers, where required, in an Address list
759
 * @param al   AddressList
760
 * @param tag  Header tag (used for wrapping calculation)
761
 *
762
 * @note rfc2047_encode() may realloc the data pointer it's given,
763
 *       so work on a copy to avoid breaking the Buffer
764
 */
765
void rfc2047_encode_addrlist(struct AddressList *al, const char *tag)
2✔
766
{
767
  if (!al)
2✔
768
    return;
1✔
769

770
  int col = tag ? strlen(tag) + 2 : 32;
1✔
771
  struct Address *a = NULL;
772
  char *data = NULL;
1✔
773
  const struct Slist *const c_send_charset = cs_subset_slist(NeoMutt->sub, "send_charset");
1✔
774
  TAILQ_FOREACH(a, al, entries)
1✔
775
  {
776
    if (a->personal)
×
777
    {
778
      data = buf_strdup(a->personal);
×
779
      rfc2047_encode(&data, AddressSpecials, col, c_send_charset);
×
780
      buf_strcpy(a->personal, data);
×
781
      FREE(&data);
×
782
    }
783
    else if (a->group && a->mailbox)
×
784
    {
785
      data = buf_strdup(a->mailbox);
×
786
      rfc2047_encode(&data, AddressSpecials, col, c_send_charset);
×
787
      buf_strcpy(a->mailbox, data);
×
788
      FREE(&data);
×
789
    }
790
  }
791
}
792

793
/**
794
 * rfc2047_decode_addrlist - Decode any RFC2047 headers in an Address list
795
 * @param al AddressList
796
 *
797
 * @note rfc2047_decode() may realloc the data pointer it's given,
798
 *       so work on a copy to avoid breaking the Buffer
799
 */
800
void rfc2047_decode_addrlist(struct AddressList *al)
9✔
801
{
802
  if (!al)
9✔
803
    return;
1✔
804

805
  const bool assumed = !slist_is_empty(cc_assumed_charset());
8✔
806
  struct Address *a = NULL;
807
  char *data = NULL;
8✔
808
  TAILQ_FOREACH(a, al, entries)
11✔
809
  {
810
    if (a->personal && ((buf_find_string(a->personal, "=?")) || assumed))
3✔
811
    {
812
      data = buf_strdup(a->personal);
×
813
      rfc2047_decode(&data);
×
814
      buf_strcpy(a->personal, data);
×
815
      FREE(&data);
×
816
    }
817
    else if (a->group && a->mailbox && buf_find_string(a->mailbox, "=?"))
3✔
818
    {
819
      data = buf_strdup(a->mailbox);
×
820
      rfc2047_decode(&data);
×
821
      buf_strcpy(a->mailbox, data);
×
822
      FREE(&data);
×
823
    }
824
  }
825
}
826

827
/**
828
 * rfc2047_decode_envelope - Decode the fields of an Envelope
829
 * @param env Envelope
830
 */
831
void rfc2047_decode_envelope(struct Envelope *env)
2✔
832
{
833
  if (!env)
2✔
834
    return;
1✔
835
  rfc2047_decode_addrlist(&env->from);
1✔
836
  rfc2047_decode_addrlist(&env->to);
1✔
837
  rfc2047_decode_addrlist(&env->cc);
1✔
838
  rfc2047_decode_addrlist(&env->bcc);
1✔
839
  rfc2047_decode_addrlist(&env->reply_to);
1✔
840
  rfc2047_decode_addrlist(&env->mail_followup_to);
1✔
841
  rfc2047_decode_addrlist(&env->return_path);
1✔
842
  rfc2047_decode_addrlist(&env->sender);
1✔
843
  rfc2047_decode(&env->x_label);
1✔
844

845
  char *subj = env->subject;
1✔
846
  *(char **) &env->subject = NULL;
1✔
847
  rfc2047_decode(&subj);
1✔
848
  mutt_env_set_subject(env, subj);
1✔
849
  FREE(&subj);
1✔
850
}
851

852
/**
853
 * rfc2047_encode_envelope - Encode the fields of an Envelope
854
 * @param env Envelope
855
 */
856
void rfc2047_encode_envelope(struct Envelope *env)
1✔
857
{
858
  if (!env)
1✔
859
    return;
1✔
860
  rfc2047_encode_addrlist(&env->from, "From");
×
861
  rfc2047_encode_addrlist(&env->to, "To");
×
862
  rfc2047_encode_addrlist(&env->cc, "Cc");
×
863
  rfc2047_encode_addrlist(&env->bcc, "Bcc");
×
864
  rfc2047_encode_addrlist(&env->reply_to, "Reply-To");
×
865
  rfc2047_encode_addrlist(&env->mail_followup_to, "Mail-Followup-To");
×
866
  rfc2047_encode_addrlist(&env->sender, "Sender");
×
867
  const struct Slist *const c_send_charset = cs_subset_slist(NeoMutt->sub, "send_charset");
×
868
  rfc2047_encode(&env->x_label, NULL, sizeof("X-Label:"), c_send_charset);
×
869

870
  char *subj = env->subject;
×
871
  *(char **) &env->subject = NULL;
×
872
  rfc2047_encode(&subj, NULL, sizeof("Subject:"), c_send_charset);
×
873
  mutt_env_set_subject(env, subj);
×
874
  FREE(&subj);
×
875
}
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