Nico Golde:
[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 #if HAVE_CONFIG_H
11 # include "config.h"
12 #endif
13
14 #include <stdlib.h>
15 #include <string.h>
16 #include <unistd.h>
17 #include <ctype.h>
18 #include <sys/wait.h>
19 #include <sys/stat.h>
20
21 #include "mutt.h"
22 #include "mutt_curses.h"
23 #include "rfc1524.h"
24 #include "keymap.h"
25 #include "mime.h"
26 #include "copy.h"
27 #include "charset.h"
28 #include "mutt_crypt.h"
29 #include "lib.h"
30
31
32 #define BUFI_SIZE 1000
33 #define BUFO_SIZE 2000
34
35
36 typedef void handler_f (BODY *, STATE *);
37 typedef handler_f *handler_t;
38
39 int Index_hex[128] = {
40   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
41   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
42   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
43   0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
44   -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
45   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
46   -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
47   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
48 };
49
50 int Index_64[128] = {
51   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
52   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
53   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
54   52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
55   -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
56   15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
57   -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
58   41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
59 };
60
61 static void state_prefix_put (const char *d, size_t dlen, STATE * s)
62 {
63   if (s->prefix)
64     while (dlen--)
65       state_prefix_putc (*d++, s);
66   else
67     fwrite (d, dlen, 1, s->fpout);
68 }
69
70 void mutt_convert_to_state (iconv_t cd, char *bufi, size_t * l, STATE * s)
71 {
72   char bufo[BUFO_SIZE];
73   ICONV_CONST char *ib;
74   char *ob;
75   size_t ibl, obl;
76
77   if (!bufi) {
78     if (cd != (iconv_t) (-1)) {
79       ob = bufo, obl = sizeof (bufo);
80       iconv (cd, 0, 0, &ob, &obl);
81       if (ob != bufo)
82         state_prefix_put (bufo, ob - bufo, s);
83     }
84     if (Quotebuf[0] != '\0')
85       state_prefix_putc ('\n', s);
86     return;
87   }
88
89   if (cd == (iconv_t) (-1)) {
90     state_prefix_put (bufi, *l, s);
91     *l = 0;
92     return;
93   }
94
95   ib = bufi, ibl = *l;
96   for (;;) {
97     ob = bufo, obl = sizeof (bufo);
98     mutt_iconv (cd, &ib, &ibl, &ob, &obl, 0, "?");
99     if (ob == bufo)
100       break;
101     state_prefix_put (bufo, ob - bufo, s);
102   }
103   memmove (bufi, ib, ibl);
104   *l = ibl;
105 }
106
107 void mutt_decode_xbit (STATE * s, long len, int istext, iconv_t cd)
108 {
109   int c, ch;
110   char bufi[BUFI_SIZE];
111   size_t l = 0;
112
113   if (istext) {
114     state_set_prefix (s);
115
116     while ((c = fgetc (s->fpin)) != EOF && len--) {
117       if (c == '\r' && len) {
118         if ((ch = fgetc (s->fpin)) == '\n') {
119           c = ch;
120           len--;
121         }
122         else
123           ungetc (ch, s->fpin);
124       }
125
126       bufi[l++] = c;
127       if (l == sizeof (bufi))
128         mutt_convert_to_state (cd, bufi, &l, s);
129     }
130
131     mutt_convert_to_state (cd, bufi, &l, s);
132     mutt_convert_to_state (cd, 0, 0, s);
133
134     state_reset_prefix (s);
135   }
136   else
137     mutt_copy_bytes (s->fpin, s->fpout, len);
138 }
139
140 static int qp_decode_triple (char *s, char *d)
141 {
142   /* soft line break */
143   if (*s == '=' && !(*(s + 1)))
144     return 1;
145
146   /* quoted-printable triple */
147   if (*s == '=' &&
148       isxdigit ((unsigned char) *(s + 1)) &&
149       isxdigit ((unsigned char) *(s + 2))) {
150     *d = (hexval (*(s + 1)) << 4) | hexval (*(s + 2));
151     return 0;
152   }
153
154   /* something else */
155   return -1;
156 }
157
158 static void qp_decode_line (char *dest, char *src, size_t * l, int last)
159 {
160   char *d, *s;
161   char c;
162
163   int kind;
164   int soft = 0;
165
166   /* decode the line */
167
168   for (d = dest, s = src; *s;) {
169     switch ((kind = qp_decode_triple (s, &c))) {
170     case 0:
171       *d++ = c;
172       s += 3;
173       break;                    /* qp triple */
174     case -1:
175       *d++ = *s++;
176       break;                    /* single character */
177     case 1:
178       soft = 1;
179       s++;
180       break;                    /* soft line break */
181     }
182   }
183
184   if (!soft && last == '\n')
185     *d++ = '\n';
186
187   *d = '\0';
188   *l = d - dest;
189 }
190
191 /* 
192  * Decode an attachment encoded with quoted-printable.
193  * 
194  * Why doesn't this overflow any buffers?  First, it's guaranteed
195  * that the length of a line grows when you _en_-code it to
196  * quoted-printable.  That means that we always can store the
197  * result in a buffer of at most the _same_ size.
198  * 
199  * Now, we don't special-case if the line we read with fgets()
200  * isn't terminated.  We don't care about this, since STRING > 78,
201  * so corrupted input will just be corrupted a bit more.  That
202  * implies that STRING+1 bytes are always sufficient to store the
203  * result of qp_decode_line.
204  * 
205  * Finally, at soft line breaks, some part of a multibyte character
206  * may have been left over by mutt_convert_to_state().  This shouldn't
207  * be more than 6 characters, so STRING + 7 should be sufficient
208  * memory to store the decoded data.
209  * 
210  * Just to make sure that I didn't make some off-by-one error
211  * above, we just use STRING*2 for the target buffer's size.
212  * 
213  */
214
215 void mutt_decode_quoted (STATE * s, long len, int istext, iconv_t cd)
216 {
217   char line[STRING];
218   char decline[2 * STRING];
219   size_t l = 0;
220   size_t linelen;               /* number of input bytes in `line' */
221   size_t l3;
222
223   int last;                     /* store the last character in the input line */
224
225   if (istext)
226     state_set_prefix (s);
227
228   while (len > 0) {
229     last = 0;
230
231     /*
232      * It's ok to use a fixed size buffer for input, even if the line turns
233      * out to be longer than this.  Just process the line in chunks.  This
234      * really shouldn't happen according the MIME spec, since Q-P encoded
235      * lines are at most 76 characters, but we should be liberal about what
236      * we accept.
237      */
238     if (fgets (line, MIN ((ssize_t) sizeof (line), len + 1), s->fpin) == NULL)
239       break;
240
241     linelen = mutt_strlen (line);
242     len -= linelen;
243
244     /*
245      * inspect the last character we read so we can tell if we got the
246      * entire line.
247      */
248     last = linelen ? line[linelen - 1] : 0;
249
250     /* chop trailing whitespace if we got the full line */
251     if (last == '\n') {
252       while (linelen > 0 && ISSPACE (line[linelen - 1]))
253         linelen--;
254       line[linelen] = 0;
255     }
256
257     /* decode and do character set conversion */
258     qp_decode_line (decline + l, line, &l3, last);
259     l += l3;
260     mutt_convert_to_state (cd, decline, &l, s);
261   }
262
263   mutt_convert_to_state (cd, 0, 0, s);
264   state_reset_prefix (s);
265 }
266
267 void mutt_decode_base64 (STATE * s, long len, int istext, iconv_t cd)
268 {
269   char buf[5];
270   int c1, c2, c3, c4, ch, cr = 0, i;
271   char bufi[BUFI_SIZE];
272   size_t l = 0;
273
274   buf[4] = 0;
275
276   if (istext)
277     state_set_prefix (s);
278
279   while (len > 0) {
280     for (i = 0; i < 4 && len > 0; len--) {
281       if ((ch = fgetc (s->fpin)) == EOF)
282         break;
283       if (ch >= 0 && ch < 128 && (base64val (ch) != -1 || ch == '='))
284         buf[i++] = ch;
285     }
286     if (i != 4) {
287       dprint (2, (debugfile, "%s:%d [mutt_decode_base64()]: "
288                   "didn't get a multiple of 4 chars.\n", __FILE__, __LINE__));
289       break;
290     }
291
292     c1 = base64val (buf[0]);
293     c2 = base64val (buf[1]);
294     ch = (c1 << 2) | (c2 >> 4);
295
296     if (cr && ch != '\n')
297       bufi[l++] = '\r';
298
299     cr = 0;
300
301     if (istext && ch == '\r')
302       cr = 1;
303     else
304       bufi[l++] = ch;
305
306     if (buf[2] == '=')
307       break;
308     c3 = base64val (buf[2]);
309     ch = ((c2 & 0xf) << 4) | (c3 >> 2);
310
311     if (cr && ch != '\n')
312       bufi[l++] = '\r';
313
314     cr = 0;
315
316     if (istext && ch == '\r')
317       cr = 1;
318     else
319       bufi[l++] = ch;
320
321     if (buf[3] == '=')
322       break;
323     c4 = base64val (buf[3]);
324     ch = ((c3 & 0x3) << 6) | c4;
325
326     if (cr && ch != '\n')
327       bufi[l++] = '\r';
328     cr = 0;
329
330     if (istext && ch == '\r')
331       cr = 1;
332     else
333       bufi[l++] = ch;
334
335     if (l + 8 >= sizeof (bufi))
336       mutt_convert_to_state (cd, bufi, &l, s);
337   }
338
339   if (cr)
340     bufi[l++] = '\r';
341
342   mutt_convert_to_state (cd, bufi, &l, s);
343   mutt_convert_to_state (cd, 0, 0, s);
344
345   state_reset_prefix (s);
346 }
347
348 unsigned char decode_byte (char ch)
349 {
350   if (ch == 96)
351     return 0;
352   return ch - 32;
353 }
354
355 void mutt_decode_uuencoded (STATE * s, long len, int istext, iconv_t cd)
356 {
357   char tmps[SHORT_STRING];
358   char linelen, c, l, out;
359   char *pt;
360   char bufi[BUFI_SIZE];
361   size_t k = 0;
362
363   if (istext)
364     state_set_prefix (s);
365
366   while (len > 0) {
367     if ((fgets (tmps, sizeof (tmps), s->fpin)) == NULL)
368       return;
369     len -= mutt_strlen (tmps);
370     if ((!mutt_strncmp (tmps, "begin", 5)) && ISSPACE (tmps[5]))
371       break;
372   }
373   while (len > 0) {
374     if ((fgets (tmps, sizeof (tmps), s->fpin)) == NULL)
375       return;
376     len -= mutt_strlen (tmps);
377     if (!mutt_strncmp (tmps, "end", 3))
378       break;
379     pt = tmps;
380     linelen = decode_byte (*pt);
381     pt++;
382     for (c = 0; c < linelen;) {
383       for (l = 2; l <= 6; l += 2) {
384         out = decode_byte (*pt) << l;
385         pt++;
386         out |= (decode_byte (*pt) >> (6 - l));
387         bufi[k++] = out;
388         c++;
389         if (c == linelen)
390           break;
391       }
392       mutt_convert_to_state (cd, bufi, &k, s);
393       pt++;
394     }
395   }
396
397   mutt_convert_to_state (cd, bufi, &k, s);
398   mutt_convert_to_state (cd, 0, 0, s);
399
400   state_reset_prefix (s);
401 }
402
403 /* ----------------------------------------------------------------------------
404  * A (not so) minimal implementation of RFC1563.
405  */
406
407 #define IndentSize (4)
408
409 enum { RICH_PARAM = 0, RICH_BOLD, RICH_UNDERLINE, RICH_ITALIC, RICH_NOFILL,
410   RICH_INDENT, RICH_INDENT_RIGHT, RICH_EXCERPT, RICH_CENTER, RICH_FLUSHLEFT,
411   RICH_FLUSHRIGHT, RICH_COLOR, RICH_LAST_TAG
412 };
413
414 static struct {
415   const char *tag_name;
416   int index;
417 } EnrichedTags[] = {
418   {
419   "param", RICH_PARAM}, {
420   "bold", RICH_BOLD}, {
421   "italic", RICH_ITALIC}, {
422   "underline", RICH_UNDERLINE}, {
423   "nofill", RICH_NOFILL}, {
424   "excerpt", RICH_EXCERPT}, {
425   "indent", RICH_INDENT}, {
426   "indentright", RICH_INDENT_RIGHT}, {
427   "center", RICH_CENTER}, {
428   "flushleft", RICH_FLUSHLEFT}, {
429   "flushright", RICH_FLUSHRIGHT}, {
430   "flushboth", RICH_FLUSHLEFT}, {
431   "color", RICH_COLOR}, {
432   "x-color", RICH_COLOR}, {
433   NULL, -1}
434 };
435
436 struct enriched_state {
437   char *buffer;
438   char *line;
439   char *param;
440   size_t buff_len;
441   size_t line_len;
442   size_t line_used;
443   size_t line_max;
444   size_t indent_len;
445   size_t word_len;
446   size_t buff_used;
447   size_t param_used;
448   size_t param_len;
449   int tag_level[RICH_LAST_TAG];
450   int WrapMargin;
451   STATE *s;
452 };
453
454 static void enriched_wrap (struct enriched_state *stte)
455 {
456   int x;
457   int extra;
458
459   if (stte->line_len) {
460     if (stte->tag_level[RICH_CENTER] || stte->tag_level[RICH_FLUSHRIGHT]) {
461       /* Strip trailing white space */
462       size_t y = stte->line_used - 1;
463
464       while (y && ISSPACE (stte->line[y])) {
465         stte->line[y] = '\0';
466         y--;
467         stte->line_used--;
468         stte->line_len--;
469       }
470       if (stte->tag_level[RICH_CENTER]) {
471         /* Strip leading whitespace */
472         y = 0;
473
474         while (stte->line[y] && ISSPACE (stte->line[y]))
475           y++;
476         if (y) {
477           size_t z;
478
479           for (z = y; z <= stte->line_used; z++) {
480             stte->line[z - y] = stte->line[z];
481           }
482
483           stte->line_len -= y;
484           stte->line_used -= y;
485         }
486       }
487     }
488
489     extra = stte->WrapMargin - stte->line_len - stte->indent_len -
490       (stte->tag_level[RICH_INDENT_RIGHT] * IndentSize);
491     if (extra > 0) {
492       if (stte->tag_level[RICH_CENTER]) {
493         x = extra / 2;
494         while (x) {
495           state_putc (' ', stte->s);
496           x--;
497         }
498       }
499       else if (stte->tag_level[RICH_FLUSHRIGHT]) {
500         x = extra - 1;
501         while (x) {
502           state_putc (' ', stte->s);
503           x--;
504         }
505       }
506     }
507     state_puts (stte->line, stte->s);
508   }
509
510   state_putc ('\n', stte->s);
511   stte->line[0] = '\0';
512   stte->line_len = 0;
513   stte->line_used = 0;
514   stte->indent_len = 0;
515   if (stte->s->prefix) {
516     state_puts (stte->s->prefix, stte->s);
517     stte->indent_len += mutt_strlen (stte->s->prefix);
518   }
519
520   if (stte->tag_level[RICH_EXCERPT]) {
521     x = stte->tag_level[RICH_EXCERPT];
522     while (x) {
523       if (stte->s->prefix) {
524         state_puts (stte->s->prefix, stte->s);
525         stte->indent_len += mutt_strlen (stte->s->prefix);
526       }
527       else {
528         state_puts ("> ", stte->s);
529         stte->indent_len += mutt_strlen ("> ");
530       }
531       x--;
532     }
533   }
534   else
535     stte->indent_len = 0;
536   if (stte->tag_level[RICH_INDENT]) {
537     x = stte->tag_level[RICH_INDENT] * IndentSize;
538     stte->indent_len += x;
539     while (x) {
540       state_putc (' ', stte->s);
541       x--;
542     }
543   }
544 }
545
546 static void enriched_flush (struct enriched_state *stte, int wrap)
547 {
548   if (!stte->tag_level[RICH_NOFILL] && (stte->line_len + stte->word_len >
549                                         (stte->WrapMargin -
550                                          (stte->tag_level[RICH_INDENT_RIGHT] *
551                                           IndentSize) - stte->indent_len)))
552     enriched_wrap (stte);
553
554   if (stte->buff_used) {
555     stte->buffer[stte->buff_used] = '\0';
556     stte->line_used += stte->buff_used;
557     if (stte->line_used > stte->line_max) {
558       stte->line_max = stte->line_used;
559       safe_realloc (&stte->line, stte->line_max + 1);
560     }
561     strcat (stte->line, stte->buffer);  /* __STRCAT_CHECKED__ */
562     stte->line_len += stte->word_len;
563     stte->word_len = 0;
564     stte->buff_used = 0;
565   }
566   if (wrap)
567     enriched_wrap (stte);
568 }
569
570
571 static void enriched_putc (int c, struct enriched_state *stte)
572 {
573   if (stte->tag_level[RICH_PARAM]) {
574     if (stte->tag_level[RICH_COLOR]) {
575       if (stte->param_used + 1 >= stte->param_len)
576         safe_realloc (&stte->param, (stte->param_len += STRING));
577
578       stte->param[stte->param_used++] = c;
579     }
580     return;                     /* nothing to do */
581   }
582
583   /* see if more space is needed (plus extra for possible rich characters) */
584   if (stte->buff_len < stte->buff_used + 3) {
585     stte->buff_len += LONG_STRING;
586     safe_realloc (&stte->buffer, stte->buff_len + 1);
587   }
588
589   if ((!stte->tag_level[RICH_NOFILL] && ISSPACE (c)) || c == '\0') {
590     if (c == '\t')
591       stte->word_len += 8 - (stte->line_len + stte->word_len) % 8;
592     else
593       stte->word_len++;
594
595     stte->buffer[stte->buff_used++] = c;
596     enriched_flush (stte, 0);
597   }
598   else {
599     if (stte->s->flags & M_DISPLAY) {
600       if (stte->tag_level[RICH_BOLD]) {
601         stte->buffer[stte->buff_used++] = c;
602         stte->buffer[stte->buff_used++] = '\010';
603         stte->buffer[stte->buff_used++] = c;
604       }
605       else if (stte->tag_level[RICH_UNDERLINE]) {
606
607         stte->buffer[stte->buff_used++] = '_';
608         stte->buffer[stte->buff_used++] = '\010';
609         stte->buffer[stte->buff_used++] = c;
610       }
611       else if (stte->tag_level[RICH_ITALIC]) {
612         stte->buffer[stte->buff_used++] = c;
613         stte->buffer[stte->buff_used++] = '\010';
614         stte->buffer[stte->buff_used++] = '_';
615       }
616       else {
617         stte->buffer[stte->buff_used++] = c;
618       }
619     }
620     else {
621       stte->buffer[stte->buff_used++] = c;
622     }
623     stte->word_len++;
624   }
625 }
626
627 static void enriched_puts (char *s, struct enriched_state *stte)
628 {
629   char *c;
630
631   if (stte->buff_len < stte->buff_used + mutt_strlen (s)) {
632     stte->buff_len += LONG_STRING;
633     safe_realloc (&stte->buffer, stte->buff_len + 1);
634   }
635   c = s;
636   while (*c) {
637     stte->buffer[stte->buff_used++] = *c;
638     c++;
639   }
640 }
641
642 static void enriched_set_flags (const char *tag, struct enriched_state *stte)
643 {
644   const char *tagptr = tag;
645   int i, j;
646
647   if (*tagptr == '/')
648     tagptr++;
649
650   for (i = 0, j = -1; EnrichedTags[i].tag_name; i++)
651     if (ascii_strcasecmp (EnrichedTags[i].tag_name, tagptr) == 0) {
652       j = EnrichedTags[i].index;
653       break;
654     }
655
656   if (j != -1) {
657     if (j == RICH_CENTER || j == RICH_FLUSHLEFT || j == RICH_FLUSHRIGHT)
658       enriched_flush (stte, 1);
659
660     if (*tag == '/') {
661       if (stte->tag_level[j])   /* make sure not to go negative */
662         stte->tag_level[j]--;
663       if ((stte->s->flags & M_DISPLAY) && j == RICH_PARAM
664           && stte->tag_level[RICH_COLOR]) {
665         stte->param[stte->param_used] = '\0';
666         if (!ascii_strcasecmp (stte->param, "black")) {
667           enriched_puts ("\033[30m", stte);
668         }
669         else if (!ascii_strcasecmp (stte->param, "red")) {
670           enriched_puts ("\033[31m", stte);
671         }
672         else if (!ascii_strcasecmp (stte->param, "green")) {
673           enriched_puts ("\033[32m", stte);
674         }
675         else if (!ascii_strcasecmp (stte->param, "yellow")) {
676           enriched_puts ("\033[33m", stte);
677         }
678         else if (!ascii_strcasecmp (stte->param, "blue")) {
679           enriched_puts ("\033[34m", stte);
680         }
681         else if (!ascii_strcasecmp (stte->param, "magenta")) {
682           enriched_puts ("\033[35m", stte);
683         }
684         else if (!ascii_strcasecmp (stte->param, "cyan")) {
685           enriched_puts ("\033[36m", stte);
686         }
687         else if (!ascii_strcasecmp (stte->param, "white")) {
688           enriched_puts ("\033[37m", stte);
689         }
690       }
691       if ((stte->s->flags & M_DISPLAY) && j == RICH_COLOR) {
692         enriched_puts ("\033[0m", stte);
693       }
694
695       /* flush parameter buffer when closing the tag */
696       if (j == RICH_PARAM) {
697         stte->param_used = 0;
698         stte->param[0] = '\0';
699       }
700     }
701     else
702       stte->tag_level[j]++;
703
704     if (j == RICH_EXCERPT)
705       enriched_flush (stte, 1);
706   }
707 }
708
709 void text_enriched_handler (BODY * a, STATE * s)
710 {
711   enum {
712     TEXT, LANGLE, TAG, BOGUS_TAG, NEWLINE, ST_EOF, DONE
713   } state = TEXT;
714
715   long bytes = a->length;
716   struct enriched_state stte;
717   int c = 0;
718   int tag_len = 0;
719   char tag[LONG_STRING + 1];
720
721   memset (&stte, 0, sizeof (stte));
722   stte.s = s;
723   stte.WrapMargin =
724     ((s->flags & M_DISPLAY) ? (COLS - 4) : ((COLS - 4) <
725                                             72) ? (COLS - 4) : 72);
726   stte.line_max = stte.WrapMargin * 4;
727   stte.line = (char *) safe_calloc (1, stte.line_max + 1);
728   stte.param = (char *) safe_calloc (1, STRING);
729
730   stte.param_len = STRING;
731   stte.param_used = 0;
732
733   if (s->prefix) {
734     state_puts (s->prefix, s);
735     stte.indent_len += mutt_strlen (s->prefix);
736   }
737
738   while (state != DONE) {
739     if (state != ST_EOF) {
740       if (!bytes || (c = fgetc (s->fpin)) == EOF)
741         state = ST_EOF;
742       else
743         bytes--;
744     }
745
746     switch (state) {
747     case TEXT:
748       switch (c) {
749       case '<':
750         state = LANGLE;
751         break;
752
753       case '\n':
754         if (stte.tag_level[RICH_NOFILL]) {
755           enriched_flush (&stte, 1);
756         }
757         else {
758           enriched_putc (' ', &stte);
759           state = NEWLINE;
760         }
761         break;
762
763       default:
764         enriched_putc (c, &stte);
765       }
766       break;
767
768     case LANGLE:
769       if (c == '<') {
770         enriched_putc (c, &stte);
771         state = TEXT;
772         break;
773       }
774       else {
775         tag_len = 0;
776         state = TAG;
777       }
778       /* Yes, fall through (it wasn't a <<, so this char is first in TAG) */
779     case TAG:
780       if (c == '>') {
781         tag[tag_len] = '\0';
782         enriched_set_flags (tag, &stte);
783         state = TEXT;
784       }
785       else if (tag_len < LONG_STRING)   /* ignore overly long tags */
786         tag[tag_len++] = c;
787       else
788         state = BOGUS_TAG;
789       break;
790
791     case BOGUS_TAG:
792       if (c == '>')
793         state = TEXT;
794       break;
795
796     case NEWLINE:
797       if (c == '\n')
798         enriched_flush (&stte, 1);
799       else {
800         ungetc (c, s->fpin);
801         bytes++;
802         state = TEXT;
803       }
804       break;
805
806     case ST_EOF:
807       enriched_putc ('\0', &stte);
808       enriched_flush (&stte, 1);
809       state = DONE;
810       break;
811
812     case DONE:                 /* not reached, but gcc complains if this is absent */
813       break;
814     }
815   }
816
817   state_putc ('\n', s);         /* add a final newline */
818
819   FREE (&(stte.buffer));
820   FREE (&(stte.line));
821   FREE (&(stte.param));
822 }
823
824 /*
825  * An implementation of RFC 2646.
826  *
827  * NOTE: This still has to be made UTF-8 aware.
828  *
829  */
830
831 #define FLOWED_MAX 77
832
833 #if 0
834 static int flowed_maybe_quoted (char *cont)
835 {
836   return regexec ((regex_t *) QuoteRegexp.rx, cont, 0, NULL, 0) == 0;
837 }
838
839 static void flowed_quote (STATE * s, int level)
840 {
841   int i;
842
843   if (s->prefix) {
844     if (option (OPTTEXTFLOWED))
845       level++;
846     else
847       state_puts (s->prefix, s);
848   }
849
850   for (i = 0; i < level; i++)
851     state_putc ('>', s);
852 }
853
854 static void flowed_stuff (STATE * s, char *cont, int level)
855 {
856   if (!option (OPTTEXTFLOWED) && !(s->flags & M_DISPLAY))
857     return;
858
859   if (s->flags & M_DISPLAY) {
860     /* 
861      * Hack: If we are in the beginning of the line and there is 
862      * some text on the line which looks like it's quoted, turn off 
863      * ANSI colors, so quote coloring doesn't affect this line. 
864      */
865     if (*cont && !level && !mutt_strcmp (Pager, "builtin")
866         && flowed_maybe_quoted (cont))
867       state_puts ("\033[0m", s);
868   }
869   else if ((!(s->flags & M_PRINTING)) && ((*cont == ' ') || (*cont == '>')
870                                           || (!level
871                                               && !mutt_strncmp (cont, "From ",
872                                                                 5))))
873     state_putc (' ', s);
874 }
875
876 static char *flowed_skip_indent (char *prefix, char *cont)
877 {
878   for (; *cont == ' ' || *cont == '\t'; cont++)
879     *prefix++ = *cont;
880   *prefix = '\0';
881   return cont;
882 }
883
884 static int flowed_visual_strlen (char *l, int i)
885 {
886   int j;
887
888   for (j = 0; *l; l++) {
889     if (*l == '\t')
890       j += 8 - ((i + j) % 8);
891     else
892       j++;
893   }
894
895   return j;
896 }
897
898 static void text_plain_flowed_handler (BODY * a, STATE * s)
899 {
900   char line[LONG_STRING];
901   char indent[LONG_STRING];
902
903   int quoted = -1;
904   int last_quoted;
905   int full = 1;
906   int last_full;
907   int col = 0, tmpcol;
908
909   int i_add = 0;
910   int add = 0;
911   int soft = 0;
912   int l, rl;
913
914   int flowed_max;
915   int bytes = a->length;
916   int actually_wrap = 0;
917
918   char *cont = NULL;
919   char *tail = NULL;
920   char *lc = NULL;
921   char *t;
922
923   *indent = '\0';
924
925   if (s->prefix)
926     add = 1;
927
928   /*
929      if ((flowed_max = FLOWED_MAX) > COLS - 3)
930      flowed_max = COLS - 3;
931      if (flowed_max > COLS - WrapMargin)
932      flowed_max = COLS - WrapMargin;
933      if (flowed_max <= 0)
934      flowed_max = COLS;
935    */
936   flowed_max = COLS - WrapMargin;
937   if (flowed_max <= 0)
938     flowed_max = COLS;
939
940   fprintf (stderr, "flowed_max = %d\n", flowed_max);
941
942   while (bytes > 0 && fgets (line, sizeof (line), s->fpin)) {
943     bytes -= mutt_strlen (line);
944     tail = NULL;
945
946     last_full = full;
947
948     /* 
949      * If the last line wasn't fully read, this is the
950      * tail of some line. 
951      */
952     actually_wrap = !last_full;
953
954     if ((t = strrchr (line, '\r')) || (t = strrchr (line, '\n'))) {
955       *t = '\0';
956       full = 1;
957     }
958     else if ((t = strrchr (line, ' ')) || (t = strrchr (line, '\t'))) {
959       /* 
960        * Bad: We have a line of more than LONG_STRING characters.
961        * (Which SHOULD NOT happen, since lines SHOULD be <= 79
962        * characters long.)
963        * 
964        * Try to simulate a soft line break at a word boundary.
965        * Handle the rest of the line next time.
966        * 
967        * Give up when we have a single word which is longer than
968        * LONG_STRING characters.  It will just be split into parts,
969        * with a hard line break in between. 
970        */
971
972       full = 0;
973       l = mutt_strlen (t + 1);
974       t[0] = ' ';
975       t[1] = '\0';
976
977       if (l) {
978         fseek (s->fpin, -l, SEEK_CUR);
979         bytes += l;
980       }
981     }
982     else
983       full = 0;
984
985     last_quoted = quoted;
986
987     if (last_full) {
988       /* 
989        * We are in the beginning of a new line. Determine quote level
990        * and indentation prefix 
991        */
992       for (quoted = 0; line[quoted] == '>'; quoted++);
993
994       cont = line + quoted;
995
996       /* undo space stuffing */
997       if (*cont == ' ')
998         cont++;
999
1000       /* If there is an indentation, record it. */
1001       cont = flowed_skip_indent (indent, cont);
1002       i_add = flowed_visual_strlen (indent, quoted + add);
1003     }
1004     else {
1005       /* 
1006        * This is just the tail of some over-long line. Keep
1007        * indentation and quote levels.  Don't unstuff.
1008        */
1009       cont = line;
1010     }
1011
1012     /* If we have a change in quoting depth, wrap. */
1013
1014     if (col && last_quoted != quoted && last_quoted >= 0) {
1015       state_putc ('\n', s);
1016       col = 0;
1017     }
1018
1019     do {
1020       if (tail)
1021         cont = tail;
1022
1023       SKIPWS (cont);
1024
1025       tail = NULL;
1026       soft = 0;
1027
1028       /* try to find a point for word wrapping */
1029
1030     retry_wrap:
1031       l = flowed_visual_strlen (cont, quoted + i_add + add + col);
1032       rl = mutt_strlen (cont);
1033       if (quoted + i_add + add + col + l > flowed_max) {
1034         actually_wrap = 1;
1035
1036         for (tmpcol = quoted + i_add + add + col, t = cont;
1037              *t && tmpcol < flowed_max; t++) {
1038           if (*t == ' ' || *t == '\t')
1039             tail = t;
1040           if (*t == '\t')
1041             tmpcol = (tmpcol & ~7) + 8;
1042           else
1043             tmpcol++;
1044         }
1045
1046         if (tail) {
1047           *tail++ = '\0';
1048           soft = 2;
1049         }
1050       }
1051
1052       /* We seem to be desperate.  Get me a new line, and retry. */
1053       if (!tail && (quoted + add + col + i_add + l > flowed_max) && col) {
1054         state_putc ('\n', s);
1055         col = 0;
1056         goto retry_wrap;
1057       }
1058
1059       /* Detect soft line breaks. */
1060       if (!soft && ascii_strcmp (cont, "-- ")) {
1061         lc = strrchr (cont, ' ');
1062         if (lc && lc[1] == '\0')
1063           soft = 1;
1064       }
1065
1066       /* 
1067        * If we are in the beginning of an output line, do quoting
1068        * and stuffing. 
1069        * 
1070        * We have to temporarily assemble the line since display
1071        * stuffing (i.e., turning off quote coloring) may depend on
1072        * the line's actual content.  You never know what people put
1073        * into their regular expressions. 
1074        */
1075       if (!col) {
1076         char tmp[LONG_STRING];
1077
1078         snprintf (tmp, sizeof (tmp), "%s%s", indent, cont);
1079
1080         flowed_quote (s, quoted);
1081         flowed_stuff (s, tmp, quoted + add);
1082
1083         state_puts (indent, s);
1084       }
1085
1086       /* output the text */
1087       state_puts (cont, s);
1088       col += flowed_visual_strlen (cont, quoted + i_add + add + col);
1089
1090       /* possibly indicate a soft line break */
1091       if (soft == 2) {
1092         state_putc (' ', s);
1093         col++;
1094       }
1095
1096       /* 
1097        * Wrap if this display line corresponds to a 
1098        * text line. Don't wrap if we changed the line.
1099        */
1100       if (!soft || (!actually_wrap && full)) {
1101         state_putc ('\n', s);
1102         col = 0;
1103       }
1104     }
1105     while (tail);
1106   }
1107
1108   if (col)
1109     state_putc ('\n', s);
1110
1111 }
1112 #endif
1113
1114 static int get_quote_level (char *line)
1115 {
1116   int quoted;
1117
1118   for (quoted = 0; line[quoted] == '>'; quoted++);
1119   return quoted;
1120 }
1121
1122 static void print_flowed_line (char *line, STATE * s, int ql)
1123 {
1124   int width;
1125   char *pos, *oldpos;
1126   int len = mutt_strlen (line);
1127   int i;
1128
1129   if (MaxLineLength > 0) {
1130     width = MaxLineLength - WrapMargin - ql - 1;
1131     if (option (OPTSTUFFQUOTED))
1132       --width;
1133     if (width < 0)
1134       width = MaxLineLength;
1135   }
1136   else {
1137     if (option (OPTMBOXPANE))
1138       width = COLS - SidebarWidth - WrapMargin - ql - 1;
1139     else
1140       width = COLS - WrapMargin - ql - 1;
1141
1142     if (option (OPTSTUFFQUOTED))
1143       --width;
1144     if (width < 0)
1145       width = COLS;
1146   }
1147
1148   /* fprintf(stderr,"print_flowed_line will print `%s' with ql = %d\n",line,ql); */
1149
1150   if (mutt_strlen (line) == 0) {
1151     if (option (OPTQUOTEEMPTY)) {
1152       if (s->prefix)
1153         state_puts(s->prefix,s);
1154       for (i=0;i<ql;++i) state_putc('>',s);
1155       if (option(OPTSTUFFQUOTED))
1156         state_putc(' ',s);
1157     }
1158     state_putc('\n',s);
1159     return;
1160   }
1161
1162   pos = line + ql + width;
1163   oldpos = line + ql;
1164   if (ql > 0 && ISBLANK (*oldpos))
1165     ++oldpos;
1166
1167   /* fprintf(stderr,"oldpos = %p line+len = %p\n",oldpos,line+len); */
1168
1169   for (; oldpos < line + len; pos += width) {
1170     /* fprintf(stderr,"outer for loop\n"); */
1171     if (pos < line + len) {     /* only search a new position when we're not over the end of the string w/ pos */
1172       /* fprintf(stderr,"if 1\n"); */
1173       if (*pos == ' ') {
1174         /* fprintf(stderr,"if 2: good luck! found a space\n"); */
1175         *pos = '\0';
1176         ++pos;
1177       }
1178       else {
1179         /* fprintf(stderr,"if 2: else\n"); */
1180         char *save = pos;
1181
1182         while (pos >= oldpos && !isspace (*pos)) {
1183           /* fprintf(stderr,"pos(%p) > oldpos(%p)\n",pos,oldpos); */
1184           --pos;
1185         }
1186         if (pos < oldpos) {
1187           /* fprintf(stderr,"wow, no space found, searching the other direction\n"); */
1188           pos = save;
1189           while (pos < line + len && *pos && !isspace (*pos)) {
1190             /* fprintf(stderr,"pos(%p) < line+len(%p)\n",pos,line+len); */
1191             ++pos;
1192           }
1193           /* fprintf(stderr,"found a space pos = %p\n",pos); */
1194         }
1195         *pos = '\0';
1196         ++pos;
1197       }
1198     }
1199     else {
1200       /* fprintf(stderr,"if 1 else\n"); */
1201     }
1202     if (s->prefix)
1203       state_puts (s->prefix, s);
1204     for (i = 0; i < ql; ++i)
1205       state_putc ('>', s);
1206     if (option (OPTSTUFFQUOTED) && (ql > 0 || s->prefix))
1207       state_putc (' ', s);
1208     state_puts (oldpos, s);
1209     /* fprintf(stderr,"print_flowed_line: `%s'\n",oldpos); */
1210     if (pos < line + len)
1211       state_putc (' ', s);
1212     state_putc ('\n', s);
1213     oldpos = pos;
1214   }
1215   /*state_puts(line,s);
1216      state_putc('\n',s); */
1217 }
1218
1219 static void text_plain_flowed_handler (BODY * a, STATE * s)
1220 {
1221   int bytes = a->length;
1222   char buf[LONG_STRING];
1223   char *curline = strdup ("");
1224   char *t;
1225   unsigned int curline_len = 1;
1226   unsigned int quotelevel = 0, newql = 0;
1227   int append_next_line = 0;
1228   int first_line = 1;
1229
1230   while (bytes > 0 && fgets (buf, sizeof (buf), s->fpin)) {
1231
1232     /* fprintf(stderr,"read `%s'",buf); */
1233     bytes -= mutt_strlen (buf);
1234
1235     newql = get_quote_level (buf);
1236
1237     if ((t = strrchr (buf, '\n')) || (t = strrchr (buf, '\r'))) {
1238       *t = '\0';
1239       if (mutt_strlen (curline) > 0 && curline[mutt_strlen (curline) - 1] == ' '
1240           && newql == quotelevel
1241           && strcmp (curline + quotelevel, "-- ") != 0) {
1242         if (buf[newql] == ' ')
1243           curline[mutt_strlen (curline) - 1] = '\0';
1244
1245         curline = realloc (curline, curline_len + mutt_strlen (buf));
1246         if (curline_len == 1)
1247           *curline = '\0';
1248         curline_len += mutt_strlen (buf);
1249         safe_strncat (curline, curline_len, buf + newql,
1250                       mutt_strlen (buf + newql));
1251       }
1252       else {
1253         if (first_line) {
1254           first_line = 0;
1255         }
1256         else {
1257           print_flowed_line (curline, s, quotelevel);
1258         }
1259         FREE (&curline);
1260         curline_len = 1;
1261         curline = realloc (curline, curline_len + mutt_strlen (buf));
1262         if (curline_len == 1)
1263           *curline = '\0';
1264         curline_len += mutt_strlen (buf);
1265         safe_strncat (curline, curline_len, buf, mutt_strlen (buf));
1266         quotelevel = newql;
1267       }
1268     }
1269     else {
1270       append_next_line = 1;
1271       /* @todo: add handling of very long lines */
1272     }
1273   }
1274   if (curline) {
1275     print_flowed_line (curline, s, quotelevel);
1276     FREE (&curline);
1277   }
1278 }
1279
1280
1281
1282 #define TXTHTML     1
1283 #define TXTPLAIN    2
1284 #define TXTENRICHED 3
1285
1286 static void alternative_handler (BODY * a, STATE * s)
1287 {
1288   BODY *choice = NULL;
1289   BODY *b;
1290   LIST *t;
1291   char buf[STRING];
1292   int type = 0;
1293   int mustfree = 0;
1294
1295   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
1296       a->encoding == ENCUUENCODED) {
1297     struct stat st;
1298
1299     mustfree = 1;
1300     fstat (fileno (s->fpin), &st);
1301     b = mutt_new_body ();
1302     b->length = (long) st.st_size;
1303     b->parts = mutt_parse_multipart (s->fpin,
1304                                      mutt_get_parameter ("boundary",
1305                                                          a->parameter),
1306                                      (long) st.st_size,
1307                                      ascii_strcasecmp ("digest",
1308                                                        a->subtype) == 0);
1309   }
1310   else
1311     b = a;
1312
1313   a = b;
1314
1315   /* First, search list of prefered types */
1316   t = AlternativeOrderList;
1317   while (t && !choice) {
1318     char *c;
1319     int btlen;                  /* length of basetype */
1320     int wild;                   /* do we have a wildcard to match all subtypes? */
1321
1322     c = strchr (t->data, '/');
1323     if (c) {
1324       wild = (c[1] == '*' && c[2] == 0);
1325       btlen = c - t->data;
1326     }
1327     else {
1328       wild = 1;
1329       btlen = mutt_strlen (t->data);
1330     }
1331
1332     if (a && a->parts)
1333       b = a->parts;
1334     else
1335       b = a;
1336     while (b) {
1337       const char *bt = TYPE (b);
1338
1339       if (!ascii_strncasecmp (bt, t->data, btlen) && bt[btlen] == 0) {
1340         /* the basetype matches */
1341         if (wild || !ascii_strcasecmp (t->data + btlen + 1, b->subtype)) {
1342           choice = b;
1343         }
1344       }
1345       b = b->next;
1346     }
1347     t = t->next;
1348   }
1349
1350   /* Next, look for an autoviewable type */
1351   if (!choice) {
1352     if (a && a->parts)
1353       b = a->parts;
1354     else
1355       b = a;
1356     while (b) {
1357       snprintf (buf, sizeof (buf), "%s/%s", TYPE (b), b->subtype);
1358       if (mutt_is_autoview (b, buf)) {
1359         rfc1524_entry *entry = rfc1524_new_entry ();
1360
1361         if (rfc1524_mailcap_lookup (b, buf, entry, M_AUTOVIEW)) {
1362           choice = b;
1363         }
1364         rfc1524_free_entry (&entry);
1365       }
1366       b = b->next;
1367     }
1368   }
1369
1370   /* Then, look for a text entry */
1371   if (!choice) {
1372     if (a && a->parts)
1373       b = a->parts;
1374     else
1375       b = a;
1376     while (b) {
1377       if (b->type == TYPETEXT) {
1378         if (!ascii_strcasecmp ("plain", b->subtype) && type <= TXTPLAIN) {
1379           choice = b;
1380           type = TXTPLAIN;
1381         }
1382         else if (!ascii_strcasecmp ("enriched", b->subtype)
1383                  && type <= TXTENRICHED) {
1384           choice = b;
1385           type = TXTENRICHED;
1386         }
1387         else if (!ascii_strcasecmp ("html", b->subtype) && type <= TXTHTML) {
1388           choice = b;
1389           type = TXTHTML;
1390         }
1391       }
1392       b = b->next;
1393     }
1394   }
1395
1396   /* Finally, look for other possibilities */
1397   if (!choice) {
1398     if (a && a->parts)
1399       b = a->parts;
1400     else
1401       b = a;
1402     while (b) {
1403       if (mutt_can_decode (b))
1404         choice = b;
1405       b = b->next;
1406     }
1407   }
1408
1409   if (choice) {
1410     if (s->flags & M_DISPLAY && !option (OPTWEED)) {
1411       fseek (s->fpin, choice->hdr_offset, 0);
1412       mutt_copy_bytes (s->fpin, s->fpout,
1413                        choice->offset - choice->hdr_offset);
1414     }
1415     mutt_body_handler (choice, s);
1416   }
1417   else if (s->flags & M_DISPLAY) {
1418     /* didn't find anything that we could display! */
1419     state_mark_attach (s);
1420     state_puts (_
1421                 ("[-- Error:  Could not display any parts of Multipart/Alternative! --]\n"),
1422                 s);
1423   }
1424
1425   if (mustfree)
1426     mutt_free_body (&a);
1427 }
1428
1429 /* handles message/rfc822 body parts */
1430 void message_handler (BODY * a, STATE * s)
1431 {
1432   struct stat st;
1433   BODY *b;
1434   long off_start;
1435
1436   off_start = ftell (s->fpin);
1437   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
1438       a->encoding == ENCUUENCODED) {
1439     fstat (fileno (s->fpin), &st);
1440     b = mutt_new_body ();
1441     b->length = (long) st.st_size;
1442     b->parts = mutt_parse_messageRFC822 (s->fpin, b);
1443   }
1444   else
1445     b = a;
1446
1447   if (b->parts) {
1448     mutt_copy_hdr (s->fpin, s->fpout, off_start, b->parts->offset,
1449                    (((s->flags & M_WEED)
1450                      || ((s->flags & (M_DISPLAY | M_PRINTING))
1451                          && option (OPTWEED))) ? (CH_WEED | CH_REORDER) : 0) |
1452                    (s->prefix ? CH_PREFIX : 0) | CH_DECODE | CH_FROM,
1453                    s->prefix);
1454
1455     if (s->prefix)
1456       state_puts (s->prefix, s);
1457     state_putc ('\n', s);
1458
1459     mutt_body_handler (b->parts, s);
1460   }
1461
1462   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
1463       a->encoding == ENCUUENCODED)
1464     mutt_free_body (&b);
1465 }
1466
1467 /* returns 1 if decoding the attachment will produce output */
1468 int mutt_can_decode (BODY * a)
1469 {
1470   char type[STRING];
1471
1472   snprintf (type, sizeof (type), "%s/%s", TYPE (a), a->subtype);
1473   if (mutt_is_autoview (a, type))
1474     return (rfc1524_mailcap_lookup (a, type, NULL, M_AUTOVIEW));
1475   else if (a->type == TYPETEXT)
1476     return (1);
1477   else if (a->type == TYPEMESSAGE)
1478     return (1);
1479   else if (a->type == TYPEMULTIPART) {
1480     BODY *p;
1481
1482     if (WithCrypto) {
1483       if (ascii_strcasecmp (a->subtype, "signed") == 0 ||
1484           ascii_strcasecmp (a->subtype, "encrypted") == 0)
1485         return (1);
1486     }
1487
1488     for (p = a->parts; p; p = p->next) {
1489       if (mutt_can_decode (p))
1490         return (1);
1491     }
1492
1493   }
1494   else if (WithCrypto && a->type == TYPEAPPLICATION) {
1495     if ((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp (a))
1496       return (1);
1497     if ((WithCrypto & APPLICATION_SMIME) && mutt_is_application_smime (a))
1498       return (1);
1499   }
1500
1501   return (0);
1502 }
1503
1504 void multipart_handler (BODY * a, STATE * s)
1505 {
1506   BODY *b, *p;
1507   char length[5];
1508   struct stat st;
1509   int count;
1510
1511   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
1512       a->encoding == ENCUUENCODED) {
1513     fstat (fileno (s->fpin), &st);
1514     b = mutt_new_body ();
1515     b->length = (long) st.st_size;
1516     b->parts = mutt_parse_multipart (s->fpin,
1517                                      mutt_get_parameter ("boundary",
1518                                                          a->parameter),
1519                                      (long) st.st_size,
1520                                      ascii_strcasecmp ("digest",
1521                                                        a->subtype) == 0);
1522   }
1523   else
1524     b = a;
1525
1526   for (p = b->parts, count = 1; p; p = p->next, count++) {
1527     if (s->flags & M_DISPLAY) {
1528       state_mark_attach (s);
1529       state_printf (s, _("[-- Attachment #%d"), count);
1530       if (p->description || p->filename || p->form_name) {
1531         state_puts (": ", s);
1532         state_puts (p->description ? p->description :
1533                     p->filename ? p->filename : p->form_name, s);
1534       }
1535       state_puts (" --]\n", s);
1536
1537       mutt_pretty_size (length, sizeof (length), p->length);
1538
1539       state_mark_attach (s);
1540       state_printf (s, _("[-- Type: %s/%s, Encoding: %s, Size: %s --]\n"),
1541                     TYPE (p), p->subtype, ENCODING (p->encoding), length);
1542       if (!option (OPTWEED)) {
1543         fseek (s->fpin, p->hdr_offset, 0);
1544         mutt_copy_bytes (s->fpin, s->fpout, p->offset - p->hdr_offset);
1545       }
1546       else
1547         state_putc ('\n', s);
1548     }
1549     else {
1550       if (p->description && mutt_can_decode (p))
1551         state_printf (s, "Content-Description: %s\n", p->description);
1552
1553       if (p->form_name)
1554         state_printf (s, "%s: \n", p->form_name);
1555
1556     }
1557     mutt_body_handler (p, s);
1558     state_putc ('\n', s);
1559     if ((s->flags & M_REPLYING)
1560         && (option (OPTINCLUDEONLYFIRST)) && (s->flags & M_FIRSTDONE))
1561       break;
1562   }
1563
1564   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
1565       a->encoding == ENCUUENCODED)
1566     mutt_free_body (&b);
1567 }
1568
1569 void autoview_handler (BODY * a, STATE * s)
1570 {
1571   rfc1524_entry *entry = rfc1524_new_entry ();
1572   char buffer[LONG_STRING];
1573   char type[STRING];
1574   char command[LONG_STRING];
1575   char tempfile[_POSIX_PATH_MAX] = "";
1576   char *fname;
1577   FILE *fpin = NULL;
1578   FILE *fpout = NULL;
1579   FILE *fperr = NULL;
1580   int piped = FALSE;
1581   pid_t thepid;
1582
1583   snprintf (type, sizeof (type), "%s/%s", TYPE (a), a->subtype);
1584   rfc1524_mailcap_lookup (a, type, entry, M_AUTOVIEW);
1585
1586   fname = safe_strdup (a->filename);
1587   mutt_sanitize_filename (fname, 1);
1588   rfc1524_expand_filename (entry->nametemplate, fname, tempfile,
1589                            sizeof (tempfile));
1590   FREE (&fname);
1591
1592   if (entry->command) {
1593     strfcpy (command, entry->command, sizeof (command));
1594
1595     /* rfc1524_expand_command returns 0 if the file is required */
1596     piped =
1597       rfc1524_expand_command (a, tempfile, type, command, sizeof (command));
1598
1599     if (s->flags & M_DISPLAY) {
1600       state_mark_attach (s);
1601       state_printf (s, _("[-- Autoview using %s --]\n"), command);
1602       mutt_message (_("Invoking autoview command: %s"), command);
1603     }
1604
1605     if ((fpin = safe_fopen (tempfile, "w+")) == NULL) {
1606       mutt_perror ("fopen");
1607       rfc1524_free_entry (&entry);
1608       return;
1609     }
1610
1611     mutt_copy_bytes (s->fpin, fpin, a->length);
1612
1613     if (!piped) {
1614       safe_fclose (&fpin);
1615       thepid = mutt_create_filter (command, NULL, &fpout, &fperr);
1616     }
1617     else {
1618       unlink (tempfile);
1619       fflush (fpin);
1620       rewind (fpin);
1621       thepid = mutt_create_filter_fd (command, NULL, &fpout, &fperr,
1622                                       fileno (fpin), -1, -1);
1623     }
1624
1625     if (thepid < 0) {
1626       mutt_perror _("Can't create filter");
1627
1628       if (s->flags & M_DISPLAY) {
1629         state_mark_attach (s);
1630         state_printf (s, _("[-- Can't run %s. --]\n"), command);
1631       }
1632       goto bail;
1633     }
1634
1635     if (s->prefix) {
1636       while (fgets (buffer, sizeof (buffer), fpout) != NULL) {
1637         state_puts (s->prefix, s);
1638         state_puts (buffer, s);
1639       }
1640       /* check for data on stderr */
1641       if (fgets (buffer, sizeof (buffer), fperr)) {
1642         if (s->flags & M_DISPLAY) {
1643           state_mark_attach (s);
1644           state_printf (s, _("[-- Autoview stderr of %s --]\n"), command);
1645         }
1646
1647         state_puts (s->prefix, s);
1648         state_puts (buffer, s);
1649         while (fgets (buffer, sizeof (buffer), fperr) != NULL) {
1650           state_puts (s->prefix, s);
1651           state_puts (buffer, s);
1652         }
1653       }
1654     }
1655     else {
1656       mutt_copy_stream (fpout, s->fpout);
1657       /* Check for stderr messages */
1658       if (fgets (buffer, sizeof (buffer), fperr)) {
1659         if (s->flags & M_DISPLAY) {
1660           state_mark_attach (s);
1661           state_printf (s, _("[-- Autoview stderr of %s --]\n"), command);
1662         }
1663
1664         state_puts (buffer, s);
1665         mutt_copy_stream (fperr, s->fpout);
1666       }
1667     }
1668
1669   bail:
1670     safe_fclose (&fpout);
1671     safe_fclose (&fperr);
1672
1673     mutt_wait_filter (thepid);
1674     if (piped)
1675       safe_fclose (&fpin);
1676     else
1677       mutt_unlink (tempfile);
1678
1679     if (s->flags & M_DISPLAY)
1680       mutt_clear_error ();
1681   }
1682   rfc1524_free_entry (&entry);
1683 }
1684
1685 static void external_body_handler (BODY * b, STATE * s)
1686 {
1687   const char *access_type;
1688   const char *expiration;
1689   time_t expire;
1690
1691   access_type = mutt_get_parameter ("access-type", b->parameter);
1692   if (!access_type) {
1693     if (s->flags & M_DISPLAY) {
1694       state_mark_attach (s);
1695       state_puts (_
1696                   ("[-- Error: message/external-body has no access-type parameter --]\n"),
1697                   s);
1698     }
1699     return;
1700   }
1701
1702   expiration = mutt_get_parameter ("expiration", b->parameter);
1703   if (expiration)
1704     expire = mutt_parse_date (expiration, NULL);
1705   else
1706     expire = -1;
1707
1708   if (!ascii_strcasecmp (access_type, "x-mutt-deleted")) {
1709     if (s->flags & (M_DISPLAY | M_PRINTING)) {
1710       char *length;
1711       char pretty_size[10];
1712
1713       state_mark_attach (s);
1714       state_printf (s, _("[-- This %s/%s attachment "),
1715                     TYPE (b->parts), b->parts->subtype);
1716       length = mutt_get_parameter ("length", b->parameter);
1717       if (length) {
1718         mutt_pretty_size (pretty_size, sizeof (pretty_size),
1719                           strtol (length, NULL, 10));
1720         state_printf (s, _("(size %s bytes) "), pretty_size);
1721       }
1722       state_puts (_("has been deleted --]\n"), s);
1723
1724       if (expire != -1) {
1725         state_mark_attach (s);
1726         state_printf (s, _("[-- on %s --]\n"), expiration);
1727       }
1728       if (b->parts->filename) {
1729         state_mark_attach (s);
1730         state_printf (s, _("[-- name: %s --]\n"), b->parts->filename);
1731       }
1732
1733       mutt_copy_hdr (s->fpin, s->fpout, ftell (s->fpin), b->parts->offset,
1734                      (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) |
1735                      CH_DECODE, NULL);
1736     }
1737   }
1738   else if (expiration && expire < time (NULL)) {
1739     if (s->flags & M_DISPLAY) {
1740       state_mark_attach (s);
1741       state_printf (s, _("[-- This %s/%s attachment is not included, --]\n"),
1742                     TYPE (b->parts), b->parts->subtype);
1743       state_attach_puts (_("[-- and the indicated external source has --]\n"
1744                            "[-- expired. --]\n"), s);
1745
1746       mutt_copy_hdr (s->fpin, s->fpout, ftell (s->fpin), b->parts->offset,
1747                      (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) |
1748                      CH_DECODE, NULL);
1749     }
1750   }
1751   else {
1752     if (s->flags & M_DISPLAY) {
1753       state_mark_attach (s);
1754       state_printf (s,
1755                     _("[-- This %s/%s attachment is not included, --]\n"),
1756                     TYPE (b->parts), b->parts->subtype);
1757       state_mark_attach (s);
1758       state_printf (s,
1759                     _
1760                     ("[-- and the indicated access-type %s is unsupported --]\n"),
1761                     access_type);
1762       mutt_copy_hdr (s->fpin, s->fpout, ftell (s->fpin), b->parts->offset,
1763                      (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) |
1764                      CH_DECODE, NULL);
1765     }
1766   }
1767 }
1768
1769 void mutt_decode_attachment (BODY * b, STATE * s)
1770 {
1771   int istext = mutt_is_text_part (b);
1772   iconv_t cd = (iconv_t) (-1);
1773
1774   Quotebuf[0] = '\0';
1775
1776   if (istext) {
1777     if (s->flags & M_CHARCONV) {
1778       char *charset = mutt_get_parameter ("charset", b->parameter);
1779
1780       if (!option (OPTSTRICTMIME) && !charset)
1781         charset = mutt_get_first_charset (AssumedCharset);
1782       if (charset && Charset)
1783         cd = mutt_iconv_open (Charset, charset, M_ICONV_HOOK_FROM);
1784     }
1785     else {
1786       if (b->file_charset)
1787         cd = mutt_iconv_open (Charset, b->file_charset, M_ICONV_HOOK_FROM);
1788     }
1789   }
1790
1791   fseek (s->fpin, b->offset, 0);
1792   switch (b->encoding) {
1793   case ENCQUOTEDPRINTABLE:
1794     mutt_decode_quoted (s, b->length, istext, cd);
1795     break;
1796   case ENCBASE64:
1797     mutt_decode_base64 (s, b->length, istext, cd);
1798     break;
1799   case ENCUUENCODED:
1800     mutt_decode_uuencoded (s, b->length, istext, cd);
1801     break;
1802   default:
1803     mutt_decode_xbit (s, b->length, istext, cd);
1804     break;
1805   }
1806
1807   if (cd != (iconv_t) (-1))
1808     iconv_close (cd);
1809 }
1810
1811 void mutt_body_handler (BODY * b, STATE * s)
1812 {
1813   int decode = 0;
1814   int plaintext = 0;
1815   FILE *fp = NULL;
1816   char tempfile[_POSIX_PATH_MAX];
1817   handler_t handler = NULL;
1818   long tmpoffset = 0;
1819   size_t tmplength = 0;
1820   char type[STRING];
1821
1822   int oflags = s->flags;
1823
1824   /* first determine which handler to use to process this part */
1825
1826   snprintf (type, sizeof (type), "%s/%s", TYPE (b), b->subtype);
1827   if (mutt_is_autoview (b, type)) {
1828     rfc1524_entry *entry = rfc1524_new_entry ();
1829
1830     if (rfc1524_mailcap_lookup (b, type, entry, M_AUTOVIEW)) {
1831       handler = autoview_handler;
1832       s->flags &= ~M_CHARCONV;
1833     }
1834     rfc1524_free_entry (&entry);
1835   }
1836   else if (b->type == TYPETEXT) {
1837     if (ascii_strcasecmp ("plain", b->subtype) == 0) {
1838       /* avoid copying this part twice since removing the transfer-encoding is
1839        * the only operation needed.
1840        */
1841       if ((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp (b))
1842         handler = crypt_pgp_application_pgp_handler;
1843       else
1844         if (ascii_strcasecmp
1845             ("flowed", mutt_get_parameter ("format", b->parameter)) == 0)
1846         handler = text_plain_flowed_handler;
1847       else
1848         plaintext = 1;
1849     }
1850     else if (ascii_strcasecmp ("enriched", b->subtype) == 0)
1851       handler = text_enriched_handler;
1852     else                        /* text body type without a handler */
1853       plaintext = 1;
1854   }
1855   else if (b->type == TYPEMESSAGE) {
1856     if (mutt_is_message_type (b->type, b->subtype))
1857       handler = message_handler;
1858     else if (!ascii_strcasecmp ("delivery-status", b->subtype))
1859       plaintext = 1;
1860     else if (!ascii_strcasecmp ("external-body", b->subtype))
1861       handler = external_body_handler;
1862   }
1863   else if (b->type == TYPEMULTIPART) {
1864     char *p;
1865
1866     if (ascii_strcasecmp ("alternative", b->subtype) == 0)
1867       handler = alternative_handler;
1868     else if (WithCrypto && ascii_strcasecmp ("signed", b->subtype) == 0) {
1869       p = mutt_get_parameter ("protocol", b->parameter);
1870
1871       if (!p)
1872         mutt_error (_("Error: multipart/signed has no protocol."));
1873
1874       else if (s->flags & M_VERIFY)
1875         handler = mutt_signed_handler;
1876     }
1877     else if ((WithCrypto & APPLICATION_PGP)
1878              && mutt_strcasecmp ("encrypted", b->subtype) == 0) {
1879       p = mutt_get_parameter ("protocol", b->parameter);
1880
1881       if (!p)
1882         mutt_error (_
1883                     ("Error: multipart/encrypted has no protocol parameter!"));
1884
1885       else if (ascii_strcasecmp ("application/pgp-encrypted", p) == 0)
1886         handler = crypt_pgp_encrypted_handler;
1887     }
1888
1889     if (!handler)
1890       handler = multipart_handler;
1891   }
1892   else if (WithCrypto && b->type == TYPEAPPLICATION) {
1893     if ((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp (b))
1894       handler = crypt_pgp_application_pgp_handler;
1895     if ((WithCrypto & APPLICATION_SMIME) && mutt_is_application_smime (b))
1896       handler = crypt_smime_application_smime_handler;
1897   }
1898
1899
1900   if (plaintext || handler) {
1901     fseek (s->fpin, b->offset, 0);
1902
1903     /* see if we need to decode this part before processing it */
1904     if (b->encoding == ENCBASE64 || b->encoding == ENCQUOTEDPRINTABLE || b->encoding == ENCUUENCODED || plaintext || mutt_is_text_part (b)) {   /* text subtypes may
1905                                                                                                                                                  * require character
1906                                                                                                                                                  * set conversion even
1907                                                                                                                                                  * with 8bit encoding.
1908                                                                                                                                                  */
1909       int origType = b->type;
1910       char *savePrefix = NULL;
1911
1912       if (!plaintext) {
1913         /* decode to a tempfile, saving the original destination */
1914         fp = s->fpout;
1915         mutt_mktemp (tempfile);
1916         if ((s->fpout = safe_fopen (tempfile, "w")) == NULL) {
1917           mutt_error _("Unable to open temporary file!");
1918
1919           goto bail;
1920         }
1921         /* decoding the attachment changes the size and offset, so save a copy
1922          * of the "real" values now, and restore them after processing
1923          */
1924         tmplength = b->length;
1925         tmpoffset = b->offset;
1926
1927         /* if we are decoding binary bodies, we don't want to prefix each
1928          * line with the prefix or else the data will get corrupted.
1929          */
1930         savePrefix = s->prefix;
1931         s->prefix = NULL;
1932
1933         decode = 1;
1934       }
1935       else
1936         b->type = TYPETEXT;
1937
1938       mutt_decode_attachment (b, s);
1939
1940       if (decode) {
1941         b->length = ftell (s->fpout);
1942         b->offset = 0;
1943         fclose (s->fpout);
1944
1945         /* restore final destination and substitute the tempfile for input */
1946         s->fpout = fp;
1947         fp = s->fpin;
1948         s->fpin = safe_fopen (tempfile, "r");
1949         unlink (tempfile);
1950
1951         /* restore the prefix */
1952         s->prefix = savePrefix;
1953       }
1954
1955       b->type = origType;
1956     }
1957
1958     /* process the (decoded) body part */
1959     if (handler) {
1960       handler (b, s);
1961
1962       if (decode) {
1963         b->length = tmplength;
1964         b->offset = tmpoffset;
1965
1966         /* restore the original source stream */
1967         fclose (s->fpin);
1968         s->fpin = fp;
1969       }
1970     }
1971     s->flags |= M_FIRSTDONE;
1972   }
1973   else if (s->flags & M_DISPLAY) {
1974     state_mark_attach (s);
1975     state_printf (s, _("[-- %s/%s is unsupported "), TYPE (b), b->subtype);
1976     if (!option (OPTVIEWATTACH)) {
1977       if (km_expand_key
1978           (type, sizeof (type),
1979            km_find_func (MENU_PAGER, OP_VIEW_ATTACHMENTS)))
1980         fprintf (s->fpout, _("(use '%s' to view this part)"), type);
1981       else
1982         fputs (_("(need 'view-attachments' bound to key!)"), s->fpout);
1983     }
1984     fputs (" --]\n", s->fpout);
1985   }
1986
1987 bail:
1988   s->flags = oflags | (s->flags & M_FIRSTDONE);
1989 }