Coverage Report

Created: 2025-04-22 06:17

/src/neomutt/handler.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Decide how to display email content
4
 *
5
 * @authors
6
 * Copyright (C) 1996-2000,2002,2010,2013 Michael R. Elkins <me@mutt.org>
7
 * Copyright (C) 2017-2022 Pietro Cerutti <gahr@gahr.ch>
8
 * Copyright (C) 2017-2023 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
 *
15
 * @copyright
16
 * This program is free software: you can redistribute it and/or modify it under
17
 * the terms of the GNU General Public License as published by the Free Software
18
 * Foundation, either version 2 of the License, or (at your option) any later
19
 * version.
20
 *
21
 * This program is distributed in the hope that it will be useful, but WITHOUT
22
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
23
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
24
 * details.
25
 *
26
 * You should have received a copy of the GNU General Public License along with
27
 * this program.  If not, see <http://www.gnu.org/licenses/>.
28
 */
29
30
/**
31
 * @page neo_handler Decide how to display email content
32
 *
33
 * Decide how to display email content
34
 */
35
36
#include "config.h"
37
#include <ctype.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 "email/lib.h"
48
#include "core/lib.h"
49
#include "gui/lib.h"
50
#include "mutt.h"
51
#include "handler.h"
52
#include "attach/lib.h"
53
#include "key/lib.h"
54
#include "menu/lib.h"
55
#include "ncrypt/lib.h"
56
#include "pager/lib.h"
57
#include "copy.h"
58
#include "enriched.h"
59
#include "globals.h"
60
#include "mailcap.h"
61
#include "mutt_logging.h"
62
#include "muttlib.h"
63
#include "rfc3676.h"
64
#ifdef ENABLE_NLS
65
#include <libintl.h>
66
#endif
67
68
#define BUFI_SIZE 1000
69
#define BUFO_SIZE 2000
70
71
0
#define TXT_HTML 1
72
0
#define TXT_PLAIN 2
73
0
#define TXT_ENRICHED 3
74
75
/**
76
 * @defgroup handler_api Mime Handler API
77
 *
78
 * Prototype for a function to handle MIME parts
79
 *
80
 * @param b_email Body of the email
81
 * @param state   State of text being processed
82
 * @retval 0 Success
83
 * @retval -1 Error
84
 */
85
typedef int (*handler_t)(struct Body *b_email, struct State *state);
86
87
/**
88
 * print_part_line - Print a separator for the Mime part
89
 * @param state State of text being processed
90
 * @param b_email     Body of the email
91
 * @param n     Part number for multipart emails (0 otherwise)
92
 */
93
static void print_part_line(struct State *state, struct Body *b_email, int n)
94
0
{
95
0
  struct Buffer *length = buf_pool_get();
96
0
  mutt_str_pretty_size(length, b_email->length);
97
0
  state_mark_attach(state);
98
0
  char *charset = mutt_param_get(&b_email->parameter, "charset");
99
0
  if (n == 0)
100
0
  {
101
0
    state_printf(state, _("[-- Type: %s/%s%s%s, Encoding: %s, Size: %s --]\n"),
102
0
                 BODY_TYPE(b_email), b_email->subtype, charset ? "; charset=" : "",
103
0
                 charset ? charset : "", ENCODING(b_email->encoding), buf_string(length));
104
0
  }
105
0
  else
106
0
  {
107
0
    state_printf(state, _("[-- Alternative Type #%d: %s/%s%s%s, Encoding: %s, Size: %s --]\n"),
108
0
                 n, BODY_TYPE(b_email), b_email->subtype,
109
0
                 charset ? "; charset=" : "", charset ? charset : "",
110
0
                 ENCODING(b_email->encoding), buf_string(length));
111
0
  }
112
0
  buf_pool_release(&length);
113
0
}
114
115
/**
116
 * convert_to_state - Convert text and write it to a file
117
 * @param cd     Iconv conversion descriptor
118
 * @param bufi   Buffer with text to convert
119
 * @param l      Length of buffer
120
 * @param state  State to write to
121
 */
122
static void convert_to_state(iconv_t cd, char *bufi, size_t *l, struct State *state)
123
0
{
124
0
  char bufo[BUFO_SIZE] = { 0 };
125
0
  const char *ib = NULL;
126
0
  char *ob = NULL;
127
0
  size_t ibl, obl;
128
129
0
  if (!bufi)
130
0
  {
131
0
    if (iconv_t_valid(cd))
132
0
    {
133
0
      ob = bufo;
134
0
      obl = sizeof(bufo);
135
0
      iconv(cd, NULL, NULL, &ob, &obl);
136
0
      if (ob != bufo)
137
0
        state_prefix_put(state, bufo, ob - bufo);
138
0
    }
139
0
    return;
140
0
  }
141
142
0
  if (!iconv_t_valid(cd))
143
0
  {
144
0
    state_prefix_put(state, bufi, *l);
145
0
    *l = 0;
146
0
    return;
147
0
  }
148
149
0
  ib = bufi;
150
0
  ibl = *l;
151
0
  while (true)
152
0
  {
153
0
    ob = bufo;
154
0
    obl = sizeof(bufo);
155
0
    mutt_ch_iconv(cd, &ib, &ibl, &ob, &obl, 0, "?", NULL);
156
0
    if (ob == bufo)
157
0
      break;
158
0
    state_prefix_put(state, bufo, ob - bufo);
159
0
  }
160
0
  memmove(bufi, ib, ibl);
161
0
  *l = ibl;
162
0
}
163
164
/**
165
 * decode_xbit - Decode xbit-encoded text
166
 * @param state  State to work with
167
 * @param len    Length of text to decode
168
 * @param istext Mime part is plain text
169
 * @param cd     Iconv conversion descriptor
170
 */
171
static void decode_xbit(struct State *state, long len, bool istext, iconv_t cd)
172
0
{
173
0
  if (!istext)
174
0
  {
175
0
    mutt_file_copy_bytes(state->fp_in, state->fp_out, len);
176
0
    return;
177
0
  }
178
179
0
  state_set_prefix(state);
180
181
0
  int c;
182
0
  char bufi[BUFI_SIZE] = { 0 };
183
0
  size_t l = 0;
184
0
  while (((c = fgetc(state->fp_in)) != EOF) && len--)
185
0
  {
186
0
    if ((c == '\r') && len)
187
0
    {
188
0
      const int ch = fgetc(state->fp_in);
189
0
      if (ch == '\n')
190
0
      {
191
0
        c = ch;
192
0
        len--;
193
0
      }
194
0
      else
195
0
      {
196
0
        ungetc(ch, state->fp_in);
197
0
      }
198
0
    }
199
200
0
    bufi[l++] = c;
201
0
    if (l == sizeof(bufi))
202
0
      convert_to_state(cd, bufi, &l, state);
203
0
  }
204
205
0
  convert_to_state(cd, bufi, &l, state);
206
0
  convert_to_state(cd, 0, 0, state);
207
208
0
  state_reset_prefix(state);
209
0
}
210
211
/**
212
 * qp_decode_triple - Decode a quoted-printable triplet
213
 * @param s State to work with
214
 * @param d Decoded character
215
 * @retval 0 Success
216
 * @retval -1 Error
217
 */
218
static int qp_decode_triple(char *s, char *d)
219
0
{
220
  /* soft line break */
221
0
  if ((s[0] == '=') && (s[1] == '\0'))
222
0
    return 1;
223
224
  /* quoted-printable triple */
225
0
  if ((s[0] == '=') && isxdigit((unsigned char) s[1]) && isxdigit((unsigned char) s[2]))
226
0
  {
227
0
    *d = (hexval(s[1]) << 4) | hexval(s[2]);
228
0
    return 0;
229
0
  }
230
231
  /* something else */
232
0
  return -1;
233
0
}
234
235
/**
236
 * qp_decode_line - Decode a line of quoted-printable text
237
 * @param dest Buffer for result
238
 * @param src  Text to decode
239
 * @param l    Bytes written to buffer
240
 * @param last Last character of the line
241
 */
242
static void qp_decode_line(char *dest, char *src, size_t *l, int last)
243
0
{
244
0
  char *d = NULL, *s = NULL;
245
0
  char c = 0;
246
247
0
  int kind = -1;
248
0
  bool soft = false;
249
250
  /* decode the line */
251
252
0
  for (d = dest, s = src; *s;)
253
0
  {
254
0
    switch ((kind = qp_decode_triple(s, &c)))
255
0
    {
256
0
      case 0:
257
0
        *d++ = c;
258
0
        s += 3;
259
0
        break; /* qp triple */
260
0
      case -1:
261
0
        *d++ = *s++;
262
0
        break; /* single character */
263
0
      case 1:
264
0
        soft = true;
265
0
        s++;
266
0
        break; /* soft line break */
267
0
    }
268
0
  }
269
270
0
  if (!soft && (last == '\n'))
271
0
  {
272
    /* neither \r nor \n as part of line-terminating CRLF
273
     * may be qp-encoded, so remove \r and \n-terminate;
274
     * see RFC2045, sect. 6.7, (1): General 8bit representation */
275
0
    if ((kind == 0) && (c == '\r'))
276
0
      *(d - 1) = '\n';
277
0
    else
278
0
      *d++ = '\n';
279
0
  }
280
281
0
  *d = '\0';
282
0
  *l = d - dest;
283
0
}
284
285
/**
286
 * decode_quoted - Decode an attachment encoded with quoted-printable
287
 * @param state  State to work with
288
 * @param len    Length of text to decode
289
 * @param istext Mime part is plain text
290
 * @param cd     Iconv conversion descriptor
291
 *
292
 * Why doesn't this overflow any buffers? First, it's guaranteed that the
293
 * length of a line grows when you _en_-code it to quoted-printable. That
294
 * means that we always can store the result in a buffer of at most the _same_
295
 * size.
296
 *
297
 * Now, we don't special-case if the line we read with fgets() isn't
298
 * terminated. We don't care about this, since 256 > 78, so corrupted input
299
 * will just be corrupted a bit more. That implies that 256+1 bytes are
300
 * always sufficient to store the result of qp_decode_line.
301
 *
302
 * Finally, at soft line breaks, some part of a multibyte character may have
303
 * been left over by convert_to_state(). This shouldn't be more than 6
304
 * characters, so 256+7 should be sufficient memory to store the decoded
305
 * data.
306
 *
307
 * Just to make sure that I didn't make some off-by-one error above, we just
308
 * use 512 for the target buffer's size.
309
 */
310
static void decode_quoted(struct State *state, long len, bool istext, iconv_t cd)
311
0
{
312
0
  char line[256] = { 0 };
313
0
  char decline[512] = { 0 };
314
0
  size_t l = 0;
315
0
  size_t l3;
316
317
0
  if (istext)
318
0
    state_set_prefix(state);
319
320
0
  while (len > 0)
321
0
  {
322
    /* It's ok to use a fixed size buffer for input, even if the line turns
323
     * out to be longer than this.  Just process the line in chunks.  This
324
     * really shouldn't happen according the MIME spec, since Q-P encoded
325
     * lines are at most 76 characters, but we should be liberal about what
326
     * we accept.  */
327
0
    if (!fgets(line, MIN((ssize_t) sizeof(line), len + 1), state->fp_in))
328
0
      break;
329
330
0
    size_t linelen = strlen(line);
331
0
    len -= linelen;
332
333
    /* inspect the last character we read so we can tell if we got the
334
     * entire line.  */
335
0
    const int last = (linelen != 0) ? line[linelen - 1] : 0;
336
337
    /* chop trailing whitespace if we got the full line */
338
0
    if (last == '\n')
339
0
    {
340
0
      while ((linelen > 0) && isspace(line[linelen - 1]))
341
0
        linelen--;
342
0
      line[linelen] = '\0';
343
0
    }
344
345
    /* decode and do character set conversion */
346
0
    qp_decode_line(decline + l, line, &l3, last);
347
0
    l += l3;
348
0
    convert_to_state(cd, decline, &l, state);
349
0
  }
350
351
0
  convert_to_state(cd, 0, 0, state);
352
0
  state_reset_prefix(state);
353
0
}
354
355
/**
356
 * decode_byte - Decode a uuencoded byte
357
 * @param ch Character to decode
358
 * @retval num Decoded value
359
 */
360
static unsigned char decode_byte(char ch)
361
0
{
362
0
  if ((ch < 32) || (ch > 95))
363
0
    return 0;
364
0
  return ch - 32;
365
0
}
366
367
/**
368
 * decode_uuencoded - Decode uuencoded text
369
 * @param state      State to work with
370
 * @param len    Length of text to decode
371
 * @param istext Mime part is plain text
372
 * @param cd     Iconv conversion descriptor
373
 */
374
static void decode_uuencoded(struct State *state, long len, bool istext, iconv_t cd)
375
0
{
376
0
  char tmps[128] = { 0 };
377
0
  char *pt = NULL;
378
0
  char bufi[BUFI_SIZE] = { 0 };
379
0
  size_t k = 0;
380
381
0
  if (istext)
382
0
    state_set_prefix(state);
383
384
0
  while (len > 0)
385
0
  {
386
0
    if (!fgets(tmps, sizeof(tmps), state->fp_in))
387
0
      goto cleanup;
388
0
    len -= mutt_str_len(tmps);
389
0
    if (mutt_str_startswith(tmps, "begin "))
390
0
      break;
391
0
  }
392
0
  while (len > 0)
393
0
  {
394
0
    if (!fgets(tmps, sizeof(tmps), state->fp_in))
395
0
      goto cleanup;
396
0
    len -= mutt_str_len(tmps);
397
0
    if (mutt_str_startswith(tmps, "end"))
398
0
      break;
399
0
    pt = tmps;
400
0
    const unsigned char linelen = decode_byte(*pt);
401
0
    pt++;
402
0
    for (unsigned char c = 0; (c < linelen) && *pt;)
403
0
    {
404
0
      for (char l = 2; (l <= 6) && pt[0] && pt[1]; l += 2)
405
0
      {
406
0
        char out = decode_byte(*pt) << l;
407
0
        pt++;
408
0
        out |= (decode_byte(*pt) >> (6 - l));
409
0
        bufi[k++] = out;
410
0
        c++;
411
0
        if (c == linelen)
412
0
          break;
413
0
      }
414
0
      convert_to_state(cd, bufi, &k, state);
415
0
      pt++;
416
0
    }
417
0
  }
418
419
0
cleanup:
420
0
  convert_to_state(cd, bufi, &k, state);
421
0
  convert_to_state(cd, 0, 0, state);
422
423
0
  state_reset_prefix(state);
424
0
}
425
426
/**
427
 * is_mmnoask - Metamail compatibility: should the attachment be autoviewed?
428
 * @param buf Mime type, e.g. 'text/plain'
429
 * @retval true Metamail "no ask" is true
430
 *
431
 * Test if the `MM_NOASK` environment variable should allow autoviewing of the
432
 * attachment.
433
 *
434
 * @note If `MM_NOASK=1` then the function will automatically return true.
435
 */
436
static bool is_mmnoask(const char *buf)
437
0
{
438
0
  const char *val = mutt_str_getenv("MM_NOASK");
439
0
  if (!val)
440
0
    return false;
441
442
0
  char *p = NULL;
443
0
  char tmp[1024] = { 0 };
444
0
  char *q = NULL;
445
446
0
  if (mutt_str_equal(val, "1"))
447
0
    return true;
448
449
0
  mutt_str_copy(tmp, val, sizeof(tmp));
450
0
  p = tmp;
451
452
0
  while ((p = strtok(p, ",")))
453
0
  {
454
0
    q = strrchr(p, '/');
455
0
    if (q)
456
0
    {
457
0
      if (q[1] == '*')
458
0
      {
459
0
        if (mutt_istrn_equal(buf, p, q - p))
460
0
          return true;
461
0
      }
462
0
      else
463
0
      {
464
0
        if (mutt_istr_equal(buf, p))
465
0
          return true;
466
0
      }
467
0
    }
468
0
    else
469
0
    {
470
0
      const size_t plen = mutt_istr_startswith(buf, p);
471
0
      if ((plen != 0) && (buf[plen] == '/'))
472
0
        return true;
473
0
    }
474
475
0
    p = NULL;
476
0
  }
477
478
0
  return false;
479
0
}
480
481
/**
482
 * is_autoview - Should email body be filtered by mailcap
483
 * @param b Body of the email
484
 * @retval 1 body part should be filtered by a mailcap entry prior to viewing inline
485
 * @retval 0 otherwise
486
 */
487
static bool is_autoview(struct Body *b)
488
0
{
489
0
  char type[256] = { 0 };
490
0
  bool is_av = false;
491
492
0
  snprintf(type, sizeof(type), "%s/%s", BODY_TYPE(b), b->subtype);
493
494
0
  const bool c_implicit_auto_view = cs_subset_bool(NeoMutt->sub, "implicit_auto_view");
495
0
  if (c_implicit_auto_view)
496
0
  {
497
    /* $implicit_auto_view is essentially the same as "auto_view *" */
498
0
    is_av = true;
499
0
  }
500
0
  else
501
0
  {
502
    /* determine if this type is on the user's auto_view list */
503
0
    mutt_check_lookup_list(b, type, sizeof(type));
504
0
    struct ListNode *np = NULL;
505
0
    STAILQ_FOREACH(np, &AutoViewList, entries)
506
0
    {
507
0
      int i = mutt_str_len(np->data);
508
0
      i--;
509
0
      if (((i > 0) && (np->data[i - 1] == '/') && (np->data[i] == '*') &&
510
0
           mutt_istrn_equal(type, np->data, i)) ||
511
0
          mutt_istr_equal(type, np->data))
512
0
      {
513
0
        is_av = true;
514
0
        break;
515
0
      }
516
0
    }
517
518
0
    if (is_mmnoask(type))
519
0
      is_av = true;
520
0
  }
521
522
  /* determine if there is a mailcap entry suitable for auto_view
523
   *
524
   * @warning type is altered by this call as a result of 'mime_lookup' support */
525
0
  if (is_av)
526
0
    return mailcap_lookup(b, type, sizeof(type), NULL, MUTT_MC_AUTOVIEW);
527
528
0
  return false;
529
0
}
530
531
/**
532
 * autoview_handler - Handler for autoviewable attachments - Implements ::handler_t - @ingroup handler_api
533
 */
534
static int autoview_handler(struct Body *b_email, struct State *state)
535
0
{
536
0
  struct MailcapEntry *entry = mailcap_entry_new();
537
0
  char buf[1024] = { 0 };
538
0
  char type[256] = { 0 };
539
0
  struct Buffer *cmd = buf_pool_get();
540
0
  struct Buffer *tempfile = buf_pool_get();
541
0
  char *fname = NULL;
542
0
  FILE *fp_in = NULL;
543
0
  FILE *fp_out = NULL;
544
0
  FILE *fp_err = NULL;
545
0
  pid_t pid;
546
0
  int rc = 0;
547
548
0
  snprintf(type, sizeof(type), "%s/%s", BODY_TYPE(b_email), b_email->subtype);
549
0
  mailcap_lookup(b_email, type, sizeof(type), entry, MUTT_MC_AUTOVIEW);
550
551
0
  fname = mutt_str_dup(b_email->filename);
552
0
  mutt_file_sanitize_filename(fname, true);
553
0
  mailcap_expand_filename(entry->nametemplate, fname, tempfile);
554
0
  FREE(&fname);
555
556
0
  if (entry->command)
557
0
  {
558
0
    buf_strcpy(cmd, entry->command);
559
560
    /* mailcap_expand_command returns 0 if the file is required */
561
0
    bool piped = mailcap_expand_command(b_email, buf_string(tempfile), type, cmd);
562
563
0
    if (state->flags & STATE_DISPLAY)
564
0
    {
565
0
      state_mark_attach(state);
566
0
      state_printf(state, _("[-- Autoview using %s --]\n"), buf_string(cmd));
567
0
      mutt_message(_("Invoking autoview command: %s"), buf_string(cmd));
568
0
    }
569
570
0
    fp_in = mutt_file_fopen(buf_string(tempfile), "w+");
571
0
    if (!fp_in)
572
0
    {
573
0
      mutt_perror("fopen");
574
0
      mailcap_entry_free(&entry);
575
0
      rc = -1;
576
0
      goto cleanup;
577
0
    }
578
579
0
    mutt_file_copy_bytes(state->fp_in, fp_in, b_email->length);
580
581
0
    if (piped)
582
0
    {
583
0
      unlink(buf_string(tempfile));
584
0
      fflush(fp_in);
585
0
      rewind(fp_in);
586
0
      pid = filter_create_fd(buf_string(cmd), NULL, &fp_out, &fp_err,
587
0
                             fileno(fp_in), -1, -1, NeoMutt->env);
588
0
    }
589
0
    else
590
0
    {
591
0
      mutt_file_fclose(&fp_in);
592
0
      pid = filter_create(buf_string(cmd), NULL, &fp_out, &fp_err, NeoMutt->env);
593
0
    }
594
595
0
    if (pid < 0)
596
0
    {
597
0
      mutt_perror(_("Can't create filter"));
598
0
      if (state->flags & STATE_DISPLAY)
599
0
      {
600
0
        state_mark_attach(state);
601
0
        state_printf(state, _("[-- Can't run %s --]\n"), buf_string(cmd));
602
0
      }
603
0
      rc = -1;
604
0
      goto bail;
605
0
    }
606
607
0
    if (state->prefix)
608
0
    {
609
      /* Remove ansi and formatting from autoview output in replies only.  The
610
       * user may want to see the formatting in the pager, but it shouldn't be
611
       * in their quoted reply text too.  */
612
0
      struct Buffer *stripped = buf_pool_get();
613
0
      while (fgets(buf, sizeof(buf), fp_out))
614
0
      {
615
0
        buf_strip_formatting(stripped, buf, false);
616
0
        state_puts(state, state->prefix);
617
0
        state_puts(state, buf_string(stripped));
618
0
      }
619
0
      buf_pool_release(&stripped);
620
621
      /* check for data on stderr */
622
0
      if (fgets(buf, sizeof(buf), fp_err))
623
0
      {
624
0
        if (state->flags & STATE_DISPLAY)
625
0
        {
626
0
          state_mark_attach(state);
627
0
          state_printf(state, _("[-- Autoview stderr of %s --]\n"), buf_string(cmd));
628
0
        }
629
630
0
        state_puts(state, state->prefix);
631
0
        state_puts(state, buf);
632
0
        while (fgets(buf, sizeof(buf), fp_err))
633
0
        {
634
0
          state_puts(state, state->prefix);
635
0
          state_puts(state, buf);
636
0
        }
637
0
      }
638
0
    }
639
0
    else
640
0
    {
641
0
      mutt_file_copy_stream(fp_out, state->fp_out);
642
      /* Check for stderr messages */
643
0
      if (fgets(buf, sizeof(buf), fp_err))
644
0
      {
645
0
        if (state->flags & STATE_DISPLAY)
646
0
        {
647
0
          state_mark_attach(state);
648
0
          state_printf(state, _("[-- Autoview stderr of %s --]\n"), buf_string(cmd));
649
0
        }
650
651
0
        state_puts(state, buf);
652
0
        mutt_file_copy_stream(fp_err, state->fp_out);
653
0
      }
654
0
    }
655
656
0
  bail:
657
0
    mutt_file_fclose(&fp_out);
658
0
    mutt_file_fclose(&fp_err);
659
660
0
    filter_wait(pid);
661
0
    if (piped)
662
0
      mutt_file_fclose(&fp_in);
663
0
    else
664
0
      mutt_file_unlink(buf_string(tempfile));
665
666
0
    if (state->flags & STATE_DISPLAY)
667
0
      mutt_clear_error();
668
0
  }
669
670
0
cleanup:
671
0
  mailcap_entry_free(&entry);
672
673
0
  buf_pool_release(&cmd);
674
0
  buf_pool_release(&tempfile);
675
676
0
  return rc;
677
0
}
678
679
/**
680
 * text_plain_handler - Handler for plain text - Implements ::handler_t - @ingroup handler_api
681
 * @retval 0 Always
682
 *
683
 * When generating format=flowed ($text_flowed is set) from format=fixed, strip
684
 * all trailing spaces to improve interoperability; if $text_flowed is unset,
685
 * simply verbatim copy input.
686
 */
687
static int text_plain_handler(struct Body *b_email, struct State *state)
688
0
{
689
0
  char *buf = NULL;
690
0
  size_t sz = 0;
691
692
0
  const bool c_text_flowed = cs_subset_bool(NeoMutt->sub, "text_flowed");
693
0
  while ((buf = mutt_file_read_line(buf, &sz, state->fp_in, NULL, MUTT_RL_NO_FLAGS)))
694
0
  {
695
0
    if (!mutt_str_equal(buf, "-- ") && c_text_flowed)
696
0
    {
697
0
      size_t len = mutt_str_len(buf);
698
0
      while ((len > 0) && (buf[len - 1] == ' '))
699
0
        buf[--len] = '\0';
700
0
    }
701
0
    if (state->prefix)
702
0
      state_puts(state, state->prefix);
703
0
    state_puts(state, buf);
704
0
    state_putc(state, '\n');
705
0
  }
706
707
0
  FREE(&buf);
708
0
  return 0;
709
0
}
710
711
/**
712
 * message_handler - Handler for message/rfc822 body parts - Implements ::handler_t - @ingroup handler_api
713
 */
714
static int message_handler(struct Body *b_email, struct State *state)
715
0
{
716
0
  struct Body *b = NULL;
717
0
  LOFF_T off_start;
718
0
  int rc = 0;
719
720
0
  off_start = ftello(state->fp_in);
721
0
  if (off_start < 0)
722
0
    return -1;
723
724
0
  if ((b_email->encoding == ENC_BASE64) || (b_email->encoding == ENC_QUOTED_PRINTABLE) ||
725
0
      (b_email->encoding == ENC_UUENCODED))
726
0
  {
727
0
    b = mutt_body_new();
728
0
    b->length = mutt_file_get_size_fp(state->fp_in);
729
0
    b->parts = mutt_rfc822_parse_message(state->fp_in, b);
730
0
  }
731
0
  else
732
0
  {
733
0
    b = b_email;
734
0
  }
735
736
0
  if (b->parts)
737
0
  {
738
0
    CopyHeaderFlags chflags = CH_DECODE | CH_FROM;
739
0
    const bool c_weed = cs_subset_bool(NeoMutt->sub, "weed");
740
0
    if ((state->flags & STATE_WEED) ||
741
0
        ((state->flags & (STATE_DISPLAY | STATE_PRINTING)) && c_weed))
742
0
    {
743
0
      chflags |= CH_WEED | CH_REORDER;
744
0
    }
745
0
    if (state->prefix)
746
0
      chflags |= CH_PREFIX;
747
0
    if (state->flags & STATE_DISPLAY)
748
0
      chflags |= CH_DISPLAY;
749
750
0
    mutt_copy_hdr(state->fp_in, state->fp_out, off_start, b->parts->offset,
751
0
                  chflags, state->prefix, 0);
752
753
0
    if (state->prefix)
754
0
      state_puts(state, state->prefix);
755
0
    state_putc(state, '\n');
756
757
0
    rc = mutt_body_handler(b->parts, state);
758
0
  }
759
760
0
  if ((b_email->encoding == ENC_BASE64) || (b_email->encoding == ENC_QUOTED_PRINTABLE) ||
761
0
      (b_email->encoding == ENC_UUENCODED))
762
0
  {
763
0
    mutt_body_free(&b);
764
0
  }
765
766
0
  return rc;
767
0
}
768
769
/**
770
 * external_body_handler - Handler for external-body emails - Implements ::handler_t - @ingroup handler_api
771
 */
772
static int external_body_handler(struct Body *b_email, struct State *state)
773
0
{
774
0
  const char *access_type = mutt_param_get(&b_email->parameter, "access-type");
775
0
  if (!access_type)
776
0
  {
777
0
    if (state->flags & STATE_DISPLAY)
778
0
    {
779
0
      state_mark_attach(state);
780
0
      state_puts(state, _("[-- Error: message/external-body has no access-type parameter --]\n"));
781
0
      return 0;
782
0
    }
783
0
    else
784
0
    {
785
0
      return -1;
786
0
    }
787
0
  }
788
789
0
  const char *fmt = NULL;
790
0
  struct Buffer *banner = buf_pool_get();
791
792
0
  const char *expiration = mutt_param_get(&b_email->parameter, "expiration");
793
0
  time_t expire;
794
0
  if (expiration)
795
0
    expire = mutt_date_parse_date(expiration, NULL);
796
0
  else
797
0
    expire = -1;
798
799
0
  const bool c_weed = cs_subset_bool(NeoMutt->sub, "weed");
800
0
  if (mutt_istr_equal(access_type, "x-mutt-deleted"))
801
0
  {
802
0
    if (state->flags & (STATE_DISPLAY | STATE_PRINTING))
803
0
    {
804
0
      struct Buffer *pretty_size = buf_pool_get();
805
0
      char *length = mutt_param_get(&b_email->parameter, "length");
806
0
      if (length)
807
0
      {
808
0
        const long size = strtol(length, NULL, 10);
809
0
        mutt_str_pretty_size(pretty_size, size);
810
0
        if (expire != -1)
811
0
        {
812
0
          fmt = ngettext(
813
              /* L10N: If the translation of this string is a multi line string, then
814
                 each line should start with "[-- " and end with " --]".
815
                 The first "%s/%s" is a MIME type, e.g. "text/plain". The last %s
816
                 expands to a date as returned by `mutt_date_parse_date()`.
817
818
                 Note: The size argument printed is not the actual number as passed
819
                 to gettext but the prettified version, e.g. size = 2048 will be
820
                 printed as 2K.  Your language might be sensitive to that: For
821
                 example although '1K' and '1024' represent the same number your
822
                 language might inflect the noun 'byte' differently.
823
824
                 Sadly, we can't do anything about that at the moment besides
825
                 passing the precise size in bytes. If you are interested the
826
                 function responsible for the prettification is
827
                 mutt_str_pretty_size() in muttlib.c */
828
0
              "[-- This %s/%s attachment (size %s byte) has been deleted --]\n"
829
0
              "[-- on %s --]\n",
830
0
              "[-- This %s/%s attachment (size %s bytes) has been deleted --]\n"
831
0
              "[-- on %s --]\n",
832
0
              size);
833
0
        }
834
0
        else
835
0
        {
836
0
          fmt = ngettext(
837
              /* L10N: If the translation of this string is a multi line string, then
838
                 each line should start with "[-- " and end with " --]".
839
                 The first "%s/%s" is a MIME type, e.g. "text/plain".
840
841
                 Note: The size argument printed is not the actual number as passed
842
                 to gettext but the prettified version, e.g. size = 2048 will be
843
                 printed as 2K.  Your language might be sensitive to that: For
844
                 example although '1K' and '1024' represent the same number your
845
                 language might inflect the noun 'byte' differently.
846
847
                 Sadly, we can't do anything about that at the moment besides
848
                 passing the precise size in bytes. If you are interested the
849
                 function responsible for the prettification is
850
                 mutt_str_pretty_size() in muttlib.c  */
851
0
              "[-- This %s/%s attachment (size %s byte) has been deleted --]\n",
852
0
              "[-- This %s/%s attachment (size %s bytes) has been deleted --]\n", size);
853
0
        }
854
0
      }
855
0
      else
856
0
      {
857
0
        if (expire != -1)
858
0
        {
859
          /* L10N: If the translation of this string is a multi line string, then
860
             each line should start with "[-- " and end with " --]".
861
             The first "%s/%s" is a MIME type, e.g. "text/plain". The last %s
862
             expands to a date as returned by `mutt_date_parse_date()`.
863
864
             Caution: Argument three %3$ is also defined but should not be used
865
             in this translation!  */
866
0
          fmt = _("[-- This %s/%s attachment has been deleted --]\n[-- on %4$s --]\n");
867
0
        }
868
0
        else
869
0
        {
870
          /* L10N: If the translation of this string is a multi line string, then
871
             each line should start with "[-- " and end with " --]".
872
             The first "%s/%s" is a MIME type, e.g. "text/plain". */
873
0
          fmt = _("[-- This %s/%s attachment has been deleted --]\n");
874
0
        }
875
0
      }
876
877
0
      buf_printf(banner, fmt, BODY_TYPE(b_email->parts),
878
0
                 b_email->parts->subtype, buf_string(pretty_size), expiration);
879
0
      state_attach_puts(state, buf_string(banner));
880
0
      if (b_email->parts->filename)
881
0
      {
882
0
        state_mark_attach(state);
883
0
        state_printf(state, _("[-- name: %s --]\n"), b_email->parts->filename);
884
0
      }
885
886
0
      CopyHeaderFlags chflags = CH_DECODE;
887
0
      if (c_weed)
888
0
        chflags |= CH_WEED | CH_REORDER;
889
890
0
      mutt_copy_hdr(state->fp_in, state->fp_out, ftello(state->fp_in),
891
0
                    b_email->parts->offset, chflags, NULL, 0);
892
0
      buf_pool_release(&pretty_size);
893
0
    }
894
0
  }
895
0
  else if (expiration && (expire < mutt_date_now()))
896
0
  {
897
0
    if (state->flags & STATE_DISPLAY)
898
0
    {
899
      /* L10N: If the translation of this string is a multi line string, then
900
         each line should start with "[-- " and end with " --]".
901
         The "%s/%s" is a MIME type, e.g. "text/plain". */
902
0
      buf_printf(banner, _("[-- This %s/%s attachment is not included, --]\n[-- and the indicated external source has expired --]\n"),
903
0
                 BODY_TYPE(b_email->parts), b_email->parts->subtype);
904
0
      state_attach_puts(state, buf_string(banner));
905
906
0
      CopyHeaderFlags chflags = CH_DECODE | CH_DISPLAY;
907
0
      if (c_weed)
908
0
        chflags |= CH_WEED | CH_REORDER;
909
910
0
      mutt_copy_hdr(state->fp_in, state->fp_out, ftello(state->fp_in),
911
0
                    b_email->parts->offset, chflags, NULL, 0);
912
0
    }
913
0
  }
914
0
  else
915
0
  {
916
0
    if (state->flags & STATE_DISPLAY)
917
0
    {
918
      /* L10N: If the translation of this string is a multi line string, then
919
         each line should start with "[-- " and end with " --]".
920
         The "%s/%s" is a MIME type, e.g. "text/plain".  The %s after
921
         access-type is an access-type as defined by the MIME RFCs, e.g. "FTP",
922
         "LOCAL-FILE", "MAIL-SERVER". */
923
0
      buf_printf(banner, _("[-- This %s/%s attachment is not included, --]\n[-- and the indicated access-type %s is unsupported --]\n"),
924
0
                 BODY_TYPE(b_email->parts), b_email->parts->subtype, access_type);
925
0
      state_attach_puts(state, buf_string(banner));
926
927
0
      CopyHeaderFlags chflags = CH_DECODE | CH_DISPLAY;
928
0
      if (c_weed)
929
0
        chflags |= CH_WEED | CH_REORDER;
930
931
0
      mutt_copy_hdr(state->fp_in, state->fp_out, ftello(state->fp_in),
932
0
                    b_email->parts->offset, chflags, NULL, 0);
933
0
    }
934
0
  }
935
0
  buf_pool_release(&banner);
936
937
0
  return 0;
938
0
}
939
940
/**
941
 * alternative_handler - Handler for multipart alternative emails - Implements ::handler_t - @ingroup handler_api
942
 */
943
static int alternative_handler(struct Body *b_email, struct State *state)
944
0
{
945
0
  struct Body *const head = b_email;
946
0
  struct Body *choice = NULL;
947
0
  struct Body *b = NULL;
948
0
  bool mustfree = false;
949
0
  int rc = 0;
950
951
0
  if ((b_email->encoding == ENC_BASE64) || (b_email->encoding == ENC_QUOTED_PRINTABLE) ||
952
0
      (b_email->encoding == ENC_UUENCODED))
953
0
  {
954
0
    mustfree = true;
955
0
    b = mutt_body_new();
956
0
    b->length = mutt_file_get_size_fp(state->fp_in);
957
0
    b->parts = mutt_parse_multipart(state->fp_in,
958
0
                                    mutt_param_get(&b_email->parameter, "boundary"),
959
0
                                    b->length,
960
0
                                    mutt_istr_equal("digest", b_email->subtype));
961
0
  }
962
0
  else
963
0
  {
964
0
    b = b_email;
965
0
  }
966
967
0
  b_email = b;
968
969
  /* First, search list of preferred types */
970
0
  struct ListNode *np = NULL;
971
0
  STAILQ_FOREACH(np, &AlternativeOrderList, entries)
972
0
  {
973
0
    int btlen; /* length of basetype */
974
0
    bool wild; /* do we have a wildcard to match all subtypes? */
975
976
0
    char *c = strchr(np->data, '/');
977
0
    if (c)
978
0
    {
979
0
      wild = ((c[1] == '*') && (c[2] == '\0'));
980
0
      btlen = c - np->data;
981
0
    }
982
0
    else
983
0
    {
984
0
      wild = true;
985
0
      btlen = mutt_str_len(np->data);
986
0
    }
987
988
0
    if (b_email->parts)
989
0
      b = b_email->parts;
990
0
    else
991
0
      b = b_email;
992
0
    while (b)
993
0
    {
994
0
      const char *bt = BODY_TYPE(b);
995
0
      if (mutt_istrn_equal(bt, np->data, btlen) && (bt[btlen] == 0))
996
0
      {
997
        /* the basetype matches */
998
0
        if (wild || mutt_istr_equal(np->data + btlen + 1, b->subtype))
999
0
        {
1000
0
          choice = b;
1001
0
        }
1002
0
      }
1003
0
      b = b->next;
1004
0
    }
1005
1006
0
    if (choice)
1007
0
      break;
1008
0
  }
1009
1010
  /* Next, look for an autoviewable type */
1011
0
  if (!choice)
1012
0
  {
1013
0
    if (b_email->parts)
1014
0
      b = b_email->parts;
1015
0
    else
1016
0
      b = b_email;
1017
0
    while (b)
1018
0
    {
1019
0
      if (is_autoview(b))
1020
0
        choice = b;
1021
0
      b = b->next;
1022
0
    }
1023
0
  }
1024
1025
  /* Then, look for a text entry */
1026
0
  if (!choice)
1027
0
  {
1028
0
    if (b_email->parts)
1029
0
      b = b_email->parts;
1030
0
    else
1031
0
      b = b_email;
1032
0
    int type = 0;
1033
0
    while (b)
1034
0
    {
1035
0
      if (b->type == TYPE_TEXT)
1036
0
      {
1037
0
        if (mutt_istr_equal("plain", b->subtype) && (type <= TXT_PLAIN))
1038
0
        {
1039
0
          choice = b;
1040
0
          type = TXT_PLAIN;
1041
0
        }
1042
0
        else if (mutt_istr_equal("enriched", b->subtype) && (type <= TXT_ENRICHED))
1043
0
        {
1044
0
          choice = b;
1045
0
          type = TXT_ENRICHED;
1046
0
        }
1047
0
        else if (mutt_istr_equal("html", b->subtype) && (type <= TXT_HTML))
1048
0
        {
1049
0
          choice = b;
1050
0
          type = TXT_HTML;
1051
0
        }
1052
0
      }
1053
0
      b = b->next;
1054
0
    }
1055
0
  }
1056
1057
  /* Finally, look for other possibilities */
1058
0
  if (!choice)
1059
0
  {
1060
0
    if (b_email->parts)
1061
0
      b = b_email->parts;
1062
0
    else
1063
0
      b = b_email;
1064
0
    while (b)
1065
0
    {
1066
0
      if (mutt_can_decode(b))
1067
0
        choice = b;
1068
0
      b = b->next;
1069
0
    }
1070
0
  }
1071
1072
0
  if (choice)
1073
0
  {
1074
0
    const bool c_weed = cs_subset_bool(NeoMutt->sub, "weed");
1075
0
    if (state->flags & STATE_DISPLAY && !c_weed &&
1076
0
        mutt_file_seek(state->fp_in, choice->hdr_offset, SEEK_SET))
1077
0
    {
1078
0
      mutt_file_copy_bytes(state->fp_in, state->fp_out, choice->offset - choice->hdr_offset);
1079
0
    }
1080
1081
0
    const char *const c_show_multipart_alternative = cs_subset_string(NeoMutt->sub, "show_multipart_alternative");
1082
0
    if (mutt_str_equal("info", c_show_multipart_alternative))
1083
0
    {
1084
0
      print_part_line(state, choice, 0);
1085
0
    }
1086
0
    mutt_body_handler(choice, state);
1087
1088
    /* Let it flow back to the main part */
1089
0
    head->nowrap = choice->nowrap;
1090
0
    choice->nowrap = false;
1091
1092
0
    if (mutt_str_equal("info", c_show_multipart_alternative))
1093
0
    {
1094
0
      if (b_email->parts)
1095
0
        b = b_email->parts;
1096
0
      else
1097
0
        b = b_email;
1098
0
      int count = 0;
1099
0
      while (b)
1100
0
      {
1101
0
        if (choice != b)
1102
0
        {
1103
0
          count += 1;
1104
0
          if (count == 1)
1105
0
            state_putc(state, '\n');
1106
1107
0
          print_part_line(state, b, count);
1108
0
        }
1109
0
        b = b->next;
1110
0
      }
1111
0
    }
1112
0
  }
1113
0
  else if (state->flags & STATE_DISPLAY)
1114
0
  {
1115
    /* didn't find anything that we could display! */
1116
0
    state_mark_attach(state);
1117
0
    state_puts(state, _("[-- Error: Could not display any parts of Multipart/Alternative --]\n"));
1118
0
    rc = -1;
1119
0
  }
1120
1121
0
  if (mustfree)
1122
0
    mutt_body_free(&b_email);
1123
1124
0
  return rc;
1125
0
}
1126
1127
/**
1128
 * multilingual_handler - Handler for multi-lingual emails - Implements ::handler_t - @ingroup handler_api
1129
 * @retval 0 Always
1130
 */
1131
static int multilingual_handler(struct Body *b_email, struct State *state)
1132
0
{
1133
0
  struct Body *b = NULL;
1134
0
  bool mustfree = false;
1135
0
  int rc = 0;
1136
1137
0
  mutt_debug(LL_DEBUG2, "RFC8255 >> entering in handler multilingual handler\n");
1138
0
  if ((b_email->encoding == ENC_BASE64) || (b_email->encoding == ENC_QUOTED_PRINTABLE) ||
1139
0
      (b_email->encoding == ENC_UUENCODED))
1140
0
  {
1141
0
    mustfree = true;
1142
0
    b = mutt_body_new();
1143
0
    b->length = mutt_file_get_size_fp(state->fp_in);
1144
0
    b->parts = mutt_parse_multipart(state->fp_in,
1145
0
                                    mutt_param_get(&b_email->parameter, "boundary"),
1146
0
                                    b->length,
1147
0
                                    mutt_istr_equal("digest", b_email->subtype));
1148
0
  }
1149
0
  else
1150
0
  {
1151
0
    b = b_email;
1152
0
  }
1153
1154
0
  b_email = b;
1155
1156
0
  if (b_email->parts)
1157
0
    b = b_email->parts;
1158
0
  else
1159
0
    b = b_email;
1160
1161
0
  struct Body *choice = NULL;
1162
0
  struct Body *first_part = NULL;
1163
0
  struct Body *zxx_part = NULL;
1164
0
  struct ListNode *np = NULL;
1165
1166
0
  while (b)
1167
0
  {
1168
0
    if (mutt_can_decode(b))
1169
0
    {
1170
0
      first_part = b;
1171
0
      break;
1172
0
    }
1173
0
    b = b->next;
1174
0
  }
1175
1176
0
  const struct Slist *c_preferred_languages = cs_subset_slist(NeoMutt->sub, "preferred_languages");
1177
0
  if (c_preferred_languages)
1178
0
  {
1179
0
    struct Buffer *langs = buf_pool_get();
1180
0
    cs_subset_str_string_get(NeoMutt->sub, "preferred_languages", langs);
1181
0
    mutt_debug(LL_DEBUG2, "RFC8255 >> preferred_languages set in config to '%s'\n",
1182
0
               buf_string(langs));
1183
0
    buf_pool_release(&langs);
1184
1185
0
    STAILQ_FOREACH(np, &c_preferred_languages->head, entries)
1186
0
    {
1187
0
      while (b)
1188
0
      {
1189
0
        if (mutt_can_decode(b))
1190
0
        {
1191
0
          if (b->language && mutt_str_equal("zxx", b->language))
1192
0
            zxx_part = b;
1193
1194
0
          mutt_debug(LL_DEBUG2, "RFC8255 >> comparing configuration preferred_language='%s' to mail part content-language='%s'\n",
1195
0
                     np->data, b->language);
1196
0
          if (b->language && mutt_str_equal(np->data, b->language))
1197
0
          {
1198
0
            mutt_debug(LL_DEBUG2, "RFC8255 >> preferred_language='%s' matches content-language='%s' >> part selected to be displayed\n",
1199
0
                       np->data, b->language);
1200
0
            choice = b;
1201
0
            break;
1202
0
          }
1203
0
        }
1204
1205
0
        b = b->next;
1206
0
      }
1207
1208
0
      if (choice)
1209
0
        break;
1210
1211
0
      if (b_email->parts)
1212
0
        b = b_email->parts;
1213
0
      else
1214
0
        b = b_email;
1215
0
    }
1216
0
  }
1217
1218
0
  if (choice)
1219
0
  {
1220
0
    mutt_body_handler(choice, state);
1221
0
  }
1222
0
  else
1223
0
  {
1224
0
    if (zxx_part)
1225
0
      mutt_body_handler(zxx_part, state);
1226
0
    else
1227
0
      mutt_body_handler(first_part, state);
1228
0
  }
1229
1230
0
  if (mustfree)
1231
0
    mutt_body_free(&b_email);
1232
1233
0
  return rc;
1234
0
}
1235
1236
/**
1237
 * multipart_handler - Handler for multipart emails - Implements ::handler_t - @ingroup handler_api
1238
 */
1239
static int multipart_handler(struct Body *b_email, struct State *state)
1240
0
{
1241
0
  struct Body *b = NULL, *p = NULL;
1242
0
  int count;
1243
0
  int rc = 0;
1244
1245
0
  if ((b_email->encoding == ENC_BASE64) || (b_email->encoding == ENC_QUOTED_PRINTABLE) ||
1246
0
      (b_email->encoding == ENC_UUENCODED))
1247
0
  {
1248
0
    b = mutt_body_new();
1249
0
    b->length = mutt_file_get_size_fp(state->fp_in);
1250
0
    b->parts = mutt_parse_multipart(state->fp_in,
1251
0
                                    mutt_param_get(&b_email->parameter, "boundary"),
1252
0
                                    b->length,
1253
0
                                    mutt_istr_equal("digest", b_email->subtype));
1254
0
  }
1255
0
  else
1256
0
  {
1257
0
    b = b_email;
1258
0
  }
1259
1260
0
  const bool c_weed = cs_subset_bool(NeoMutt->sub, "weed");
1261
0
  const bool c_include_only_first = cs_subset_bool(NeoMutt->sub, "include_only_first");
1262
1263
0
  for (p = b->parts, count = 1; p; p = p->next, count++)
1264
0
  {
1265
0
    if (state->flags & STATE_DISPLAY)
1266
0
    {
1267
0
      state_mark_attach(state);
1268
0
      if (p->description || p->filename || p->form_name)
1269
0
      {
1270
        /* L10N: %s is the attachment description, filename or form_name. */
1271
0
        state_printf(state, _("[-- Attachment #%d: %s --]\n"), count,
1272
0
                     p->description ? p->description :
1273
0
                     p->filename    ? p->filename :
1274
0
                                      p->form_name);
1275
0
      }
1276
0
      else
1277
0
      {
1278
0
        state_printf(state, _("[-- Attachment #%d --]\n"), count);
1279
0
      }
1280
0
      print_part_line(state, p, 0);
1281
0
      if (c_weed)
1282
0
      {
1283
0
        state_putc(state, '\n');
1284
0
      }
1285
0
      else if (mutt_file_seek(state->fp_in, p->hdr_offset, SEEK_SET))
1286
0
      {
1287
0
        mutt_file_copy_bytes(state->fp_in, state->fp_out, p->offset - p->hdr_offset);
1288
0
      }
1289
0
    }
1290
1291
0
    rc = mutt_body_handler(p, state);
1292
0
    state_putc(state, '\n');
1293
1294
0
    if (rc != 0)
1295
0
    {
1296
0
      mutt_error(_("One or more parts of this message could not be displayed"));
1297
0
      mutt_debug(LL_DEBUG1, "Failed on attachment #%d, type %s/%s\n", count,
1298
0
                 BODY_TYPE(p), NONULL(p->subtype));
1299
0
    }
1300
1301
0
    if ((state->flags & STATE_REPLYING) && c_include_only_first && (state->flags & STATE_FIRSTDONE))
1302
0
    {
1303
0
      break;
1304
0
    }
1305
0
  }
1306
1307
0
  if ((b_email->encoding == ENC_BASE64) || (b_email->encoding == ENC_QUOTED_PRINTABLE) ||
1308
0
      (b_email->encoding == ENC_UUENCODED))
1309
0
  {
1310
0
    mutt_body_free(&b);
1311
0
  }
1312
1313
  /* make failure of a single part non-fatal */
1314
0
  if (rc < 0)
1315
0
    rc = 1;
1316
0
  return rc;
1317
0
}
1318
1319
/**
1320
 * run_decode_and_handler - Run an appropriate decoder for an email
1321
 * @param b         Body of the email
1322
 * @param state         State to work with
1323
 * @param handler   Callback function to process the content - Implements ::handler_t
1324
 * @param plaintext Is the content in plain text
1325
 * @retval 0 Success
1326
 * @retval -1 Error
1327
 */
1328
static int run_decode_and_handler(struct Body *b, struct State *state,
1329
                                  handler_t handler, bool plaintext)
1330
0
{
1331
0
  const char *save_prefix = NULL;
1332
0
  FILE *fp = NULL;
1333
0
  size_t tmplength = 0;
1334
0
  LOFF_T tmpoffset = 0;
1335
0
  int decode = 0;
1336
0
  int rc = 0;
1337
0
#ifndef USE_FMEMOPEN
1338
0
  struct Buffer *tempfile = NULL;
1339
0
#endif
1340
1341
0
  if (!mutt_file_seek(state->fp_in, b->offset, SEEK_SET))
1342
0
  {
1343
0
    return -1;
1344
0
  }
1345
1346
#ifdef USE_FMEMOPEN
1347
  char *temp = NULL;
1348
  size_t tempsize = 0;
1349
#endif
1350
1351
  /* see if we need to decode this part before processing it */
1352
0
  if ((b->encoding == ENC_BASE64) || (b->encoding == ENC_QUOTED_PRINTABLE) ||
1353
0
      (b->encoding == ENC_UUENCODED) || (plaintext || mutt_is_text_part(b)))
1354
  /* text subtypes may require character set conversion even with 8bit encoding */
1355
0
  {
1356
0
    const int orig_type = b->type;
1357
0
    if (plaintext)
1358
0
    {
1359
0
      b->type = TYPE_TEXT;
1360
0
    }
1361
0
    else
1362
0
    {
1363
      /* decode to a tempfile, saving the original destination */
1364
0
      fp = state->fp_out;
1365
#ifdef USE_FMEMOPEN
1366
      state->fp_out = open_memstream(&temp, &tempsize);
1367
      if (!state->fp_out)
1368
      {
1369
        mutt_error(_("Unable to open 'memory stream'"));
1370
        mutt_debug(LL_DEBUG1, "Can't open 'memory stream'\n");
1371
        return -1;
1372
      }
1373
#else
1374
0
      tempfile = buf_pool_get();
1375
0
      buf_mktemp(tempfile);
1376
0
      state->fp_out = mutt_file_fopen(buf_string(tempfile), "w");
1377
0
      if (!state->fp_out)
1378
0
      {
1379
0
        mutt_error(_("Unable to open temporary file"));
1380
0
        mutt_debug(LL_DEBUG1, "Can't open %s\n", buf_string(tempfile));
1381
0
        buf_pool_release(&tempfile);
1382
0
        return -1;
1383
0
      }
1384
0
#endif
1385
      /* decoding the attachment changes the size and offset, so save a copy
1386
       * of the "real" values now, and restore them after processing */
1387
0
      tmplength = b->length;
1388
0
      tmpoffset = b->offset;
1389
1390
      /* if we are decoding binary bodies, we don't want to prefix each
1391
       * line with the prefix or else the data will get corrupted.  */
1392
0
      save_prefix = state->prefix;
1393
0
      state->prefix = NULL;
1394
1395
0
      decode = 1;
1396
0
    }
1397
1398
0
    mutt_decode_attachment(b, state);
1399
1400
0
    if (decode)
1401
0
    {
1402
0
      b->length = ftello(state->fp_out);
1403
0
      b->offset = 0;
1404
#ifdef USE_FMEMOPEN
1405
      /* When running under torify, mutt_file_fclose(&state->fp_out) does not seem to
1406
       * update tempsize. On the other hand, fflush does.  See
1407
       * https://github.com/neomutt/neomutt/issues/440 */
1408
      fflush(state->fp_out);
1409
#endif
1410
0
      mutt_file_fclose(&state->fp_out);
1411
1412
      /* restore final destination and substitute the tempfile for input */
1413
0
      state->fp_out = fp;
1414
0
      fp = state->fp_in;
1415
#ifdef USE_FMEMOPEN
1416
      if (tempsize)
1417
      {
1418
        state->fp_in = fmemopen(temp, tempsize, "r");
1419
      }
1420
      else
1421
      { /* fmemopen can't handle zero-length buffers */
1422
        state->fp_in = mutt_file_fopen("/dev/null", "r");
1423
      }
1424
      if (!state->fp_in)
1425
      {
1426
        mutt_perror(_("failed to re-open 'memory stream'"));
1427
        FREE(&temp);
1428
        return -1;
1429
      }
1430
#else
1431
0
      state->fp_in = mutt_file_fopen(buf_string(tempfile), "r");
1432
0
      unlink(buf_string(tempfile));
1433
0
      buf_pool_release(&tempfile);
1434
0
#endif
1435
      /* restore the prefix */
1436
0
      state->prefix = save_prefix;
1437
0
    }
1438
1439
0
    b->type = orig_type;
1440
0
  }
1441
1442
  /* process the (decoded) body part */
1443
0
  if (handler)
1444
0
  {
1445
0
    rc = handler(b, state);
1446
0
    if (rc != 0)
1447
0
    {
1448
0
      mutt_debug(LL_DEBUG1, "Failed on attachment of type %s/%s\n",
1449
0
                 BODY_TYPE(b), NONULL(b->subtype));
1450
0
    }
1451
1452
0
    if (decode)
1453
0
    {
1454
0
      b->length = tmplength;
1455
0
      b->offset = tmpoffset;
1456
1457
      /* restore the original source stream */
1458
0
      mutt_file_fclose(&state->fp_in);
1459
0
      state->fp_in = fp;
1460
0
    }
1461
0
  }
1462
0
  state->flags |= STATE_FIRSTDONE;
1463
#ifdef USE_FMEMOPEN
1464
  FREE(&temp);
1465
#endif
1466
1467
0
  return rc;
1468
0
}
1469
1470
/**
1471
 * valid_pgp_encrypted_handler - Handler for valid pgp-encrypted emails - Implements ::handler_t - @ingroup handler_api
1472
 */
1473
static int valid_pgp_encrypted_handler(struct Body *b_email, struct State *state)
1474
0
{
1475
0
  struct Body *octetstream = b_email->parts->next;
1476
1477
  /* clear out any mime headers before the handler, so they can't be spoofed. */
1478
0
  mutt_env_free(&b_email->mime_headers);
1479
0
  mutt_env_free(&octetstream->mime_headers);
1480
1481
0
  int rc;
1482
  /* Some clients improperly encode the octetstream part. */
1483
0
  if (octetstream->encoding != ENC_7BIT)
1484
0
    rc = run_decode_and_handler(octetstream, state, crypt_pgp_encrypted_handler, 0);
1485
0
  else
1486
0
    rc = crypt_pgp_encrypted_handler(octetstream, state);
1487
0
  b_email->goodsig |= octetstream->goodsig;
1488
1489
  /* Relocate protected headers onto the multipart/encrypted part */
1490
0
  if (!rc && octetstream->mime_headers)
1491
0
  {
1492
0
    b_email->mime_headers = octetstream->mime_headers;
1493
0
    octetstream->mime_headers = NULL;
1494
0
  }
1495
1496
0
  return rc;
1497
0
}
1498
1499
/**
1500
 * malformed_pgp_encrypted_handler - Handler for invalid pgp-encrypted emails - Implements ::handler_t - @ingroup handler_api
1501
 */
1502
static int malformed_pgp_encrypted_handler(struct Body *b_email, struct State *state)
1503
0
{
1504
0
  struct Body *octetstream = b_email->parts->next->next;
1505
1506
  /* clear out any mime headers before the handler, so they can't be spoofed. */
1507
0
  mutt_env_free(&b_email->mime_headers);
1508
0
  mutt_env_free(&octetstream->mime_headers);
1509
1510
  /* exchange encodes the octet-stream, so re-run it through the decoder */
1511
0
  int rc = run_decode_and_handler(octetstream, state, crypt_pgp_encrypted_handler, false);
1512
0
  b_email->goodsig |= octetstream->goodsig;
1513
#ifdef USE_AUTOCRYPT
1514
  b_email->is_autocrypt |= octetstream->is_autocrypt;
1515
#endif
1516
1517
  /* Relocate protected headers onto the multipart/encrypted part */
1518
0
  if (!rc && octetstream->mime_headers)
1519
0
  {
1520
0
    b_email->mime_headers = octetstream->mime_headers;
1521
0
    octetstream->mime_headers = NULL;
1522
0
  }
1523
1524
0
  return rc;
1525
0
}
1526
1527
/**
1528
 * mutt_decode_base64 - Decode base64-encoded text
1529
 * @param state      State to work with
1530
 * @param len    Length of text to decode
1531
 * @param istext Mime part is plain text
1532
 * @param cd     Iconv conversion descriptor
1533
 */
1534
void mutt_decode_base64(struct State *state, size_t len, bool istext, iconv_t cd)
1535
0
{
1536
0
  char buf[5] = { 0 };
1537
0
  int ch, i;
1538
0
  bool cr = false;
1539
0
  char bufi[BUFI_SIZE] = { 0 };
1540
0
  size_t l = 0;
1541
1542
0
  buf[4] = '\0';
1543
1544
0
  if (istext)
1545
0
    state_set_prefix(state);
1546
1547
0
  while (len > 0)
1548
0
  {
1549
0
    for (i = 0; (i < 4) && (len > 0); len--)
1550
0
    {
1551
0
      ch = fgetc(state->fp_in);
1552
0
      if (ch == EOF)
1553
0
        break;
1554
0
      if ((ch >= 0) && (ch < 128) && ((base64val(ch) != -1) || (ch == '=')))
1555
0
        buf[i++] = ch;
1556
0
    }
1557
0
    if (i != 4)
1558
0
    {
1559
      /* "i" may be zero if there is trailing whitespace, which is not an error */
1560
0
      if (i != 0)
1561
0
        mutt_debug(LL_DEBUG2, "didn't get a multiple of 4 chars\n");
1562
0
      break;
1563
0
    }
1564
1565
0
    const int c1 = base64val(buf[0]);
1566
0
    const int c2 = base64val(buf[1]);
1567
1568
    /* first char */
1569
0
    ch = (c1 << 2) | (c2 >> 4);
1570
1571
0
    if (cr && (ch != '\n'))
1572
0
      bufi[l++] = '\r';
1573
1574
0
    cr = false;
1575
1576
0
    if (istext && (ch == '\r'))
1577
0
      cr = true;
1578
0
    else
1579
0
      bufi[l++] = ch;
1580
1581
    /* second char */
1582
0
    if (buf[2] == '=')
1583
0
      break;
1584
0
    const int c3 = base64val(buf[2]);
1585
0
    ch = ((c2 & 0xf) << 4) | (c3 >> 2);
1586
1587
0
    if (cr && (ch != '\n'))
1588
0
      bufi[l++] = '\r';
1589
1590
0
    cr = false;
1591
1592
0
    if (istext && (ch == '\r'))
1593
0
      cr = true;
1594
0
    else
1595
0
      bufi[l++] = ch;
1596
1597
    /* third char */
1598
0
    if (buf[3] == '=')
1599
0
      break;
1600
0
    const int c4 = base64val(buf[3]);
1601
0
    ch = ((c3 & 0x3) << 6) | c4;
1602
1603
0
    if (cr && (ch != '\n'))
1604
0
      bufi[l++] = '\r';
1605
1606
0
    cr = false;
1607
1608
0
    if (istext && (ch == '\r'))
1609
0
      cr = true;
1610
0
    else
1611
0
      bufi[l++] = ch;
1612
1613
0
    if ((l + 8) >= sizeof(bufi))
1614
0
      convert_to_state(cd, bufi, &l, state);
1615
0
  }
1616
1617
0
  if (cr)
1618
0
    bufi[l++] = '\r';
1619
1620
0
  convert_to_state(cd, bufi, &l, state);
1621
0
  convert_to_state(cd, 0, 0, state);
1622
1623
0
  state_reset_prefix(state);
1624
0
}
1625
1626
/**
1627
 * mutt_body_handler - Handler for the Body of an email
1628
 * @param b     Body of the email
1629
 * @param state State to work with
1630
 * @retval 0 Success
1631
 * @retval -1 Error
1632
 */
1633
int mutt_body_handler(struct Body *b, struct State *state)
1634
0
{
1635
0
  if (!b || !state)
1636
0
    return -1;
1637
1638
0
  bool plaintext = false;
1639
0
  handler_t handler = NULL;
1640
0
  handler_t encrypted_handler = NULL;
1641
0
  int rc = 0;
1642
0
  static unsigned short recurse_level = 0;
1643
1644
0
  const int oflags = state->flags;
1645
0
  const bool is_attachment_display = (oflags & STATE_DISPLAY_ATTACH);
1646
1647
0
  if (recurse_level >= MUTT_MIME_MAX_DEPTH)
1648
0
  {
1649
0
    mutt_debug(LL_DEBUG1, "recurse level too deep. giving up\n");
1650
0
    return 1;
1651
0
  }
1652
0
  recurse_level++;
1653
1654
  /* first determine which handler to use to process this part */
1655
1656
0
  if (is_autoview(b))
1657
0
  {
1658
0
    handler = autoview_handler;
1659
0
    state->flags &= ~STATE_CHARCONV;
1660
0
  }
1661
0
  else if (b->type == TYPE_TEXT)
1662
0
  {
1663
0
    if (mutt_istr_equal("plain", b->subtype))
1664
0
    {
1665
0
      const bool c_reflow_text = cs_subset_bool(NeoMutt->sub, "reflow_text");
1666
      /* avoid copying this part twice since removing the transfer-encoding is
1667
       * the only operation needed.  */
1668
0
      if (((WithCrypto & APPLICATION_PGP) != 0) && mutt_is_application_pgp(b))
1669
0
      {
1670
0
        encrypted_handler = crypt_pgp_application_handler;
1671
0
        handler = encrypted_handler;
1672
0
      }
1673
0
      else if (c_reflow_text &&
1674
0
               mutt_istr_equal("flowed", mutt_param_get(&b->parameter, "format")))
1675
0
      {
1676
0
        handler = rfc3676_handler;
1677
0
      }
1678
0
      else
1679
0
      {
1680
0
        handler = text_plain_handler;
1681
0
      }
1682
0
    }
1683
0
    else if (mutt_istr_equal("enriched", b->subtype))
1684
0
    {
1685
0
      handler = text_enriched_handler;
1686
0
    }
1687
0
    else /* text body type without a handler */
1688
0
    {
1689
0
      plaintext = false;
1690
0
    }
1691
0
  }
1692
0
  else if (b->type == TYPE_MESSAGE)
1693
0
  {
1694
0
    if (mutt_is_message_type(b->type, b->subtype))
1695
0
      handler = message_handler;
1696
0
    else if (mutt_istr_equal("delivery-status", b->subtype))
1697
0
      plaintext = true;
1698
0
    else if (mutt_istr_equal("external-body", b->subtype))
1699
0
      handler = external_body_handler;
1700
0
  }
1701
0
  else if (b->type == TYPE_MULTIPART)
1702
0
  {
1703
0
    const char *const c_show_multipart_alternative = cs_subset_string(NeoMutt->sub, "show_multipart_alternative");
1704
0
    if (!mutt_str_equal("inline", c_show_multipart_alternative) &&
1705
0
        mutt_istr_equal("alternative", b->subtype))
1706
0
    {
1707
0
      handler = alternative_handler;
1708
0
    }
1709
0
    else if (!mutt_str_equal("inline", c_show_multipart_alternative) &&
1710
0
             mutt_istr_equal("multilingual", b->subtype))
1711
0
    {
1712
0
      handler = multilingual_handler;
1713
0
    }
1714
0
    else if ((WithCrypto != 0) && mutt_istr_equal("signed", b->subtype))
1715
0
    {
1716
0
      if (!mutt_param_get(&b->parameter, "protocol"))
1717
0
        mutt_error(_("Error: multipart/signed has no protocol"));
1718
0
      else if (state->flags & STATE_VERIFY)
1719
0
        handler = mutt_signed_handler;
1720
0
    }
1721
0
    else if (mutt_is_valid_multipart_pgp_encrypted(b))
1722
0
    {
1723
0
      encrypted_handler = valid_pgp_encrypted_handler;
1724
0
      handler = encrypted_handler;
1725
0
    }
1726
0
    else if (mutt_is_malformed_multipart_pgp_encrypted(b))
1727
0
    {
1728
0
      encrypted_handler = malformed_pgp_encrypted_handler;
1729
0
      handler = encrypted_handler;
1730
0
    }
1731
1732
0
    if (!handler)
1733
0
      handler = multipart_handler;
1734
1735
0
    if ((b->encoding != ENC_7BIT) && (b->encoding != ENC_8BIT) && (b->encoding != ENC_BINARY))
1736
0
    {
1737
0
      mutt_debug(LL_DEBUG1, "Bad encoding type %d for multipart entity, assuming 7 bit\n",
1738
0
                 b->encoding);
1739
0
      b->encoding = ENC_7BIT;
1740
0
    }
1741
0
  }
1742
0
  else if ((WithCrypto != 0) && (b->type == TYPE_APPLICATION))
1743
0
  {
1744
0
    if (OptDontHandlePgpKeys && mutt_istr_equal("pgp-keys", b->subtype))
1745
0
    {
1746
      /* pass raw part through for key extraction */
1747
0
      plaintext = true;
1748
0
    }
1749
0
    else if (((WithCrypto & APPLICATION_PGP) != 0) && mutt_is_application_pgp(b))
1750
0
    {
1751
0
      encrypted_handler = crypt_pgp_application_handler;
1752
0
      handler = encrypted_handler;
1753
0
    }
1754
0
    else if (((WithCrypto & APPLICATION_SMIME) != 0) && mutt_is_application_smime(b))
1755
0
    {
1756
0
      encrypted_handler = crypt_smime_application_handler;
1757
0
      handler = encrypted_handler;
1758
0
    }
1759
0
  }
1760
1761
0
  if ((plaintext || handler) && (is_attachment_display || !mutt_prefer_as_attachment(b)))
1762
0
  {
1763
    /* only respect disposition == attachment if we're not
1764
     * displaying from the attachment menu (i.e. pager) */
1765
    /* Prevent encrypted attachments from being included in replies
1766
     * unless $include_encrypted is set. */
1767
0
    const bool c_include_encrypted = cs_subset_bool(NeoMutt->sub, "include_encrypted");
1768
0
    if ((state->flags & STATE_REPLYING) && (state->flags & STATE_FIRSTDONE) &&
1769
0
        encrypted_handler && !c_include_encrypted)
1770
0
    {
1771
0
      goto cleanup;
1772
0
    }
1773
1774
0
    rc = run_decode_and_handler(b, state, handler, plaintext);
1775
0
  }
1776
0
  else if (state->flags & STATE_DISPLAY)
1777
0
  {
1778
    /* print hint to use attachment menu for disposition == attachment
1779
     * if we're not already being called from there */
1780
0
    const bool c_honor_disposition = cs_subset_bool(NeoMutt->sub, "honor_disposition");
1781
0
    struct Buffer *msg = buf_pool_get();
1782
1783
0
    if (is_attachment_display)
1784
0
    {
1785
0
      if (c_honor_disposition && (b->disposition == DISP_ATTACH))
1786
0
      {
1787
0
        buf_strcpy(msg, _("[-- This is an attachment --]\n"));
1788
0
      }
1789
0
      else
1790
0
      {
1791
        /* L10N: %s/%s is a MIME type, e.g. "text/plain". */
1792
0
        buf_printf(msg, _("[-- %s/%s is unsupported --]\n"), BODY_TYPE(b), b->subtype);
1793
0
      }
1794
0
    }
1795
0
    else
1796
0
    {
1797
0
      struct Buffer *keystroke = buf_pool_get();
1798
0
      if (km_expand_key(km_find_func(MENU_PAGER, OP_VIEW_ATTACHMENTS), keystroke))
1799
0
      {
1800
0
        if (c_honor_disposition && (b->disposition == DISP_ATTACH))
1801
0
        {
1802
          /* L10N: %s expands to a keystroke/key binding, e.g. 'v'.  */
1803
0
          buf_printf(msg, _("[-- This is an attachment (use '%s' to view this part) --]\n"),
1804
0
                     buf_string(keystroke));
1805
0
        }
1806
0
        else
1807
0
        {
1808
          /* L10N: %s/%s is a MIME type, e.g. "text/plain".
1809
             The last %s expands to a keystroke/key binding, e.g. 'v'. */
1810
0
          buf_printf(msg, _("[-- %s/%s is unsupported (use '%s' to view this part) --]\n"),
1811
0
                     BODY_TYPE(b), b->subtype, buf_string(keystroke));
1812
0
        }
1813
0
      }
1814
0
      else
1815
0
      {
1816
0
        if (c_honor_disposition && (b->disposition == DISP_ATTACH))
1817
0
        {
1818
0
          buf_strcpy(msg, _("[-- This is an attachment (need 'view-attachments' bound to key) --]\n"));
1819
0
        }
1820
0
        else
1821
0
        {
1822
          /* L10N: %s/%s is a MIME type, e.g. "text/plain". */
1823
0
          buf_printf(msg, _("[-- %s/%s is unsupported (need 'view-attachments' bound to key) --]\n"),
1824
0
                     BODY_TYPE(b), b->subtype);
1825
0
        }
1826
0
      }
1827
0
      buf_pool_release(&keystroke);
1828
0
    }
1829
0
    state_mark_attach(state);
1830
0
    state_printf(state, "%s", buf_string(msg));
1831
0
    buf_pool_release(&msg);
1832
0
  }
1833
1834
0
cleanup:
1835
0
  recurse_level--;
1836
0
  state->flags = oflags | (state->flags & STATE_FIRSTDONE);
1837
0
  if (rc != 0)
1838
0
  {
1839
0
    mutt_debug(LL_DEBUG1, "Bailing on attachment of type %s/%s\n", BODY_TYPE(b),
1840
0
               NONULL(b->subtype));
1841
0
  }
1842
1843
0
  return rc;
1844
0
}
1845
1846
/**
1847
 * mutt_prefer_as_attachment - Do we want this part as an attachment?
1848
 * @param b Body of email to test
1849
 * @retval true We want this part as an attachment
1850
 */
1851
bool mutt_prefer_as_attachment(struct Body *b)
1852
0
{
1853
0
  if (!mutt_can_decode(b))
1854
0
    return true;
1855
1856
0
  if (b->disposition != DISP_ATTACH)
1857
0
    return false;
1858
1859
0
  return cs_subset_bool(NeoMutt->sub, "honor_disposition");
1860
0
}
1861
1862
/**
1863
 * mutt_can_decode - Will decoding the attachment produce any output
1864
 * @param b Body of email to test
1865
 * @retval true Decoding the attachment will produce output
1866
 */
1867
bool mutt_can_decode(struct Body *b)
1868
0
{
1869
0
  if (is_autoview(b))
1870
0
    return true;
1871
0
  if (b->type == TYPE_TEXT)
1872
0
    return true;
1873
0
  if (b->type == TYPE_MESSAGE)
1874
0
    return true;
1875
0
  if (b->type == TYPE_MULTIPART)
1876
0
  {
1877
0
    if (WithCrypto)
1878
0
    {
1879
0
      if (mutt_istr_equal(b->subtype, "signed") || mutt_istr_equal(b->subtype, "encrypted"))
1880
0
      {
1881
0
        return true;
1882
0
      }
1883
0
    }
1884
1885
0
    for (struct Body *part = b->parts; part; part = part->next)
1886
0
    {
1887
0
      if (mutt_can_decode(part))
1888
0
        return true;
1889
0
    }
1890
0
  }
1891
0
  else if ((WithCrypto != 0) && (b->type == TYPE_APPLICATION))
1892
0
  {
1893
0
    if (((WithCrypto & APPLICATION_PGP) != 0) && mutt_is_application_pgp(b))
1894
0
      return true;
1895
0
    if (((WithCrypto & APPLICATION_SMIME) != 0) && mutt_is_application_smime(b))
1896
0
      return true;
1897
0
  }
1898
1899
0
  return false;
1900
0
}
1901
1902
/**
1903
 * mutt_decode_attachment - Decode an email's attachment
1904
 * @param b Body of the email
1905
 * @param state State of text being processed
1906
 */
1907
void mutt_decode_attachment(const struct Body *b, struct State *state)
1908
0
{
1909
0
  int istext = mutt_is_text_part(b) && (b->disposition == DISP_INLINE);
1910
0
  iconv_t cd = ICONV_T_INVALID;
1911
1912
0
  if (!mutt_file_seek(state->fp_in, b->offset, SEEK_SET))
1913
0
  {
1914
0
    return;
1915
0
  }
1916
1917
0
  if (istext && (b->charset || (state->flags & STATE_CHARCONV)))
1918
0
  {
1919
0
    const char *charset = b->charset;
1920
0
    if (!charset)
1921
0
    {
1922
0
      charset = mutt_param_get(&b->parameter, "charset");
1923
0
      if (!charset && !slist_is_empty(cc_assumed_charset()))
1924
0
        charset = mutt_ch_get_default_charset(cc_assumed_charset());
1925
0
    }
1926
0
    if (charset && cc_charset())
1927
0
      cd = mutt_ch_iconv_open(cc_charset(), charset, MUTT_ICONV_HOOK_FROM);
1928
0
  }
1929
1930
0
  switch (b->encoding)
1931
0
  {
1932
0
    case ENC_QUOTED_PRINTABLE:
1933
0
      decode_quoted(state, b->length,
1934
0
                    istext || (((WithCrypto & APPLICATION_PGP) != 0) &&
1935
0
                               mutt_is_application_pgp(b)),
1936
0
                    cd);
1937
0
      break;
1938
0
    case ENC_BASE64:
1939
0
      mutt_decode_base64(state, b->length,
1940
0
                         istext || (((WithCrypto & APPLICATION_PGP) != 0) &&
1941
0
                                    mutt_is_application_pgp(b)),
1942
0
                         cd);
1943
0
      break;
1944
0
    case ENC_UUENCODED:
1945
0
      decode_uuencoded(state, b->length,
1946
0
                       istext || (((WithCrypto & APPLICATION_PGP) != 0) &&
1947
0
                                  mutt_is_application_pgp(b)),
1948
0
                       cd);
1949
0
      break;
1950
0
    default:
1951
0
      decode_xbit(state, b->length,
1952
0
                  istext || (((WithCrypto & APPLICATION_PGP) != 0) &&
1953
0
                             mutt_is_application_pgp(b)),
1954
0
                  cd);
1955
0
      break;
1956
0
  }
1957
0
}