workaround a stupid issue in how decoding is performed in mutt *sigh*
[apps/madmutt.git] / handler.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #include <lib-lib/lib-lib.h>
11
12 #include <lib-mime/mime.h>
13 #include <lib-mime/rfc3676.h>
14
15 #include <lib-ui/lib-ui.h>
16
17 #include <lib-sys/unix.h>
18
19 #include "mutt.h"
20 #include "recvattach.h"
21 #include "handler.h"
22 #include "keymap.h"
23 #include "copy.h"
24 #include "charset.h"
25 #include "crypt.h"
26 #include "state.h"
27 #include "attach.h"
28
29 typedef int handler_f (BODY *, STATE *);
30 typedef handler_f *handler_t;
31
32 static void mutt_decode_xbit (STATE * s, long len, int istext, iconv_t cd)
33 {
34   int c, ch;
35   char bufi[BUFI_SIZE];
36   ssize_t l = 0;
37
38   if (istext) {
39     state_set_prefix (s);
40
41     while ((c = fgetc (s->fpin)) != EOF && len--) {
42       if (c == '\r' && len) {
43         if ((ch = fgetc (s->fpin)) == '\n') {
44           c = ch;
45           len--;
46         }
47         else
48           ungetc (ch, s->fpin);
49       }
50
51       bufi[l++] = c;
52       if (l == sizeof (bufi))
53         mutt_convert_to_state (cd, bufi, &l, s);
54     }
55
56     mutt_convert_to_state (cd, bufi, &l, s);
57     mutt_convert_to_state (cd, 0, 0, s);
58
59     state_reset_prefix (s);
60   }
61   else
62     mutt_copy_bytes (s->fpin, s->fpout, len);
63 }
64
65 static int qp_decode_triple (char *s, char *d)
66 {
67   /* soft line break */
68   if (*s == '=' && !(*(s + 1)))
69     return 1;
70
71   /* quoted-printable triple */
72   if (*s == '=' &&
73       isxdigit ((unsigned char) *(s + 1)) &&
74       isxdigit ((unsigned char) *(s + 2))) {
75     *d = (hexval (*(s + 1)) << 4) | hexval (*(s + 2));
76     return 0;
77   }
78
79   /* something else */
80   return -1;
81 }
82
83 static void qp_decode_line (char *dest, char *src, ssize_t * l, int last)
84 {
85   char *d, *s;
86   char c;
87
88   int kind;
89   int soft = 0;
90
91   /* decode the line */
92
93   for (d = dest, s = src; *s;) {
94     switch ((kind = qp_decode_triple (s, &c))) {
95     case 0:
96       *d++ = c;
97       s += 3;
98       break;                    /* qp triple */
99     case -1:
100       *d++ = *s++;
101       break;                    /* single character */
102     case 1:
103       soft = 1;
104       s++;
105       break;                    /* soft line break */
106     }
107   }
108
109   if (!soft && last == '\n')
110     *d++ = '\n';
111
112   *d = '\0';
113   *l = d - dest;
114 }
115
116 /* 
117  * Decode an attachment encoded with quoted-printable.
118  * 
119  * Why doesn't this overflow any buffers?  First, it's guaranteed
120  * that the length of a line grows when you _en_-code it to
121  * quoted-printable.  That means that we always can store the
122  * result in a buffer of at most the _same_ size.
123  * 
124  * Now, we don't special-case if the line we read with fgets()
125  * isn't terminated.  We don't care about this, since STRING > 78,
126  * so corrupted input will just be corrupted a bit more.  That
127  * implies that STRING+1 bytes are always sufficient to store the
128  * result of qp_decode_line.
129  * 
130  * Finally, at soft line breaks, some part of a multibyte character
131  * may have been left over by mutt_convert_to_state().  This shouldn't
132  * be more than 6 characters, so STRING + 7 should be sufficient
133  * memory to store the decoded data.
134  * 
135  * Just to make sure that I didn't make some off-by-one error
136  * above, we just use STRING*2 for the target buffer's size.
137  * 
138  */
139
140 static void mutt_decode_quoted (STATE * s, long len, int istext, iconv_t cd)
141 {
142   char line[10 * STRING];
143   char decline[20 * STRING];
144   ssize_t l = 0;
145   ssize_t linelen;               /* number of input bytes in `line' */
146   ssize_t l3;
147
148   int last;                     /* store the last character in the input line */
149
150   if (istext)
151     state_set_prefix (s);
152
153   while (len > 0) {
154     last = 0;
155
156     /*
157      * It's ok to use a fixed size buffer for input, even if the line turns
158      * out to be longer than this.  Just process the line in chunks.  This
159      * really shouldn't happen according the MIME spec, since Q-P encoded
160      * lines are at most 76 characters, but we should be liberal about what
161      * we accept.
162      */
163     if (fgets (line, MIN ((ssize_t) sizeof (line), len + 1), s->fpin) == NULL)
164       break;
165
166     linelen = m_strlen(line);
167     len -= linelen;
168
169     /*
170      * inspect the last character we read so we can tell if we got the
171      * entire line.
172      */
173     last = linelen ? line[linelen - 1] : 0;
174
175     /* chop trailing whitespace if we got the full line */
176     if (last == '\n') {
177       while (linelen > 0 && ISSPACE (line[linelen - 1]))
178         linelen--;
179       line[linelen] = 0;
180     }
181
182     /* decode and do character set conversion */
183     qp_decode_line (decline + l, line, &l3, last);
184     l += l3;
185     assert (l < sizeof(decline));
186     mutt_convert_to_state (cd, decline, &l, s);
187   }
188
189   mutt_convert_to_state (cd, 0, 0, s);
190   state_reset_prefix (s);
191 }
192
193 static unsigned char decode_byte(int ch)
194 {
195     return ch == 96 ? 0 : ch - 32;
196 }
197
198 static void mutt_decode_uuencoded (STATE * s, long len, int istext, iconv_t cd)
199 {
200   char tmps[STRING];
201   char linelen, c, l, out;
202   char *pt;
203   char bufi[BUFI_SIZE];
204   ssize_t k = 0;
205
206   if (istext)
207     state_set_prefix (s);
208
209   while (len > 0) {
210     if ((fgets (tmps, sizeof (tmps), s->fpin)) == NULL)
211       return;
212     len -= m_strlen(tmps);
213     if ((!m_strncmp(tmps, "begin", 5)) && ISSPACE (tmps[5]))
214       break;
215   }
216   while (len > 0) {
217     if ((fgets (tmps, sizeof (tmps), s->fpin)) == NULL)
218       return;
219     len -= m_strlen(tmps);
220     if (!m_strncmp(tmps, "end", 3))
221       break;
222     pt = tmps;
223     linelen = decode_byte(*pt);
224     pt++;
225     for (c = 0; c < linelen;) {
226       for (l = 2; l <= 6; l += 2) {
227         out = decode_byte(*pt) << l;
228         pt++;
229         out |= (decode_byte(*pt) >> (6 - l));
230         bufi[k++] = out;
231         c++;
232         if (c == linelen)
233           break;
234       }
235       mutt_convert_to_state (cd, bufi, &k, s);
236       pt++;
237     }
238   }
239
240   mutt_convert_to_state (cd, bufi, &k, s);
241   mutt_convert_to_state (cd, 0, 0, s);
242
243   state_reset_prefix (s);
244 }
245
246 /* ----------------------------------------------------------------------------
247  * A (not so) minimal implementation of RFC1563.
248  */
249
250 #define IndentSize (4)
251
252 enum { RICH_PARAM = 0, RICH_BOLD, RICH_UNDERLINE, RICH_ITALIC, RICH_NOFILL,
253   RICH_INDENT, RICH_INDENT_RIGHT, RICH_EXCERPT, RICH_CENTER, RICH_FLUSHLEFT,
254   RICH_FLUSHRIGHT, RICH_COLOR, RICH_LAST_TAG
255 };
256
257 static struct {
258   const char *tag_name;
259   int index;
260 } EnrichedTags[] = {
261   {"param", RICH_PARAM},
262   {"bold", RICH_BOLD},
263   {"italic", RICH_ITALIC},
264   {"underline", RICH_UNDERLINE},
265   {"nofill", RICH_NOFILL},
266   {"excerpt", RICH_EXCERPT},
267   {"indent", RICH_INDENT},
268   {"indentright", RICH_INDENT_RIGHT},
269   {"center", RICH_CENTER},
270   {"flushleft", RICH_FLUSHLEFT},
271   {"flushright", RICH_FLUSHRIGHT},
272   {"flushboth", RICH_FLUSHLEFT},
273   {"color", RICH_COLOR},
274   {"x-color", RICH_COLOR},
275   {NULL, -1}
276 };
277
278 struct enriched_state {
279   char *buffer;
280   char *line;
281   char *param;
282   ssize_t buff_len;
283   ssize_t line_len;
284   ssize_t line_used;
285   ssize_t line_max;
286   ssize_t indent_len;
287   ssize_t word_len;
288   ssize_t buff_used;
289   ssize_t param_used;
290   ssize_t param_len;
291   int tag_level[RICH_LAST_TAG];
292   int WrapMargin;
293   STATE *s;
294 };
295
296 static void enriched_wrap (struct enriched_state *stte)
297 {
298   int x;
299   int extra;
300
301   if (stte->line_len) {
302     if (stte->tag_level[RICH_CENTER] || stte->tag_level[RICH_FLUSHRIGHT]) {
303       /* Strip trailing white space */
304       ssize_t y = stte->line_used - 1;
305
306       while (y && ISSPACE (stte->line[y])) {
307         stte->line[y] = '\0';
308         y--;
309         stte->line_used--;
310         stte->line_len--;
311       }
312       if (stte->tag_level[RICH_CENTER]) {
313         /* Strip leading whitespace */
314         y = 0;
315
316         while (stte->line[y] && ISSPACE (stte->line[y]))
317           y++;
318         if (y) {
319           ssize_t z;
320
321           for (z = y; z <= stte->line_used; z++) {
322             stte->line[z - y] = stte->line[z];
323           }
324
325           stte->line_len -= y;
326           stte->line_used -= y;
327         }
328       }
329     }
330
331     extra = stte->WrapMargin - stte->line_len - stte->indent_len -
332       (stte->tag_level[RICH_INDENT_RIGHT] * IndentSize);
333     if (extra > 0) {
334       if (stte->tag_level[RICH_CENTER]) {
335         x = extra / 2;
336         while (x) {
337           state_putc (' ', stte->s);
338           x--;
339         }
340       }
341       else if (stte->tag_level[RICH_FLUSHRIGHT]) {
342         x = extra - 1;
343         while (x) {
344           state_putc (' ', stte->s);
345           x--;
346         }
347       }
348     }
349     state_puts (stte->line, stte->s);
350   }
351
352   state_putc ('\n', stte->s);
353   stte->line[0] = '\0';
354   stte->line_len = 0;
355   stte->line_used = 0;
356   stte->indent_len = 0;
357   if (stte->s->prefix) {
358     state_puts (stte->s->prefix, stte->s);
359     stte->indent_len += m_strlen(stte->s->prefix);
360   }
361
362   if (stte->tag_level[RICH_EXCERPT]) {
363     x = stte->tag_level[RICH_EXCERPT];
364     while (x) {
365       if (stte->s->prefix) {
366         state_puts (stte->s->prefix, stte->s);
367         stte->indent_len += m_strlen(stte->s->prefix);
368       }
369       else {
370         state_puts ("> ", stte->s);
371         stte->indent_len += m_strlen("> ");
372       }
373       x--;
374     }
375   }
376   else
377     stte->indent_len = 0;
378   if (stte->tag_level[RICH_INDENT]) {
379     x = stte->tag_level[RICH_INDENT] * IndentSize;
380     stte->indent_len += x;
381     while (x) {
382       state_putc (' ', stte->s);
383       x--;
384     }
385   }
386 }
387
388 static void enriched_flush (struct enriched_state *stte, int wrap)
389 {
390   if (!stte->tag_level[RICH_NOFILL] && (stte->line_len + stte->word_len >
391                                         (stte->WrapMargin -
392                                          (stte->tag_level[RICH_INDENT_RIGHT] *
393                                           IndentSize) - stte->indent_len)))
394     enriched_wrap (stte);
395
396   if (stte->buff_used) {
397     stte->buffer[stte->buff_used] = '\0';
398     stte->line_used += stte->buff_used;
399     if (stte->line_used > stte->line_max) {
400       stte->line_max = stte->line_used;
401       p_realloc(&stte->line, stte->line_max + 1);
402     }
403     m_strcat(stte->line, stte->line_max + 1, stte->buffer);
404     stte->line_len += stte->word_len;
405     stte->word_len = 0;
406     stte->buff_used = 0;
407   }
408   if (wrap)
409     enriched_wrap (stte);
410 }
411
412
413 static void enriched_putc (int c, struct enriched_state *stte)
414 {
415   if (stte->tag_level[RICH_PARAM]) {
416     if (stte->tag_level[RICH_COLOR]) {
417       if (stte->param_used + 1 >= stte->param_len)
418         p_realloc(&stte->param, (stte->param_len += STRING));
419
420       stte->param[stte->param_used++] = c;
421     }
422     return;                     /* nothing to do */
423   }
424
425   /* see if more space is needed (plus extra for possible rich characters) */
426   if (stte->buff_len < stte->buff_used + 3) {
427     stte->buff_len += LONG_STRING;
428     p_realloc(&stte->buffer, stte->buff_len + 1);
429   }
430
431   if ((!stte->tag_level[RICH_NOFILL] && ISSPACE (c)) || c == '\0') {
432     if (c == '\t')
433       stte->word_len += 8 - (stte->line_len + stte->word_len) % 8;
434     else
435       stte->word_len++;
436
437     stte->buffer[stte->buff_used++] = c;
438     enriched_flush (stte, 0);
439   }
440   else {
441     if (stte->s->flags & M_DISPLAY) {
442       if (stte->tag_level[RICH_BOLD]) {
443         stte->buffer[stte->buff_used++] = c;
444         stte->buffer[stte->buff_used++] = '\010';
445         stte->buffer[stte->buff_used++] = c;
446       }
447       else if (stte->tag_level[RICH_UNDERLINE]) {
448
449         stte->buffer[stte->buff_used++] = '_';
450         stte->buffer[stte->buff_used++] = '\010';
451         stte->buffer[stte->buff_used++] = c;
452       }
453       else if (stte->tag_level[RICH_ITALIC]) {
454         stte->buffer[stte->buff_used++] = c;
455         stte->buffer[stte->buff_used++] = '\010';
456         stte->buffer[stte->buff_used++] = '_';
457       }
458       else {
459         stte->buffer[stte->buff_used++] = c;
460       }
461     } else {
462       stte->buffer[stte->buff_used++] = c;
463     }
464     stte->word_len++;
465   }
466 }
467
468 static void enriched_puts (const char *s, struct enriched_state *stte)
469 {
470   const char *p;
471
472   if (stte->buff_len < stte->buff_used + m_strlen(s)) {
473     stte->buff_len += LONG_STRING;
474     p_realloc(&stte->buffer, stte->buff_len + 1);
475   }
476
477   p = s;
478   while (*p) {
479     stte->buffer[stte->buff_used++] = *p++;
480   }
481 }
482
483 static void enriched_set_flags (const char *tag, struct enriched_state *stte)
484 {
485   const char *tagptr = tag;
486   int i, j;
487
488   if (*tagptr == '/')
489     tagptr++;
490
491   for (i = 0, j = -1; EnrichedTags[i].tag_name; i++)
492     if (ascii_strcasecmp (EnrichedTags[i].tag_name, tagptr) == 0) {
493       j = EnrichedTags[i].index;
494       break;
495     }
496
497   if (j != -1) {
498     if (j == RICH_CENTER || j == RICH_FLUSHLEFT || j == RICH_FLUSHRIGHT)
499       enriched_flush (stte, 1);
500
501     if (*tag == '/') {
502       if (stte->tag_level[j])   /* make sure not to go negative */
503         stte->tag_level[j]--;
504       if ((stte->s->flags & M_DISPLAY) && j == RICH_PARAM
505           && stte->tag_level[RICH_COLOR]) {
506         stte->param[stte->param_used] = '\0';
507         if (!ascii_strcasecmp (stte->param, "black")) {
508           enriched_puts ("\033[30m", stte);
509         }
510         else if (!ascii_strcasecmp (stte->param, "red")) {
511           enriched_puts ("\033[31m", stte);
512         }
513         else if (!ascii_strcasecmp (stte->param, "green")) {
514           enriched_puts ("\033[32m", stte);
515         }
516         else if (!ascii_strcasecmp (stte->param, "yellow")) {
517           enriched_puts ("\033[33m", stte);
518         }
519         else if (!ascii_strcasecmp (stte->param, "blue")) {
520           enriched_puts ("\033[34m", stte);
521         }
522         else if (!ascii_strcasecmp (stte->param, "magenta")) {
523           enriched_puts ("\033[35m", stte);
524         }
525         else if (!ascii_strcasecmp (stte->param, "cyan")) {
526           enriched_puts ("\033[36m", stte);
527         }
528         else if (!ascii_strcasecmp (stte->param, "white")) {
529           enriched_puts ("\033[37m", stte);
530         }
531       }
532       if ((stte->s->flags & M_DISPLAY) && j == RICH_COLOR) {
533         enriched_puts ("\033[0m", stte);
534       }
535
536       /* flush parameter buffer when closing the tag */
537       if (j == RICH_PARAM) {
538         stte->param_used = 0;
539         stte->param[0] = '\0';
540       }
541     }
542     else
543       stte->tag_level[j]++;
544
545     if (j == RICH_EXCERPT)
546       enriched_flush (stte, 1);
547   }
548 }
549
550 static int text_enriched_handler (BODY * a, STATE * s)
551 {
552   enum {
553     TEXT, LANGLE, TAG, BOGUS_TAG, NEWLINE, ST_EOF, DONE
554   } state = TEXT;
555
556   long bytes = a->length;
557   struct enriched_state stte;
558   int c = 0;
559   int tag_len = 0;
560   char tag[LONG_STRING + 1];
561
562   p_clear(&stte, 1);
563   stte.s = s;
564   stte.WrapMargin = ((s->flags & M_DISPLAY) ? (getmaxx(main_w) - 4) :
565                      ((getmaxx(main_w) - 4) < 72) ? (getmaxx(main_w) - 4) : 72);
566   stte.line_max = stte.WrapMargin * 4;
567   stte.line = p_new(char, stte.line_max + 1);
568   stte.param = p_new(char, STRING);
569
570   stte.param_len = STRING;
571   stte.param_used = 0;
572
573   if (s->prefix) {
574     state_puts (s->prefix, s);
575     stte.indent_len += m_strlen(s->prefix);
576   }
577
578   while (state != DONE) {
579     if (state != ST_EOF) {
580       if (!bytes || (c = fgetc (s->fpin)) == EOF)
581         state = ST_EOF;
582       else
583         bytes--;
584     }
585
586     switch (state) {
587     case TEXT:
588       switch (c) {
589       case '<':
590         state = LANGLE;
591         break;
592
593       case '\n':
594         if (stte.tag_level[RICH_NOFILL]) {
595           enriched_flush (&stte, 1);
596         }
597         else {
598           enriched_putc (' ', &stte);
599           state = NEWLINE;
600         }
601         break;
602
603       default:
604         enriched_putc (c, &stte);
605       }
606       break;
607
608     case LANGLE:
609       if (c == '<') {
610         enriched_putc (c, &stte);
611         state = TEXT;
612         break;
613       }
614       else {
615         tag_len = 0;
616         state = TAG;
617       }
618       /* Yes, fall through (it wasn't a <<, so this char is first in TAG) */
619     case TAG:
620       if (c == '>') {
621         tag[tag_len] = '\0';
622         enriched_set_flags (tag, &stte);
623         state = TEXT;
624       }
625       else if (tag_len < LONG_STRING)   /* ignore overly long tags */
626         tag[tag_len++] = c;
627       else
628         state = BOGUS_TAG;
629       break;
630
631     case BOGUS_TAG:
632       if (c == '>')
633         state = TEXT;
634       break;
635
636     case NEWLINE:
637       if (c == '\n')
638         enriched_flush (&stte, 1);
639       else {
640         ungetc (c, s->fpin);
641         bytes++;
642         state = TEXT;
643       }
644       break;
645
646     case ST_EOF:
647       enriched_putc ('\0', &stte);
648       enriched_flush (&stte, 1);
649       state = DONE;
650       break;
651
652     case DONE:                 /* not reached, but gcc complains if this is absent */
653       break;
654     }
655   }
656
657   state_putc ('\n', s);         /* add a final newline */
658
659   p_delete(&(stte.buffer));
660   p_delete(&(stte.line));
661   p_delete(&(stte.param));
662
663   return (0);
664 }
665
666 #define TXTHTML     1
667 #define TXTPLAIN    2
668 #define TXTENRICHED 3
669
670 static int alternative_handler (BODY * a, STATE * s)
671 {
672   BODY *choice = NULL;
673   BODY *b;
674   string_list_t *t;
675   char buf[STRING];
676   int type = 0;
677   int mustfree = 0;
678   int rc = 0;
679
680   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
681       a->encoding == ENCUUENCODED) {
682     struct stat st;
683
684     mustfree = 1;
685     fstat (fileno (s->fpin), &st);
686     b = body_new();
687     b->length = (long) st.st_size;
688     b->parts = mutt_parse_multipart(s->fpin,
689                                     parameter_getval(a->parameter, "boundary"),
690                                     (long)st.st_size,
691                                     mime_which_token(a->subtype, -1) == MIME_DIGEST);
692   }
693   else
694     b = a;
695
696   a = b;
697
698   /* First, search list of prefered types */
699   t = AlternativeOrderList;
700   while (t && !choice) {
701     char *c;
702     int btlen;                  /* length of basetype */
703     int wild;                   /* do we have a wildcard to match all subtypes? */
704
705     c = strchr (t->data, '/');
706     if (c) {
707       wild = (c[1] == '*' && c[2] == 0);
708       btlen = c - t->data;
709     }
710     else {
711       wild = 1;
712       btlen = m_strlen(t->data);
713     }
714
715     if (a && a->parts)
716       b = a->parts;
717     else
718       b = a;
719     while (b) {
720       const char *bt = TYPE (b);
721
722       if (!ascii_strncasecmp (bt, t->data, btlen) && bt[btlen] == 0) {
723         /* the basetype matches */
724         if (wild || !ascii_strcasecmp (t->data + btlen + 1, b->subtype)) {
725           choice = b;
726         }
727       }
728       b = b->next;
729     }
730     t = t->next;
731   }
732
733   /* Next, look for an autoviewable type */
734   if (!choice) {
735     if (a && a->parts)
736       b = a->parts;
737     else
738       b = a;
739     while (b) {
740       snprintf (buf, sizeof (buf), "%s/%s", TYPE (b), b->subtype);
741       if (mutt_is_autoview (b, buf)) {
742         rfc1524_entry *entry = rfc1524_entry_new();
743
744         if (rfc1524_mailcap_lookup (b, buf, entry, M_AUTOVIEW)) {
745           choice = b;
746         }
747         rfc1524_entry_delete(&entry);
748       }
749       b = b->next;
750     }
751   }
752
753   /* Then, look for a text entry */
754   if (!choice) {
755     if (a && a->parts)
756       b = a->parts;
757     else
758       b = a;
759     while (b) {
760       if (b->type == TYPETEXT) {
761         switch (mime_which_token(b->subtype, -1)) {
762           case MIME_PLAIN:
763             if (type <= TXTPLAIN) {
764               choice = b;
765               type = TXTPLAIN;
766             }
767             break;
768           case MIME_ENRICHED:
769             if (type <= TXTENRICHED) {
770               choice = b;
771               type = TXTENRICHED;
772             }
773             break;
774           case MIME_HTML:
775             if (type <= TXTHTML) {
776               choice = b;
777               type = TXTHTML;
778             }
779             break;
780           default:
781             break;
782         }
783       }
784       b = b->next;
785     }
786   }
787
788   /* Finally, look for other possibilities */
789   if (!choice) {
790     if (a && a->parts)
791       b = a->parts;
792     else
793       b = a;
794     while (b) {
795       if (mutt_can_decode (b))
796         choice = b;
797       b = b->next;
798     }
799   }
800
801   if (choice) {
802     if (s->flags & M_DISPLAY && !option (OPTWEED)) {
803       fseeko (s->fpin, choice->hdr_offset, 0);
804       mutt_copy_bytes (s->fpin, s->fpout,
805                        choice->offset - choice->hdr_offset);
806     }
807     mutt_body_handler (choice, s);
808   }
809   else if (s->flags & M_DISPLAY) {
810     /* didn't find anything that we could display! */
811     state_mark_attach (s);
812     state_puts (_("[-- Error:  Could not display any parts of Multipart/Alternative! --]\n"), s);
813     rc = -1;
814   }
815
816   if (mustfree)
817     body_list_wipe(&a);
818
819   return (rc);
820 }
821
822 /* handles message/rfc822 body parts */
823 static int message_handler (BODY * a, STATE * s)
824 {
825   struct stat st;
826   BODY *b;
827   off_t off_start;
828   int rc = 0;
829
830   off_start = ftello (s->fpin);
831   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
832       a->encoding == ENCUUENCODED) {
833     fstat (fileno (s->fpin), &st);
834     b = body_new();
835     b->length = (long) st.st_size;
836     b->parts = mutt_parse_messageRFC822 (s->fpin, b);
837   }
838   else
839     b = a;
840
841   if (b->parts) {
842     mutt_copy_hdr (s->fpin, s->fpout, off_start, b->parts->offset,
843                    (((s->flags & M_WEED)
844                      || ((s->flags & (M_DISPLAY | M_PRINTING))
845                          && option (OPTWEED))) ? (CH_WEED | CH_REORDER) : 0) |
846                    (s->prefix ? CH_PREFIX : 0) | CH_DECODE | CH_FROM,
847                    s->prefix);
848
849     if (s->prefix)
850       state_puts (s->prefix, s);
851     state_putc ('\n', s);
852
853     rc = mutt_body_handler (b->parts, s);
854   }
855
856   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
857       a->encoding == ENCUUENCODED)
858     body_list_wipe(&b);
859
860   return (rc);
861 }
862
863 /* returns 1 if decoding the attachment will produce output */
864 int mutt_can_decode (BODY * a)
865 {
866   char type[STRING];
867
868   snprintf (type, sizeof (type), "%s/%s", TYPE (a), a->subtype);
869   if (mutt_is_autoview (a, type))
870     return (rfc1524_mailcap_lookup (a, type, NULL, M_AUTOVIEW));
871   else if (a->type == TYPETEXT)
872     return (1);
873   else if (a->type == TYPEMESSAGE)
874     return (1);
875   else if (a->type == TYPEMULTIPART) {
876     BODY *p;
877     int tok = mime_which_token(a->subtype, -1);
878
879     if (tok == MIME_SIGNED || tok == MIME_ENCRYPTED)
880       return 1;
881
882     for (p = a->parts; p; p = p->next) {
883       if (mutt_can_decode (p))
884         return (1);
885     }
886   }
887   else if (a->type == TYPEAPPLICATION) {
888     if (mutt_is_application_pgp(a))
889       return (1);
890     if (mutt_is_application_smime (a))
891       return (1);
892   }
893
894   return (0);
895 }
896
897 static int multipart_handler (BODY * a, STATE * s)
898 {
899   BODY *b, *p;
900   char length[5];
901   struct stat st;
902   int count;
903   int rc = 0;
904
905   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
906       a->encoding == ENCUUENCODED) {
907     fstat (fileno (s->fpin), &st);
908     b = body_new();
909     b->length = (long) st.st_size;
910     b->parts = mutt_parse_multipart(s->fpin,
911                                     parameter_getval(a->parameter, "boundary"),
912                                     (long)st.st_size,
913                                     mime_which_token(a->subtype, -1) == MIME_DIGEST);
914   } else {
915     b = a;
916   }
917
918   for (p = b->parts, count = 1; p; p = p->next, count++) {
919     if (s->flags & M_DISPLAY) {
920       state_mark_attach (s);
921       state_printf (s, _("[-- Attachment #%d"), count);
922       if (p->description || p->filename || p->form_name) {
923         state_puts (": ", s);
924         state_puts (p->description ? p->description :
925                     p->filename ? p->filename : p->form_name, s);
926       }
927       state_puts (" --]\n", s);
928
929       mutt_pretty_size (length, sizeof (length), p->length);
930
931       state_mark_attach (s);
932       state_printf (s, _("[-- Type: %s/%s, Encoding: %s, Size: %s --]\n"),
933                     TYPE (p), p->subtype, ENCODING (p->encoding), length);
934       if (!option (OPTWEED)) {
935         fseeko (s->fpin, p->hdr_offset, 0);
936         mutt_copy_bytes (s->fpin, s->fpout, p->offset - p->hdr_offset);
937       }
938       else
939         state_putc ('\n', s);
940     }
941     else {
942       if (p->description && mutt_can_decode (p))
943         state_printf (s, "Content-Description: %s\n", p->description);
944
945       if (p->form_name)
946         state_printf (s, "%s: \n", p->form_name);
947
948     }
949     rc = mutt_body_handler (p, s);
950     state_putc ('\n', s);
951     if (rc)
952       break;
953   }
954
955   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
956       a->encoding == ENCUUENCODED)
957     body_list_wipe(&b);
958
959   return (rc);
960 }
961
962 static int autoview_handler (BODY * a, STATE * s)
963 {
964   rfc1524_entry *entry = rfc1524_entry_new();
965   char buffer[LONG_STRING];
966   char type[STRING];
967   char command[LONG_STRING];
968   char tempfile[_POSIX_PATH_MAX] = "";
969   char *fname;
970   FILE *fpin = NULL;
971   FILE *fpout = NULL;
972   FILE *fperr = NULL;
973   int piped = FALSE;
974   pid_t thepid;
975   int rc = 0;
976
977   snprintf (type, sizeof (type), "%s/%s", TYPE (a), a->subtype);
978   rfc1524_mailcap_lookup (a, type, entry, M_AUTOVIEW);
979
980   fname = m_strdup(a->filename);
981   mutt_sanitize_filename (fname, 1);
982   rfc1524_expand_filename (entry->nametemplate, fname, tempfile,
983                            sizeof (tempfile));
984   p_delete(&fname);
985
986   if (entry->command) {
987     m_strcpy(command, sizeof(command), entry->command);
988
989     /* rfc1524_expand_command returns 0 if the file is required */
990     piped =
991       rfc1524_expand_command (a, tempfile, type, command, sizeof (command));
992
993     if (s->flags & M_DISPLAY) {
994       state_mark_attach (s);
995       state_printf (s, _("[-- Autoview using %s --]\n"), command);
996       mutt_message (_("Invoking autoview command: %s"), command);
997     }
998
999     if ((fpin = safe_fopen (tempfile, "w+")) == NULL) {
1000       mutt_perror ("fopen");
1001       rfc1524_entry_delete(&entry);
1002       return (-1);
1003     }
1004
1005     mutt_copy_bytes (s->fpin, fpin, a->length);
1006
1007     if (!piped) {
1008       m_fclose(&fpin);
1009       thepid = mutt_create_filter (command, NULL, &fpout, &fperr);
1010     } else {
1011       unlink (tempfile);
1012       fflush (fpin);
1013       rewind (fpin);
1014       thepid = mutt_create_filter_fd (command, NULL, &fpout, &fperr,
1015                                       fileno (fpin), -1, -1);
1016     }
1017
1018     if (thepid < 0) {
1019       mutt_perror (_("Can't create filter"));
1020
1021       if (s->flags & M_DISPLAY) {
1022         state_mark_attach (s);
1023         state_printf (s, _("[-- Can't run %s. --]\n"), command);
1024       }
1025       rc = -1;
1026       goto bail;
1027     }
1028
1029     if (s->prefix) {
1030       while (fgets (buffer, sizeof (buffer), fpout) != NULL) {
1031         state_puts (s->prefix, s);
1032         state_puts (buffer, s);
1033       }
1034       /* check for data on stderr */
1035       if (fgets (buffer, sizeof (buffer), fperr)) {
1036         if (s->flags & M_DISPLAY) {
1037           state_mark_attach (s);
1038           state_printf (s, _("[-- Autoview stderr of %s --]\n"), command);
1039         }
1040
1041         state_puts (s->prefix, s);
1042         state_puts (buffer, s);
1043         while (fgets (buffer, sizeof (buffer), fperr) != NULL) {
1044           state_puts (s->prefix, s);
1045           state_puts (buffer, s);
1046         }
1047       }
1048     } else {
1049       mutt_copy_stream (fpout, s->fpout);
1050       /* Check for stderr messages */
1051       if (fgets (buffer, sizeof (buffer), fperr)) {
1052         if (s->flags & M_DISPLAY) {
1053           state_mark_attach (s);
1054           state_printf (s, _("[-- Autoview stderr of %s --]\n"), command);
1055         }
1056
1057         state_puts (buffer, s);
1058         mutt_copy_stream (fperr, s->fpout);
1059       }
1060     }
1061
1062   bail:
1063     m_fclose(&fpout);
1064     m_fclose(&fperr);
1065
1066     mutt_wait_filter (thepid);
1067     if (piped)
1068       m_fclose(&fpin);
1069     else
1070       mutt_unlink (tempfile);
1071
1072     if (s->flags & M_DISPLAY)
1073       mutt_clear_error ();
1074   }
1075   rfc1524_entry_delete(&entry);
1076   return (rc);
1077 }
1078
1079 static int external_body_handler (BODY * b, STATE * s)
1080 {
1081   const char *access_type;
1082   const char *expiration;
1083   time_t expire;
1084
1085   access_type = parameter_getval(b->parameter, "access-type");
1086   if (!access_type) {
1087     if (s->flags & M_DISPLAY) {
1088       state_mark_attach (s);
1089       state_puts (_("[-- Error: message/external-body has no access-type parameter --]\n"), s);
1090     }
1091     return (-1);
1092   }
1093
1094   expiration = parameter_getval(b->parameter, "expiration");
1095   if (expiration)
1096     expire = mutt_parse_date (expiration, NULL);
1097   else
1098     expire = -1;
1099
1100   if (mime_which_token(access_type, -1) == MIME_X_MUTT_DELETED) {
1101     if (s->flags & (M_DISPLAY | M_PRINTING)) {
1102       char *length;
1103       char pretty_size[10];
1104
1105       state_mark_attach (s);
1106       state_printf (s, _("[-- This %s/%s attachment "),
1107                     TYPE (b->parts), b->parts->subtype);
1108       length = parameter_getval(b->parameter, "length");
1109       if (length) {
1110         mutt_pretty_size (pretty_size, sizeof (pretty_size),
1111                           strtol (length, NULL, 10));
1112         state_printf (s, _("(size %s bytes) "), pretty_size);
1113       }
1114       state_puts (_("has been deleted --]\n"), s);
1115
1116       if (expire != -1) {
1117         state_mark_attach (s);
1118         state_printf (s, _("[-- on %s --]\n"), expiration);
1119       }
1120       if (b->parts->filename) {
1121         state_mark_attach (s);
1122         state_printf (s, _("[-- name: %s --]\n"), b->parts->filename);
1123       }
1124
1125       mutt_copy_hdr (s->fpin, s->fpout, ftello (s->fpin), b->parts->offset,
1126                      (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) |
1127                      CH_DECODE, NULL);
1128     }
1129   }
1130   else if (expiration && expire < time (NULL)) {
1131     if (s->flags & M_DISPLAY) {
1132       state_mark_attach (s);
1133       state_printf (s, _("[-- This %s/%s attachment is not included, --]\n"),
1134                     TYPE (b->parts), b->parts->subtype);
1135       state_attach_puts (_("[-- and the indicated external source has --]\n"
1136                            "[-- expired. --]\n"), s);
1137
1138       mutt_copy_hdr (s->fpin, s->fpout, ftello (s->fpin), b->parts->offset,
1139                      (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) |
1140                      CH_DECODE, NULL);
1141     }
1142   } else {
1143     if (s->flags & M_DISPLAY) {
1144       state_mark_attach (s);
1145       state_printf (s,
1146                     _("[-- This %s/%s attachment is not included, --]\n"),
1147                     TYPE (b->parts), b->parts->subtype);
1148       state_mark_attach (s);
1149       state_printf (s,
1150                     _
1151                     ("[-- and the indicated access-type %s is unsupported --]\n"),
1152                     access_type);
1153       mutt_copy_hdr (s->fpin, s->fpout, ftello (s->fpin), b->parts->offset,
1154                      (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) |
1155                      CH_DECODE, NULL);
1156     }
1157   }
1158   return (0);
1159 }
1160
1161 static void mutt_decode_base64(STATE *s, long len, int istext, iconv_t cd)
1162 {
1163   char buf[5];
1164   int c1, c2, c3, c4, ch, cr = 0, i;
1165   char bufi[BUFI_SIZE];
1166   ssize_t l = 0;
1167
1168   buf[4] = 0;
1169
1170   if (istext)
1171     state_set_prefix (s);
1172
1173   while (len > 0) {
1174     for (i = 0; i < 4 && len > 0; len--) {
1175       if ((ch = fgetc (s->fpin)) == EOF)
1176         break;
1177       if (base64val(ch) >= 0 || ch == '=')
1178         buf[i++] = ch;
1179     }
1180     if (i != 4) {
1181       break;
1182     }
1183
1184     c1 = base64val(buf[0]);
1185     c2 = base64val(buf[1]);
1186     ch = (c1 << 2) | (c2 >> 4);
1187
1188     if (cr && ch != '\n')
1189       bufi[l++] = '\r';
1190
1191     cr = 0;
1192
1193     if (istext && ch == '\r')
1194       cr = 1;
1195     else
1196       bufi[l++] = ch;
1197
1198     if (buf[2] == '=')
1199       break;
1200     c3 = base64val(buf[2]);
1201     ch = ((c2 & 0xf) << 4) | (c3 >> 2);
1202
1203     if (cr && ch != '\n')
1204       bufi[l++] = '\r';
1205
1206     cr = 0;
1207
1208     if (istext && ch == '\r')
1209       cr = 1;
1210     else
1211       bufi[l++] = ch;
1212
1213     if (buf[3] == '=')
1214       break;
1215     c4 = base64val(buf[3]);
1216     ch = ((c3 & 0x3) << 6) | c4;
1217
1218     if (cr && ch != '\n')
1219       bufi[l++] = '\r';
1220     cr = 0;
1221
1222     if (istext && ch == '\r')
1223       cr = 1;
1224     else
1225       bufi[l++] = ch;
1226
1227     if (l + 8 >= ssizeof (bufi))
1228       mutt_convert_to_state (cd, bufi, &l, s);
1229   }
1230
1231   if (cr)
1232     bufi[l++] = '\r';
1233
1234   mutt_convert_to_state (cd, bufi, &l, s);
1235   mutt_convert_to_state (cd, 0, 0, s);
1236
1237   state_reset_prefix (s);
1238 }
1239
1240 void mutt_decode_attachment (BODY * b, STATE * s)
1241 {
1242   int istext = mutt_is_text_part (b);
1243   iconv_t cd = MUTT_ICONV_ERROR;
1244
1245   Quotebuf[0] = '\0';
1246
1247   if (istext) {
1248     if (s->flags & M_CHARCONV) {
1249       const char *charset = parameter_getval(b->parameter, "charset");
1250
1251       if (!charset)
1252         charset = charset_getfirst(mod_cset.assumed_charset);
1253       if (charset && mod_cset.charset)
1254         cd = mutt_iconv_open (mod_cset.charset, charset, M_ICONV_HOOK_FROM);
1255     } else {
1256       if (b->file_charset)
1257         cd = mutt_iconv_open (mod_cset.charset, b->file_charset, M_ICONV_HOOK_FROM);
1258     }
1259   }
1260
1261   fseeko (s->fpin, b->offset, 0);
1262   switch (b->encoding) {
1263   case ENCQUOTEDPRINTABLE:
1264     mutt_decode_quoted(s, b->length,
1265                        istext || mutt_is_application_pgp(b), cd);
1266     break;
1267   case ENCBASE64:
1268     mutt_decode_base64(s, b->length,
1269                        istext || mutt_is_application_pgp(b), cd);
1270     break;
1271   case ENCUUENCODED:
1272     mutt_decode_uuencoded(s, b->length,
1273                           istext || mutt_is_application_pgp(b), cd);
1274     break;
1275   default:
1276     mutt_decode_xbit(s, b->length,
1277                      istext || mutt_is_application_pgp(b), cd);
1278     break;
1279   }
1280
1281   if (cd != MUTT_ICONV_ERROR)
1282     iconv_close (cd);
1283 }
1284
1285 int mutt_body_handler (BODY * b, STATE * s)
1286 {
1287   int decode = 0;
1288   int plaintext = 0;
1289   FILE *fp = NULL;
1290   char tempfile[_POSIX_PATH_MAX];
1291   handler_t handler = NULL;
1292   long tmpoffset = 0;
1293   ssize_t tmplength = 0;
1294   char type[STRING];
1295   int rc = 0;
1296
1297   int oflags = s->flags;
1298   int tok = mime_which_token(b->subtype, -1);
1299
1300   /* first determine which handler to use to process this part */
1301
1302   snprintf (type, sizeof (type), "%s/%s", TYPE (b), b->subtype);
1303   if (mutt_is_autoview (b, type)) {
1304     rfc1524_entry *entry = rfc1524_entry_new();
1305
1306     if (rfc1524_mailcap_lookup (b, type, entry, M_AUTOVIEW)) {
1307       handler = autoview_handler;
1308       s->flags &= ~M_CHARCONV;
1309     }
1310     rfc1524_entry_delete(&entry);
1311   }
1312   else if (b->type == TYPETEXT) {
1313     if (tok == MIME_PLAIN) {
1314       /* avoid copying this part twice since removing the transfer-encoding is
1315        * the only operation needed.
1316        */
1317       if (mutt_is_application_pgp (b))
1318         handler = crypt_pgp_application_pgp_handler;
1319       else
1320         if (!ascii_strcasecmp("flowed", parameter_getval(b->parameter, "format")))
1321           handler = rfc3676_handler;
1322       else
1323         plaintext = 1;
1324     }
1325     else if (tok == MIME_ENRICHED)
1326       handler = text_enriched_handler;
1327     else                        /* text body type without a handler */
1328       plaintext = 1;
1329   }
1330   else if (b->type == TYPEMESSAGE) {
1331     if (mutt_is_message_type (b))
1332       handler = message_handler;
1333     else if (tok == MIME_DELIVERY_STATUS)
1334       plaintext = 1;
1335     else if (tok == MIME_EXTERNAL_BODY)
1336       handler = external_body_handler;
1337   }
1338   else if (b->type == TYPEMULTIPART) {
1339     char *p;
1340
1341     if (tok == MIME_ALTERNATIVE)
1342       handler = alternative_handler;
1343     else if (tok == MIME_SIGNED) {
1344       p = parameter_getval(b->parameter, "protocol");
1345
1346       if (!p)
1347         mutt_error (_("Error: multipart/signed has no protocol."));
1348
1349       else if (s->flags & M_VERIFY)
1350         handler = mutt_signed_handler;
1351     }
1352     else if (tok == MIME_ENCRYPTED) {
1353       p = parameter_getval(b->parameter, "protocol");
1354
1355       if (!p)
1356         mutt_error(_("Error: multipart/encrypted has no protocol parameter!"));
1357       else if (mime_which_token(p, -1) == MIME_APPLICATION_PGP_ENCRYPTED)
1358         handler = crypt_pgp_encrypted_handler;
1359     }
1360
1361     if (!handler)
1362       handler = multipart_handler;
1363   }
1364   else if (b->type == TYPEAPPLICATION) {
1365     if (mutt_is_application_pgp (b))
1366       handler = crypt_pgp_application_pgp_handler;
1367     if (mutt_is_application_smime (b))
1368       handler = crypt_smime_application_smime_handler;
1369   }
1370
1371   if (plaintext || handler) {
1372     fseeko (s->fpin, b->offset, 0);
1373
1374     /* see if we need to decode this part before processing it */
1375     if (b->encoding == ENCBASE64 || b->encoding == ENCQUOTEDPRINTABLE
1376     ||  b->encoding == ENCUUENCODED || plaintext || mutt_is_text_part (b)) {
1377         /* text subtypes may require character set conversion even with 8bit
1378            encoding.  */
1379       int origType = b->type;
1380       char *savePrefix = NULL;
1381
1382       if (!plaintext) {
1383         /* decode to a tempfile, saving the original destination */
1384         fp = s->fpout;
1385         s->fpout = m_tempfile(tempfile, sizeof(tempfile), NONULL(mod_core.tmpdir), NULL);
1386         if (!s->fpout) {
1387           mutt_error _("Unable to open temporary file!");
1388           goto bail;
1389         }
1390         /* decoding the attachment changes the size and offset, so save a copy
1391          * of the "real" values now, and restore them after processing
1392          */
1393         tmplength = b->length;
1394         tmpoffset = b->offset;
1395
1396         /* if we are decoding binary bodies, we don't want to prefix each
1397          * line with the prefix or else the data will get corrupted.
1398          */
1399         savePrefix = s->prefix;
1400         s->prefix = NULL;
1401
1402         decode = 1;
1403       } else {
1404         b->type = TYPETEXT;
1405       }
1406
1407       mutt_decode_attachment (b, s);
1408
1409       if (decode) {
1410         b->length = ftello (s->fpout);
1411         b->offset = 0;
1412         m_fclose(&s->fpout);
1413
1414         /* restore final destination and substitute the tempfile for input */
1415         s->fpout = fp;
1416         fp = s->fpin;
1417         s->fpin = safe_fopen (tempfile, "r");
1418         unlink (tempfile);
1419
1420         /* restore the prefix */
1421         s->prefix = savePrefix;
1422       }
1423
1424       b->type = origType;
1425     }
1426
1427     /* process the (decoded) body part */
1428     if (handler) {
1429       rc = handler (b, s);
1430
1431       if (decode) {
1432         b->length = tmplength;
1433         b->offset = tmpoffset;
1434
1435         /* restore the original source stream */
1436         m_fclose(&s->fpin);
1437         s->fpin = fp;
1438       }
1439     }
1440   }
1441   else if (s->flags & M_DISPLAY) {
1442     state_mark_attach (s);
1443     state_printf (s, _("[-- %s/%s is unsupported "), TYPE (b), b->subtype);
1444     if (!option (OPTVIEWATTACH)) {
1445       if (km_expand_key
1446           (type, sizeof (type),
1447            km_find_func (MENU_PAGER, OP_VIEW_ATTACHMENTS)))
1448         fprintf (s->fpout, _("(use '%s' to view this part)"), type);
1449       else
1450         fputs (_("(need 'view-attachments' bound to key!)"), s->fpout);
1451     }
1452     fputs (" --]\n", s->fpout);
1453   }
1454
1455 bail:
1456   s->flags = oflags;
1457   return (rc);
1458 }