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