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

neomutt / neomutt / 22049757745

15 Feb 2026 05:32PM UTC coverage: 42.487% (+0.3%) from 42.162%
22049757745

push

github

flatcap
merge: modules: encapsulate global data 2

 * mod: send init/cleanup/data
 * cmd: move my-header to send
 * send: encapsulate globals
 * mod: alias init/cleanup/data
 * cmd: move alternates,alias to alias
 * alias: encapsulate globals
 * cmd: move tag-formats,-transforms to email
 * email: encapsulate globals

102 of 138 new or added lines in 9 files covered. (73.91%)

2339 existing lines in 12 files now uncovered.

12020 of 28291 relevant lines covered (42.49%)

2766.45 hits per line

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

0.0
/email/handler.c
1
/**
2
 * @file
3
 * Decide how to display email content
4
 *
5
 * @authors
6
 * Copyright (C) 1996-2013 Michael R. Elkins <me@mutt.org>
7
 * Copyright (C) 2017-2022 Pietro Cerutti <gahr@gahr.ch>
8
 * Copyright (C) 2017-2026 Richard Russon <rich@flatcap.org>
9
 * Copyright (C) 2018 Federico Kircheis <federico.kircheis@gmail.com>
10
 * Copyright (C) 2018 Reis Radomil
11
 * Copyright (C) 2019 Ian Zimmerman <itz@no-use.mooo.com>
12
 * Copyright (C) 2021 David Purton <dcpurton@marshwiggle.net>
13
 * Copyright (C) 2023 Dennis Schön <mail@dennis-schoen.de>
14
 * Copyright (C) 2025 Thomas Klausner <wiz@gatalith.at>
15
 *
16
 * @copyright
17
 * This program is free software: you can redistribute it and/or modify it under
18
 * the terms of the GNU General Public License as published by the Free Software
19
 * Foundation, either version 2 of the License, or (at your option) any later
20
 * version.
21
 *
22
 * This program is distributed in the hope that it will be useful, but WITHOUT
23
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
24
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
25
 * details.
26
 *
27
 * You should have received a copy of the GNU General Public License along with
28
 * this program.  If not, see <http://www.gnu.org/licenses/>.
29
 */
30

31
/**
32
 * @page email_handler Decide how to display email content
33
 *
34
 * Decide how to display email content
35
 */
36

37
#include "config.h"
38
#include <iconv.h>
39
#include <stdbool.h>
40
#include <stdio.h>
41
#include <stdlib.h>
42
#include <string.h>
43
#include <sys/types.h>
44
#include <unistd.h>
45
#include "mutt/lib.h"
46
#include "config/lib.h"
47
#include "core/lib.h"
48
#include "gui/lib.h"
49
#include "mutt.h"
50
#include "handler.h"
51
#include "attach/lib.h"
52
#include "key/lib.h"
53
#include "menu/lib.h"
54
#include "ncrypt/lib.h"
55
#include "pager/lib.h"
56
#include "body.h"
57
#include "copy_email.h"
58
#include "enriched.h"
59
#include "envelope.h"
60
#include "globals.h"
61
#include "mailcap.h"
62
#include "mime.h"
63
#include "module_data.h"
64
#include "mutt_logging.h"
65
#include "muttlib.h"
66
#include "parameter.h"
67
#include "parse.h"
68
#include "rfc3676.h"
69
#ifdef ENABLE_NLS
70
#include <libintl.h>
71
#endif
72

73
#define BUFI_SIZE 1000 ///< Input buffer size for handler operations
74
#define BUFO_SIZE 2000 ///< Output buffer size for handler operations
75

76
#define TXT_HTML 1     ///< HTML text format
77
#define TXT_PLAIN 2    ///< Plain text format
78
#define TXT_ENRICHED 3 ///< Enriched text format
79

80
/**
81
 * @defgroup handler_api Mime Handler API
82
 *
83
 * Prototype for a function to handle MIME parts
84
 *
85
 * @param b_email Body of the email
86
 * @param state   State of text being processed
87
 * @retval 0 Success
88
 * @retval -1 Error
89
 */
90
typedef int (*handler_t)(struct Body *b_email, struct State *state);
91

92
/**
93
 * print_part_line - Print a separator for the Mime part
94
 * @param state State of text being processed
95
 * @param b_email     Body of the email
96
 * @param n     Part number for multipart emails (0 otherwise)
97
 */
UNCOV
98
static void print_part_line(struct State *state, struct Body *b_email, int n)
×
99
{
100
  struct Buffer *length = buf_pool_get();
×
101
  mutt_str_pretty_size(length, b_email->length);
×
102
  state_mark_attach(state);
×
103
  char *charset = mutt_param_get(&b_email->parameter, "charset");
×
UNCOV
104
  if (n == 0)
×
105
  {
106
    state_printf(state, _("[-- Type: %s/%s%s%s, Encoding: %s, Size: %s --]\n"),
×
107
                 BODY_TYPE(b_email), b_email->subtype, charset ? "; charset=" : "",
×
UNCOV
108
                 charset ? charset : "", ENCODING(b_email->encoding), buf_string(length));
×
109
  }
110
  else
111
  {
112
    state_printf(state, _("[-- Alternative Type #%d: %s/%s%s%s, Encoding: %s, Size: %s --]\n"),
×
UNCOV
113
                 n, BODY_TYPE(b_email), b_email->subtype,
×
114
                 charset ? "; charset=" : "", charset ? charset : "",
UNCOV
115
                 ENCODING(b_email->encoding), buf_string(length));
×
116
  }
117
  buf_pool_release(&length);
×
UNCOV
118
}
×
119

120
/**
121
 * convert_to_state - Convert text and write it to a file
122
 * @param cd     Iconv conversion descriptor
123
 * @param bufi   Buffer with text to convert
124
 * @param l      Length of buffer
125
 * @param state  State to write to
126
 */
UNCOV
127
static void convert_to_state(iconv_t cd, char *bufi, size_t *l, struct State *state)
×
128
{
129
  char bufo[BUFO_SIZE] = { 0 };
×
130
  const char *ib = NULL;
×
UNCOV
131
  char *ob = NULL;
×
132
  size_t ibl, obl;
133

UNCOV
134
  if (!bufi)
×
135
  {
UNCOV
136
    if (iconv_t_valid(cd))
×
137
    {
138
      ob = bufo;
×
139
      obl = sizeof(bufo);
×
140
      iconv(cd, NULL, NULL, &ob, &obl);
×
141
      if (ob != bufo)
×
UNCOV
142
        state_prefix_put(state, bufo, ob - bufo);
×
143
    }
UNCOV
144
    return;
×
145
  }
146

UNCOV
147
  if (!iconv_t_valid(cd))
×
148
  {
149
    state_prefix_put(state, bufi, *l);
×
150
    *l = 0;
×
UNCOV
151
    return;
×
152
  }
153

154
  ib = bufi;
×
UNCOV
155
  ibl = *l;
×
156
  while (true)
157
  {
158
    ob = bufo;
×
159
    obl = sizeof(bufo);
×
160
    mutt_ch_iconv(cd, &ib, &ibl, &ob, &obl, 0, "?", NULL);
×
UNCOV
161
    if (ob == bufo)
×
162
      break;
UNCOV
163
    state_prefix_put(state, bufo, ob - bufo);
×
164
  }
165
  memmove(bufi, ib, ibl);
×
UNCOV
166
  *l = ibl;
×
167
}
168

169
/**
170
 * decode_xbit - Decode xbit-encoded text
171
 * @param state  State to work with
172
 * @param len    Length of text to decode
173
 * @param istext Mime part is plain text
174
 * @param cd     Iconv conversion descriptor
175
 */
UNCOV
176
static void decode_xbit(struct State *state, long len, bool istext, iconv_t cd)
×
177
{
UNCOV
178
  if (!istext)
×
179
  {
180
    mutt_file_copy_bytes(state->fp_in, state->fp_out, len);
×
UNCOV
181
    return;
×
182
  }
183

UNCOV
184
  state_set_prefix(state);
×
185

186
  int c;
187
  char bufi[BUFI_SIZE] = { 0 };
×
188
  size_t l = 0;
×
UNCOV
189
  while (((c = fgetc(state->fp_in)) != EOF) && len--)
×
190
  {
UNCOV
191
    if ((c == '\r') && len)
×
192
    {
193
      const int ch = fgetc(state->fp_in);
×
UNCOV
194
      if (ch == '\n')
×
195
      {
196
        c = ch;
UNCOV
197
        len--;
×
198
      }
199
      else
200
      {
UNCOV
201
        ungetc(ch, state->fp_in);
×
202
      }
203
    }
204

205
    bufi[l++] = c;
×
206
    if (l == sizeof(bufi))
×
UNCOV
207
      convert_to_state(cd, bufi, &l, state);
×
208
  }
209

210
  convert_to_state(cd, bufi, &l, state);
×
UNCOV
211
  convert_to_state(cd, 0, 0, state);
×
212

UNCOV
213
  state_reset_prefix(state);
×
214
}
215

216
/**
217
 * qp_decode_triple - Decode a quoted-printable triplet
218
 * @param s State to work with
219
 * @param d Decoded character
220
 * @retval 0 Success
221
 * @retval -1 Error
222
 */
UNCOV
223
static int qp_decode_triple(char *s, char *d)
×
224
{
225
  /* soft line break */
UNCOV
226
  if ((s[0] == '=') && (s[1] == '\0'))
×
227
    return 1;
228

229
  /* quoted-printable triple */
UNCOV
230
  if ((s[0] == '=') && mutt_isxdigit(s[1]) && mutt_isxdigit(s[2]))
×
231
  {
232
    *d = (hexval(s[1]) << 4) | hexval(s[2]);
×
UNCOV
233
    return 0;
×
234
  }
235

236
  /* something else */
237
  return -1;
238
}
239

240
/**
241
 * qp_decode_line - Decode a line of quoted-printable text
242
 * @param dest Buffer for result
243
 * @param src  Text to decode
244
 * @param l    Bytes written to buffer
245
 * @param last Last character of the line
246
 */
UNCOV
247
static void qp_decode_line(char *dest, char *src, size_t *l, int last)
×
248
{
249
  char *d = NULL, *s = NULL;
UNCOV
250
  char c = 0;
×
251

252
  int kind = -1;
253
  bool soft = false;
254

255
  /* decode the line */
256

UNCOV
257
  for (d = dest, s = src; *s;)
×
258
  {
UNCOV
259
    switch ((kind = qp_decode_triple(s, &c)))
×
260
    {
261
      case 0:
×
262
        *d++ = c;
×
263
        s += 3;
×
264
        break; /* qp triple */
×
265
      case -1:
×
266
        *d++ = *s++;
×
267
        break; /* single character */
×
UNCOV
268
      case 1:
×
269
        soft = true;
270
        s++;
×
UNCOV
271
        break; /* soft line break */
×
272
    }
273
  }
274

UNCOV
275
  if (!soft && (last == '\n'))
×
276
  {
277
    /* neither \r nor \n as part of line-terminating CRLF
278
     * may be qp-encoded, so remove \r and \n-terminate;
279
     * see RFC2045, sect. 6.7, (1): General 8bit representation */
280
    if ((kind == 0) && (c == '\r'))
×
UNCOV
281
      *(d - 1) = '\n';
×
282
    else
UNCOV
283
      *d++ = '\n';
×
284
  }
285

286
  *d = '\0';
×
287
  *l = d - dest;
×
UNCOV
288
}
×
289

290
/**
291
 * decode_quoted - Decode an attachment encoded with quoted-printable
292
 * @param state  State to work with
293
 * @param len    Length of text to decode
294
 * @param istext Mime part is plain text
295
 * @param cd     Iconv conversion descriptor
296
 *
297
 * Why doesn't this overflow any buffers? First, it's guaranteed that the
298
 * length of a line grows when you _en_-code it to quoted-printable. That
299
 * means that we always can store the result in a buffer of at most the _same_
300
 * size.
301
 *
302
 * Now, we don't special-case if the line we read with fgets() isn't
303
 * terminated. We don't care about this, since 256 > 78, so corrupted input
304
 * will just be corrupted a bit more. That implies that 256+1 bytes are
305
 * always sufficient to store the result of qp_decode_line.
306
 *
307
 * Finally, at soft line breaks, some part of a multibyte character may have
308
 * been left over by convert_to_state(). This shouldn't be more than 6
309
 * characters, so 256+7 should be sufficient memory to store the decoded
310
 * data.
311
 *
312
 * Just to make sure that I didn't make some off-by-one error above, we just
313
 * use 512 for the target buffer's size.
314
 */
UNCOV
315
static void decode_quoted(struct State *state, long len, bool istext, iconv_t cd)
×
316
{
317
  char line[256] = { 0 };
×
318
  char decline[512] = { 0 };
×
UNCOV
319
  size_t l = 0;
×
320
  size_t l3;
321

322
  if (istext)
×
UNCOV
323
    state_set_prefix(state);
×
324

UNCOV
325
  while (len > 0)
×
326
  {
327
    /* It's ok to use a fixed size buffer for input, even if the line turns
328
     * out to be longer than this.  Just process the line in chunks.  This
329
     * really shouldn't happen according the MIME spec, since Q-P encoded
330
     * lines are at most 76 characters, but we should be liberal about what
331
     * we accept.  */
UNCOV
332
    if (!fgets(line, MIN((ssize_t) sizeof(line), len + 1), state->fp_in))
×
333
      break;
334

335
    size_t linelen = strlen(line);
×
UNCOV
336
    len -= linelen;
×
337

338
    /* inspect the last character we read so we can tell if we got the
339
     * entire line.  */
UNCOV
340
    const int last = (linelen != 0) ? line[linelen - 1] : 0;
×
341

342
    /* chop trailing whitespace if we got the full line */
UNCOV
343
    if (last == '\n')
×
344
    {
UNCOV
345
      while ((linelen > 0) && mutt_isspace(line[linelen - 1]))
×
346
        linelen--;
UNCOV
347
      line[linelen] = '\0';
×
348
    }
349

350
    /* decode and do character set conversion */
351
    qp_decode_line(decline + l, line, &l3, last);
×
352
    l += l3;
×
UNCOV
353
    convert_to_state(cd, decline, &l, state);
×
354
  }
355

356
  convert_to_state(cd, 0, 0, state);
×
357
  state_reset_prefix(state);
×
UNCOV
358
}
×
359

360
/**
361
 * decode_byte - Decode a uuencoded byte
362
 * @param ch Character to decode
363
 * @retval num Decoded value
364
 */
365
static unsigned char decode_byte(char ch)
366
{
367
  if ((ch < 32) || (ch > 95))
×
UNCOV
368
    return 0;
×
369
  return ch - 32;
370
}
371

372
/**
373
 * decode_uuencoded - Decode uuencoded text
374
 * @param state      State to work with
375
 * @param len    Length of text to decode
376
 * @param istext Mime part is plain text
377
 * @param cd     Iconv conversion descriptor
378
 */
UNCOV
379
static void decode_uuencoded(struct State *state, long len, bool istext, iconv_t cd)
×
380
{
UNCOV
381
  char tmps[128] = { 0 };
×
382
  char *pt = NULL;
383
  char bufi[BUFI_SIZE] = { 0 };
×
UNCOV
384
  size_t k = 0;
×
385

386
  if (istext)
×
UNCOV
387
    state_set_prefix(state);
×
388

UNCOV
389
  while (len > 0)
×
390
  {
391
    if (!fgets(tmps, sizeof(tmps), state->fp_in))
×
392
      goto cleanup;
×
393
    len -= mutt_str_len(tmps);
×
UNCOV
394
    if (mutt_str_startswith(tmps, "begin "))
×
395
      break;
396
  }
UNCOV
397
  while (len > 0)
×
398
  {
399
    if (!fgets(tmps, sizeof(tmps), state->fp_in))
×
400
      goto cleanup;
×
401
    len -= mutt_str_len(tmps);
×
UNCOV
402
    if (mutt_str_startswith(tmps, "end"))
×
403
      break;
404
    pt = tmps;
UNCOV
405
    const unsigned char linelen = decode_byte(*pt);
×
406
    pt++;
UNCOV
407
    for (unsigned char c = 0; (c < linelen) && *pt;)
×
408
    {
UNCOV
409
      for (char l = 2; (l <= 6) && pt[0] && pt[1]; l += 2)
×
410
      {
411
        char out = decode_byte(*pt) << l;
×
412
        pt++;
×
413
        out |= (decode_byte(*pt) >> (6 - l));
×
414
        bufi[k++] = out;
×
415
        c++;
×
UNCOV
416
        if (c == linelen)
×
417
          break;
418
      }
419
      convert_to_state(cd, bufi, &k, state);
×
UNCOV
420
      pt++;
×
421
    }
422
  }
423

424
cleanup:
×
425
  convert_to_state(cd, bufi, &k, state);
×
UNCOV
426
  convert_to_state(cd, 0, 0, state);
×
427

428
  state_reset_prefix(state);
×
UNCOV
429
}
×
430

431
/**
432
 * is_mmnoask - Metamail compatibility: should the attachment be autoviewed?
433
 * @param buf Mime type, e.g. 'text/plain'
434
 * @retval true Metamail "no ask" is true
435
 *
436
 * Test if the `MM_NOASK` environment variable should allow autoviewing of the
437
 * attachment.
438
 *
439
 * @note If `MM_NOASK=1` then the function will automatically return true.
440
 */
UNCOV
441
static bool is_mmnoask(const char *buf)
×
442
{
443
  const char *val = mutt_str_getenv("MM_NOASK");
×
UNCOV
444
  if (!val)
×
445
    return false;
446

447
  char *p = NULL;
UNCOV
448
  char tmp[1024] = { 0 };
×
449
  char *q = NULL;
450

UNCOV
451
  if (mutt_str_equal(val, "1"))
×
452
    return true;
453

UNCOV
454
  mutt_str_copy(tmp, val, sizeof(tmp));
×
455
  p = tmp;
456

UNCOV
457
  while ((p = strtok(p, ",")))
×
458
  {
459
    q = strrchr(p, '/');
×
UNCOV
460
    if (q)
×
461
    {
UNCOV
462
      if (q[1] == '*')
×
463
      {
UNCOV
464
        if (mutt_istrn_equal(buf, p, q - p))
×
465
          return true;
466
      }
467
      else
468
      {
UNCOV
469
        if (mutt_istr_equal(buf, p))
×
470
          return true;
471
      }
472
    }
473
    else
474
    {
475
      const size_t plen = mutt_istr_startswith(buf, p);
×
UNCOV
476
      if ((plen != 0) && (buf[plen] == '/'))
×
477
        return true;
478
    }
479

480
    p = NULL;
481
  }
482

483
  return false;
484
}
485

486
/**
487
 * is_autoview - Should email body be filtered by mailcap
488
 * @param b Body of the email
489
 * @retval 1 body part should be filtered by a mailcap entry prior to viewing inline
490
 * @retval 0 otherwise
491
 */
UNCOV
492
static bool is_autoview(struct Body *b)
×
493
{
UNCOV
494
  char type[256] = { 0 };
×
495
  bool is_av = false;
496

UNCOV
497
  snprintf(type, sizeof(type), "%s/%s", BODY_TYPE(b), b->subtype);
×
498

499
  struct EmailModuleData *md = neomutt_get_module_data(NeoMutt, MODULE_ID_EMAIL);
×
UNCOV
500
  ASSERT(md);
×
501

UNCOV
502
  const bool c_implicit_auto_view = cs_subset_bool(NeoMutt->sub, "implicit_auto_view");
×
UNCOV
503
  if (c_implicit_auto_view)
×
504
  {
505
    /* $implicit_auto_view is essentially the same as "auto-view *" */
506
    is_av = true;
507
  }
508
  else
509
  {
510
    /* determine if this type is on the user's auto-view list */
511
    mutt_check_lookup_list(b, type, sizeof(type));
×
512
    struct ListNode *np = NULL;
513
    STAILQ_FOREACH(np, &md->auto_view, entries)
×
514
    {
515
      int i = mutt_str_len(np->data);
×
UNCOV
516
      i--;
×
UNCOV
517
      if (((i > 0) && (np->data[i - 1] == '/') && (np->data[i] == '*') &&
×
UNCOV
518
           mutt_istrn_equal(type, np->data, i)) ||
×
UNCOV
519
          mutt_istr_equal(type, np->data))
×
520
      {
521
        is_av = true;
522
        break;
523
      }
524
    }
525

UNCOV
526
    if (is_mmnoask(type))
×
527
      is_av = true;
528
  }
529

530
  /* determine if there is a mailcap entry suitable for auto-view
531
   *
532
   * @warning type is altered by this call as a result of 'mime-lookup' support */
UNCOV
533
  if (is_av)
×
UNCOV
534
    return mailcap_lookup(b, type, sizeof(type), NULL, MUTT_MC_AUTOVIEW);
×
535

536
  return false;
537
}
538

539
/**
540
 * autoview_handler - Handler for autoviewable attachments - Implements ::handler_t - @ingroup handler_api
541
 */
542
static int autoview_handler(struct Body *b_email, struct State *state)
×
543
{
544
  struct MailcapEntry *entry = mailcap_entry_new();
×
545
  char buf[1024] = { 0 };
×
546
  char type[256] = { 0 };
×
547
  struct Buffer *cmd = buf_pool_get();
×
548
  struct Buffer *tempfile = buf_pool_get();
×
UNCOV
549
  char *fname = NULL;
×
UNCOV
550
  FILE *fp_in = NULL;
×
UNCOV
551
  FILE *fp_out = NULL;
×
552
  FILE *fp_err = NULL;
×
553
  pid_t pid;
554
  int rc = 0;
555

556
  snprintf(type, sizeof(type), "%s/%s", BODY_TYPE(b_email), b_email->subtype);
×
557
  mailcap_lookup(b_email, type, sizeof(type), entry, MUTT_MC_AUTOVIEW);
×
558

UNCOV
559
  fname = mutt_str_dup(b_email->filename);
×
560
  mutt_file_sanitize_filename(fname, true);
×
UNCOV
561
  mailcap_expand_filename(entry->nametemplate, fname, tempfile);
×
562
  FREE(&fname);
×
563

UNCOV
564
  if (entry->command)
×
565
  {
UNCOV
566
    buf_strcpy(cmd, entry->command);
×
567

568
    /* mailcap_expand_command returns 0 if the file is required */
569
    bool piped = mailcap_expand_command(b_email, buf_string(tempfile), type, cmd);
×
570

571
    if (state->flags & STATE_DISPLAY)
×
572
    {
UNCOV
573
      state_mark_attach(state);
×
574
      state_printf(state, _("[-- Autoview using %s --]\n"), buf_string(cmd));
×
575
      mutt_message(_("Invoking autoview command: %s"), buf_string(cmd));
×
576
    }
577

578
    fp_in = mutt_file_fopen(buf_string(tempfile), "w+");
×
UNCOV
579
    if (!fp_in)
×
580
    {
UNCOV
581
      mutt_perror("fopen");
×
UNCOV
582
      mailcap_entry_free(&entry);
×
583
      rc = -1;
UNCOV
584
      goto cleanup;
×
585
    }
586

587
    mutt_file_copy_bytes(state->fp_in, fp_in, b_email->length);
×
588

589
    if (piped)
×
590
    {
591
      unlink(buf_string(tempfile));
×
UNCOV
592
      fflush(fp_in);
×
UNCOV
593
      rewind(fp_in);
×
UNCOV
594
      pid = filter_create_fd(buf_string(cmd), NULL, &fp_out, &fp_err,
×
595
                             fileno(fp_in), -1, -1, NeoMutt->env);
×
596
    }
597
    else
598
    {
599
      mutt_file_fclose(&fp_in);
×
UNCOV
600
      pid = filter_create(buf_string(cmd), NULL, &fp_out, &fp_err, NeoMutt->env);
×
601
    }
602

UNCOV
603
    if (pid < 0)
×
604
    {
605
      mutt_perror(_("Can't create filter"));
×
UNCOV
606
      if (state->flags & STATE_DISPLAY)
×
607
      {
608
        state_mark_attach(state);
×
UNCOV
609
        state_printf(state, _("[-- Can't run %s --]\n"), buf_string(cmd));
×
610
      }
611
      rc = -1;
UNCOV
612
      goto bail;
×
613
    }
614

UNCOV
615
    if (state->prefix)
×
616
    {
617
      /* Remove ansi and formatting from autoview output in replies only.  The
618
       * user may want to see the formatting in the pager, but it shouldn't be
619
       * in their quoted reply text too.  */
620
      struct Buffer *stripped = buf_pool_get();
×
621
      while (fgets(buf, sizeof(buf), fp_out))
×
622
      {
623
        buf_strip_formatting(stripped, buf, false);
×
UNCOV
624
        state_puts(state, state->prefix);
×
UNCOV
625
        state_puts(state, buf_string(stripped));
×
626
      }
UNCOV
627
      buf_pool_release(&stripped);
×
628

629
      /* check for data on stderr */
630
      if (fgets(buf, sizeof(buf), fp_err))
×
631
      {
UNCOV
632
        if (state->flags & STATE_DISPLAY)
×
633
        {
634
          state_mark_attach(state);
×
635
          state_printf(state, _("[-- Autoview stderr of %s --]\n"), buf_string(cmd));
×
636
        }
637

638
        state_puts(state, state->prefix);
×
639
        state_puts(state, buf);
×
UNCOV
640
        while (fgets(buf, sizeof(buf), fp_err))
×
641
        {
UNCOV
642
          state_puts(state, state->prefix);
×
UNCOV
643
          state_puts(state, buf);
×
644
        }
645
      }
646
    }
647
    else
648
    {
649
      mutt_file_copy_stream(fp_out, state->fp_out);
×
650
      /* Check for stderr messages */
651
      if (fgets(buf, sizeof(buf), fp_err))
×
652
      {
UNCOV
653
        if (state->flags & STATE_DISPLAY)
×
654
        {
655
          state_mark_attach(state);
×
656
          state_printf(state, _("[-- Autoview stderr of %s --]\n"), buf_string(cmd));
×
657
        }
658

UNCOV
659
        state_puts(state, buf);
×
660
        mutt_file_copy_stream(fp_err, state->fp_out);
×
661
      }
662
    }
663

664
  bail:
×
665
    mutt_file_fclose(&fp_out);
×
666
    mutt_file_fclose(&fp_err);
×
667

668
    filter_wait(pid);
×
UNCOV
669
    if (piped)
×
670
      mutt_file_fclose(&fp_in);
×
671
    else
UNCOV
672
      mutt_file_unlink(buf_string(tempfile));
×
673

674
    if (state->flags & STATE_DISPLAY)
×
675
      mutt_clear_error();
×
676
  }
677

678
cleanup:
×
UNCOV
679
  mailcap_entry_free(&entry);
×
680

UNCOV
681
  buf_pool_release(&cmd);
×
UNCOV
682
  buf_pool_release(&tempfile);
×
683

UNCOV
684
  return rc;
×
685
}
686

687
/**
688
 * text_plain_handler - Handler for plain text - Implements ::handler_t - @ingroup handler_api
689
 * @retval 0 Always
690
 *
691
 * When generating format=flowed ($text_flowed is set) from format=fixed, strip
692
 * all trailing spaces to improve interoperability; if $text_flowed is unset,
693
 * simply verbatim copy input.
694
 */
UNCOV
695
static int text_plain_handler(struct Body *b_email, struct State *state)
×
696
{
697
  char *buf = NULL;
×
UNCOV
698
  size_t sz = 0;
×
699

UNCOV
700
  const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
×
701
  while ((buf = mutt_file_read_line(buf, &sz, state->fp_in, NULL, MUTT_RL_NO_FLAGS)))
×
702
  {
703
    if (!mutt_str_equal(buf, "-- ") && c_text_flowed)
×
704
    {
705
      size_t len = mutt_str_len(buf);
×
706
      while ((len > 0) && (buf[len - 1] == ' '))
×
707
        buf[--len] = '\0';
×
708
    }
UNCOV
709
    if (state->prefix)
×
UNCOV
710
      state_puts(state, state->prefix);
×
711
    state_puts(state, buf);
×
712
    state_putc(state, '\n');
×
713
  }
714

UNCOV
715
  FREE(&buf);
×
UNCOV
716
  return 0;
×
717
}
718

719
/**
720
 * message_handler - Handler for message/rfc822 body parts - Implements ::handler_t - @ingroup handler_api
721
 */
UNCOV
722
static int message_handler(struct Body *b_email, struct State *state)
×
723
{
724
  struct Body *b = NULL;
×
725
  LOFF_T off_start;
726
  int rc = 0;
727

728
  off_start = ftello(state->fp_in);
×
UNCOV
729
  if (off_start < 0)
×
730
    return -1;
731

732
  if ((b_email->encoding == ENC_BASE64) || (b_email->encoding == ENC_QUOTED_PRINTABLE) ||
×
733
      (b_email->encoding == ENC_UUENCODED))
734
  {
UNCOV
735
    b = mutt_body_new();
×
UNCOV
736
    b->length = mutt_file_get_size_fp(state->fp_in);
×
737
    b->parts = mutt_rfc822_parse_message(state->fp_in, b);
×
738
  }
739
  else
740
  {
UNCOV
741
    b = b_email;
×
742
  }
743

744
  if (b->parts)
×
745
  {
746
    CopyHeaderFlags chflags = CH_DECODE | CH_FROM;
UNCOV
747
    const bool c_weed = cs_subset_bool(NeoMutt->sub, "weed");
×
UNCOV
748
    if ((state->flags & STATE_WEED) ||
×
749
        ((state->flags & (STATE_DISPLAY | STATE_PRINTING)) && c_weed))
×
750
    {
751
      chflags |= CH_WEED | CH_REORDER;
752
    }
UNCOV
753
    if (state->prefix)
×
754
      chflags |= CH_PREFIX;
×
UNCOV
755
    if (state->flags & STATE_DISPLAY)
×
UNCOV
756
      chflags |= CH_DISPLAY;
×
757

758
    mutt_copy_hdr(state->fp_in, state->fp_out, off_start, b->parts->offset,
×
759
                  chflags, state->prefix, 0);
760

761
    if (state->prefix)
×
UNCOV
762
      state_puts(state, state->prefix);
×
UNCOV
763
    state_putc(state, '\n');
×
764

UNCOV
765
    rc = mutt_body_handler(b->parts, state);
×
766
  }
767

UNCOV
768
  if ((b_email->encoding == ENC_BASE64) || (b_email->encoding == ENC_QUOTED_PRINTABLE) ||
×
769
      (b_email->encoding == ENC_UUENCODED))
770
  {
UNCOV
771
    mutt_body_free(&b);
×
772
  }
773

774
  return rc;
775
}
776

777
/**
778
 * external_body_handler - Handler for external-body emails - Implements ::handler_t - @ingroup handler_api
779
 */
UNCOV
780
static int external_body_handler(struct Body *b_email, struct State *state)
×
781
{
UNCOV
782
  const char *access_type = mutt_param_get(&b_email->parameter, "access-type");
×
783
  if (!access_type)
×
784
  {
785
    if (state->flags & STATE_DISPLAY)
×
786
    {
UNCOV
787
      state_mark_attach(state);
×
UNCOV
788
      state_puts(state, _("[-- Error: message/external-body has no access-type parameter --]\n"));
×
UNCOV
789
      return 0;
×
790
    }
791
    else
792
    {
793
      return -1;
794
    }
795
  }
796

797
  const char *fmt = NULL;
798
  struct Buffer *banner = buf_pool_get();
×
799

UNCOV
800
  const char *expiration = mutt_param_get(&b_email->parameter, "expiration");
×
801
  time_t expire;
UNCOV
802
  if (expiration)
×
803
    expire = mutt_date_parse_date(expiration, NULL);
×
804
  else
805
    expire = -1;
806

UNCOV
807
  const bool c_weed = cs_subset_bool(NeoMutt->sub, "weed");
×
808
  if (mutt_istr_equal(access_type, "x-mutt-deleted"))
×
809
  {
810
    if (state->flags & (STATE_DISPLAY | STATE_PRINTING))
×
811
    {
812
      struct Buffer *pretty_size = buf_pool_get();
×
813
      char *length = mutt_param_get(&b_email->parameter, "length");
×
814
      if (length)
×
815
      {
816
        const long size = strtol(length, NULL, 10);
×
UNCOV
817
        mutt_str_pretty_size(pretty_size, size);
×
UNCOV
818
        if (expire != -1)
×
819
        {
UNCOV
820
          fmt = ngettext(
×
821
              /* L10N: If the translation of this string is a multi line string, then
822
                 each line should start with "[-- " and end with " --]".
823
                 The first "%s/%s" is a MIME type, e.g. "text/plain". The last %s
824
                 expands to a date as returned by `mutt_date_parse_date()`.
825

826
                 Note: The size argument printed is not the actual number as passed
827
                 to gettext but the prettified version, e.g. size = 2048 will be
828
                 printed as 2K.  Your language might be sensitive to that: For
829
                 example although '1K' and '1024' represent the same number your
830
                 language might inflect the noun 'byte' differently.
831

832
                 Sadly, we can't do anything about that at the moment besides
833
                 passing the precise size in bytes. If you are interested the
834
                 function responsible for the prettification is
835
                 mutt_str_pretty_size() in muttlib.c */
836
              "[-- This %s/%s attachment (size %s byte) has been deleted --]\n"
837
              "[-- on %s --]\n",
838
              "[-- This %s/%s attachment (size %s bytes) has been deleted --]\n"
839
              "[-- on %s --]\n",
840
              size);
841
        }
842
        else
843
        {
UNCOV
844
          fmt = ngettext(
×
845
              /* L10N: If the translation of this string is a multi line string, then
846
                 each line should start with "[-- " and end with " --]".
847
                 The first "%s/%s" is a MIME type, e.g. "text/plain".
848

849
                 Note: The size argument printed is not the actual number as passed
850
                 to gettext but the prettified version, e.g. size = 2048 will be
851
                 printed as 2K.  Your language might be sensitive to that: For
852
                 example although '1K' and '1024' represent the same number your
853
                 language might inflect the noun 'byte' differently.
854

855
                 Sadly, we can't do anything about that at the moment besides
856
                 passing the precise size in bytes. If you are interested the
857
                 function responsible for the prettification is
858
                 mutt_str_pretty_size() in muttlib.c  */
859
              "[-- This %s/%s attachment (size %s byte) has been deleted --]\n",
860
              "[-- This %s/%s attachment (size %s bytes) has been deleted --]\n", size);
861
        }
862
      }
863
      else
864
      {
UNCOV
865
        if (expire != -1)
×
866
        {
867
          /* L10N: If the translation of this string is a multi line string, then
868
             each line should start with "[-- " and end with " --]".
869
             The first "%s/%s" is a MIME type, e.g. "text/plain". The last %s
870
             expands to a date as returned by `mutt_date_parse_date()`.
871

872
             Caution: Argument three %3$ is also defined but should not be used
873
             in this translation!  */
UNCOV
874
          fmt = _("[-- This %s/%s attachment has been deleted --]\n[-- on %4$s --]\n");
×
875
        }
876
        else
877
        {
878
          /* L10N: If the translation of this string is a multi line string, then
879
             each line should start with "[-- " and end with " --]".
880
             The first "%s/%s" is a MIME type, e.g. "text/plain". */
881
          fmt = _("[-- This %s/%s attachment has been deleted --]\n");
×
882
        }
883
      }
884

UNCOV
885
      buf_printf(banner, fmt, BODY_TYPE(b_email->parts),
×
886
                 b_email->parts->subtype, buf_string(pretty_size), expiration);
×
887
      state_attach_puts(state, buf_string(banner));
×
UNCOV
888
      if (b_email->parts->filename)
×
889
      {
UNCOV
890
        state_mark_attach(state);
×
891
        state_printf(state, _("[-- name: %s --]\n"), b_email->parts->filename);
×
892
      }
893

894
      CopyHeaderFlags chflags = CH_DECODE;
895
      if (c_weed)
×
896
        chflags |= CH_WEED | CH_REORDER;
897

UNCOV
898
      mutt_copy_hdr(state->fp_in, state->fp_out, ftello(state->fp_in),
×
899
                    b_email->parts->offset, chflags, NULL, 0);
×
UNCOV
900
      buf_pool_release(&pretty_size);
×
901
    }
902
  }
UNCOV
903
  else if (expiration && (expire < mutt_date_now()))
×
904
  {
UNCOV
905
    if (state->flags & STATE_DISPLAY)
×
906
    {
907
      /* L10N: If the translation of this string is a multi line string, then
908
         each line should start with "[-- " and end with " --]".
909
         The "%s/%s" is a MIME type, e.g. "text/plain". */
UNCOV
910
      buf_printf(banner, _("[-- This %s/%s attachment is not included, --]\n[-- and the indicated external source has expired --]\n"),
×
911
                 BODY_TYPE(b_email->parts), b_email->parts->subtype);
×
UNCOV
912
      state_attach_puts(state, buf_string(banner));
×
913

914
      CopyHeaderFlags chflags = CH_DECODE | CH_DISPLAY;
915
      if (c_weed)
×
916
        chflags |= CH_WEED | CH_REORDER;
917

UNCOV
918
      mutt_copy_hdr(state->fp_in, state->fp_out, ftello(state->fp_in),
×
UNCOV
919
                    b_email->parts->offset, chflags, NULL, 0);
×
920
    }
921
  }
922
  else
923
  {
UNCOV
924
    if (state->flags & STATE_DISPLAY)
×
925
    {
926
      /* L10N: If the translation of this string is a multi line string, then
927
         each line should start with "[-- " and end with " --]".
928
         The "%s/%s" is a MIME type, e.g. "text/plain".  The %s after
929
         access-type is an access-type as defined by the MIME RFCs, e.g. "FTP",
930
         "LOCAL-FILE", "MAIL-SERVER". */
UNCOV
931
      buf_printf(banner, _("[-- This %s/%s attachment is not included, --]\n[-- and the indicated access-type %s is unsupported --]\n"),
×
932
                 BODY_TYPE(b_email->parts), b_email->parts->subtype, access_type);
×
UNCOV
933
      state_attach_puts(state, buf_string(banner));
×
934

935
      CopyHeaderFlags chflags = CH_DECODE | CH_DISPLAY;
936
      if (c_weed)
×
937
        chflags |= CH_WEED | CH_REORDER;
938

939
      mutt_copy_hdr(state->fp_in, state->fp_out, ftello(state->fp_in),
×
UNCOV
940
                    b_email->parts->offset, chflags, NULL, 0);
×
941
    }
942
  }
UNCOV
943
  buf_pool_release(&banner);
×
944

UNCOV
945
  return 0;
×
946
}
947

948
/**
949
 * alternative_handler - Handler for multipart alternative emails - Implements ::handler_t - @ingroup handler_api
950
 */
UNCOV
951
static int alternative_handler(struct Body *b_email, struct State *state)
×
952
{
UNCOV
953
  struct Body *const head = b_email;
×
954
  struct Body *choice = NULL;
955
  struct Body *b = NULL;
956
  bool mustfree = false;
957
  int rc = 0;
958

959
  if ((b_email->encoding == ENC_BASE64) || (b_email->encoding == ENC_QUOTED_PRINTABLE) ||
×
960
      (b_email->encoding == ENC_UUENCODED))
961
  {
962
    mustfree = true;
UNCOV
963
    b = mutt_body_new();
×
964
    b->length = mutt_file_get_size_fp(state->fp_in);
×
UNCOV
965
    b->parts = mutt_parse_multipart(state->fp_in,
×
UNCOV
966
                                    mutt_param_get(&b_email->parameter, "boundary"),
×
967
                                    b->length,
UNCOV
968
                                    mutt_istr_equal("digest", b_email->subtype));
×
969
  }
970
  else
971
  {
972
    b = b_email;
973
  }
974

975
  b_email = b;
×
976

UNCOV
977
  struct EmailModuleData *md = neomutt_get_module_data(NeoMutt, MODULE_ID_EMAIL);
×
UNCOV
978
  ASSERT(md);
×
979

980
  /* First, search list of preferred types */
981
  struct ListNode *np = NULL;
UNCOV
982
  STAILQ_FOREACH(np, &md->alternative_order, entries)
×
983
  {
984
    int btlen; /* length of basetype */
985
    bool wild; /* do we have a wildcard to match all subtypes? */
986

UNCOV
987
    char *c = strchr(np->data, '/');
×
UNCOV
988
    if (c)
×
989
    {
UNCOV
990
      wild = ((c[1] == '*') && (c[2] == '\0'));
×
UNCOV
991
      btlen = c - np->data;
×
992
    }
993
    else
994
    {
995
      wild = true;
996
      btlen = mutt_str_len(np->data);
×
997
    }
998

999
    if (b_email->parts)
×
1000
      b = b_email->parts;
1001
    else
1002
      b = b_email;
UNCOV
1003
    while (b)
×
1004
    {
UNCOV
1005
      const char *bt = BODY_TYPE(b);
×
UNCOV
1006
      if (mutt_istrn_equal(bt, np->data, btlen) && (bt[btlen] == 0))
×
1007
      {
1008
        /* the basetype matches */
UNCOV
1009
        if (wild || mutt_istr_equal(np->data + btlen + 1, b->subtype))
×
1010
        {
1011
          choice = b;
1012
        }
1013
      }
UNCOV
1014
      b = b->next;
×
1015
    }
1016

1017
    if (choice)
×
1018
      break;
1019
  }
1020

1021
  /* Next, look for an autoviewable type */
UNCOV
1022
  if (!choice)
×
1023
  {
UNCOV
1024
    if (b_email->parts)
×
1025
      b = b_email->parts;
1026
    else
1027
      b = b_email;
UNCOV
1028
    while (b)
×
1029
    {
1030
      if (is_autoview(b))
×
1031
        choice = b;
1032
      b = b->next;
×
1033
    }
1034
  }
1035

1036
  /* Then, look for a text entry */
1037
  if (!choice)
×
1038
  {
1039
    if (b_email->parts)
×
1040
      b = b_email->parts;
1041
    else
1042
      b = b_email;
1043
    int type = 0;
UNCOV
1044
    while (b)
×
1045
    {
1046
      if (b->type == TYPE_TEXT)
×
1047
      {
UNCOV
1048
        if (mutt_istr_equal("plain", b->subtype) && (type <= TXT_PLAIN))
×
1049
        {
1050
          choice = b;
1051
          type = TXT_PLAIN;
1052
        }
UNCOV
1053
        else if (mutt_istr_equal("enriched", b->subtype) && (type <= TXT_ENRICHED))
×
1054
        {
1055
          choice = b;
1056
          type = TXT_ENRICHED;
1057
        }
UNCOV
1058
        else if (mutt_istr_equal("html", b->subtype) && (type <= TXT_HTML))
×
1059
        {
1060
          choice = b;
1061
          type = TXT_HTML;
1062
        }
1063
      }
1064
      b = b->next;
×
1065
    }
1066
  }
1067

1068
  /* Finally, look for other possibilities */
UNCOV
1069
  if (!choice)
×
1070
  {
UNCOV
1071
    if (b_email->parts)
×
1072
      b = b_email->parts;
1073
    else
1074
      b = b_email;
UNCOV
1075
    while (b)
×
1076
    {
UNCOV
1077
      if (mutt_can_decode(b))
×
1078
        choice = b;
1079
      b = b->next;
×
1080
    }
1081
  }
1082

UNCOV
1083
  if (choice)
×
1084
  {
1085
    const bool c_weed = cs_subset_bool(NeoMutt->sub, "weed");
×
1086
    if (state->flags & STATE_DISPLAY && !c_weed &&
×
UNCOV
1087
        mutt_file_seek(state->fp_in, choice->hdr_offset, SEEK_SET))
×
1088
    {
UNCOV
1089
      mutt_file_copy_bytes(state->fp_in, state->fp_out, choice->offset - choice->hdr_offset);
×
1090
    }
1091

UNCOV
1092
    const char *const c_show_multipart_alternative = cs_subset_string(NeoMutt->sub, "show_multipart_alternative");
×
1093
    if (mutt_str_equal("info", c_show_multipart_alternative))
×
1094
    {
UNCOV
1095
      print_part_line(state, choice, 0);
×
1096
    }
UNCOV
1097
    mutt_body_handler(choice, state);
×
1098

1099
    /* Let it flow back to the main part */
UNCOV
1100
    head->nowrap = choice->nowrap;
×
UNCOV
1101
    choice->nowrap = false;
×
1102

1103
    if (mutt_str_equal("info", c_show_multipart_alternative))
×
1104
    {
1105
      if (b_email->parts)
×
1106
        b = b_email->parts;
1107
      else
1108
        b = b_email;
1109
      int count = 0;
UNCOV
1110
      while (b)
×
1111
      {
UNCOV
1112
        if (choice != b)
×
1113
        {
UNCOV
1114
          count += 1;
×
UNCOV
1115
          if (count == 1)
×
UNCOV
1116
            state_putc(state, '\n');
×
1117

UNCOV
1118
          print_part_line(state, b, count);
×
1119
        }
1120
        b = b->next;
×
1121
      }
1122
    }
1123
  }
UNCOV
1124
  else if (state->flags & STATE_DISPLAY)
×
1125
  {
1126
    /* didn't find anything that we could display! */
UNCOV
1127
    state_mark_attach(state);
×
1128
    state_puts(state, _("[-- Error: Could not display any parts of Multipart/Alternative --]\n"));
×
1129
    rc = -1;
1130
  }
1131

UNCOV
1132
  if (mustfree)
×
UNCOV
1133
    mutt_body_free(&b_email);
×
1134

1135
  return rc;
×
1136
}
1137

1138
/**
1139
 * multilingual_handler - Handler for multi-lingual emails - Implements ::handler_t - @ingroup handler_api
1140
 * @retval 0 Always
1141
 */
1142
static int multilingual_handler(struct Body *b_email, struct State *state)
×
1143
{
1144
  struct Body *b = NULL;
1145
  bool mustfree = false;
1146
  int rc = 0;
1147

1148
  mutt_debug(LL_DEBUG2, "RFC8255 >> entering in handler multilingual handler\n");
×
1149
  if ((b_email->encoding == ENC_BASE64) || (b_email->encoding == ENC_QUOTED_PRINTABLE) ||
×
1150
      (b_email->encoding == ENC_UUENCODED))
1151
  {
1152
    mustfree = true;
UNCOV
1153
    b = mutt_body_new();
×
UNCOV
1154
    b->length = mutt_file_get_size_fp(state->fp_in);
×
UNCOV
1155
    b->parts = mutt_parse_multipart(state->fp_in,
×
UNCOV
1156
                                    mutt_param_get(&b_email->parameter, "boundary"),
×
1157
                                    b->length,
1158
                                    mutt_istr_equal("digest", b_email->subtype));
×
1159
  }
1160
  else
1161
  {
1162
    b = b_email;
1163
  }
1164

UNCOV
1165
  b_email = b;
×
1166

UNCOV
1167
  if (b_email->parts)
×
1168
    b = b_email->parts;
1169
  else
1170
    b = b_email;
1171

1172
  struct Body *choice = NULL;
1173
  struct Body *first_part = NULL;
1174
  struct Body *zxx_part = NULL;
1175
  struct ListNode *np = NULL;
1176

1177
  while (b)
×
1178
  {
UNCOV
1179
    if (mutt_can_decode(b))
×
1180
    {
1181
      first_part = b;
1182
      break;
1183
    }
1184
    b = b->next;
×
1185
  }
1186

1187
  const struct Slist *c_preferred_languages = cs_subset_slist(NeoMutt->sub, "preferred_languages");
×
UNCOV
1188
  if (c_preferred_languages)
×
1189
  {
UNCOV
1190
    struct Buffer *langs = buf_pool_get();
×
1191
    cs_subset_str_string_get(NeoMutt->sub, "preferred_languages", langs);
×
UNCOV
1192
    mutt_debug(LL_DEBUG2, "RFC8255 >> preferred_languages set in config to '%s'\n",
×
1193
               buf_string(langs));
UNCOV
1194
    buf_pool_release(&langs);
×
1195

UNCOV
1196
    STAILQ_FOREACH(np, &c_preferred_languages->head, entries)
×
1197
    {
1198
      while (b)
×
1199
      {
1200
        if (mutt_can_decode(b))
×
1201
        {
1202
          if (b->language && mutt_str_equal("zxx", b->language))
×
1203
            zxx_part = b;
1204

1205
          mutt_debug(LL_DEBUG2, "RFC8255 >> comparing configuration preferred_language='%s' to mail part content-language='%s'\n",
×
1206
                     np->data, b->language);
UNCOV
1207
          if (b->language && mutt_str_equal(np->data, b->language))
×
1208
          {
1209
            mutt_debug(LL_DEBUG2, "RFC8255 >> preferred_language='%s' matches content-language='%s' >> part selected to be displayed\n",
×
1210
                       np->data, b->language);
1211
            choice = b;
1212
            break;
×
1213
          }
1214
        }
1215

UNCOV
1216
        b = b->next;
×
1217
      }
1218

UNCOV
1219
      if (choice)
×
1220
        break;
1221

1222
      if (b_email->parts)
×
1223
        b = b_email->parts;
1224
      else
1225
        b = b_email;
1226
    }
1227
  }
1228

1229
  if (choice)
×
1230
  {
1231
    mutt_body_handler(choice, state);
×
1232
  }
1233
  else
1234
  {
1235
    if (zxx_part)
×
UNCOV
1236
      mutt_body_handler(zxx_part, state);
×
1237
    else
UNCOV
1238
      mutt_body_handler(first_part, state);
×
1239
  }
1240

UNCOV
1241
  if (mustfree)
×
UNCOV
1242
    mutt_body_free(&b_email);
×
1243

UNCOV
1244
  return rc;
×
1245
}
1246

1247
/**
1248
 * multipart_handler - Handler for multipart emails - Implements ::handler_t - @ingroup handler_api
1249
 */
UNCOV
1250
static int multipart_handler(struct Body *b_email, struct State *state)
×
1251
{
1252
  struct Body *b = NULL, *p = NULL;
×
1253
  int count;
1254
  int rc = 0;
1255

1256
  if ((b_email->encoding == ENC_BASE64) || (b_email->encoding == ENC_QUOTED_PRINTABLE) ||
×
1257
      (b_email->encoding == ENC_UUENCODED))
1258
  {
UNCOV
1259
    b = mutt_body_new();
×
UNCOV
1260
    b->length = mutt_file_get_size_fp(state->fp_in);
×
1261
    b->parts = mutt_parse_multipart(state->fp_in,
×
UNCOV
1262
                                    mutt_param_get(&b_email->parameter, "boundary"),
×
UNCOV
1263
                                    b->length,
×
1264
                                    mutt_istr_equal("digest", b_email->subtype));
×
1265
  }
1266
  else
1267
  {
UNCOV
1268
    b = b_email;
×
1269
  }
1270

1271
  const bool c_weed = cs_subset_bool(NeoMutt->sub, "weed");
×
1272
  const bool c_include_only_first = cs_subset_bool(NeoMutt->sub, "include_only_first");
×
1273

UNCOV
1274
  for (p = b->parts, count = 1; p; p = p->next, count++)
×
1275
  {
UNCOV
1276
    if (state->flags & STATE_DISPLAY)
×
1277
    {
UNCOV
1278
      state_mark_attach(state);
×
UNCOV
1279
      if (p->description || p->filename || p->form_name)
×
1280
      {
1281
        /* L10N: %s is the attachment description, filename or form_name. */
1282
        state_printf(state, _("[-- Attachment #%d: %s --]\n"), count,
×
1283
                     p->description ? p->description :
1284
                     p->filename    ? p->filename :
×
1285
                                      p->form_name);
1286
      }
1287
      else
1288
      {
1289
        state_printf(state, _("[-- Attachment #%d --]\n"), count);
×
1290
      }
1291
      print_part_line(state, p, 0);
×
UNCOV
1292
      if (c_weed)
×
1293
      {
UNCOV
1294
        state_putc(state, '\n');
×
1295
      }
1296
      else if (mutt_file_seek(state->fp_in, p->hdr_offset, SEEK_SET))
×
1297
      {
1298
        mutt_file_copy_bytes(state->fp_in, state->fp_out, p->offset - p->hdr_offset);
×
1299
      }
1300
    }
1301

UNCOV
1302
    rc = mutt_body_handler(p, state);
×
UNCOV
1303
    state_putc(state, '\n');
×
1304

1305
    if (rc != 0)
×
1306
    {
UNCOV
1307
      mutt_error(_("One or more parts of this message could not be displayed"));
×
UNCOV
1308
      mutt_debug(LL_DEBUG1, "Failed on attachment #%d, type %s/%s\n", count,
×
1309
                 BODY_TYPE(p), NONULL(p->subtype));
1310
    }
1311

UNCOV
1312
    if ((state->flags & STATE_REPLYING) && c_include_only_first && (state->flags & STATE_FIRSTDONE))
×
1313
    {
1314
      break;
1315
    }
1316
  }
1317

1318
  if ((b_email->encoding == ENC_BASE64) || (b_email->encoding == ENC_QUOTED_PRINTABLE) ||
×
1319
      (b_email->encoding == ENC_UUENCODED))
1320
  {
UNCOV
1321
    mutt_body_free(&b);
×
1322
  }
1323

1324
  /* make failure of a single part non-fatal */
UNCOV
1325
  if (rc < 0)
×
1326
    rc = 1;
UNCOV
1327
  return rc;
×
1328
}
1329

1330
/**
1331
 * run_decode_and_handler - Run an appropriate decoder for an email
1332
 * @param b         Body of the email
1333
 * @param state         State to work with
1334
 * @param handler   Callback function to process the content - Implements ::handler_t
1335
 * @param plaintext Is the content in plain text
1336
 * @retval 0 Success
1337
 * @retval -1 Error
1338
 */
UNCOV
1339
static int run_decode_and_handler(struct Body *b, struct State *state,
×
1340
                                  handler_t handler, bool plaintext)
1341
{
1342
  const char *save_prefix = NULL;
1343
  FILE *fp = NULL;
1344
  size_t tmplength = 0;
1345
  LOFF_T tmpoffset = 0;
1346
  int decode = 0;
1347
  int rc = 0;
1348
#ifndef USE_FMEMOPEN
UNCOV
1349
  struct Buffer *tempfile = NULL;
×
1350
#endif
1351

UNCOV
1352
  if (!mutt_file_seek(state->fp_in, b->offset, SEEK_SET))
×
1353
  {
1354
    return -1;
1355
  }
1356

1357
#ifdef USE_FMEMOPEN
1358
  char *temp = NULL;
1359
  size_t tempsize = 0;
1360
#endif
1361

1362
  /* see if we need to decode this part before processing it */
1363
  if ((b->encoding == ENC_BASE64) || (b->encoding == ENC_QUOTED_PRINTABLE) ||
×
UNCOV
1364
      (b->encoding == ENC_UUENCODED) || (plaintext || mutt_is_text_part(b)))
×
1365
  /* text subtypes may require character set conversion even with 8bit encoding */
1366
  {
UNCOV
1367
    const int orig_type = b->type;
×
1368
    if (plaintext)
×
1369
    {
UNCOV
1370
      b->type = TYPE_TEXT;
×
1371
    }
1372
    else
1373
    {
1374
      /* decode to a tempfile, saving the original destination */
UNCOV
1375
      fp = state->fp_out;
×
1376
#ifdef USE_FMEMOPEN
1377
      state->fp_out = open_memstream(&temp, &tempsize);
1378
      if (!state->fp_out)
1379
      {
1380
        mutt_error(_("Unable to open 'memory stream'"));
1381
        mutt_debug(LL_DEBUG1, "Can't open 'memory stream'\n");
1382
        return -1;
1383
      }
1384
#else
1385
      tempfile = buf_pool_get();
×
1386
      buf_mktemp(tempfile);
×
UNCOV
1387
      state->fp_out = mutt_file_fopen(buf_string(tempfile), "w");
×
UNCOV
1388
      if (!state->fp_out)
×
1389
      {
UNCOV
1390
        mutt_error(_("Unable to open temporary file"));
×
1391
        mutt_debug(LL_DEBUG1, "Can't open %s\n", buf_string(tempfile));
×
1392
        buf_pool_release(&tempfile);
×
UNCOV
1393
        return -1;
×
1394
      }
1395
#endif
1396
      /* decoding the attachment changes the size and offset, so save a copy
1397
       * of the "real" values now, and restore them after processing */
UNCOV
1398
      tmplength = b->length;
×
UNCOV
1399
      tmpoffset = b->offset;
×
1400

1401
      /* if we are decoding binary bodies, we don't want to prefix each
1402
       * line with the prefix or else the data will get corrupted.  */
UNCOV
1403
      save_prefix = state->prefix;
×
1404
      state->prefix = NULL;
×
1405

1406
      decode = 1;
1407
    }
1408

UNCOV
1409
    mutt_decode_attachment(b, state);
×
1410

UNCOV
1411
    if (decode)
×
1412
    {
UNCOV
1413
      b->length = ftello(state->fp_out);
×
1414
      b->offset = 0;
×
1415
#ifdef USE_FMEMOPEN
1416
      /* When running under torify, mutt_file_fclose(&state->fp_out) does not seem to
1417
       * update tempsize. On the other hand, fflush does.  See
1418
       * https://github.com/neomutt/neomutt/issues/440 */
1419
      fflush(state->fp_out);
1420
#endif
UNCOV
1421
      mutt_file_fclose(&state->fp_out);
×
1422

1423
      /* restore final destination and substitute the tempfile for input */
UNCOV
1424
      state->fp_out = fp;
×
UNCOV
1425
      fp = state->fp_in;
×
1426
#ifdef USE_FMEMOPEN
1427
      if (tempsize)
1428
      {
1429
        state->fp_in = fmemopen(temp, tempsize, "r");
1430
      }
1431
      else
1432
      { /* fmemopen can't handle zero-length buffers */
1433
        state->fp_in = mutt_file_fopen("/dev/null", "r");
1434
      }
1435
      if (!state->fp_in)
1436
      {
1437
        mutt_perror(_("failed to re-open 'memory stream'"));
1438
        FREE(&temp);
1439
        return -1;
1440
      }
1441
#else
UNCOV
1442
      state->fp_in = mutt_file_fopen(buf_string(tempfile), "r");
×
1443
      unlink(buf_string(tempfile));
×
UNCOV
1444
      buf_pool_release(&tempfile);
×
1445
#endif
1446
      /* restore the prefix */
1447
      state->prefix = save_prefix;
×
1448
    }
1449

1450
    b->type = orig_type;
×
1451
  }
1452

1453
  /* process the (decoded) body part */
UNCOV
1454
  if (handler)
×
1455
  {
1456
    rc = handler(b, state);
×
UNCOV
1457
    if (rc != 0)
×
1458
    {
1459
      mutt_debug(LL_DEBUG1, "Failed on attachment of type %s/%s\n",
×
1460
                 BODY_TYPE(b), NONULL(b->subtype));
1461
    }
1462

1463
    if (decode)
×
1464
    {
UNCOV
1465
      b->length = tmplength;
×
1466
      b->offset = tmpoffset;
×
1467

1468
      /* restore the original source stream */
UNCOV
1469
      mutt_file_fclose(&state->fp_in);
×
UNCOV
1470
      state->fp_in = fp;
×
1471
    }
1472
  }
UNCOV
1473
  state->flags |= STATE_FIRSTDONE;
×
1474
#ifdef USE_FMEMOPEN
1475
  FREE(&temp);
1476
#endif
1477

UNCOV
1478
  return rc;
×
1479
}
1480

1481
/**
1482
 * valid_pgp_encrypted_handler - Handler for valid pgp-encrypted emails - Implements ::handler_t - @ingroup handler_api
1483
 */
UNCOV
1484
static int valid_pgp_encrypted_handler(struct Body *b_email, struct State *state)
×
1485
{
UNCOV
1486
  struct Body *octetstream = b_email->parts->next;
×
1487

1488
  /* clear out any mime headers before the handler, so they can't be spoofed. */
UNCOV
1489
  mutt_env_free(&b_email->mime_headers);
×
1490
  mutt_env_free(&octetstream->mime_headers);
×
1491

1492
  int rc;
1493
  /* Some clients improperly encode the octetstream part. */
1494
  if (octetstream->encoding != ENC_7BIT)
×
UNCOV
1495
    rc = run_decode_and_handler(octetstream, state, crypt_pgp_encrypted_handler, 0);
×
1496
  else
1497
    rc = crypt_pgp_encrypted_handler(octetstream, state);
×
UNCOV
1498
  b_email->goodsig |= octetstream->goodsig;
×
1499

1500
  /* Relocate protected headers onto the multipart/encrypted part */
UNCOV
1501
  if (!rc && octetstream->mime_headers)
×
1502
  {
UNCOV
1503
    b_email->mime_headers = octetstream->mime_headers;
×
UNCOV
1504
    octetstream->mime_headers = NULL;
×
1505
  }
1506

UNCOV
1507
  return rc;
×
1508
}
1509

1510
/**
1511
 * malformed_pgp_encrypted_handler - Handler for invalid pgp-encrypted emails - Implements ::handler_t - @ingroup handler_api
1512
 */
UNCOV
1513
static int malformed_pgp_encrypted_handler(struct Body *b_email, struct State *state)
×
1514
{
1515
  struct Body *octetstream = b_email->parts->next->next;
×
1516

1517
  /* clear out any mime headers before the handler, so they can't be spoofed. */
1518
  mutt_env_free(&b_email->mime_headers);
×
UNCOV
1519
  mutt_env_free(&octetstream->mime_headers);
×
1520

1521
  /* exchange encodes the octet-stream, so re-run it through the decoder */
1522
  int rc = run_decode_and_handler(octetstream, state, crypt_pgp_encrypted_handler, false);
×
UNCOV
1523
  b_email->goodsig |= octetstream->goodsig;
×
1524
#ifdef USE_AUTOCRYPT
1525
  b_email->is_autocrypt |= octetstream->is_autocrypt;
×
1526
#endif
1527

1528
  /* Relocate protected headers onto the multipart/encrypted part */
UNCOV
1529
  if (!rc && octetstream->mime_headers)
×
1530
  {
UNCOV
1531
    b_email->mime_headers = octetstream->mime_headers;
×
UNCOV
1532
    octetstream->mime_headers = NULL;
×
1533
  }
1534

UNCOV
1535
  return rc;
×
1536
}
1537

1538
/**
1539
 * mutt_decode_base64 - Decode base64-encoded text
1540
 * @param state      State to work with
1541
 * @param len    Length of text to decode
1542
 * @param istext Mime part is plain text
1543
 * @param cd     Iconv conversion descriptor
1544
 */
UNCOV
1545
void mutt_decode_base64(struct State *state, size_t len, bool istext, iconv_t cd)
×
1546
{
UNCOV
1547
  char buf[5] = { 0 };
×
1548
  int ch, i;
1549
  bool cr = false;
UNCOV
1550
  char bufi[BUFI_SIZE] = { 0 };
×
1551
  size_t l = 0;
×
1552

1553
  buf[4] = '\0';
1554

1555
  if (istext)
×
1556
    state_set_prefix(state);
×
1557

1558
  while (len > 0)
×
1559
  {
UNCOV
1560
    for (i = 0; (i < 4) && (len > 0); len--)
×
1561
    {
UNCOV
1562
      ch = fgetc(state->fp_in);
×
UNCOV
1563
      if (ch == EOF)
×
1564
        break;
1565
      if ((ch >= 0) && (ch < 128) && ((base64val(ch) != -1) || (ch == '=')))
×
UNCOV
1566
        buf[i++] = ch;
×
1567
    }
UNCOV
1568
    if (i != 4)
×
1569
    {
1570
      /* "i" may be zero if there is trailing whitespace, which is not an error */
UNCOV
1571
      if (i != 0)
×
UNCOV
1572
        mutt_debug(LL_DEBUG2, "didn't get a multiple of 4 chars\n");
×
1573
      break;
1574
    }
1575

1576
    const int c1 = base64val(buf[0]);
×
UNCOV
1577
    const int c2 = base64val(buf[1]);
×
1578

1579
    /* first char */
1580
    ch = (c1 << 2) | (c2 >> 4);
×
1581

UNCOV
1582
    if (cr && (ch != '\n'))
×
1583
      bufi[l++] = '\r';
×
1584

1585
    cr = false;
1586

UNCOV
1587
    if (istext && (ch == '\r'))
×
1588
      cr = true;
1589
    else
UNCOV
1590
      bufi[l++] = ch;
×
1591

1592
    /* second char */
UNCOV
1593
    if (buf[2] == '=')
×
1594
      break;
UNCOV
1595
    const int c3 = base64val(buf[2]);
×
1596
    ch = ((c2 & 0xf) << 4) | (c3 >> 2);
×
1597

UNCOV
1598
    if (cr && (ch != '\n'))
×
1599
      bufi[l++] = '\r';
×
1600

1601
    cr = false;
1602

UNCOV
1603
    if (istext && (ch == '\r'))
×
1604
      cr = true;
1605
    else
UNCOV
1606
      bufi[l++] = ch;
×
1607

1608
    /* third char */
UNCOV
1609
    if (buf[3] == '=')
×
1610
      break;
UNCOV
1611
    const int c4 = base64val(buf[3]);
×
1612
    ch = ((c3 & 0x3) << 6) | c4;
×
1613

UNCOV
1614
    if (cr && (ch != '\n'))
×
1615
      bufi[l++] = '\r';
×
1616

1617
    cr = false;
1618

UNCOV
1619
    if (istext && (ch == '\r'))
×
1620
      cr = true;
1621
    else
1622
      bufi[l++] = ch;
×
1623

1624
    if ((l + 8) >= sizeof(bufi))
×
1625
      convert_to_state(cd, bufi, &l, state);
×
1626
  }
1627

1628
  if (cr)
×
UNCOV
1629
    bufi[l++] = '\r';
×
1630

UNCOV
1631
  convert_to_state(cd, bufi, &l, state);
×
UNCOV
1632
  convert_to_state(cd, 0, 0, state);
×
1633

UNCOV
1634
  state_reset_prefix(state);
×
UNCOV
1635
}
×
1636

1637
/**
1638
 * mutt_body_handler - Handler for the Body of an email
1639
 * @param b     Body of the email
1640
 * @param state State to work with
1641
 * @retval 0 Success
1642
 * @retval -1 Error
1643
 */
UNCOV
1644
int mutt_body_handler(struct Body *b, struct State *state)
×
1645
{
UNCOV
1646
  if (!b || !state)
×
1647
    return -1;
1648

1649
  bool plaintext = false;
1650
  handler_t handler = NULL;
1651
  handler_t encrypted_handler = NULL;
1652
  int rc = 0;
1653
  static unsigned short recurse_level = 0;
1654

UNCOV
1655
  const int oflags = state->flags;
×
1656
  const bool is_attachment_display = (oflags & STATE_DISPLAY_ATTACH);
1657

UNCOV
1658
  if (recurse_level >= MUTT_MIME_MAX_DEPTH)
×
1659
  {
1660
    mutt_debug(LL_DEBUG1, "recurse level too deep. giving up\n");
×
UNCOV
1661
    return 1;
×
1662
  }
1663
  recurse_level++;
×
1664

1665
  /* first determine which handler to use to process this part */
1666

1667
  if (is_autoview(b))
×
1668
  {
1669
    handler = autoview_handler;
UNCOV
1670
    state->flags &= ~STATE_CHARCONV;
×
1671
  }
1672
  else if (b->type == TYPE_TEXT)
×
1673
  {
UNCOV
1674
    if (mutt_istr_equal("plain", b->subtype))
×
1675
    {
UNCOV
1676
      const bool c_reflow_text = cs_subset_bool(NeoMutt->sub, "reflow_text");
×
1677
      /* avoid copying this part twice since removing the transfer-encoding is
1678
       * the only operation needed.  */
UNCOV
1679
      if (((WithCrypto & APPLICATION_PGP) != 0) && mutt_is_application_pgp(b))
×
1680
      {
1681
        encrypted_handler = crypt_pgp_application_handler;
1682
        handler = encrypted_handler;
1683
      }
UNCOV
1684
      else if (c_reflow_text &&
×
UNCOV
1685
               mutt_istr_equal("flowed", mutt_param_get(&b->parameter, "format")))
×
1686
      {
1687
        handler = rfc3676_handler;
1688
      }
1689
      else
1690
      {
1691
        handler = text_plain_handler;
1692
      }
1693
    }
UNCOV
1694
    else if (mutt_istr_equal("enriched", b->subtype))
×
1695
    {
1696
      handler = text_enriched_handler;
1697
    }
1698
    else /* text body type without a handler */
1699
    {
1700
      plaintext = false;
1701
    }
1702
  }
UNCOV
1703
  else if (b->type == TYPE_MESSAGE)
×
1704
  {
1705
    if (mutt_is_message_type(b->type, b->subtype))
×
1706
      handler = message_handler;
1707
    else if (mutt_istr_equal("delivery-status", b->subtype))
×
1708
      plaintext = true;
1709
    else if (mutt_istr_equal("external-body", b->subtype))
×
1710
      handler = external_body_handler;
1711
  }
UNCOV
1712
  else if (b->type == TYPE_MULTIPART)
×
1713
  {
1714
    const char *const c_show_multipart_alternative = cs_subset_string(NeoMutt->sub, "show_multipart_alternative");
×
UNCOV
1715
    if (!mutt_str_equal("inline", c_show_multipart_alternative) &&
×
UNCOV
1716
        mutt_istr_equal("alternative", b->subtype))
×
1717
    {
1718
      handler = alternative_handler;
1719
    }
1720
    else if (!mutt_str_equal("inline", c_show_multipart_alternative) &&
×
1721
             mutt_istr_equal("multilingual", b->subtype))
×
1722
    {
1723
      handler = multilingual_handler;
1724
    }
1725
    else if ((WithCrypto != 0) && mutt_istr_equal("signed", b->subtype))
×
1726
    {
UNCOV
1727
      if (!mutt_param_get(&b->parameter, "protocol"))
×
UNCOV
1728
        mutt_error(_("Error: multipart/signed has no protocol"));
×
UNCOV
1729
      else if (state->flags & STATE_VERIFY)
×
1730
        handler = mutt_signed_handler;
1731
    }
UNCOV
1732
    else if (mutt_is_valid_multipart_pgp_encrypted(b))
×
1733
    {
1734
      encrypted_handler = valid_pgp_encrypted_handler;
1735
      handler = encrypted_handler;
1736
    }
UNCOV
1737
    else if (mutt_is_malformed_multipart_pgp_encrypted(b))
×
1738
    {
1739
      encrypted_handler = malformed_pgp_encrypted_handler;
1740
      handler = encrypted_handler;
1741
    }
1742

1743
    if (!handler)
×
1744
      handler = multipart_handler;
1745

1746
    if ((b->encoding != ENC_7BIT) && (b->encoding != ENC_8BIT) && (b->encoding != ENC_BINARY))
×
1747
    {
1748
      mutt_debug(LL_DEBUG1, "Bad encoding type %d for multipart entity, assuming 7 bit\n",
×
1749
                 b->encoding);
UNCOV
1750
      b->encoding = ENC_7BIT;
×
1751
    }
1752
  }
1753
  else if ((WithCrypto != 0) && (b->type == TYPE_APPLICATION))
×
1754
  {
UNCOV
1755
    if (OptDontHandlePgpKeys && mutt_istr_equal("pgp-keys", b->subtype))
×
1756
    {
1757
      /* pass raw part through for key extraction */
1758
      plaintext = true;
1759
    }
UNCOV
1760
    else if (((WithCrypto & APPLICATION_PGP) != 0) && mutt_is_application_pgp(b))
×
1761
    {
1762
      encrypted_handler = crypt_pgp_application_handler;
1763
      handler = encrypted_handler;
1764
    }
1765
    else if (((WithCrypto & APPLICATION_SMIME) != 0) && mutt_is_application_smime(b))
×
1766
    {
1767
      encrypted_handler = crypt_smime_application_handler;
1768
      handler = encrypted_handler;
1769
    }
1770
  }
1771

1772
  if ((plaintext || handler) && (is_attachment_display || !mutt_prefer_as_attachment(b)))
×
1773
  {
×
1774
    /* only respect disposition == attachment if we're not
1775
     * displaying from the attachment menu (i.e. pager) */
1776
    /* Prevent encrypted attachments from being included in replies
1777
     * unless $include_encrypted is set. */
1778
    const bool c_include_encrypted = cs_subset_bool(NeoMutt->sub, "include_encrypted");
×
UNCOV
1779
    if ((state->flags & STATE_REPLYING) && (state->flags & STATE_FIRSTDONE) &&
×
1780
        encrypted_handler && !c_include_encrypted)
×
1781
    {
UNCOV
1782
      goto cleanup;
×
1783
    }
1784

1785
    rc = run_decode_and_handler(b, state, handler, plaintext);
×
1786
  }
1787
  else if (state->flags & STATE_DISPLAY)
×
1788
  {
1789
    /* print hint to use attachment menu for disposition == attachment
1790
     * if we're not already being called from there */
1791
    const bool c_honor_disposition = cs_subset_bool(NeoMutt->sub, "honor_disposition");
×
UNCOV
1792
    struct Buffer *msg = buf_pool_get();
×
1793

UNCOV
1794
    if (is_attachment_display)
×
1795
    {
1796
      if (c_honor_disposition && (b->disposition == DISP_ATTACH))
×
1797
      {
UNCOV
1798
        buf_strcpy(msg, _("[-- This is an attachment --]\n"));
×
1799
      }
1800
      else
1801
      {
1802
        /* L10N: %s/%s is a MIME type, e.g. "text/plain". */
UNCOV
1803
        buf_printf(msg, _("[-- %s/%s is unsupported --]\n"), BODY_TYPE(b), b->subtype);
×
1804
      }
1805
    }
1806
    else
1807
    {
UNCOV
1808
      struct Buffer *keystroke = buf_pool_get();
×
UNCOV
1809
      if (keymap_expand_key(km_find_func(MENU_PAGER, OP_VIEW_ATTACHMENTS), keystroke))
×
1810
      {
UNCOV
1811
        if (c_honor_disposition && (b->disposition == DISP_ATTACH))
×
1812
        {
1813
          /* L10N: %s expands to a keystroke/key binding, e.g. 'v'.  */
1814
          buf_printf(msg, _("[-- This is an attachment (use '%s' to view this part) --]\n"),
×
1815
                     buf_string(keystroke));
1816
        }
1817
        else
1818
        {
1819
          /* L10N: %s/%s is a MIME type, e.g. "text/plain".
1820
             The last %s expands to a keystroke/key binding, e.g. 'v'. */
UNCOV
1821
          buf_printf(msg, _("[-- %s/%s is unsupported (use '%s' to view this part) --]\n"),
×
1822
                     BODY_TYPE(b), b->subtype, buf_string(keystroke));
×
1823
        }
1824
      }
1825
      else
1826
      {
1827
        if (c_honor_disposition && (b->disposition == DISP_ATTACH))
×
1828
        {
UNCOV
1829
          buf_strcpy(msg, _("[-- This is an attachment (need 'view-attachments' bound to key) --]\n"));
×
1830
        }
1831
        else
1832
        {
1833
          /* L10N: %s/%s is a MIME type, e.g. "text/plain". */
1834
          buf_printf(msg, _("[-- %s/%s is unsupported (need 'view-attachments' bound to key) --]\n"),
×
1835
                     BODY_TYPE(b), b->subtype);
×
1836
        }
1837
      }
1838
      buf_pool_release(&keystroke);
×
1839
    }
1840
    state_mark_attach(state);
×
1841
    state_printf(state, "%s", buf_string(msg));
×
UNCOV
1842
    buf_pool_release(&msg);
×
1843
  }
1844

UNCOV
1845
cleanup:
×
UNCOV
1846
  recurse_level--;
×
UNCOV
1847
  state->flags = oflags | (state->flags & STATE_FIRSTDONE);
×
UNCOV
1848
  if (rc != 0)
×
1849
  {
UNCOV
1850
    mutt_debug(LL_DEBUG1, "Bailing on attachment of type %s/%s\n", BODY_TYPE(b),
×
1851
               NONULL(b->subtype));
1852
  }
1853

1854
  return rc;
1855
}
1856

1857
/**
1858
 * mutt_prefer_as_attachment - Do we want this part as an attachment?
1859
 * @param b Body of email to test
1860
 * @retval true We want this part as an attachment
1861
 */
UNCOV
1862
bool mutt_prefer_as_attachment(struct Body *b)
×
1863
{
UNCOV
1864
  if (!mutt_can_decode(b))
×
1865
    return true;
1866

UNCOV
1867
  if (b->disposition != DISP_ATTACH)
×
1868
    return false;
1869

UNCOV
1870
  return cs_subset_bool(NeoMutt->sub, "honor_disposition");
×
1871
}
1872

1873
/**
1874
 * mutt_can_decode - Will decoding the attachment produce any output
1875
 * @param b Body of email to test
1876
 * @retval true Decoding the attachment will produce output
1877
 */
UNCOV
1878
bool mutt_can_decode(struct Body *b)
×
1879
{
UNCOV
1880
  if (is_autoview(b))
×
1881
    return true;
UNCOV
1882
  if (b->type == TYPE_TEXT)
×
1883
    return true;
UNCOV
1884
  if (b->type == TYPE_MESSAGE)
×
1885
    return true;
UNCOV
1886
  if (b->type == TYPE_MULTIPART)
×
1887
  {
1888
    if (WithCrypto)
1889
    {
UNCOV
1890
      if (mutt_istr_equal(b->subtype, "signed") || mutt_istr_equal(b->subtype, "encrypted"))
×
1891
      {
UNCOV
1892
        return true;
×
1893
      }
1894
    }
1895

UNCOV
1896
    for (struct Body *part = b->parts; part; part = part->next)
×
1897
    {
UNCOV
1898
      if (mutt_can_decode(part))
×
1899
        return true;
1900
    }
1901
  }
UNCOV
1902
  else if ((WithCrypto != 0) && (b->type == TYPE_APPLICATION))
×
1903
  {
UNCOV
1904
    if (((WithCrypto & APPLICATION_PGP) != 0) && mutt_is_application_pgp(b))
×
1905
      return true;
UNCOV
1906
    if (((WithCrypto & APPLICATION_SMIME) != 0) && mutt_is_application_smime(b))
×
1907
      return true;
1908
  }
1909

1910
  return false;
1911
}
1912

1913
/**
1914
 * mutt_decode_attachment - Decode an email's attachment
1915
 * @param b Body of the email
1916
 * @param state State of text being processed
1917
 */
UNCOV
1918
void mutt_decode_attachment(const struct Body *b, struct State *state)
×
1919
{
UNCOV
1920
  int istext = mutt_is_text_part(b) && (b->disposition == DISP_INLINE);
×
1921
  iconv_t cd = ICONV_T_INVALID;
1922

UNCOV
1923
  if (!mutt_file_seek(state->fp_in, b->offset, SEEK_SET))
×
1924
  {
1925
    return;
1926
  }
1927

1928
  if (istext && (b->charset || (state->flags & STATE_CHARCONV)))
×
1929
  {
1930
    const char *charset = b->charset;
1931
    if (!charset)
×
1932
    {
UNCOV
1933
      charset = mutt_param_get(&b->parameter, "charset");
×
1934
      if (!charset && !slist_is_empty(cc_assumed_charset()))
×
UNCOV
1935
        charset = mutt_ch_get_default_charset(cc_assumed_charset());
×
1936
    }
1937
    if (charset && cc_charset())
×
1938
      cd = mutt_ch_iconv_open(cc_charset(), charset, MUTT_ICONV_HOOK_FROM);
×
1939
  }
1940

1941
  switch (b->encoding)
×
1942
  {
1943
    case ENC_QUOTED_PRINTABLE:
×
1944
      decode_quoted(state, b->length,
×
1945
                    istext || (((WithCrypto & APPLICATION_PGP) != 0) &&
×
UNCOV
1946
                               mutt_is_application_pgp(b)),
×
1947
                    cd);
1948
      break;
×
1949
    case ENC_BASE64:
×
1950
      mutt_decode_base64(state, b->length,
×
1951
                         istext || (((WithCrypto & APPLICATION_PGP) != 0) &&
×
UNCOV
1952
                                    mutt_is_application_pgp(b)),
×
1953
                         cd);
1954
      break;
×
1955
    case ENC_UUENCODED:
×
1956
      decode_uuencoded(state, b->length,
×
1957
                       istext || (((WithCrypto & APPLICATION_PGP) != 0) &&
×
UNCOV
1958
                                  mutt_is_application_pgp(b)),
×
1959
                       cd);
UNCOV
1960
      break;
×
UNCOV
1961
    default:
×
UNCOV
1962
      decode_xbit(state, b->length,
×
UNCOV
1963
                  istext || (((WithCrypto & APPLICATION_PGP) != 0) &&
×
UNCOV
1964
                             mutt_is_application_pgp(b)),
×
1965
                  cd);
UNCOV
1966
      break;
×
1967
  }
1968
}
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