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