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