NeoMutt  2020-06-26-89-g172cd3
Teaching an old dog new tricks
DOXYGEN
handler.c
Go to the documentation of this file.
1 
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 <sys/stat.h>
38 #include <time.h>
39 #include <unistd.h>
40 #include "mutt/lib.h"
41 #include "config/lib.h"
42 #include "email/lib.h"
43 #include "core/lib.h"
44 #include "mutt.h"
45 #include "handler.h"
46 #include "copy.h"
47 #include "enriched.h"
48 #include "keymap.h"
49 #include "mailcap.h"
50 #include "mutt_attach.h"
51 #include "mutt_globals.h"
52 #include "mutt_logging.h"
53 #include "muttlib.h"
54 #include "opcodes.h"
55 #include "options.h"
56 #include "rfc3676.h"
57 #include "state.h"
58 #include "ncrypt/lib.h"
59 #ifdef ENABLE_NLS
60 #include <libintl.h>
61 #endif
62 
63 /* These Config Variables are only used in handler.c */
71 
72 #define BUFI_SIZE 1000
73 #define BUFO_SIZE 2000
74 
75 #define TXT_HTML 1
76 #define TXT_PLAIN 2
77 #define TXT_ENRICHED 3
78 
86 typedef int (*handler_t)(struct Body *b, struct State *s);
87 
94 static void print_part_line(struct State *s, struct Body *b, int n)
95 {
96  char length[5];
97  mutt_str_pretty_size(length, sizeof(length), b->length);
99  char *charset = mutt_param_get(&b->parameter, "charset");
100  if (n == 0)
101  {
102  state_printf(s, _("[-- Type: %s/%s%s%s, Encoding: %s, Size: %s --]\n"),
103  TYPE(b), b->subtype, charset ? "; charset=" : "",
104  charset ? charset : "", ENCODING(b->encoding), length);
105  }
106  else
107  {
108  state_printf(s, _("[-- Alternative Type #%d: %s/%s%s%s, Encoding: %s, Size: %s --]\n"),
109  n, TYPE(b), b->subtype, charset ? "; charset=" : "",
110  charset ? charset : "", ENCODING(b->encoding), length);
111  }
112 }
113 
121 static void convert_to_state(iconv_t cd, char *bufi, size_t *l, struct State *s)
122 {
123  char bufo[BUFO_SIZE];
124  const char *ib = NULL;
125  char *ob = NULL;
126  size_t ibl, obl;
127 
128  if (!bufi)
129  {
130  if (cd != (iconv_t)(-1))
131  {
132  ob = bufo;
133  obl = sizeof(bufo);
134  iconv(cd, NULL, NULL, &ob, &obl);
135  if (ob != bufo)
136  state_prefix_put(s, bufo, ob - bufo);
137  }
138  return;
139  }
140 
141  if (cd == (iconv_t)(-1))
142  {
143  state_prefix_put(s, bufi, *l);
144  *l = 0;
145  return;
146  }
147 
148  ib = bufi;
149  ibl = *l;
150  while (true)
151  {
152  ob = bufo;
153  obl = sizeof(bufo);
154  mutt_ch_iconv(cd, &ib, &ibl, &ob, &obl, 0, "?", NULL);
155  if (ob == bufo)
156  break;
157  state_prefix_put(s, bufo, ob - bufo);
158  }
159  memmove(bufi, ib, ibl);
160  *l = ibl;
161 }
162 
170 static void decode_xbit(struct State *s, long len, bool istext, iconv_t cd)
171 {
172  if (!istext)
173  {
174  mutt_file_copy_bytes(s->fp_in, s->fp_out, len);
175  return;
176  }
177 
178  state_set_prefix(s);
179 
180  int c;
181  char bufi[BUFI_SIZE];
182  size_t l = 0;
183  while (((c = fgetc(s->fp_in)) != EOF) && len--)
184  {
185  if ((c == '\r') && len)
186  {
187  const int ch = fgetc(s->fp_in);
188  if (ch == '\n')
189  {
190  c = ch;
191  len--;
192  }
193  else
194  ungetc(ch, s->fp_in);
195  }
196 
197  bufi[l++] = c;
198  if (l == sizeof(bufi))
199  convert_to_state(cd, bufi, &l, s);
200  }
201 
202  convert_to_state(cd, bufi, &l, s);
203  convert_to_state(cd, 0, 0, s);
204 
206 }
207 
215 static int qp_decode_triple(char *s, char *d)
216 {
217  /* soft line break */
218  if ((s[0] == '=') && (s[1] == '\0'))
219  return 1;
220 
221  /* quoted-printable triple */
222  if ((s[0] == '=') && isxdigit((unsigned char) s[1]) && isxdigit((unsigned char) s[2]))
223  {
224  *d = (hexval(s[1]) << 4) | hexval(s[2]);
225  return 0;
226  }
227 
228  /* something else */
229  return -1;
230 }
231 
239 static void qp_decode_line(char *dest, char *src, size_t *l, int last)
240 {
241  char *d = NULL, *s = NULL;
242  char c = 0;
243 
244  int kind = -1;
245  bool soft = false;
246 
247  /* decode the line */
248 
249  for (d = dest, s = src; *s;)
250  {
251  switch ((kind = qp_decode_triple(s, &c)))
252  {
253  case 0:
254  *d++ = c;
255  s += 3;
256  break; /* qp triple */
257  case -1:
258  *d++ = *s++;
259  break; /* single character */
260  case 1:
261  soft = true;
262  s++;
263  break; /* soft line break */
264  }
265  }
266 
267  if (!soft && (last == '\n'))
268  {
269  /* neither \r nor \n as part of line-terminating CRLF
270  * may be qp-encoded, so remove \r and \n-terminate;
271  * see RFC2045, sect. 6.7, (1): General 8bit representation */
272  if ((kind == 0) && (c == '\r'))
273  *(d - 1) = '\n';
274  else
275  *d++ = '\n';
276  }
277 
278  *d = '\0';
279  *l = d - dest;
280 }
281 
307 static void decode_quoted(struct State *s, long len, bool istext, iconv_t cd)
308 {
309  char line[256];
310  char decline[512];
311  size_t l = 0;
312  size_t l3;
313 
314  if (istext)
315  state_set_prefix(s);
316 
317  while (len > 0)
318  {
319  /* It's ok to use a fixed size buffer for input, even if the line turns
320  * out to be longer than this. Just process the line in chunks. This
321  * really shouldn't happen according the MIME spec, since Q-P encoded
322  * lines are at most 76 characters, but we should be liberal about what
323  * we accept. */
324  if (!fgets(line, MIN((ssize_t) sizeof(line), len + 1), s->fp_in))
325  break;
326 
327  size_t linelen = strlen(line);
328  len -= linelen;
329 
330  /* inspect the last character we read so we can tell if we got the
331  * entire line. */
332  const int last = (linelen != 0) ? line[linelen - 1] : 0;
333 
334  /* chop trailing whitespace if we got the full line */
335  if (last == '\n')
336  {
337  while ((linelen > 0) && IS_SPACE(line[linelen - 1]))
338  linelen--;
339  line[linelen] = '\0';
340  }
341 
342  /* decode and do character set conversion */
343  qp_decode_line(decline + l, line, &l3, last);
344  l += l3;
345  convert_to_state(cd, decline, &l, s);
346  }
347 
348  convert_to_state(cd, 0, 0, s);
350 }
351 
357 static unsigned char decode_byte(char ch)
358 {
359  if ((ch < 32) || (ch > 95))
360  return 0;
361  return ch - 32;
362 }
363 
371 static void decode_uuencoded(struct State *s, long len, bool istext, iconv_t cd)
372 {
373  char tmps[128];
374  char *pt = NULL;
375  char bufi[BUFI_SIZE];
376  size_t k = 0;
377 
378  if (istext)
379  state_set_prefix(s);
380 
381  while (len > 0)
382  {
383  if (!fgets(tmps, sizeof(tmps), s->fp_in))
384  return;
385  len -= mutt_str_len(tmps);
386  if (mutt_str_startswith(tmps, "begin "))
387  break;
388  }
389  while (len > 0)
390  {
391  if (!fgets(tmps, sizeof(tmps), s->fp_in))
392  return;
393  len -= mutt_str_len(tmps);
394  if (mutt_str_startswith(tmps, "end"))
395  break;
396  pt = tmps;
397  const unsigned char linelen = decode_byte(*pt);
398  pt++;
399  for (unsigned char c = 0; c < linelen;)
400  {
401  for (char l = 2; l <= 6; l += 2)
402  {
403  char out = decode_byte(*pt) << l;
404  pt++;
405  out |= (decode_byte(*pt) >> (6 - l));
406  bufi[k++] = out;
407  c++;
408  if (c == linelen)
409  break;
410  }
411  convert_to_state(cd, bufi, &k, s);
412  pt++;
413  }
414  }
415 
416  convert_to_state(cd, bufi, &k, s);
417  convert_to_state(cd, 0, 0, s);
418 
420 }
421 
432 static bool is_mmnoask(const char *buf)
433 {
434  const char *val = mutt_str_getenv("MM_NOASK");
435  if (!val)
436  return false;
437 
438  char *p = NULL;
439  char tmp[1024];
440  char *q = NULL;
441 
442  if (mutt_str_equal(val, "1"))
443  return true;
444 
445  mutt_str_copy(tmp, val, sizeof(tmp));
446  p = tmp;
447 
448  while ((p = strtok(p, ",")))
449  {
450  q = strrchr(p, '/');
451  if (q)
452  {
453  if (q[1] == '*')
454  {
455  if (mutt_istrn_equal(buf, p, q - p))
456  return true;
457  }
458  else
459  {
460  if (mutt_istr_equal(buf, p))
461  return true;
462  }
463  }
464  else
465  {
466  const size_t plen = mutt_istr_startswith(buf, p);
467  if ((plen != 0) && (buf[plen] == '/'))
468  return true;
469  }
470 
471  p = NULL;
472  }
473 
474  return false;
475 }
476 
483 static bool is_autoview(struct Body *b)
484 {
485  char type[256];
486  bool is_av = false;
487 
488  snprintf(type, sizeof(type), "%s/%s", TYPE(b), b->subtype);
489 
490  if (C_ImplicitAutoview)
491  {
492  /* $implicit_autoview is essentially the same as "auto_view *" */
493  is_av = true;
494  }
495  else
496  {
497  /* determine if this type is on the user's auto_view list */
498  mutt_check_lookup_list(b, type, sizeof(type));
499  struct ListNode *np = NULL;
500  STAILQ_FOREACH(np, &AutoViewList, entries)
501  {
502  int i = mutt_str_len(np->data) - 1;
503  if (((i > 0) && (np->data[i - 1] == '/') && (np->data[i] == '*') &&
504  mutt_istrn_equal(type, np->data, i)) ||
505  mutt_istr_equal(type, np->data))
506  {
507  is_av = true;
508  break;
509  }
510  }
511 
512  if (is_mmnoask(type))
513  is_av = true;
514  }
515 
516  /* determine if there is a mailcap entry suitable for auto_view
517  *
518  * @warning type is altered by this call as a result of 'mime_lookup' support */
519  if (is_av)
520  return mailcap_lookup(b, type, sizeof(type), NULL, MUTT_MC_AUTOVIEW);
521 
522  return false;
523 }
524 
528 static int autoview_handler(struct Body *a, struct State *s)
529 {
530  struct MailcapEntry *entry = mailcap_entry_new();
531  char buf[1024];
532  char type[256];
533  struct Buffer *cmd = mutt_buffer_pool_get();
534  struct Buffer *tempfile = mutt_buffer_pool_get();
535  char *fname = NULL;
536  FILE *fp_in = NULL;
537  FILE *fp_out = NULL;
538  FILE *fp_err = NULL;
539  pid_t pid;
540  int rc = 0;
541 
542  snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype);
543  mailcap_lookup(a, type, sizeof(type), entry, MUTT_MC_AUTOVIEW);
544 
545  fname = mutt_str_dup(a->filename);
546  mutt_file_sanitize_filename(fname, true);
547  mailcap_expand_filename(entry->nametemplate, fname, tempfile);
548  FREE(&fname);
549 
550  if (entry->command)
551  {
552  mutt_buffer_strcpy(cmd, entry->command);
553 
554  /* mailcap_expand_command returns 0 if the file is required */
555  bool piped = mailcap_expand_command(a, mutt_b2s(tempfile), type, cmd);
556 
557  if (s->flags & MUTT_DISPLAY)
558  {
560  state_printf(s, _("[-- Autoview using %s --]\n"), mutt_b2s(cmd));
561  mutt_message(_("Invoking autoview command: %s"), mutt_b2s(cmd));
562  }
563 
564  fp_in = mutt_file_fopen(mutt_b2s(tempfile), "w+");
565  if (!fp_in)
566  {
567  mutt_perror("fopen");
568  mailcap_entry_free(&entry);
569  rc = -1;
570  goto cleanup;
571  }
572 
573  mutt_file_copy_bytes(s->fp_in, fp_in, a->length);
574 
575  if (piped)
576  {
577  unlink(mutt_b2s(tempfile));
578  fflush(fp_in);
579  rewind(fp_in);
580  pid = filter_create_fd(mutt_b2s(cmd), NULL, &fp_out, &fp_err, fileno(fp_in), -1, -1);
581  }
582  else
583  {
584  mutt_file_fclose(&fp_in);
585  pid = filter_create(mutt_b2s(cmd), NULL, &fp_out, &fp_err);
586  }
587 
588  if (pid < 0)
589  {
590  mutt_perror(_("Can't create filter"));
591  if (s->flags & MUTT_DISPLAY)
592  {
594  state_printf(s, _("[-- Can't run %s. --]\n"), mutt_b2s(cmd));
595  }
596  rc = -1;
597  goto bail;
598  }
599 
600  if (s->prefix)
601  {
602  while (fgets(buf, sizeof(buf), fp_out))
603  {
604  state_puts(s, s->prefix);
605  state_puts(s, buf);
606  }
607  /* check for data on stderr */
608  if (fgets(buf, sizeof(buf), fp_err))
609  {
610  if (s->flags & MUTT_DISPLAY)
611  {
613  state_printf(s, _("[-- Autoview stderr of %s --]\n"), mutt_b2s(cmd));
614  }
615 
616  state_puts(s, s->prefix);
617  state_puts(s, buf);
618  while (fgets(buf, sizeof(buf), fp_err))
619  {
620  state_puts(s, s->prefix);
621  state_puts(s, buf);
622  }
623  }
624  }
625  else
626  {
627  mutt_file_copy_stream(fp_out, s->fp_out);
628  /* Check for stderr messages */
629  if (fgets(buf, sizeof(buf), fp_err))
630  {
631  if (s->flags & MUTT_DISPLAY)
632  {
634  state_printf(s, _("[-- Autoview stderr of %s --]\n"), mutt_b2s(cmd));
635  }
636 
637  state_puts(s, buf);
638  mutt_file_copy_stream(fp_err, s->fp_out);
639  }
640  }
641 
642  bail:
643  mutt_file_fclose(&fp_out);
644  mutt_file_fclose(&fp_err);
645 
646  filter_wait(pid);
647  if (piped)
648  mutt_file_fclose(&fp_in);
649  else
650  mutt_file_unlink(mutt_b2s(tempfile));
651 
652  if (s->flags & MUTT_DISPLAY)
654  }
655 
656 cleanup:
657  mailcap_entry_free(&entry);
658 
660  mutt_buffer_pool_release(&tempfile);
661 
662  return rc;
663 }
664 
673 static int text_plain_handler(struct Body *b, struct State *s)
674 {
675  char *buf = NULL;
676  size_t sz = 0;
677 
678  while ((buf = mutt_file_read_line(buf, &sz, s->fp_in, NULL, 0)))
679  {
680  if (!mutt_str_equal(buf, "-- ") && C_TextFlowed)
681  {
682  size_t len = mutt_str_len(buf);
683  while ((len > 0) && (buf[len - 1] == ' '))
684  buf[--len] = '\0';
685  }
686  if (s->prefix)
687  state_puts(s, s->prefix);
688  state_puts(s, buf);
689  state_putc(s, '\n');
690  }
691 
692  FREE(&buf);
693  return 0;
694 }
695 
699 static int message_handler(struct Body *a, struct State *s)
700 {
701  struct stat st;
702  struct Body *b = NULL;
703  LOFF_T off_start;
704  int rc = 0;
705 
706  off_start = ftello(s->fp_in);
707  if (off_start < 0)
708  return -1;
709 
710  if ((a->encoding == ENC_BASE64) || (a->encoding == ENC_QUOTED_PRINTABLE) ||
711  (a->encoding == ENC_UUENCODED))
712  {
713  fstat(fileno(s->fp_in), &st);
714  b = mutt_body_new();
715  b->length = (LOFF_T) st.st_size;
717  }
718  else
719  b = a;
720 
721  if (b->parts)
722  {
723  CopyHeaderFlags chflags = CH_DECODE | CH_FROM;
724  if ((s->flags & MUTT_WEED) || ((s->flags & (MUTT_DISPLAY | MUTT_PRINTING)) && C_Weed))
725  chflags |= CH_WEED | CH_REORDER;
726  if (s->prefix)
727  chflags |= CH_PREFIX;
728  if (s->flags & MUTT_DISPLAY)
729  chflags |= CH_DISPLAY;
730 
731  mutt_copy_hdr(s->fp_in, s->fp_out, off_start, b->parts->offset, chflags, s->prefix, 0);
732 
733  if (s->prefix)
734  state_puts(s, s->prefix);
735  state_putc(s, '\n');
736 
737  rc = mutt_body_handler(b->parts, s);
738  }
739 
740  if ((a->encoding == ENC_BASE64) || (a->encoding == ENC_QUOTED_PRINTABLE) ||
741  (a->encoding == ENC_UUENCODED))
742  {
743  mutt_body_free(&b);
744  }
745 
746  return rc;
747 }
748 
752 static int external_body_handler(struct Body *b, struct State *s)
753 {
754  const char *str = NULL;
755  char strbuf[1024];
756 
757  const char *access_type = mutt_param_get(&b->parameter, "access-type");
758  if (!access_type)
759  {
760  if (s->flags & MUTT_DISPLAY)
761  {
763  state_puts(s, _("[-- Error: message/external-body has no access-type "
764  "parameter --]\n"));
765  return 0;
766  }
767  else
768  return -1;
769  }
770 
771  const char *expiration = mutt_param_get(&b->parameter, "expiration");
772  time_t expire;
773  if (expiration)
774  expire = mutt_date_parse_date(expiration, NULL);
775  else
776  expire = -1;
777 
778  if (mutt_istr_equal(access_type, "x-mutt-deleted"))
779  {
780  if (s->flags & (MUTT_DISPLAY | MUTT_PRINTING))
781  {
782  char pretty_size[10];
783  char *length = mutt_param_get(&b->parameter, "length");
784  if (length)
785  {
786  long size = strtol(length, NULL, 10);
787  mutt_str_pretty_size(pretty_size, sizeof(pretty_size), size);
788  if (expire != -1)
789  {
790  str = ngettext(
791  /* L10N: If the translation of this string is a multi line string, then
792  each line should start with "[-- " and end with " --]".
793  The first "%s/%s" is a MIME type, e.g. "text/plain". The last %s
794  expands to a date as returned by `mutt_date_parse_date()`.
795 
796  Note: The size argument printed is not the actual number as passed
797  to gettext but the prettified version, e.g. size = 2048 will be
798  printed as 2K. Your language might be sensitive to that: For
799  example although '1K' and '1024' represent the same number your
800  language might inflect the noun 'byte' differently.
801 
802  Sadly, we can't do anything about that at the moment besides
803  passing the precise size in bytes. If you are interested the
804  function responsible for the prettification is
805  mutt_str_pretty_size() in mutt/string.c. */
806  "[-- This %s/%s attachment (size %s byte) has been deleted --]\n"
807  "[-- on %s --]\n",
808  "[-- This %s/%s attachment (size %s bytes) has been deleted --]\n"
809  "[-- on %s --]\n",
810  size);
811  }
812  else
813  {
814  str = ngettext(
815  /* L10N: If the translation of this string is a multi line string, then
816  each line should start with "[-- " and end with " --]".
817  The first "%s/%s" is a MIME type, e.g. "text/plain".
818 
819  Note: The size argument printed is not the actual number as passed
820  to gettext but the prettified version, e.g. size = 2048 will be
821  printed as 2K. Your language might be sensitive to that: For
822  example although '1K' and '1024' represent the same number your
823  language might inflect the noun 'byte' differently.
824 
825  Sadly, we can't do anything about that at the moment besides
826  passing the precise size in bytes. If you are interested the
827  function responsible for the prettification is
828  mutt_str_pretty_size() in mutt/string.c. */
829  "[-- This %s/%s attachment (size %s byte) has been deleted --]\n",
830  "[-- This %s/%s attachment (size %s bytes) has been deleted "
831  "--]\n",
832  size);
833  }
834  }
835  else
836  {
837  pretty_size[0] = '\0';
838  if (expire != -1)
839  {
840  /* L10N: If the translation of this string is a multi line string, then
841  each line should start with "[-- " and end with " --]".
842  The first "%s/%s" is a MIME type, e.g. "text/plain". The last %s
843  expands to a date as returned by `mutt_date_parse_date()`.
844 
845  Caution: Argument three %3$ is also defined but should not be used
846  in this translation! */
847  str = _("[-- This %s/%s attachment has been deleted --]\n[-- on %4$s "
848  "--]\n");
849  }
850  else
851  {
852  /* L10N: If the translation of this string is a multi line string, then
853  each line should start with "[-- " and end with " --]".
854  The first "%s/%s" is a MIME type, e.g. "text/plain". */
855  str = _("[-- This %s/%s attachment has been deleted --]\n");
856  }
857  }
858 
859  snprintf(strbuf, sizeof(strbuf), str, TYPE(b->parts), b->parts->subtype,
860  pretty_size, expiration);
861  state_attach_puts(s, strbuf);
862  if (b->parts->filename)
863  {
865  state_printf(s, _("[-- name: %s --]\n"), b->parts->filename);
866  }
867 
868  CopyHeaderFlags chflags = CH_DECODE;
869  if (C_Weed)
870  chflags |= CH_WEED | CH_REORDER;
871 
872  mutt_copy_hdr(s->fp_in, s->fp_out, ftello(s->fp_in), b->parts->offset,
873  chflags, NULL, 0);
874  }
875  }
876  else if (expiration && (expire < mutt_date_epoch()))
877  {
878  if (s->flags & MUTT_DISPLAY)
879  {
880  /* L10N: If the translation of this string is a multi line string, then
881  each line should start with "[-- " and end with " --]".
882  The "%s/%s" is a MIME type, e.g. "text/plain". */
883  snprintf(strbuf, sizeof(strbuf), _("[-- This %s/%s attachment is not included, --]\n[-- and the indicated external source has --]\n[-- expired. --]\n"),
884  TYPE(b->parts), b->parts->subtype);
885  state_attach_puts(s, strbuf);
886 
888  if (C_Weed)
889  chflags |= CH_WEED | CH_REORDER;
890 
891  mutt_copy_hdr(s->fp_in, s->fp_out, ftello(s->fp_in), b->parts->offset,
892  chflags, NULL, 0);
893  }
894  }
895  else
896  {
897  if (s->flags & MUTT_DISPLAY)
898  {
899  /* L10N: If the translation of this string is a multi line string, then
900  each line should start with "[-- " and end with " --]".
901  The "%s/%s" is a MIME type, e.g. "text/plain". The %s after
902  access-type is an access-type as defined by the MIME RFCs, e.g. "FTP",
903  "LOCAL-FILE", "MAIL-SERVER". */
904  snprintf(strbuf, sizeof(strbuf), _("[-- This %s/%s attachment is not included, --]\n[-- and the indicated access-type %s is unsupported --]\n"),
905  TYPE(b->parts), b->parts->subtype, access_type);
906  state_attach_puts(s, strbuf);
907 
909  if (C_Weed)
910  chflags |= CH_WEED | CH_REORDER;
911 
912  mutt_copy_hdr(s->fp_in, s->fp_out, ftello(s->fp_in), b->parts->offset,
913  chflags, NULL, 0);
914  }
915  }
916 
917  return 0;
918 }
919 
923 static int alternative_handler(struct Body *a, struct State *s)
924 {
925  struct Body *choice = NULL;
926  struct Body *b = NULL;
927  bool mustfree = false;
928  int rc = 0;
929 
930  if ((a->encoding == ENC_BASE64) || (a->encoding == ENC_QUOTED_PRINTABLE) ||
931  (a->encoding == ENC_UUENCODED))
932  {
933  struct stat st;
934  mustfree = true;
935  fstat(fileno(s->fp_in), &st);
936  b = mutt_body_new();
937  b->length = (long) st.st_size;
938  b->parts =
939  mutt_parse_multipart(s->fp_in, mutt_param_get(&a->parameter, "boundary"),
940  (long) st.st_size, mutt_istr_equal("digest", a->subtype));
941  }
942  else
943  b = a;
944 
945  a = b;
946 
947  /* First, search list of preferred types */
948  struct ListNode *np = NULL;
949  STAILQ_FOREACH(np, &AlternativeOrderList, entries)
950  {
951  int btlen; /* length of basetype */
952  bool wild; /* do we have a wildcard to match all subtypes? */
953 
954  char *c = strchr(np->data, '/');
955  if (c)
956  {
957  wild = ((c[1] == '*') && (c[2] == '\0'));
958  btlen = c - np->data;
959  }
960  else
961  {
962  wild = true;
963  btlen = mutt_str_len(np->data);
964  }
965 
966  if (a->parts)
967  b = a->parts;
968  else
969  b = a;
970  while (b)
971  {
972  const char *bt = TYPE(b);
973  if (mutt_istrn_equal(bt, np->data, btlen) && (bt[btlen] == 0))
974  {
975  /* the basetype matches */
976  if (wild || mutt_istr_equal(np->data + btlen + 1, b->subtype))
977  {
978  choice = b;
979  }
980  }
981  b = b->next;
982  }
983 
984  if (choice)
985  break;
986  }
987 
988  /* Next, look for an autoviewable type */
989  if (!choice)
990  {
991  if (a->parts)
992  b = a->parts;
993  else
994  b = a;
995  while (b)
996  {
997  if (is_autoview(b))
998  choice = b;
999  b = b->next;
1000  }
1001  }
1002 
1003  /* Then, look for a text entry */
1004  if (!choice)
1005  {
1006  if (a->parts)
1007  b = a->parts;
1008  else
1009  b = a;
1010  int type = 0;
1011  while (b)
1012  {
1013  if (b->type == TYPE_TEXT)
1014  {
1015  if (mutt_istr_equal("plain", b->subtype) && (type <= TXT_PLAIN))
1016  {
1017  choice = b;
1018  type = TXT_PLAIN;
1019  }
1020  else if (mutt_istr_equal("enriched", b->subtype) && (type <= TXT_ENRICHED))
1021  {
1022  choice = b;
1023  type = TXT_ENRICHED;
1024  }
1025  else if (mutt_istr_equal("html", b->subtype) && (type <= TXT_HTML))
1026  {
1027  choice = b;
1028  type = TXT_HTML;
1029  }
1030  }
1031  b = b->next;
1032  }
1033  }
1034 
1035  /* Finally, look for other possibilities */
1036  if (!choice)
1037  {
1038  if (a->parts)
1039  b = a->parts;
1040  else
1041  b = a;
1042  while (b)
1043  {
1044  if (mutt_can_decode(b))
1045  choice = b;
1046  b = b->next;
1047  }
1048  }
1049 
1050  if (choice)
1051  {
1052  if (s->flags & MUTT_DISPLAY && !C_Weed)
1053  {
1054  fseeko(s->fp_in, choice->hdr_offset, SEEK_SET);
1055  mutt_file_copy_bytes(s->fp_in, s->fp_out, choice->offset - choice->hdr_offset);
1056  }
1057 
1059  {
1060  print_part_line(s, choice, 0);
1061  }
1062  mutt_body_handler(choice, s);
1063 
1065  {
1066  if (a->parts)
1067  b = a->parts;
1068  else
1069  b = a;
1070  int count = 0;
1071  while (b)
1072  {
1073  if (choice != b)
1074  {
1075  count += 1;
1076  if (count == 1)
1077  state_putc(s, '\n');
1078 
1079  print_part_line(s, b, count);
1080  }
1081  b = b->next;
1082  }
1083  }
1084  }
1085  else if (s->flags & MUTT_DISPLAY)
1086  {
1087  /* didn't find anything that we could display! */
1088  state_mark_attach(s);
1089  state_puts(s, _("[-- Error: Could not display any parts of "
1090  "Multipart/Alternative --]\n"));
1091  rc = -1;
1092  }
1093 
1094  if (mustfree)
1095  mutt_body_free(&a);
1096 
1097  return rc;
1098 }
1099 
1104 static int multilingual_handler(struct Body *a, struct State *s)
1105 {
1106  struct Body *b = NULL;
1107  bool mustfree = false;
1108  int rc = 0;
1109 
1111  "RFC8255 >> entering in handler multilingual handler\n");
1112  if ((a->encoding == ENC_BASE64) || (a->encoding == ENC_QUOTED_PRINTABLE) ||
1113  (a->encoding == ENC_UUENCODED))
1114  {
1115  struct stat st;
1116  mustfree = true;
1117  fstat(fileno(s->fp_in), &st);
1118  b = mutt_body_new();
1119  b->length = (long) st.st_size;
1120  b->parts =
1121  mutt_parse_multipart(s->fp_in, mutt_param_get(&a->parameter, "boundary"),
1122  (long) st.st_size, mutt_istr_equal("digest", a->subtype));
1123  }
1124  else
1125  b = a;
1126 
1127  a = b;
1128 
1129  if (a->parts)
1130  b = a->parts;
1131  else
1132  b = a;
1133 
1134  struct Body *choice = NULL;
1135  struct Body *first_part = NULL;
1136  struct Body *zxx_part = NULL;
1137  struct ListNode *np = NULL;
1138 
1139  if (C_PreferredLanguages)
1140  {
1141  struct Buffer *langs = mutt_buffer_pool_get();
1142  cs_subset_str_string_get(NeoMutt->sub, "preferred_languages", langs);
1143  mutt_debug(LL_DEBUG2, "RFC8255 >> preferred_languages set in config to '%s'\n",
1144  mutt_b2s(langs));
1145  mutt_buffer_pool_release(&langs);
1146 
1147  STAILQ_FOREACH(np, &C_PreferredLanguages->head, entries)
1148  {
1149  while (b)
1150  {
1151  if (mutt_can_decode(b))
1152  {
1153  if (!first_part)
1154  first_part = b;
1155 
1156  if (b->language && mutt_str_equal("zxx", b->language))
1157  zxx_part = b;
1158 
1159  mutt_debug(LL_DEBUG2, "RFC8255 >> comparing configuration preferred_language='%s' to mail part content-language='%s'\n",
1160  np->data, b->language);
1161  if (b->language && mutt_str_equal(np->data, b->language))
1162  {
1163  mutt_debug(LL_DEBUG2, "RFC8255 >> preferred_language='%s' matches content-language='%s' >> part selected to be displayed\n",
1164  np->data, b->language);
1165  choice = b;
1166  break;
1167  }
1168  }
1169 
1170  b = b->next;
1171  }
1172 
1173  if (choice)
1174  break;
1175 
1176  if (a->parts)
1177  b = a->parts;
1178  else
1179  b = a;
1180  }
1181  }
1182 
1183  if (choice)
1184  mutt_body_handler(choice, s);
1185  else
1186  {
1187  if (zxx_part)
1188  mutt_body_handler(zxx_part, s);
1189  else
1190  mutt_body_handler(first_part, s);
1191  }
1192 
1193  if (mustfree)
1194  mutt_body_free(&a);
1195 
1196  return rc;
1197 }
1198 
1202 static int multipart_handler(struct Body *a, struct State *s)
1203 {
1204  struct Body *b = NULL, *p = NULL;
1205  struct stat st;
1206  int count;
1207  int rc = 0;
1208 
1209  if ((a->encoding == ENC_BASE64) || (a->encoding == ENC_QUOTED_PRINTABLE) ||
1210  (a->encoding == ENC_UUENCODED))
1211  {
1212  fstat(fileno(s->fp_in), &st);
1213  b = mutt_body_new();
1214  b->length = (long) st.st_size;
1215  b->parts =
1216  mutt_parse_multipart(s->fp_in, mutt_param_get(&a->parameter, "boundary"),
1217  (long) st.st_size, mutt_istr_equal("digest", a->subtype));
1218  }
1219  else
1220  b = a;
1221 
1222  for (p = b->parts, count = 1; p; p = p->next, count++)
1223  {
1224  if (s->flags & MUTT_DISPLAY)
1225  {
1226  state_mark_attach(s);
1227  if (p->description || p->filename || p->form_name)
1228  {
1229  /* L10N: %s is the attachment description, filename or form_name. */
1230  state_printf(s, _("[-- Attachment #%d: %s --]\n"), count,
1231  p->description ? p->description :
1232  p->filename ? p->filename : p->form_name);
1233  }
1234  else
1235  state_printf(s, _("[-- Attachment #%d --]\n"), count);
1236  print_part_line(s, p, 0);
1237  if (C_Weed)
1238  {
1239  state_putc(s, '\n');
1240  }
1241  else
1242  {
1243  fseeko(s->fp_in, p->hdr_offset, SEEK_SET);
1244  mutt_file_copy_bytes(s->fp_in, s->fp_out, p->offset - p->hdr_offset);
1245  }
1246  }
1247 
1248  rc = mutt_body_handler(p, s);
1249  state_putc(s, '\n');
1250 
1251  if (rc != 0)
1252  {
1253  mutt_error(_("One or more parts of this message could not be displayed"));
1254  mutt_debug(LL_DEBUG1, "Failed on attachment #%d, type %s/%s\n", count,
1255  TYPE(p), NONULL(p->subtype));
1256  }
1257 
1258  if ((s->flags & MUTT_REPLYING) && C_IncludeOnlyfirst && (s->flags & MUTT_FIRSTDONE))
1259  {
1260  break;
1261  }
1262  }
1263 
1264  if ((a->encoding == ENC_BASE64) || (a->encoding == ENC_QUOTED_PRINTABLE) ||
1265  (a->encoding == ENC_UUENCODED))
1266  {
1267  mutt_body_free(&b);
1268  }
1269 
1270  /* make failure of a single part non-fatal */
1271  if (rc < 0)
1272  rc = 1;
1273  return rc;
1274 }
1275 
1285 static int run_decode_and_handler(struct Body *b, struct State *s,
1286  handler_t handler, bool plaintext)
1287 {
1288  char *save_prefix = NULL;
1289  FILE *fp = NULL;
1290  size_t tmplength = 0;
1291  LOFF_T tmpoffset = 0;
1292  int decode = 0;
1293  int rc = 0;
1294 #ifndef USE_FMEMOPEN
1295  struct Buffer *tempfile = NULL;
1296 #endif
1297 
1298  fseeko(s->fp_in, b->offset, SEEK_SET);
1299 
1300 #ifdef USE_FMEMOPEN
1301  char *temp = NULL;
1302  size_t tempsize = 0;
1303 #endif
1304 
1305  /* see if we need to decode this part before processing it */
1306  if ((b->encoding == ENC_BASE64) || (b->encoding == ENC_QUOTED_PRINTABLE) ||
1307  (b->encoding == ENC_UUENCODED) || (plaintext || mutt_is_text_part(b)))
1308  /* text subtypes may require character set conversion even with 8bit encoding */
1309  {
1310  const int orig_type = b->type;
1311  if (!plaintext)
1312  {
1313  /* decode to a tempfile, saving the original destination */
1314  fp = s->fp_out;
1315 #ifdef USE_FMEMOPEN
1316  s->fp_out = open_memstream(&temp, &tempsize);
1317  if (!s->fp_out)
1318  {
1319  mutt_error(_("Unable to open 'memory stream'"));
1320  mutt_debug(LL_DEBUG1, "Can't open 'memory stream'\n");
1321  return -1;
1322  }
1323 #else
1324  tempfile = mutt_buffer_pool_get();
1325  mutt_buffer_mktemp(tempfile);
1326  s->fp_out = mutt_file_fopen(mutt_b2s(tempfile), "w");
1327  if (!s->fp_out)
1328  {
1329  mutt_error(_("Unable to open temporary file"));
1330  mutt_debug(LL_DEBUG1, "Can't open %s\n", mutt_b2s(tempfile));
1331  mutt_buffer_pool_release(&tempfile);
1332  return -1;
1333  }
1334 #endif
1335  /* decoding the attachment changes the size and offset, so save a copy
1336  * of the "real" values now, and restore them after processing */
1337  tmplength = b->length;
1338  tmpoffset = b->offset;
1339 
1340  /* if we are decoding binary bodies, we don't want to prefix each
1341  * line with the prefix or else the data will get corrupted. */
1342  save_prefix = s->prefix;
1343  s->prefix = NULL;
1344 
1345  decode = 1;
1346  }
1347  else
1348  b->type = TYPE_TEXT;
1349 
1350  mutt_decode_attachment(b, s);
1351 
1352  if (decode)
1353  {
1354  b->length = ftello(s->fp_out);
1355  b->offset = 0;
1356 #ifdef USE_FMEMOPEN
1357  /* When running under torify, mutt_file_fclose(&s->fp_out) does not seem to
1358  * update tempsize. On the other hand, fflush does. See
1359  * https://github.com/neomutt/neomutt/issues/440 */
1360  fflush(s->fp_out);
1361 #endif
1362  mutt_file_fclose(&s->fp_out);
1363 
1364  /* restore final destination and substitute the tempfile for input */
1365  s->fp_out = fp;
1366  fp = s->fp_in;
1367 #ifdef USE_FMEMOPEN
1368  if (tempsize)
1369  {
1370  s->fp_in = fmemopen(temp, tempsize, "r");
1371  }
1372  else
1373  { /* fmemopen can't handle zero-length buffers */
1374  s->fp_in = mutt_file_fopen("/dev/null", "r");
1375  }
1376  if (!s->fp_in)
1377  {
1378  mutt_perror(_("failed to re-open 'memory stream'"));
1379  FREE(&temp);
1380  return -1;
1381  }
1382 #else
1383  s->fp_in = fopen(mutt_b2s(tempfile), "r");
1384  unlink(mutt_b2s(tempfile));
1385  mutt_buffer_pool_release(&tempfile);
1386 #endif
1387  /* restore the prefix */
1388  s->prefix = save_prefix;
1389  }
1390 
1391  b->type = orig_type;
1392  }
1393 
1394  /* process the (decoded) body part */
1395  if (handler)
1396  {
1397  rc = handler(b, s);
1398  if (rc != 0)
1399  {
1400  mutt_debug(LL_DEBUG1, "Failed on attachment of type %s/%s\n", TYPE(b),
1401  NONULL(b->subtype));
1402  }
1403 
1404  if (decode)
1405  {
1406  b->length = tmplength;
1407  b->offset = tmpoffset;
1408 
1409  /* restore the original source stream */
1410  mutt_file_fclose(&s->fp_in);
1411  s->fp_in = fp;
1412  }
1413  }
1414  s->flags |= MUTT_FIRSTDONE;
1415 #ifdef USE_FMEMOPEN
1416  FREE(&temp);
1417 #endif
1418 
1419  return rc;
1420 }
1421 
1425 static int valid_pgp_encrypted_handler(struct Body *b, struct State *s)
1426 {
1427  struct Body *octetstream = b->parts->next;
1428 
1429  /* clear out any mime headers before the handler, so they can't be spoofed. */
1431  mutt_env_free(&octetstream->mime_headers);
1432 
1433  int rc;
1434  /* Some clients improperly encode the octetstream part. */
1435  if (octetstream->encoding != ENC_7BIT)
1436  rc = run_decode_and_handler(octetstream, s, crypt_pgp_encrypted_handler, 0);
1437  else
1438  rc = crypt_pgp_encrypted_handler(octetstream, s);
1439  b->goodsig |= octetstream->goodsig;
1440 
1441  /* Relocate protected headers onto the multipart/encrypted part */
1442  if (!rc && octetstream->mime_headers)
1443  {
1444  b->mime_headers = octetstream->mime_headers;
1445  octetstream->mime_headers = NULL;
1446  }
1447 
1448  return rc;
1449 }
1450 
1454 static int malformed_pgp_encrypted_handler(struct Body *b, struct State *s)
1455 {
1456  struct Body *octetstream = b->parts->next->next;
1457 
1458  /* clear out any mime headers before the handler, so they can't be spoofed. */
1460  mutt_env_free(&octetstream->mime_headers);
1461 
1462  /* exchange encodes the octet-stream, so re-run it through the decoder */
1463  int rc = run_decode_and_handler(octetstream, s, crypt_pgp_encrypted_handler, false);
1464  b->goodsig |= octetstream->goodsig;
1465 #ifdef USE_AUTOCRYPT
1466  b->is_autocrypt |= octetstream->is_autocrypt;
1467 #endif
1468 
1469  /* Relocate protected headers onto the multipart/encrypted part */
1470  if (!rc && octetstream->mime_headers)
1471  {
1472  b->mime_headers = octetstream->mime_headers;
1473  octetstream->mime_headers = NULL;
1474  }
1475 
1476  return rc;
1477 }
1478 
1486 void mutt_decode_base64(struct State *s, size_t len, bool istext, iconv_t cd)
1487 {
1488  char buf[5];
1489  int ch, i;
1490  bool cr = false;
1491  char bufi[BUFI_SIZE];
1492  size_t l = 0;
1493 
1494  buf[4] = '\0';
1495 
1496  if (istext)
1497  state_set_prefix(s);
1498 
1499  while (len > 0)
1500  {
1501  for (i = 0; (i < 4) && (len > 0); len--)
1502  {
1503  ch = fgetc(s->fp_in);
1504  if (ch == EOF)
1505  break;
1506  if ((ch >= 0) && (ch < 128) && ((base64val(ch) != -1) || (ch == '=')))
1507  buf[i++] = ch;
1508  }
1509  if (i != 4)
1510  {
1511  /* "i" may be zero if there is trailing whitespace, which is not an error */
1512  if (i != 0)
1513  mutt_debug(LL_DEBUG2, "didn't get a multiple of 4 chars\n");
1514  break;
1515  }
1516 
1517  const int c1 = base64val(buf[0]);
1518  const int c2 = base64val(buf[1]);
1519 
1520  /* first char */
1521  ch = (c1 << 2) | (c2 >> 4);
1522 
1523  if (cr && (ch != '\n'))
1524  bufi[l++] = '\r';
1525 
1526  cr = false;
1527 
1528  if (istext && (ch == '\r'))
1529  cr = true;
1530  else
1531  bufi[l++] = ch;
1532 
1533  /* second char */
1534  if (buf[2] == '=')
1535  break;
1536  const int c3 = base64val(buf[2]);
1537  ch = ((c2 & 0xf) << 4) | (c3 >> 2);
1538 
1539  if (cr && (ch != '\n'))
1540  bufi[l++] = '\r';
1541 
1542  cr = false;
1543 
1544  if (istext && (ch == '\r'))
1545  cr = true;
1546  else
1547  bufi[l++] = ch;
1548 
1549  /* third char */
1550  if (buf[3] == '=')
1551  break;
1552  const int c4 = base64val(buf[3]);
1553  ch = ((c3 & 0x3) << 6) | c4;
1554 
1555  if (cr && (ch != '\n'))
1556  bufi[l++] = '\r';
1557 
1558  cr = false;
1559 
1560  if (istext && (ch == '\r'))
1561  cr = true;
1562  else
1563  bufi[l++] = ch;
1564 
1565  if ((l + 8) >= sizeof(bufi))
1566  convert_to_state(cd, bufi, &l, s);
1567  }
1568 
1569  if (cr)
1570  bufi[l++] = '\r';
1571 
1572  convert_to_state(cd, bufi, &l, s);
1573  convert_to_state(cd, 0, 0, s);
1574 
1575  state_reset_prefix(s);
1576 }
1577 
1585 int mutt_body_handler(struct Body *b, struct State *s)
1586 {
1587  if (!b || !s)
1588  return -1;
1589 
1590  bool plaintext = false;
1591  handler_t handler = NULL;
1592  handler_t encrypted_handler = NULL;
1593  int rc = 0;
1594  static unsigned short recurse_level = 0;
1595 
1596  int oflags = s->flags;
1597 
1598  if (recurse_level >= 100)
1599  {
1600  mutt_debug(LL_DEBUG1, "recurse level too deep. giving up.\n");
1601  return -1;
1602  }
1603  recurse_level++;
1604 
1605  /* first determine which handler to use to process this part */
1606 
1607  if (is_autoview(b))
1608  {
1609  handler = autoview_handler;
1610  s->flags &= ~MUTT_CHARCONV;
1611  }
1612  else if (b->type == TYPE_TEXT)
1613  {
1614  if (mutt_istr_equal("plain", b->subtype))
1615  {
1616  /* avoid copying this part twice since removing the transfer-encoding is
1617  * the only operation needed. */
1618  if (((WithCrypto & APPLICATION_PGP) != 0) && mutt_is_application_pgp(b))
1619  {
1620  encrypted_handler = crypt_pgp_application_handler;
1621  handler = encrypted_handler;
1622  }
1623  else if (C_ReflowText && mutt_istr_equal("flowed", mutt_param_get(&b->parameter, "format")))
1624  {
1625  handler = rfc3676_handler;
1626  }
1627  else
1628  {
1629  handler = text_plain_handler;
1630  }
1631  }
1632  else if (mutt_istr_equal("enriched", b->subtype))
1633  handler = text_enriched_handler;
1634  else /* text body type without a handler */
1635  plaintext = false;
1636  }
1637  else if (b->type == TYPE_MESSAGE)
1638  {
1639  if (mutt_is_message_type(b->type, b->subtype))
1640  handler = message_handler;
1641  else if (mutt_istr_equal("delivery-status", b->subtype))
1642  plaintext = true;
1643  else if (mutt_istr_equal("external-body", b->subtype))
1644  handler = external_body_handler;
1645  }
1646  else if (b->type == TYPE_MULTIPART)
1647  {
1648  if (!mutt_str_equal("inline", C_ShowMultipartAlternative) &&
1649  mutt_istr_equal("alternative", b->subtype))
1650  {
1651  handler = alternative_handler;
1652  }
1653  else if (!mutt_str_equal("inline", C_ShowMultipartAlternative) &&
1654  mutt_istr_equal("multilingual", b->subtype))
1655  {
1656  handler = multilingual_handler;
1657  }
1658  else if ((WithCrypto != 0) && mutt_istr_equal("signed", b->subtype))
1659  {
1660  if (!mutt_param_get(&b->parameter, "protocol"))
1661  mutt_error(_("Error: multipart/signed has no protocol"));
1662  else if (s->flags & MUTT_VERIFY)
1663  handler = mutt_signed_handler;
1664  }
1666  {
1667  encrypted_handler = valid_pgp_encrypted_handler;
1668  handler = encrypted_handler;
1669  }
1671  {
1672  encrypted_handler = malformed_pgp_encrypted_handler;
1673  handler = encrypted_handler;
1674  }
1675 
1676  if (!handler)
1677  handler = multipart_handler;
1678 
1679  if ((b->encoding != ENC_7BIT) && (b->encoding != ENC_8BIT) && (b->encoding != ENC_BINARY))
1680  {
1681  mutt_debug(LL_DEBUG1, "Bad encoding type %d for multipart entity, assuming 7 bit\n",
1682  b->encoding);
1683  b->encoding = ENC_7BIT;
1684  }
1685  }
1686  else if ((WithCrypto != 0) && (b->type == TYPE_APPLICATION))
1687  {
1688  if (OptDontHandlePgpKeys && mutt_istr_equal("pgp-keys", b->subtype))
1689  {
1690  /* pass raw part through for key extraction */
1691  plaintext = true;
1692  }
1693  else if (((WithCrypto & APPLICATION_PGP) != 0) && mutt_is_application_pgp(b))
1694  {
1695  encrypted_handler = crypt_pgp_application_handler;
1696  handler = encrypted_handler;
1697  }
1698  else if (((WithCrypto & APPLICATION_SMIME) != 0) && mutt_is_application_smime(b))
1699  {
1700  encrypted_handler = crypt_smime_application_handler;
1701  handler = encrypted_handler;
1702  }
1703  }
1704 
1705  /* only respect disposition == attachment if we're not
1706  * displaying from the attachment menu (i.e. pager) */
1707  if ((!C_HonorDisposition || ((b->disposition != DISP_ATTACH) || OptViewAttach)) &&
1708  (plaintext || handler))
1709  {
1710  /* Prevent encrypted attachments from being included in replies
1711  * unless $include_encrypted is set. */
1712  if ((s->flags & MUTT_REPLYING) && (s->flags & MUTT_FIRSTDONE) &&
1713  encrypted_handler && !C_IncludeEncrypted)
1714  {
1715  goto cleanup;
1716  }
1717 
1718  rc = run_decode_and_handler(b, s, handler, plaintext);
1719  }
1720  /* print hint to use attachment menu for disposition == attachment
1721  * if we're not already being called from there */
1722  else if (s->flags & MUTT_DISPLAY)
1723  {
1724  struct Buffer msg = mutt_buffer_make(256);
1725 
1726  if (!OptViewAttach)
1727  {
1728  char keystroke[128] = { 0 };
1729  if (km_expand_key(keystroke, sizeof(keystroke),
1730  km_find_func(MENU_PAGER, OP_VIEW_ATTACHMENTS)))
1731  {
1733  {
1734  /* L10N: %s expands to a keystroke/key binding, e.g. 'v'. */
1735  mutt_buffer_printf(&msg, _("[-- This is an attachment (use '%s' to view this part) --]\n"),
1736  keystroke);
1737  }
1738  else
1739  {
1740  /* L10N: %s/%s is a MIME type, e.g. "text/plain".
1741  The last %s expands to a keystroke/key binding, e.g. 'v'. */
1742  mutt_buffer_printf(&msg, _("[-- %s/%s is unsupported (use '%s' to view this part) --]\n"),
1743  TYPE(b), b->subtype, keystroke);
1744  }
1745  }
1746  else
1747  {
1749  {
1750  mutt_buffer_strcpy(&msg, _("[-- This is an attachment (need "
1751  "'view-attachments' bound to key) --]\n"));
1752  }
1753  else
1754  {
1755  /* L10N: %s/%s is a MIME type, e.g. "text/plain". */
1756  mutt_buffer_printf(&msg, _("[-- %s/%s is unsupported (need 'view-attachments' bound to key) --]\n"),
1757  TYPE(b), b->subtype);
1758  }
1759  }
1760  }
1761  else
1762  {
1764  {
1765  mutt_buffer_strcpy(&msg, _("[-- This is an attachment --]\n"));
1766  }
1767  else
1768  {
1769  /* L10N: %s/%s is a MIME type, e.g. "text/plain". */
1770  mutt_buffer_printf(&msg, _("[-- %s/%s is unsupported --]\n"), TYPE(b), b->subtype);
1771  }
1772  }
1773  state_mark_attach(s);
1774  state_printf(s, "%s", mutt_b2s(&msg));
1775  mutt_buffer_dealloc(&msg);
1776  }
1777 
1778 cleanup:
1779  recurse_level--;
1780  s->flags = oflags | (s->flags & MUTT_FIRSTDONE);
1781  if (rc != 0)
1782  {
1783  mutt_debug(LL_DEBUG1, "Bailing on attachment of type %s/%s\n", TYPE(b),
1784  NONULL(b->subtype));
1785  }
1786 
1787  return rc;
1788 }
1789 
1795 bool mutt_can_decode(struct Body *a)
1796 {
1797  if (is_autoview(a))
1798  return true;
1799  if (a->type == TYPE_TEXT)
1800  return true;
1801  if (a->type == TYPE_MESSAGE)
1802  return true;
1803  if (a->type == TYPE_MULTIPART)
1804  {
1805  if (WithCrypto)
1806  {
1807  if (mutt_istr_equal(a->subtype, "signed") || mutt_istr_equal(a->subtype, "encrypted"))
1808  {
1809  return true;
1810  }
1811  }
1812 
1813  for (struct Body *b = a->parts; b; b = b->next)
1814  {
1815  if (mutt_can_decode(b))
1816  return true;
1817  }
1818  }
1819  else if ((WithCrypto != 0) && (a->type == TYPE_APPLICATION))
1820  {
1821  if (((WithCrypto & APPLICATION_PGP) != 0) && mutt_is_application_pgp(a))
1822  return true;
1824  return true;
1825  }
1826 
1827  return false;
1828 }
1829 
1835 void mutt_decode_attachment(struct Body *b, struct State *s)
1836 {
1837  int istext = mutt_is_text_part(b) && (b->disposition == DISP_INLINE);
1838  iconv_t cd = (iconv_t)(-1);
1839 
1840  if (istext && (b->charset || (s->flags & MUTT_CHARCONV)))
1841  {
1842  const char *charset = b->charset;
1843  if (!charset)
1844  {
1845  charset = mutt_param_get(&b->parameter, "charset");
1846  if (!charset && C_AssumedCharset)
1847  charset = mutt_ch_get_default_charset();
1848  }
1849  if (charset && C_Charset)
1851  }
1852 
1853  fseeko(s->fp_in, b->offset, SEEK_SET);
1854  switch (b->encoding)
1855  {
1856  case ENC_QUOTED_PRINTABLE:
1857  decode_quoted(s, b->length,
1858  istext || (((WithCrypto & APPLICATION_PGP) != 0) &&
1860  cd);
1861  break;
1862  case ENC_BASE64:
1863  mutt_decode_base64(s, b->length,
1864  istext || (((WithCrypto & APPLICATION_PGP) != 0) &&
1866  cd);
1867  break;
1868  case ENC_UUENCODED:
1869  decode_uuencoded(s, b->length,
1870  istext || (((WithCrypto & APPLICATION_PGP) != 0) &&
1872  cd);
1873  break;
1874  default:
1875  decode_xbit(s, b->length,
1876  istext || (((WithCrypto & APPLICATION_PGP) != 0) &&
1878  cd);
1879  break;
1880  }
1881 
1882  if (cd != (iconv_t)(-1))
1883  iconv_close(cd);
1884 }
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:876
time_t mutt_date_epoch(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:414
char * C_AssumedCharset
Config: If a message is missing a character set, assume this character set.
Definition: charset.c:52
A mailcap entry.
Definition: mailcap.h:38
void mutt_decode_attachment(struct Body *b, struct State *s)
Decode an email&#39;s attachment.
Definition: handler.c:1835
int state_printf(struct State *s, const char *fmt,...)
Write a formatted string to the State.
Definition: state.c:153
struct MailcapEntry * mailcap_entry_new(void)
Allocate memory for a new rfc1524 entry.
Definition: mailcap.c:427
#define mutt_buffer_mktemp(buf)
Definition: muttlib.h:77
#define MUTT_DISPLAY
Output is displayed to the user.
Definition: state.h:32
static void decode_xbit(struct State *s, long len, bool istext, iconv_t cd)
Decode xbit-encoded text.
Definition: handler.c:170
char * filename
when sending a message, this is the file to which this structure refers
Definition: body.h:46
Manage keymappings.
#define NONULL(x)
Definition: string2.h:37
struct Envelope * mime_headers
Memory hole protected headers.
Definition: body.h:63
pid_t filter_create(const char *cmd, FILE **fp_in, FILE **fp_out, FILE **fp_err)
Set up filter program.
Definition: filter.c:206
#define WithCrypto
Definition: lib.h:118
bool mutt_is_message_type(int type, const char *subtype)
Determine if a mime type matches a message or not.
Definition: parse.c:1361
#define state_puts(STATE, STR)
Definition: state.h:55
struct Buffer * mutt_buffer_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:101
static bool is_autoview(struct Body *b)
Should email body be filtered by mailcap.
Definition: handler.c:483
#define MIN(a, b)
Definition: memory.h:31
#define mutt_perror(...)
Definition: logging.h:85
static int alternative_handler(struct Body *a, struct State *s)
Handler for multipart alternative emails - Implements handler_t.
Definition: handler.c:923
static void decode_uuencoded(struct State *s, long len, bool istext, iconv_t cd)
Decode uuencoded text.
Definition: handler.c:371
#define MUTT_CHARCONV
Do character set conversions.
Definition: state.h:36
Structs that make up an email.
Rich text handler.
Mailcap autoview field.
Definition: mailcap.h:62
int(* handler_t)(struct Body *b, struct State *s)
Manage a PGP or S/MIME encrypted MIME part.
Definition: handler.c:86
int crypt_pgp_application_handler(struct Body *m, struct State *s)
Wrapper for CryptModuleSpecs::application_handler()
Definition: cryptglue.c:234
7-bit text
Definition: mime.h:49
#define mutt_message(...)
Definition: logging.h:83
int mutt_signed_handler(struct Body *a, struct State *s)
Verify a "multipart/signed" body - Implements handler_t.
Definition: crypt.c:1111
#define TXT_HTML
Definition: handler.c:75
static size_t plen
Length of cached packet.
Definition: pgppacket.c:39
NeoMutt Logging.
static void decode_quoted(struct State *s, long len, bool istext, iconv_t cd)
Decode an attachment encoded with quoted-printable.
Definition: handler.c:307
void mutt_buffer_pool_release(struct Buffer **pbuf)
Free a Buffer from the pool.
Definition: pool.c:112
void mutt_file_unlink(const char *s)
Delete a file, carefully.
Definition: file.c:195
struct Buffer mutt_buffer_make(size_t size)
Make a new buffer on the stack.
Definition: buffer.c:61
RFC1524 Mailcap routines.
String manipulation buffer.
Definition: buffer.h:33
char * prefix
String to add to the beginning of each output line.
Definition: state.h:48
#define state_reset_prefix(state)
Definition: state.h:54
int mutt_is_valid_multipart_pgp_encrypted(struct Body *b)
Is this a valid multi-part encrypted message?
Definition: crypt.c:469
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:375
LOFF_T offset
offset where the actual data begins
Definition: body.h:44
#define CH_FROM
Retain the "From " message separator?
Definition: copy.h:55
#define _(a)
Definition: message.h:28
bool is_autocrypt
Flag autocrypt-decrypted messages for replying.
Definition: body.h:79
char * mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, int flags)
Read a line from a file.
Definition: file.c:667
int text_enriched_handler(struct Body *a, struct State *s)
Handler for enriched text - Implements handler_t.
Definition: enriched.c:460
struct Body * mutt_parse_multipart(FILE *fp, const char *boundary, LOFF_T end_off, bool digest)
parse a multipart structure
Definition: parse.c:1441
struct Body * next
next attachment in the list
Definition: body.h:53
8-bit text
Definition: mime.h:50
FILE * fp_out
File to write to.
Definition: state.h:47
#define MUTT_PRINTING
Are we printing? - MUTT_DISPLAY "light".
Definition: state.h:37
String list.
Definition: slist.h:43
int mutt_buffer_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:160
time_t mutt_date_parse_date(const char *s, struct Tz *tz_out)
Parse a date string in RFC822 format.
Definition: date.c:443
All user-callable functions.
static unsigned char decode_byte(char ch)
Decode a uuencoded byte.
Definition: handler.c:357
int crypt_smime_application_handler(struct Body *m, struct State *s)
Wrapper for CryptModuleSpecs::application_handler()
Definition: cryptglue.c:444
Container for Accounts, Notifications.
Definition: neomutt.h:36
FILE * fp_in
File to read from.
Definition: state.h:46
void mutt_decode_base64(struct State *s, size_t len, bool istext, iconv_t cd)
Decode base64-encoded text.
Definition: handler.c:1486
The body of an email.
Definition: body.h:34
unsigned int disposition
content-disposition, ContentDisposition
Definition: body.h:67
Convenience wrapper for the config headers.
bool mailcap_lookup(struct Body *a, char *type, size_t typelen, struct MailcapEntry *entry, enum MailcapLookup opt)
Find given type in the list of mailcap files.
Definition: mailcap.c:465
int mailcap_expand_command(struct Body *a, const char *filename, const char *type, struct Buffer *command)
Expand expandos in a command.
Definition: mailcap.c:69
int cs_subset_str_string_get(const struct ConfigSubset *sub, const char *name, struct Buffer *result)
Get a config item as a string.
Definition: subset.c:357
const char * mutt_str_getenv(const char *name)
Get an environment variable.
Definition: string.c:996
void mailcap_expand_filename(const char *nametemplate, const char *oldfile, struct Buffer *newfile)
Expand a new filename from a template or existing filename.
Definition: mailcap.c:520
bool mutt_istrn_equal(const char *a, const char *b, size_t l)
Check for equality of two strings ignoring case (to a maximum), safely.
Definition: string.c:626
struct Body * mutt_body_new(void)
Create a new Body.
Definition: body.c:43
Some miscellaneous functions.
StateFlags flags
Flags, e.g. MUTT_DISPLAY.
Definition: state.h:49
static bool is_mmnoask(const char *buf)
Metamail compatibility: should the attachment be autoviewed?
Definition: handler.c:432
Pager pager (email viewer)
Definition: keymap.h:79
struct Body * mutt_rfc822_parse_message(FILE *fp, struct Body *parent)
parse a Message/RFC822 body
Definition: parse.c:1543
static void qp_decode_line(char *dest, char *src, size_t *l, int last)
Decode a line of quoted-printable text.
Definition: handler.c:239
Many unsorted constants and some structs.
Log at debug level 2.
Definition: logging.h:41
static int malformed_pgp_encrypted_handler(struct Body *b, struct State *s)
Handler for invalid pgp-encrypted emails - Implements handler_t.
Definition: handler.c:1454
struct ListHead head
Definition: slist.h:45
#define CH_WEED
Weed the headers?
Definition: copy.h:52
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:153
Convenience wrapper for the core headers.
#define TXT_PLAIN
Definition: handler.c:76
#define ENCODING(x)
Definition: mime.h:85
void mutt_buffer_dealloc(struct Buffer *buf)
Release the memory allocated by a buffer.
Definition: buffer.c:294
Content is attached.
Definition: mime.h:63
int crypt_pgp_encrypted_handler(struct Body *a, struct State *s)
Wrapper for CryptModuleSpecs::encrypted_handler()
Definition: cryptglue.c:247
WHERE bool OptDontHandlePgpKeys
(pseudo) used to extract PGP keys
Definition: options.h:36
void mutt_clear_error(void)
Clear the message line (bottom line of screen)
Definition: mutt_logging.c:113
unsigned int encoding
content-transfer-encoding, ContentEncoding
Definition: body.h:66
Base-64 encoded text.
Definition: mime.h:52
static int message_handler(struct Body *a, struct State *s)
Handler for message/rfc822 body parts - Implements handler_t.
Definition: handler.c:699
bool C_ReflowText
Config: Reformat paragraphs of &#39;format=flowed&#39; text.
Definition: handler.c:69
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition: string.c:888
char * command
Definition: mailcap.h:40
char * subtype
content-type subtype
Definition: body.h:37
struct Keymap * km_find_func(enum MenuType menu, int func)
Find a function&#39;s mapping in a Menu.
Definition: keymap.c:899
size_t mutt_ch_iconv(iconv_t cd, const char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft, const char **inrepls, const char *outrepl, int *iconverrno)
Change the encoding of a string.
Definition: charset.c:618
void mutt_file_sanitize_filename(char *path, bool slash)
Replace unsafe characters in a filename.
Definition: file.c:618
#define mutt_b2s(buf)
Definition: buffer.h:41
bool goodsig
Good cryptographic signature.
Definition: body.h:75
#define APPLICATION_SMIME
Use SMIME to encrypt/sign.
Definition: lib.h:93
#define BUFI_SIZE
Definition: handler.c:72
SecurityFlags mutt_is_application_pgp(struct Body *m)
Does the message use PGP?
Definition: crypt.c:550
#define TXT_ENRICHED
Definition: handler.c:77
#define APPLICATION_PGP
Use PGP to encrypt/sign.
Definition: lib.h:92
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:165
LOFF_T length
length (in bytes) of attachment
Definition: body.h:45
void mailcap_entry_free(struct MailcapEntry **ptr)
Deallocate an struct MailcapEntry.
Definition: mailcap.c:436
int km_expand_key(char *s, size_t len, struct Keymap *map)
Get the key string bound to a Keymap.
Definition: keymap.c:871
#define MUTT_VERIFY
Perform signature verification.
Definition: state.h:33
#define CH_REORDER
Re-order output of headers (specified by &#39;hdr_order&#39;)
Definition: copy.h:58
Type: &#39;text/*&#39;.
Definition: mime.h:38
static int valid_pgp_encrypted_handler(struct Body *b, struct State *s)
Handler for valid pgp-encrypted emails - Implements handler_t.
Definition: handler.c:1425
void mutt_str_pretty_size(char *buf, size_t buflen, size_t num)
Display an abbreviated size, like 3.4K.
Definition: muttlib.c:1659
void mutt_env_free(struct Envelope **ptr)
Free an Envelope.
Definition: envelope.c:96
bool mutt_can_decode(struct Body *a)
Will decoding the attachment produce any output.
Definition: handler.c:1795
static int run_decode_and_handler(struct Body *b, struct State *s, handler_t handler, bool plaintext)
Run an appropriate decoder for an email.
Definition: handler.c:1285
static void print_part_line(struct State *s, struct Body *b, int n)
Print a separator for the Mime part.
Definition: handler.c:94
#define CH_DECODE
Do RFC2047 header decoding.
Definition: copy.h:53
struct Body * parts
parts of a multipart or message/rfc822
Definition: body.h:54
#define CH_DISPLAY
Display result to user.
Definition: copy.h:69
iconv_t mutt_ch_iconv_open(const char *tocode, const char *fromcode, int flags)
Set up iconv for conversions.
Definition: charset.c:565
#define MUTT_WEED
Weed headers even when not in display mode.
Definition: state.h:35
char * charset
Send mode: charset of attached file as stored on disk.
Definition: body.h:49
RFC3676 Format Flowed routines.
API for encryption/signing of emails.
SecurityFlags mutt_is_malformed_multipart_pgp_encrypted(struct Body *b)
Check for malformed layout.
Definition: crypt.c:506
static int multilingual_handler(struct Body *a, struct State *s)
Handler for multi-lingual emails - Implements handler_t.
Definition: handler.c:1104
void mutt_body_free(struct Body **ptr)
Free a Body.
Definition: body.c:57
bool C_IncludeEncrypted
Config: Whether to include encrypted content when replying.
Definition: handler.c:66
SecurityFlags mutt_is_application_smime(struct Body *m)
Does the message use S/MIME?
Definition: crypt.c:608
Handling of email attachments.
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:177
int mutt_file_copy_bytes(FILE *fp_in, FILE *fp_out, size_t size)
Copy some content from one file to another.
Definition: file.c:241
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:349
#define hexval(ch)
Definition: mime.h:75
bool mutt_is_text_part(struct Body *b)
Is this part of an email in plain text?
Definition: muttlib.c:433
void state_mark_attach(struct State *s)
Write a unique marker around content.
Definition: state.c:41
static int qp_decode_triple(char *s, char *d)
Decode a quoted-printable triplet.
Definition: handler.c:215
size_t mutt_buffer_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:312
bool C_HonorDisposition
Config: Don&#39;t display MIME parts inline if they have a disposition of &#39;attachment&#39;.
Definition: handler.c:64
unsigned int type
content-type primary type, ContentType
Definition: body.h:65
Type: &#39;message/*&#39;.
Definition: mime.h:35
#define IS_SPACE(ch)
Definition: string2.h:38
char * nametemplate
Definition: mailcap.h:46
void state_attach_puts(struct State *s, const char *t)
Write a string to the state.
Definition: state.c:70
Keep track when processing files.
#define TYPE(body)
Definition: mime.h:83
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:636
#define state_set_prefix(state)
Definition: state.h:53
#define base64val(ch)
Definition: base64.h:30
char * data
String.
Definition: list.h:36
Type: &#39;multipart/*&#39;.
Definition: mime.h:37
Duplicate the structure of an entire email.
Log at debug level 1.
Definition: logging.h:40
int n
Definition: acutest.h:492
void mutt_check_lookup_list(struct Body *b, char *type, size_t len)
Update the mime type.
Definition: mutt_attach.c:332
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:721
static int autoview_handler(struct Body *a, struct State *s)
Handler for autoviewable attachments - Implements handler_t.
Definition: handler.c:528
int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
Copy the contents of one file into another.
Definition: file.c:271
pid_t filter_create_fd(const char *cmd, FILE **fp_in, FILE **fp_out, FILE **fp_err, int fdin, int fdout, int fderr)
Run a command on a pipe (optionally connect stdin/stdout)
Definition: filter.c:61
bool C_ImplicitAutoview
Config: Display MIME attachments inline if a &#39;copiousoutput&#39; mailcap entry exists.
Definition: handler.c:65
void state_prefix_put(struct State *s, const char *buf, size_t buflen)
Write a prefixed fixed-string to the State.
Definition: state.c:171
uint32_t CopyHeaderFlags
Flags for mutt_copy_header(), e.g. CH_UPDATE.
Definition: copy.h:49
#define mutt_error(...)
Definition: logging.h:84
Binary.
Definition: mime.h:53
int mutt_copy_hdr(FILE *fp_in, FILE *fp_out, LOFF_T off_start, LOFF_T off_end, CopyHeaderFlags chflags, const char *prefix, int wraplen)
Copy header from one file to another.
Definition: copy.c:77
Quoted-printable text.
Definition: mime.h:51
static void convert_to_state(iconv_t cd, char *bufi, size_t *l, struct State *s)
Convert text and write it to a file.
Definition: handler.c:121
#define FREE(x)
Definition: memory.h:40
char * mutt_param_get(const struct ParameterList *pl, const char *s)
Find a matching Parameter.
Definition: parameter.c:84
char * C_ShowMultipartAlternative
Config: How to display &#39;multipart/alternative&#39; MIME parts.
Definition: handler.c:70
#define MUTT_REPLYING
Are we replying?
Definition: state.h:38
char * mutt_ch_get_default_charset(void)
Get the default character set.
Definition: charset.c:440
bool C_Weed
Config: Filter headers when displaying/forwarding/printing/replying.
Definition: globals.c:40
char * language
content-language (RFC8255)
Definition: body.h:38
Keep track when processing files.
Definition: state.h:44
static int multipart_handler(struct Body *a, struct State *s)
Handler for multipart emails - Implements handler_t.
Definition: handler.c:1202
WHERE bool OptViewAttach
(pseudo) signals that we are viewing attachments
Definition: options.h:55
char * C_Charset
Config: Default character set for displaying text on screen.
Definition: charset.c:53
int mutt_body_handler(struct Body *b, struct State *s)
Handler for the Body of an email.
Definition: handler.c:1585
#define MUTT_FIRSTDONE
The first attachment has been done.
Definition: state.h:39
#define BUFO_SIZE
Definition: handler.c:73
Hundreds of global variables to back the user variables.
Handling of global boolean variables.
#define mutt_debug(LEVEL,...)
Definition: logging.h:81
#define state_putc(STATE, STR)
Definition: state.h:56
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
int rfc3676_handler(struct Body *a, struct State *s)
Body handler implementing RFC3676 for format=flowed - Implements handler_t.
Definition: rfc3676.c:316
long hdr_offset
Offset in stream where the headers begin.
Definition: body.h:42
static int external_body_handler(struct Body *b, struct State *s)
Handler for external-body emails - Implements handler_t.
Definition: handler.c:752
int const char int line
Definition: acutest.h:617
Convenience wrapper for the library headers.
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:588
static int text_plain_handler(struct Body *b, struct State *s)
Handler for plain text - Implements handler_t.
Definition: handler.c:673
#define MUTT_ICONV_HOOK_FROM
apply charset-hooks to fromcode
Definition: charset.h:72
A List node for strings.
Definition: list.h:34
int filter_wait(pid_t pid)
Wait for the exit of a process and return its status.
Definition: filter.c:217
Decide how to display email content.
Content is inline.
Definition: mime.h:62
struct ParameterList parameter
parameters of the content-type
Definition: body.h:39
#define CH_PREFIX
Quote header using C_IndentString string?
Definition: copy.h:56
struct Slist * C_PreferredLanguages
Config: Preferred languages for multilingual MIME.
Definition: handler.c:68
bool C_IncludeOnlyfirst
Config: Only include the first attachment when replying.
Definition: handler.c:67
Type: &#39;application/*&#39;.
Definition: mime.h:33
UUEncoded text.
Definition: mime.h:54
WHERE bool C_TextFlowed
Config: Generate &#39;format=flowed&#39; messages.
Definition: mutt_globals.h:168