LISTGROUP isn't as efficient as XOVER. Only use it if needed.
[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/curses.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[STRING];
143   char decline[2 * 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     mutt_convert_to_state (cd, decline, &l, s);
186   }
187
188   mutt_convert_to_state (cd, 0, 0, s);
189   state_reset_prefix (s);
190 }
191
192 void mutt_decode_base64 (STATE * s, long len, int istext, iconv_t cd)
193 {
194   char buf[5];
195   int c1, c2, c3, c4, ch, cr = 0, i;
196   char bufi[BUFI_SIZE];
197   ssize_t l = 0;
198
199   buf[4] = 0;
200
201   if (istext)
202     state_set_prefix (s);
203
204   while (len > 0) {
205     for (i = 0; i < 4 && len > 0; len--) {
206       if ((ch = fgetc (s->fpin)) == EOF)
207         break;
208       if (base64val(ch) >= 0 || ch == '=')
209         buf[i++] = ch;
210     }
211     if (i != 4) {
212       break;
213     }
214
215     c1 = base64val(buf[0]);
216     c2 = base64val(buf[1]);
217     ch = (c1 << 2) | (c2 >> 4);
218
219     if (cr && ch != '\n')
220       bufi[l++] = '\r';
221
222     cr = 0;
223
224     if (istext && ch == '\r')
225       cr = 1;
226     else
227       bufi[l++] = ch;
228
229     if (buf[2] == '=')
230       break;
231     c3 = base64val(buf[2]);
232     ch = ((c2 & 0xf) << 4) | (c3 >> 2);
233
234     if (cr && ch != '\n')
235       bufi[l++] = '\r';
236
237     cr = 0;
238
239     if (istext && ch == '\r')
240       cr = 1;
241     else
242       bufi[l++] = ch;
243
244     if (buf[3] == '=')
245       break;
246     c4 = base64val(buf[3]);
247     ch = ((c3 & 0x3) << 6) | c4;
248
249     if (cr && ch != '\n')
250       bufi[l++] = '\r';
251     cr = 0;
252
253     if (istext && ch == '\r')
254       cr = 1;
255     else
256       bufi[l++] = ch;
257
258     if (l + 8 >= ssizeof (bufi))
259       mutt_convert_to_state (cd, bufi, &l, s);
260   }
261
262   if (cr)
263     bufi[l++] = '\r';
264
265   mutt_convert_to_state (cd, bufi, &l, s);
266   mutt_convert_to_state (cd, 0, 0, s);
267
268   state_reset_prefix (s);
269 }
270
271 static unsigned char decode_byte(int ch)
272 {
273     return ch == 96 ? 0 : ch - 32;
274 }
275
276 static void mutt_decode_uuencoded (STATE * s, long len, int istext, iconv_t cd)
277 {
278   char tmps[STRING];
279   char linelen, c, l, out;
280   char *pt;
281   char bufi[BUFI_SIZE];
282   ssize_t k = 0;
283
284   if (istext)
285     state_set_prefix (s);
286
287   while (len > 0) {
288     if ((fgets (tmps, sizeof (tmps), s->fpin)) == NULL)
289       return;
290     len -= m_strlen(tmps);
291     if ((!m_strncmp(tmps, "begin", 5)) && ISSPACE (tmps[5]))
292       break;
293   }
294   while (len > 0) {
295     if ((fgets (tmps, sizeof (tmps), s->fpin)) == NULL)
296       return;
297     len -= m_strlen(tmps);
298     if (!m_strncmp(tmps, "end", 3))
299       break;
300     pt = tmps;
301     linelen = decode_byte(*pt);
302     pt++;
303     for (c = 0; c < linelen;) {
304       for (l = 2; l <= 6; l += 2) {
305         out = decode_byte(*pt) << l;
306         pt++;
307         out |= (decode_byte(*pt) >> (6 - l));
308         bufi[k++] = out;
309         c++;
310         if (c == linelen)
311           break;
312       }
313       mutt_convert_to_state (cd, bufi, &k, s);
314       pt++;
315     }
316   }
317
318   mutt_convert_to_state (cd, bufi, &k, s);
319   mutt_convert_to_state (cd, 0, 0, s);
320
321   state_reset_prefix (s);
322 }
323
324 /* ----------------------------------------------------------------------------
325  * A (not so) minimal implementation of RFC1563.
326  */
327
328 #define IndentSize (4)
329
330 enum { RICH_PARAM = 0, RICH_BOLD, RICH_UNDERLINE, RICH_ITALIC, RICH_NOFILL,
331   RICH_INDENT, RICH_INDENT_RIGHT, RICH_EXCERPT, RICH_CENTER, RICH_FLUSHLEFT,
332   RICH_FLUSHRIGHT, RICH_COLOR, RICH_LAST_TAG
333 };
334
335 static struct {
336   const char *tag_name;
337   int index;
338 } EnrichedTags[] = {
339   {"param", RICH_PARAM},
340   {"bold", RICH_BOLD},
341   {"italic", RICH_ITALIC},
342   {"underline", RICH_UNDERLINE},
343   {"nofill", RICH_NOFILL},
344   {"excerpt", RICH_EXCERPT},
345   {"indent", RICH_INDENT},
346   {"indentright", RICH_INDENT_RIGHT},
347   {"center", RICH_CENTER},
348   {"flushleft", RICH_FLUSHLEFT},
349   {"flushright", RICH_FLUSHRIGHT},
350   {"flushboth", RICH_FLUSHLEFT},
351   {"color", RICH_COLOR},
352   {"x-color", RICH_COLOR},
353   {NULL, -1}
354 };
355
356 struct enriched_state {
357   char *buffer;
358   char *line;
359   char *param;
360   ssize_t buff_len;
361   ssize_t line_len;
362   ssize_t line_used;
363   ssize_t line_max;
364   ssize_t indent_len;
365   ssize_t word_len;
366   ssize_t buff_used;
367   ssize_t param_used;
368   ssize_t param_len;
369   int tag_level[RICH_LAST_TAG];
370   int WrapMargin;
371   STATE *s;
372 };
373
374 static void enriched_wrap (struct enriched_state *stte)
375 {
376   int x;
377   int extra;
378
379   if (stte->line_len) {
380     if (stte->tag_level[RICH_CENTER] || stte->tag_level[RICH_FLUSHRIGHT]) {
381       /* Strip trailing white space */
382       ssize_t y = stte->line_used - 1;
383
384       while (y && ISSPACE (stte->line[y])) {
385         stte->line[y] = '\0';
386         y--;
387         stte->line_used--;
388         stte->line_len--;
389       }
390       if (stte->tag_level[RICH_CENTER]) {
391         /* Strip leading whitespace */
392         y = 0;
393
394         while (stte->line[y] && ISSPACE (stte->line[y]))
395           y++;
396         if (y) {
397           ssize_t z;
398
399           for (z = y; z <= stte->line_used; z++) {
400             stte->line[z - y] = stte->line[z];
401           }
402
403           stte->line_len -= y;
404           stte->line_used -= y;
405         }
406       }
407     }
408
409     extra = stte->WrapMargin - stte->line_len - stte->indent_len -
410       (stte->tag_level[RICH_INDENT_RIGHT] * IndentSize);
411     if (extra > 0) {
412       if (stte->tag_level[RICH_CENTER]) {
413         x = extra / 2;
414         while (x) {
415           state_putc (' ', stte->s);
416           x--;
417         }
418       }
419       else if (stte->tag_level[RICH_FLUSHRIGHT]) {
420         x = extra - 1;
421         while (x) {
422           state_putc (' ', stte->s);
423           x--;
424         }
425       }
426     }
427     state_puts (stte->line, stte->s);
428   }
429
430   state_putc ('\n', stte->s);
431   stte->line[0] = '\0';
432   stte->line_len = 0;
433   stte->line_used = 0;
434   stte->indent_len = 0;
435   if (stte->s->prefix) {
436     state_puts (stte->s->prefix, stte->s);
437     stte->indent_len += m_strlen(stte->s->prefix);
438   }
439
440   if (stte->tag_level[RICH_EXCERPT]) {
441     x = stte->tag_level[RICH_EXCERPT];
442     while (x) {
443       if (stte->s->prefix) {
444         state_puts (stte->s->prefix, stte->s);
445         stte->indent_len += m_strlen(stte->s->prefix);
446       }
447       else {
448         state_puts ("> ", stte->s);
449         stte->indent_len += m_strlen("> ");
450       }
451       x--;
452     }
453   }
454   else
455     stte->indent_len = 0;
456   if (stte->tag_level[RICH_INDENT]) {
457     x = stte->tag_level[RICH_INDENT] * IndentSize;
458     stte->indent_len += x;
459     while (x) {
460       state_putc (' ', stte->s);
461       x--;
462     }
463   }
464 }
465
466 static void enriched_flush (struct enriched_state *stte, int wrap)
467 {
468   if (!stte->tag_level[RICH_NOFILL] && (stte->line_len + stte->word_len >
469                                         (stte->WrapMargin -
470                                          (stte->tag_level[RICH_INDENT_RIGHT] *
471                                           IndentSize) - stte->indent_len)))
472     enriched_wrap (stte);
473
474   if (stte->buff_used) {
475     stte->buffer[stte->buff_used] = '\0';
476     stte->line_used += stte->buff_used;
477     if (stte->line_used > stte->line_max) {
478       stte->line_max = stte->line_used;
479       p_realloc(&stte->line, stte->line_max + 1);
480     }
481     m_strcat(stte->line, stte->line_max + 1, stte->buffer);
482     stte->line_len += stte->word_len;
483     stte->word_len = 0;
484     stte->buff_used = 0;
485   }
486   if (wrap)
487     enriched_wrap (stte);
488 }
489
490
491 static void enriched_putc (int c, struct enriched_state *stte)
492 {
493   if (stte->tag_level[RICH_PARAM]) {
494     if (stte->tag_level[RICH_COLOR]) {
495       if (stte->param_used + 1 >= stte->param_len)
496         p_realloc(&stte->param, (stte->param_len += STRING));
497
498       stte->param[stte->param_used++] = c;
499     }
500     return;                     /* nothing to do */
501   }
502
503   /* see if more space is needed (plus extra for possible rich characters) */
504   if (stte->buff_len < stte->buff_used + 3) {
505     stte->buff_len += LONG_STRING;
506     p_realloc(&stte->buffer, stte->buff_len + 1);
507   }
508
509   if ((!stte->tag_level[RICH_NOFILL] && ISSPACE (c)) || c == '\0') {
510     if (c == '\t')
511       stte->word_len += 8 - (stte->line_len + stte->word_len) % 8;
512     else
513       stte->word_len++;
514
515     stte->buffer[stte->buff_used++] = c;
516     enriched_flush (stte, 0);
517   }
518   else {
519     if (stte->s->flags & M_DISPLAY) {
520       if (stte->tag_level[RICH_BOLD]) {
521         stte->buffer[stte->buff_used++] = c;
522         stte->buffer[stte->buff_used++] = '\010';
523         stte->buffer[stte->buff_used++] = c;
524       }
525       else if (stte->tag_level[RICH_UNDERLINE]) {
526
527         stte->buffer[stte->buff_used++] = '_';
528         stte->buffer[stte->buff_used++] = '\010';
529         stte->buffer[stte->buff_used++] = c;
530       }
531       else if (stte->tag_level[RICH_ITALIC]) {
532         stte->buffer[stte->buff_used++] = c;
533         stte->buffer[stte->buff_used++] = '\010';
534         stte->buffer[stte->buff_used++] = '_';
535       }
536       else {
537         stte->buffer[stte->buff_used++] = c;
538       }
539     } else {
540       stte->buffer[stte->buff_used++] = c;
541     }
542     stte->word_len++;
543   }
544 }
545
546 static void enriched_puts (const char *s, struct enriched_state *stte)
547 {
548   const char *p;
549
550   if (stte->buff_len < stte->buff_used + m_strlen(s)) {
551     stte->buff_len += LONG_STRING;
552     p_realloc(&stte->buffer, stte->buff_len + 1);
553   }
554
555   p = s;
556   while (*p) {
557     stte->buffer[stte->buff_used++] = *p++;
558   }
559 }
560
561 static void enriched_set_flags (const char *tag, struct enriched_state *stte)
562 {
563   const char *tagptr = tag;
564   int i, j;
565
566   if (*tagptr == '/')
567     tagptr++;
568
569   for (i = 0, j = -1; EnrichedTags[i].tag_name; i++)
570     if (ascii_strcasecmp (EnrichedTags[i].tag_name, tagptr) == 0) {
571       j = EnrichedTags[i].index;
572       break;
573     }
574
575   if (j != -1) {
576     if (j == RICH_CENTER || j == RICH_FLUSHLEFT || j == RICH_FLUSHRIGHT)
577       enriched_flush (stte, 1);
578
579     if (*tag == '/') {
580       if (stte->tag_level[j])   /* make sure not to go negative */
581         stte->tag_level[j]--;
582       if ((stte->s->flags & M_DISPLAY) && j == RICH_PARAM
583           && stte->tag_level[RICH_COLOR]) {
584         stte->param[stte->param_used] = '\0';
585         if (!ascii_strcasecmp (stte->param, "black")) {
586           enriched_puts ("\033[30m", stte);
587         }
588         else if (!ascii_strcasecmp (stte->param, "red")) {
589           enriched_puts ("\033[31m", stte);
590         }
591         else if (!ascii_strcasecmp (stte->param, "green")) {
592           enriched_puts ("\033[32m", stte);
593         }
594         else if (!ascii_strcasecmp (stte->param, "yellow")) {
595           enriched_puts ("\033[33m", stte);
596         }
597         else if (!ascii_strcasecmp (stte->param, "blue")) {
598           enriched_puts ("\033[34m", stte);
599         }
600         else if (!ascii_strcasecmp (stte->param, "magenta")) {
601           enriched_puts ("\033[35m", stte);
602         }
603         else if (!ascii_strcasecmp (stte->param, "cyan")) {
604           enriched_puts ("\033[36m", stte);
605         }
606         else if (!ascii_strcasecmp (stte->param, "white")) {
607           enriched_puts ("\033[37m", stte);
608         }
609       }
610       if ((stte->s->flags & M_DISPLAY) && j == RICH_COLOR) {
611         enriched_puts ("\033[0m", stte);
612       }
613
614       /* flush parameter buffer when closing the tag */
615       if (j == RICH_PARAM) {
616         stte->param_used = 0;
617         stte->param[0] = '\0';
618       }
619     }
620     else
621       stte->tag_level[j]++;
622
623     if (j == RICH_EXCERPT)
624       enriched_flush (stte, 1);
625   }
626 }
627
628 static int text_enriched_handler (BODY * a, STATE * s)
629 {
630   enum {
631     TEXT, LANGLE, TAG, BOGUS_TAG, NEWLINE, ST_EOF, DONE
632   } state = TEXT;
633
634   long bytes = a->length;
635   struct enriched_state stte;
636   int c = 0;
637   int tag_len = 0;
638   char tag[LONG_STRING + 1];
639
640   p_clear(&stte, 1);
641   stte.s = s;
642   stte.WrapMargin =
643     ((s->flags & M_DISPLAY) ? (COLS - 4) : ((COLS - 4) <
644                                             72) ? (COLS - 4) : 72);
645   stte.line_max = stte.WrapMargin * 4;
646   stte.line = p_new(char, stte.line_max + 1);
647   stte.param = p_new(char, STRING);
648
649   stte.param_len = STRING;
650   stte.param_used = 0;
651
652   if (s->prefix) {
653     state_puts (s->prefix, s);
654     stte.indent_len += m_strlen(s->prefix);
655   }
656
657   while (state != DONE) {
658     if (state != ST_EOF) {
659       if (!bytes || (c = fgetc (s->fpin)) == EOF)
660         state = ST_EOF;
661       else
662         bytes--;
663     }
664
665     switch (state) {
666     case TEXT:
667       switch (c) {
668       case '<':
669         state = LANGLE;
670         break;
671
672       case '\n':
673         if (stte.tag_level[RICH_NOFILL]) {
674           enriched_flush (&stte, 1);
675         }
676         else {
677           enriched_putc (' ', &stte);
678           state = NEWLINE;
679         }
680         break;
681
682       default:
683         enriched_putc (c, &stte);
684       }
685       break;
686
687     case LANGLE:
688       if (c == '<') {
689         enriched_putc (c, &stte);
690         state = TEXT;
691         break;
692       }
693       else {
694         tag_len = 0;
695         state = TAG;
696       }
697       /* Yes, fall through (it wasn't a <<, so this char is first in TAG) */
698     case TAG:
699       if (c == '>') {
700         tag[tag_len] = '\0';
701         enriched_set_flags (tag, &stte);
702         state = TEXT;
703       }
704       else if (tag_len < LONG_STRING)   /* ignore overly long tags */
705         tag[tag_len++] = c;
706       else
707         state = BOGUS_TAG;
708       break;
709
710     case BOGUS_TAG:
711       if (c == '>')
712         state = TEXT;
713       break;
714
715     case NEWLINE:
716       if (c == '\n')
717         enriched_flush (&stte, 1);
718       else {
719         ungetc (c, s->fpin);
720         bytes++;
721         state = TEXT;
722       }
723       break;
724
725     case ST_EOF:
726       enriched_putc ('\0', &stte);
727       enriched_flush (&stte, 1);
728       state = DONE;
729       break;
730
731     case DONE:                 /* not reached, but gcc complains if this is absent */
732       break;
733     }
734   }
735
736   state_putc ('\n', s);         /* add a final newline */
737
738   p_delete(&(stte.buffer));
739   p_delete(&(stte.line));
740   p_delete(&(stte.param));
741
742   return (0);
743 }
744
745 #define TXTHTML     1
746 #define TXTPLAIN    2
747 #define TXTENRICHED 3
748
749 static int alternative_handler (BODY * a, STATE * s)
750 {
751   BODY *choice = NULL;
752   BODY *b;
753   string_list_t *t;
754   char buf[STRING];
755   int type = 0;
756   int mustfree = 0;
757   int rc = 0;
758
759   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
760       a->encoding == ENCUUENCODED) {
761     struct stat st;
762
763     mustfree = 1;
764     fstat (fileno (s->fpin), &st);
765     b = body_new();
766     b->length = (long) st.st_size;
767     b->parts = mutt_parse_multipart(s->fpin,
768                                     parameter_getval(a->parameter, "boundary"),
769                                     (long)st.st_size,
770                                     mime_which_token(a->subtype, -1) == MIME_DIGEST);
771   }
772   else
773     b = a;
774
775   a = b;
776
777   /* First, search list of prefered types */
778   t = AlternativeOrderList;
779   while (t && !choice) {
780     char *c;
781     int btlen;                  /* length of basetype */
782     int wild;                   /* do we have a wildcard to match all subtypes? */
783
784     c = strchr (t->data, '/');
785     if (c) {
786       wild = (c[1] == '*' && c[2] == 0);
787       btlen = c - t->data;
788     }
789     else {
790       wild = 1;
791       btlen = m_strlen(t->data);
792     }
793
794     if (a && a->parts)
795       b = a->parts;
796     else
797       b = a;
798     while (b) {
799       const char *bt = TYPE (b);
800
801       if (!ascii_strncasecmp (bt, t->data, btlen) && bt[btlen] == 0) {
802         /* the basetype matches */
803         if (wild || !ascii_strcasecmp (t->data + btlen + 1, b->subtype)) {
804           choice = b;
805         }
806       }
807       b = b->next;
808     }
809     t = t->next;
810   }
811
812   /* Next, look for an autoviewable type */
813   if (!choice) {
814     if (a && a->parts)
815       b = a->parts;
816     else
817       b = a;
818     while (b) {
819       snprintf (buf, sizeof (buf), "%s/%s", TYPE (b), b->subtype);
820       if (mutt_is_autoview (b, buf)) {
821         rfc1524_entry *entry = rfc1524_entry_new();
822
823         if (rfc1524_mailcap_lookup (b, buf, entry, M_AUTOVIEW)) {
824           choice = b;
825         }
826         rfc1524_entry_delete(&entry);
827       }
828       b = b->next;
829     }
830   }
831
832   /* Then, look for a text entry */
833   if (!choice) {
834     if (a && a->parts)
835       b = a->parts;
836     else
837       b = a;
838     while (b) {
839       if (b->type == TYPETEXT) {
840         switch (mime_which_token(b->subtype, -1)) {
841           case MIME_PLAIN:
842             if (type <= TXTPLAIN) {
843               choice = b;
844               type = TXTPLAIN;
845             }
846             break;
847           case MIME_ENRICHED:
848             if (type <= TXTENRICHED) {
849               choice = b;
850               type = TXTENRICHED;
851             }
852             break;
853           case MIME_HTML:
854             if (type <= TXTHTML) {
855               choice = b;
856               type = TXTHTML;
857             }
858             break;
859           default:
860             break;
861         }
862       }
863       b = b->next;
864     }
865   }
866
867   /* Finally, look for other possibilities */
868   if (!choice) {
869     if (a && a->parts)
870       b = a->parts;
871     else
872       b = a;
873     while (b) {
874       if (mutt_can_decode (b))
875         choice = b;
876       b = b->next;
877     }
878   }
879
880   if (choice) {
881     if (s->flags & M_DISPLAY && !option (OPTWEED)) {
882       fseeko (s->fpin, choice->hdr_offset, 0);
883       mutt_copy_bytes (s->fpin, s->fpout,
884                        choice->offset - choice->hdr_offset);
885     }
886     mutt_body_handler (choice, s);
887   }
888   else if (s->flags & M_DISPLAY) {
889     /* didn't find anything that we could display! */
890     state_mark_attach (s);
891     state_puts (_("[-- Error:  Could not display any parts of Multipart/Alternative! --]\n"), s);
892     rc = -1;
893   }
894
895   if (mustfree)
896     body_list_wipe(&a);
897
898   return (rc);
899 }
900
901 /* handles message/rfc822 body parts */
902 static int message_handler (BODY * a, STATE * s)
903 {
904   struct stat st;
905   BODY *b;
906   off_t off_start;
907   int rc = 0;
908
909   off_start = ftello (s->fpin);
910   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
911       a->encoding == ENCUUENCODED) {
912     fstat (fileno (s->fpin), &st);
913     b = body_new();
914     b->length = (long) st.st_size;
915     b->parts = mutt_parse_messageRFC822 (s->fpin, b);
916   }
917   else
918     b = a;
919
920   if (b->parts) {
921     mutt_copy_hdr (s->fpin, s->fpout, off_start, b->parts->offset,
922                    (((s->flags & M_WEED)
923                      || ((s->flags & (M_DISPLAY | M_PRINTING))
924                          && option (OPTWEED))) ? (CH_WEED | CH_REORDER) : 0) |
925                    (s->prefix ? CH_PREFIX : 0) | CH_DECODE | CH_FROM,
926                    s->prefix);
927
928     if (s->prefix)
929       state_puts (s->prefix, s);
930     state_putc ('\n', s);
931
932     rc = mutt_body_handler (b->parts, s);
933   }
934
935   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
936       a->encoding == ENCUUENCODED)
937     body_list_wipe(&b);
938
939   return (rc);
940 }
941
942 /* returns 1 if decoding the attachment will produce output */
943 int mutt_can_decode (BODY * a)
944 {
945   char type[STRING];
946
947   snprintf (type, sizeof (type), "%s/%s", TYPE (a), a->subtype);
948   if (mutt_is_autoview (a, type))
949     return (rfc1524_mailcap_lookup (a, type, NULL, M_AUTOVIEW));
950   else if (a->type == TYPETEXT)
951     return (1);
952   else if (a->type == TYPEMESSAGE)
953     return (1);
954   else if (a->type == TYPEMULTIPART) {
955     BODY *p;
956     int tok = mime_which_token(a->subtype, -1);
957
958     if (tok == MIME_SIGNED || tok == MIME_ENCRYPTED)
959       return 1;
960
961     for (p = a->parts; p; p = p->next) {
962       if (mutt_can_decode (p))
963         return (1);
964     }
965   }
966   else if (a->type == TYPEAPPLICATION) {
967     if (mutt_is_application_pgp(a))
968       return (1);
969     if (mutt_is_application_smime (a))
970       return (1);
971   }
972
973   return (0);
974 }
975
976 static int multipart_handler (BODY * a, STATE * s)
977 {
978   BODY *b, *p;
979   char length[5];
980   struct stat st;
981   int count;
982   int rc = 0;
983
984   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
985       a->encoding == ENCUUENCODED) {
986     fstat (fileno (s->fpin), &st);
987     b = body_new();
988     b->length = (long) st.st_size;
989     b->parts = mutt_parse_multipart(s->fpin,
990                                     parameter_getval(a->parameter, "boundary"),
991                                     (long)st.st_size,
992                                     mime_which_token(a->subtype, -1) == MIME_DIGEST);
993   } else {
994     b = a;
995   }
996
997   for (p = b->parts, count = 1; p; p = p->next, count++) {
998     if (s->flags & M_DISPLAY) {
999       state_mark_attach (s);
1000       state_printf (s, _("[-- Attachment #%d"), count);
1001       if (p->description || p->filename || p->form_name) {
1002         state_puts (": ", s);
1003         state_puts (p->description ? p->description :
1004                     p->filename ? p->filename : p->form_name, s);
1005       }
1006       state_puts (" --]\n", s);
1007
1008       mutt_pretty_size (length, sizeof (length), p->length);
1009
1010       state_mark_attach (s);
1011       state_printf (s, _("[-- Type: %s/%s, Encoding: %s, Size: %s --]\n"),
1012                     TYPE (p), p->subtype, ENCODING (p->encoding), length);
1013       if (!option (OPTWEED)) {
1014         fseeko (s->fpin, p->hdr_offset, 0);
1015         mutt_copy_bytes (s->fpin, s->fpout, p->offset - p->hdr_offset);
1016       }
1017       else
1018         state_putc ('\n', s);
1019     }
1020     else {
1021       if (p->description && mutt_can_decode (p))
1022         state_printf (s, "Content-Description: %s\n", p->description);
1023
1024       if (p->form_name)
1025         state_printf (s, "%s: \n", p->form_name);
1026
1027     }
1028     rc = mutt_body_handler (p, s);
1029     state_putc ('\n', s);
1030     if (rc)
1031       break;
1032   }
1033
1034   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
1035       a->encoding == ENCUUENCODED)
1036     body_list_wipe(&b);
1037
1038   return (rc);
1039 }
1040
1041 static int autoview_handler (BODY * a, STATE * s)
1042 {
1043   rfc1524_entry *entry = rfc1524_entry_new();
1044   char buffer[LONG_STRING];
1045   char type[STRING];
1046   char command[LONG_STRING];
1047   char tempfile[_POSIX_PATH_MAX] = "";
1048   char *fname;
1049   FILE *fpin = NULL;
1050   FILE *fpout = NULL;
1051   FILE *fperr = NULL;
1052   int piped = FALSE;
1053   pid_t thepid;
1054   int rc = 0;
1055
1056   snprintf (type, sizeof (type), "%s/%s", TYPE (a), a->subtype);
1057   rfc1524_mailcap_lookup (a, type, entry, M_AUTOVIEW);
1058
1059   fname = m_strdup(a->filename);
1060   mutt_sanitize_filename (fname, 1);
1061   rfc1524_expand_filename (entry->nametemplate, fname, tempfile,
1062                            sizeof (tempfile));
1063   p_delete(&fname);
1064
1065   if (entry->command) {
1066     m_strcpy(command, sizeof(command), entry->command);
1067
1068     /* rfc1524_expand_command returns 0 if the file is required */
1069     piped =
1070       rfc1524_expand_command (a, tempfile, type, command, sizeof (command));
1071
1072     if (s->flags & M_DISPLAY) {
1073       state_mark_attach (s);
1074       state_printf (s, _("[-- Autoview using %s --]\n"), command);
1075       mutt_message (_("Invoking autoview command: %s"), command);
1076     }
1077
1078     if ((fpin = safe_fopen (tempfile, "w+")) == NULL) {
1079       mutt_perror ("fopen");
1080       rfc1524_entry_delete(&entry);
1081       return (-1);
1082     }
1083
1084     mutt_copy_bytes (s->fpin, fpin, a->length);
1085
1086     if (!piped) {
1087       m_fclose(&fpin);
1088       thepid = mutt_create_filter (command, NULL, &fpout, &fperr);
1089     } else {
1090       unlink (tempfile);
1091       fflush (fpin);
1092       rewind (fpin);
1093       thepid = mutt_create_filter_fd (command, NULL, &fpout, &fperr,
1094                                       fileno (fpin), -1, -1);
1095     }
1096
1097     if (thepid < 0) {
1098       mutt_perror (_("Can't create filter"));
1099
1100       if (s->flags & M_DISPLAY) {
1101         state_mark_attach (s);
1102         state_printf (s, _("[-- Can't run %s. --]\n"), command);
1103       }
1104       rc = -1;
1105       goto bail;
1106     }
1107
1108     if (s->prefix) {
1109       while (fgets (buffer, sizeof (buffer), fpout) != NULL) {
1110         state_puts (s->prefix, s);
1111         state_puts (buffer, s);
1112       }
1113       /* check for data on stderr */
1114       if (fgets (buffer, sizeof (buffer), fperr)) {
1115         if (s->flags & M_DISPLAY) {
1116           state_mark_attach (s);
1117           state_printf (s, _("[-- Autoview stderr of %s --]\n"), command);
1118         }
1119
1120         state_puts (s->prefix, s);
1121         state_puts (buffer, s);
1122         while (fgets (buffer, sizeof (buffer), fperr) != NULL) {
1123           state_puts (s->prefix, s);
1124           state_puts (buffer, s);
1125         }
1126       }
1127     } else {
1128       mutt_copy_stream (fpout, s->fpout);
1129       /* Check for stderr messages */
1130       if (fgets (buffer, sizeof (buffer), fperr)) {
1131         if (s->flags & M_DISPLAY) {
1132           state_mark_attach (s);
1133           state_printf (s, _("[-- Autoview stderr of %s --]\n"), command);
1134         }
1135
1136         state_puts (buffer, s);
1137         mutt_copy_stream (fperr, s->fpout);
1138       }
1139     }
1140
1141   bail:
1142     m_fclose(&fpout);
1143     m_fclose(&fperr);
1144
1145     mutt_wait_filter (thepid);
1146     if (piped)
1147       m_fclose(&fpin);
1148     else
1149       mutt_unlink (tempfile);
1150
1151     if (s->flags & M_DISPLAY)
1152       mutt_clear_error ();
1153   }
1154   rfc1524_entry_delete(&entry);
1155   return (rc);
1156 }
1157
1158 static int external_body_handler (BODY * b, STATE * s)
1159 {
1160   const char *access_type;
1161   const char *expiration;
1162   time_t expire;
1163
1164   access_type = parameter_getval(b->parameter, "access-type");
1165   if (!access_type) {
1166     if (s->flags & M_DISPLAY) {
1167       state_mark_attach (s);
1168       state_puts (_("[-- Error: message/external-body has no access-type parameter --]\n"), s);
1169     }
1170     return (-1);
1171   }
1172
1173   expiration = parameter_getval(b->parameter, "expiration");
1174   if (expiration)
1175     expire = mutt_parse_date (expiration, NULL);
1176   else
1177     expire = -1;
1178
1179   if (mime_which_token(access_type, -1) == MIME_X_MUTT_DELETED) {
1180     if (s->flags & (M_DISPLAY | M_PRINTING)) {
1181       char *length;
1182       char pretty_size[10];
1183
1184       state_mark_attach (s);
1185       state_printf (s, _("[-- This %s/%s attachment "),
1186                     TYPE (b->parts), b->parts->subtype);
1187       length = parameter_getval(b->parameter, "length");
1188       if (length) {
1189         mutt_pretty_size (pretty_size, sizeof (pretty_size),
1190                           strtol (length, NULL, 10));
1191         state_printf (s, _("(size %s bytes) "), pretty_size);
1192       }
1193       state_puts (_("has been deleted --]\n"), s);
1194
1195       if (expire != -1) {
1196         state_mark_attach (s);
1197         state_printf (s, _("[-- on %s --]\n"), expiration);
1198       }
1199       if (b->parts->filename) {
1200         state_mark_attach (s);
1201         state_printf (s, _("[-- name: %s --]\n"), b->parts->filename);
1202       }
1203
1204       mutt_copy_hdr (s->fpin, s->fpout, ftello (s->fpin), b->parts->offset,
1205                      (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) |
1206                      CH_DECODE, NULL);
1207     }
1208   }
1209   else if (expiration && expire < time (NULL)) {
1210     if (s->flags & M_DISPLAY) {
1211       state_mark_attach (s);
1212       state_printf (s, _("[-- This %s/%s attachment is not included, --]\n"),
1213                     TYPE (b->parts), b->parts->subtype);
1214       state_attach_puts (_("[-- and the indicated external source has --]\n"
1215                            "[-- expired. --]\n"), s);
1216
1217       mutt_copy_hdr (s->fpin, s->fpout, ftello (s->fpin), b->parts->offset,
1218                      (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) |
1219                      CH_DECODE, NULL);
1220     }
1221   } else {
1222     if (s->flags & M_DISPLAY) {
1223       state_mark_attach (s);
1224       state_printf (s,
1225                     _("[-- This %s/%s attachment is not included, --]\n"),
1226                     TYPE (b->parts), b->parts->subtype);
1227       state_mark_attach (s);
1228       state_printf (s,
1229                     _
1230                     ("[-- and the indicated access-type %s is unsupported --]\n"),
1231                     access_type);
1232       mutt_copy_hdr (s->fpin, s->fpout, ftello (s->fpin), b->parts->offset,
1233                      (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) |
1234                      CH_DECODE, NULL);
1235     }
1236   }
1237   return (0);
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 }