import lemon in the source tree.
[apps/madmutt.git] / recvattach.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
5  *
6  * This file is part of mutt-ng, see http://www.muttng.org/.
7  * It's licensed under the GNU General Public License,
8  * please see the file GPL in the top level source directory.
9  */
10
11 #include <lib-lib/lib-lib.h>
12
13 #include <lib-mime/mime.h>
14 #include <lib-sys/unix.h>
15
16 #include <lib-ui/curses.h>
17 #include <lib-ui/enter.h>
18 #include <lib-ui/menu.h>
19 #include <lib-mx/mx.h>
20 #include <lib-crypt/crypt.h>
21
22 #include "mutt.h"
23 #include "handler.h"
24 #include "recvattach.h"
25 #include "attach.h"
26 #include "copy.h"
27
28 #define SW    (option(OPTMBOXPANE)?SidebarWidth:0)
29
30 static struct mapping_t AttachHelp[] = {
31   {N_("Exit"), OP_EXIT},
32   {N_("Save"), OP_SAVE},
33   {N_("Pipe"), OP_PIPE},
34   {N_("Print"), OP_PRINT},
35   {N_("Help"), OP_HELP},
36   {NULL, OP_NULL}
37 };
38
39 void mutt_update_tree (ATTACHPTR ** idx, short idxlen)
40 {
41   char buf[STRING];
42   char *s;
43   int x;
44
45   for (x = 0; x < idxlen; x++) {
46     idx[x]->num = x;
47     if (2 * (idx[x]->level + 2) < ssizeof (buf)) {
48       if (idx[x]->level) {
49         s = buf + 2 * (idx[x]->level - 1);
50         *s++ = (idx[x]->content->next) ? M_TREE_LTEE : M_TREE_LLCORNER;
51         *s++ = M_TREE_HLINE;
52         *s++ = M_TREE_RARROW;
53       }
54       else
55         s = buf;
56       *s = 0;
57     }
58
59     if (idx[x]->tree) {
60       if (m_strcmp(idx[x]->tree, buf) != 0)
61         m_strreplace(&idx[x]->tree, buf);
62     }
63     else
64       idx[x]->tree = m_strdup(buf);
65
66     if (2 * (idx[x]->level + 2) < ssizeof (buf) && idx[x]->level) {
67       s = buf + 2 * (idx[x]->level - 1);
68       *s++ = (idx[x]->content->next) ? '\005' : '\006';
69       *s++ = '\006';
70     }
71   }
72 }
73
74 ATTACHPTR **mutt_gen_attach_list (BODY * m,
75                                   int parent_type,
76                                   ATTACHPTR ** idx,
77                                   short *idxlen,
78                                   short *idxmax, int level, int compose)
79 {
80   ATTACHPTR *new;
81   int i;
82
83   for (; m; m = m->next) {
84     if (*idxlen == *idxmax) {
85       p_realloc(&idx, (*idxmax) += 5);
86       for (i = *idxlen; i < *idxmax; i++)
87         idx[i] = NULL;
88     }
89
90     if (m->type == TYPEMULTIPART && m->parts
91         && (compose
92             || (parent_type == -1
93                 && ascii_strcasecmp ("alternative", m->subtype)))
94         && (!mutt_is_multipart_encrypted (m))
95       ) {
96       idx =
97         mutt_gen_attach_list (m->parts, m->type, idx, idxlen, idxmax, level,
98                               compose);
99     }
100     else {
101       if (!idx[*idxlen])
102         idx[*idxlen] = p_new(ATTACHPTR, 1);
103
104       new = idx[(*idxlen)++];
105       new->content = m;
106       m->aptr = new;
107       new->parent_type = parent_type;
108       new->level = level;
109
110       /* We don't support multipart messages in the compose menu yet */
111       if (!compose && !m->collapsed
112       &&  ((m->type == TYPEMULTIPART && !mutt_is_multipart_encrypted(m))
113       ||  mutt_is_message_type(m)))
114       {
115         idx = mutt_gen_attach_list (m->parts, m->type, idx, idxlen, idxmax,
116                                     level + 1, compose);
117       }
118     }
119   }
120
121   if (level == 0)
122     mutt_update_tree (idx, *idxlen);
123
124   return (idx);
125 }
126
127 /* %c = character set: convert?
128  * %C = character set
129  * %D = deleted flag
130  * %d = description
131  * %e = MIME content-transfer-encoding
132  * %f = filename
133  * %I = content-disposition, either I (inline) or A (attachment)
134  * %t = tagged flag
135  * %T = tree chars
136  * %m = major MIME type
137  * %M = MIME subtype
138  * %n = attachment number
139  * %s = size
140  * %u = unlink 
141  */
142 const char *
143 mutt_attach_fmt(char *dest, ssize_t destlen, char op, const char *src,
144                 const char *prefix, const char *ifstr, const char *elstr,
145                 anytype data, format_flag flags)
146 {
147   char fmt[16];
148   char tmp[STRING];
149   char charset[STRING];
150   ATTACHPTR *aptr = data.ptr;
151   int optional = (flags & M_FORMAT_OPTIONAL);
152   ssize_t l;
153
154   switch (op) {
155   case 'C':
156     if (!optional) {
157       if (mutt_is_text_part (aptr->content) &&
158           mutt_get_body_charset (charset, sizeof (charset), aptr->content))
159         mutt_format_s (dest, destlen, prefix, charset);
160       else
161         mutt_format_s (dest, destlen, prefix, "");
162     }
163     else if (!mutt_is_text_part (aptr->content) ||
164              !mutt_get_body_charset (charset, sizeof (charset),
165                                      aptr->content))
166       optional = 0;
167     break;
168   case 'c':
169     /* XXX */
170     if (!optional) {
171       snprintf (fmt, sizeof (fmt), "%%%sc", prefix);
172       snprintf (dest, destlen, fmt, aptr->content->type != TYPETEXT ||
173                 aptr->content->noconv ? 'n' : 'c');
174     }
175     else if (aptr->content->type != TYPETEXT || aptr->content->noconv)
176       optional = 0;
177     break;
178   case 'd':
179     if (!optional) {
180       if (aptr->content->description) {
181         mutt_format_s (dest, destlen, prefix, aptr->content->description);
182         break;
183       }
184       if (mutt_is_message_type(aptr->content) && MsgFmt && aptr->content->hdr)
185       {
186         char s[STRING];
187
188         _mutt_make_string (s, sizeof (s), MsgFmt, NULL, aptr->content->hdr,
189                            M_FORMAT_FORCESUBJ | M_FORMAT_MAKEPRINT |
190                            (option(OPTARROWCURSOR) ? M_FORMAT_ARROWCURSOR : 0));
191         if (*s) {
192           mutt_format_s (dest, destlen, prefix, s);
193           break;
194         }
195       }
196       if (!aptr->content->filename) {
197         mutt_format_s (dest, destlen, prefix, "<no description>");
198         break;
199       }
200     }
201     else if (aptr->content->description ||
202              (mutt_is_message_type(aptr->content)
203               && MsgFmt && aptr->content->hdr))
204       break;
205     /* FALLS THROUGH TO 'f' */
206   case 'f':
207     if (!optional) {
208       if (aptr->content->filename && *aptr->content->filename == '/') {
209         char path[_POSIX_PATH_MAX];
210
211         m_strcpy(path, sizeof(path), aptr->content->filename);
212         mutt_pretty_mailbox (path);
213         mutt_format_s (dest, destlen, prefix, path);
214       }
215       else
216         mutt_format_s (dest, destlen, prefix,
217                        NONULL (aptr->content->filename));
218     }
219     else if (!aptr->content->filename)
220       optional = 0;
221     break;
222   case 'D':
223     if (!optional)
224       snprintf (dest, destlen, "%c", aptr->content->deleted ? 'D' : ' ');
225     else if (!aptr->content->deleted)
226       optional = 0;
227     break;
228   case 'e':
229     if (!optional)
230       mutt_format_s (dest, destlen, prefix,
231                      ENCODING (aptr->content->encoding));
232     break;
233   case 'I':
234     if (!optional) {
235       snprintf (dest, destlen, "%c",
236                 (aptr->content->disposition == DISPINLINE) ? 'I' : 'A');
237     }
238     break;
239   case 'm':
240     if (!optional)
241       mutt_format_s (dest, destlen, prefix, TYPE (aptr->content));
242     break;
243   case 'M':
244     if (!optional)
245       mutt_format_s (dest, destlen, prefix, aptr->content->subtype);
246     else if (!aptr->content->subtype)
247       optional = 0;
248     break;
249   case 'n':
250     if (!optional) {
251       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
252       snprintf (dest, destlen, fmt, aptr->num + 1);
253     }
254     break;
255   case 'Q':
256     if (optional)
257       optional = aptr->content->attach_qualifies;
258     else {
259       snprintf (fmt, sizeof (fmt), "%%%sc", prefix);
260       mutt_format_s (dest, destlen, fmt, "Q");
261     }
262     break;
263   case 's':
264     if (flags & M_FORMAT_STAT_FILE) {
265       struct stat st;
266
267       stat (aptr->content->filename, &st);
268       l = st.st_size;
269     }
270     else
271       l = aptr->content->length;
272
273     if (!optional) {
274       mutt_pretty_size (tmp, sizeof (tmp), l);
275       mutt_format_s (dest, destlen, prefix, tmp);
276     }
277     else if (l == 0)
278       optional = 0;
279
280     break;
281   case 't':
282     if (!optional)
283       snprintf (dest, destlen, "%c", aptr->content->tagged ? '*' : ' ');
284     else if (!aptr->content->tagged)
285       optional = 0;
286     break;
287   case 'T':
288     if (!optional)
289       mutt_format_s_tree (dest, destlen, prefix, NONULL (aptr->tree));
290     else if (!aptr->tree)
291       optional = 0;
292     break;
293   case 'u':
294     if (!optional)
295       snprintf (dest, destlen, "%c", aptr->content->unlink ? '-' : ' ');
296     else if (!aptr->content->unlink)
297       optional = 0;
298     break;
299   case 'X':
300     if (optional)
301       optional = (aptr->content->attach_count + aptr->content->attach_qualifies) != 0;
302     else {
303       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
304       snprintf (dest, destlen, fmt, aptr->content->attach_count + aptr->content->attach_qualifies);
305     }
306     break;
307   default:
308     *dest = 0;
309   }
310
311   if (flags & M_FORMAT_OPTIONAL)
312     m_strformat(dest, destlen, 0, optional ? ifstr : elstr,
313                 mutt_attach_fmt, data, 0);
314   return src;
315 }
316
317 static void attach_entry(char *b, ssize_t blen, MUTTMENU * menu, int num)
318 {
319     m_strformat(b, blen, COLS - SW, AttachFormat, mutt_attach_fmt,
320                 ((ATTACHPTR **) menu->data)[num],
321                 option(OPTARROWCURSOR) ? M_FORMAT_ARROWCURSOR : 0);
322 }
323
324 int mutt_tag_attach (MUTTMENU * menu, int n, int m)
325 {
326   BODY *cur = ((ATTACHPTR **) menu->data)[n]->content;
327   int ot = cur->tagged;
328
329   cur->tagged = (m >= 0 ? m : !cur->tagged);
330   return cur->tagged - ot;
331 }
332
333 static int mutt_query_save_attachment (FILE * fp, BODY * body, HEADER * hdr,
334                                        char **directory)
335 {
336   char *prompt;
337   char buf[_POSIX_PATH_MAX], tfile[_POSIX_PATH_MAX];
338   int is_message;
339   int append = 0;
340   int rc;
341   int ret = -1;
342
343   if (body->filename) {
344     if (directory && *directory)
345       mutt_concat_path(buf, sizeof(buf), *directory,
346                        mutt_basename(body->filename));
347     else
348       m_strcpy(buf, sizeof(buf), body->filename);
349   }
350   else if (body->hdr &&
351            body->encoding != ENCBASE64 &&
352            body->encoding != ENCQUOTEDPRINTABLE &&
353            mutt_is_message_type(body))
354     mutt_default_save (buf, sizeof (buf), body->hdr);
355   else
356     buf[0] = 0;
357
358   prompt = _("Save to file ('#' for last used folder): ");
359   while (prompt) {
360     static char LastSaveFolder[_POSIX_PATH_MAX] = ".";
361
362     ret = mutt_get_field(prompt, buf, sizeof (buf),
363                          M_FILE | M_CLEAR | M_LASTFOLDER);
364     if (((ret != 0) && (ret != 2)) || (!buf[0] && ret != 2))
365       return -1;
366
367     if (ret == 2) {
368       char tmpbuf[_POSIX_PATH_MAX];
369
370       snprintf (tmpbuf, sizeof (tmpbuf), "%s/%s", LastSaveFolder, buf);
371       m_strcpy(buf, sizeof(buf), tmpbuf);
372       ret = mutt_get_field(_("Save to file: "), buf, sizeof (buf), M_FILE);
373       if ((ret != 0) || (!buf[0]))
374         return -1;
375     }
376     m_dirname(LastSaveFolder, sizeof(LastSaveFolder), buf);
377
378     prompt = NULL;
379     mutt_expand_path(buf, sizeof(buf));
380
381     is_message = (fp && body->hdr &&
382                   body->encoding != ENCBASE64 &&
383                   body->encoding != ENCQUOTEDPRINTABLE &&
384                   mutt_is_message_type(body));
385
386     if (is_message) {
387       struct stat st;
388
389       /* check to make sure that this file is really the one the user wants */
390       rc = mutt_save_confirm(buf, &st);
391       m_strcpy(tfile, sizeof(tfile), buf);
392     } else {
393       rc = mutt_check_overwrite(body->filename, buf, tfile, sizeof(tfile),
394                                 &append, directory);
395     }
396     if (rc == -1)
397       return -1;
398     if (rc == 1) {
399       prompt = _("Save to file: ");
400       continue;
401     }
402
403     mutt_message _("Saving...");
404
405     if (mutt_save_attachment(fp, body, tfile, append,
406                              (hdr || !is_message) ?  hdr : body->hdr) == 0)
407     {
408       mutt_message _("Attachment saved.");
409       return 0;
410     } else {
411       prompt = _("Save to file: ");
412       continue;
413     }
414   }
415   return 0;
416 }
417
418 void mutt_save_attachment_list (FILE * fp, int tag, BODY * top, HEADER * hdr,
419                                 MUTTMENU * menu)
420 {
421   char buf[_POSIX_PATH_MAX], tfile[_POSIX_PATH_MAX];
422   char *directory = NULL;
423   int rc = 1;
424   int last = menu ? menu->current : -1;
425   FILE *fpout;
426
427   buf[0] = 0;
428
429   for (; top; top = top->next) {
430     if (!tag || top->tagged) {
431       if (!option (OPTATTACHSPLIT)) {
432         if (!buf[0]) {
433           int append = 0;
434
435           m_strcpy(buf, sizeof(buf), NONULL(top->filename));
436           if (mutt_get_field (_("Save to file: "), buf, sizeof (buf),
437                               M_FILE | M_CLEAR) != 0 || !buf[0])
438             return;
439           mutt_expand_path (buf, sizeof (buf));
440           if (mutt_check_overwrite (top->filename, buf, tfile,
441                                     sizeof (tfile), &append, NULL))
442             return;
443           rc = mutt_save_attachment (fp, top, tfile, append, hdr);
444           if (rc == 0 && AttachSep && (fpout = fopen (tfile, "a")) != NULL) {
445             fprintf (fpout, "%s", AttachSep);
446             m_fclose(&fpout);
447           }
448         }
449         else {
450           rc = mutt_save_attachment (fp, top, tfile, M_SAVE_APPEND, hdr);
451           if (rc == 0 && AttachSep && (fpout = fopen (tfile, "a")) != NULL) {
452             fprintf (fpout, "%s", AttachSep);
453             m_fclose(&fpout);
454           }
455         }
456       } else {
457         if (tag && menu && top->aptr) {
458           menu->oldcurrent = menu->current;
459           menu->current = top->aptr->num;
460           menu_check_recenter (menu);
461           menu->redraw |= REDRAW_MOTION;
462
463           menu_redraw (menu);
464         }
465         if (mutt_query_save_attachment (fp, top, hdr, &directory) == -1)
466           break;
467       }
468     }
469     else if (top->parts)
470       mutt_save_attachment_list (fp, 1, top->parts, hdr, menu);
471     if (!tag)
472       break;
473   }
474
475   p_delete(&directory);
476
477   if (tag && menu) {
478     menu->oldcurrent = menu->current;
479     menu->current = last;
480     menu_check_recenter (menu);
481     menu->redraw |= REDRAW_MOTION;
482   }
483
484   if (!option (OPTATTACHSPLIT) && (rc == 0))
485     mutt_message _("Attachment saved.");
486 }
487
488 static void
489 mutt_query_pipe_attachment (char *command, FILE * fp, BODY * body, int afilter)
490 {
491   char tfile[_POSIX_PATH_MAX];
492   char warning[STRING + _POSIX_PATH_MAX];
493
494   if (afilter) {
495     snprintf (warning, sizeof (warning),
496               _("WARNING!  You are about to overwrite %s, continue?"),
497               body->filename);
498     if (mutt_yesorno (warning, M_NO) != M_YES) {
499       CLEARLINE (LINES - 1);
500       return;
501     }
502     mutt_mktemp (tfile);
503   } else {
504     tfile[0] = '\0';
505   }
506
507   if (mutt_pipe_attachment (fp, body, command, tfile)) {
508     if (afilter) {
509       mutt_unlink (body->filename);
510       mutt_rename_file (tfile, body->filename);
511       mutt_update_encoding (body);
512       mutt_message _("Attachment filtered.");
513     }
514   } else {
515     if (afilter && tfile[0])
516       mutt_unlink (tfile);
517   }
518 }
519
520 static void pipe_attachment (FILE * fp, BODY * b, STATE * state)
521 {
522   FILE *ifp;
523
524   if (fp) {
525     state->fpin = fp;
526     mutt_decode_attachment (b, state);
527     if (AttachSep)
528       state_puts (AttachSep, state);
529   } else {
530     if ((ifp = fopen (b->filename, "r")) == NULL) {
531       mutt_perror ("fopen");
532       return;
533     }
534     mutt_copy_stream (ifp, state->fpout);
535     m_fclose(&ifp);
536     if (AttachSep)
537       state_puts (AttachSep, state);
538   }
539 }
540
541 static void
542 pipe_attachment_list (char *command, FILE * fp, int tag, BODY * top,
543                       int afilter, STATE * state)
544 {
545   for (; top; top = top->next) {
546     if (!tag || top->tagged) {
547       if (!afilter && !option (OPTATTACHSPLIT))
548         pipe_attachment (fp, top, state);
549       else
550         mutt_query_pipe_attachment (command, fp, top, afilter);
551     }
552     else if (top->parts)
553       pipe_attachment_list (command, fp, tag, top->parts, afilter, state);
554     if (!tag)
555       break;
556   }
557 }
558
559 void mutt_pipe_attachment_list (FILE * fp, int tag, BODY * top, int afilter)
560 {
561   STATE state;
562   char buf[STRING];
563   pid_t thepid;
564
565   if (fp)
566     afilter = 0;                 /* sanity check: we can't filter in the recv case yet */
567
568   buf[0] = '\0';
569   p_clear(&state, 1);
570
571   if (mutt_get_field ((afilter ? _("Filter through: ") : _("Pipe to: ")),
572                       buf, sizeof (buf), M_CMD) != 0 || !buf[0])
573     return;
574
575   mutt_expand_path (buf, sizeof (buf));
576
577   if (!afilter && !option (OPTATTACHSPLIT)) {
578     mutt_endwin (NULL);
579     thepid = mutt_create_filter (buf, &state.fpout, NULL, NULL);
580     pipe_attachment_list (buf, fp, tag, top, afilter, &state);
581     m_fclose(&state.fpout);
582     if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY))
583       mutt_any_key_to_continue (NULL);
584   } else {
585     pipe_attachment_list (buf, fp, tag, top, afilter, &state);
586   }
587 }
588
589 static int can_print(BODY * top, int tag)
590 {
591   for (; top; top = top->next) {
592     char type[STRING];
593     int tok = mime_which_token(top->subtype, -1);
594
595     snprintf(type, sizeof(type), "%s/%s", TYPE(top), top->subtype);
596
597     if (!tag || top->tagged) {
598       if (!rfc1524_mailcap_lookup(top, type, NULL, M_PRINT)
599       && !(top->type == TYPETEXT && tok == MIME_PLAIN)
600       && !(top->type == TYPEAPPLICATION && tok == MIME_POSTSCRIPT)
601       && !mutt_can_decode(top))
602       {
603         mutt_error(_("I dont know how to print %s attachments!"), type);
604         return 0;
605       }
606     }
607     else if (top->parts)
608       return (can_print(top->parts, tag));
609     if (!tag)
610       break;
611   }
612   return 1;
613 }
614
615 static void print_attachment_list (FILE * fp, int tag, BODY * top,
616                                    STATE * state)
617 {
618   char type[STRING];
619
620   for (; top; top = top->next) {
621     if (!tag || top->tagged) {
622       int tok = mime_which_token(top->subtype, -1);
623
624       snprintf (type, sizeof (type), "%s/%s", TYPE (top), top->subtype);
625       if (!option (OPTATTACHSPLIT)
626           && !rfc1524_mailcap_lookup (top, type, NULL, M_PRINT)) {
627         if ((top->type == TYPETEXT && tok == MIME_PLAIN)
628         ||  (top->type == TYPEAPPLICATION && tok == MIME_POSTSCRIPT)) {
629           pipe_attachment (fp, top, state);
630         } else
631         if (mutt_can_decode (top)) {
632           /* decode and print */
633
634           char newfile[_POSIX_PATH_MAX] = "";
635           FILE *ifp;
636
637           mutt_mktemp (newfile);
638           if (mutt_decode_save_attachment (fp, top, newfile, M_PRINTING, 0) ==
639               0) {
640             if ((ifp = fopen (newfile, "r")) != NULL) {
641               mutt_copy_stream (ifp, state->fpout);
642               m_fclose(&ifp);
643               if (AttachSep)
644                 state_puts (AttachSep, state);
645             }
646           }
647           mutt_unlink (newfile);
648         }
649       } else {
650         mutt_print_attachment(fp, top);
651       }
652     }
653     else if (top->parts)
654       print_attachment_list (fp, tag, top->parts, state);
655     if (!tag)
656       return;
657   }
658 }
659
660 void mutt_print_attachment_list (FILE * fp, int tag, BODY * top)
661 {
662   STATE state;
663
664   pid_t thepid;
665
666   if (query_quadoption(OPT_PRINT,
667        tag ? _("Print tagged attachment(s)?") : _("Print attachment?")) !=
668       M_YES)
669     return;
670
671   if (!option (OPTATTACHSPLIT)) {
672     if (!can_print (top, tag))
673       return;
674     mutt_endwin (NULL);
675     p_clear(&state, 1);
676     thepid = mutt_create_filter (NONULL (PrintCmd), &state.fpout, NULL, NULL);
677     print_attachment_list (fp, tag, top, &state);
678     m_fclose(&state.fpout);
679     if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY))
680       mutt_any_key_to_continue (NULL);
681   }
682   else
683     print_attachment_list (fp, tag, top, &state);
684 }
685
686 static void
687 mutt_update_attach_index (BODY * cur, ATTACHPTR *** idxp,
688                           short *idxlen, short *idxmax, MUTTMENU * menu)
689 {
690   ATTACHPTR **idx = *idxp;
691
692   while (--(*idxlen) >= 0)
693     idx[(*idxlen)]->content = NULL;
694   *idxlen = 0;
695
696   idx = *idxp = mutt_gen_attach_list (cur, -1, idx, idxlen, idxmax, 0, 0);
697
698   menu->max = *idxlen;
699   menu->data = *idxp;
700
701   if (menu->current >= menu->max)
702     menu->current = menu->max - 1;
703   menu_check_recenter (menu);
704   menu->redraw |= REDRAW_INDEX;
705 }
706
707 int
708 mutt_attach_display_loop (MUTTMENU * menu, int op, FILE * fp, HEADER * hdr,
709                           BODY * cur, ATTACHPTR *** idxp, short *idxlen,
710                           short *idxmax, int recv)
711 {
712   ATTACHPTR **idx = *idxp;
713
714   do {
715     switch (op) {
716     case OP_DISPLAY_HEADERS:
717       toggle_option (OPTWEED);
718       /* fall through */
719
720     case OP_VIEW_ATTACH:
721       op = mutt_view_attachment (fp, idx[menu->current]->content, M_REGULAR,
722                                  hdr, idx, *idxlen);
723       break;
724
725     case OP_NEXT_ENTRY:
726     case OP_MAIN_NEXT_UNDELETED:       /* hack */
727       if (menu->current < menu->max - 1) {
728         menu->current++;
729         op = OP_VIEW_ATTACH;
730       }
731       else
732         op = OP_NULL;
733       break;
734     case OP_PREV_ENTRY:
735     case OP_MAIN_PREV_UNDELETED:       /* hack */
736       if (menu->current > 0) {
737         menu->current--;
738         op = OP_VIEW_ATTACH;
739       }
740       else
741         op = OP_NULL;
742       break;
743     case OP_EDIT_TYPE:
744       /* when we edit the content-type, we should redisplay the attachment
745          immediately */
746       mutt_edit_content_type (hdr, idx[menu->current]->content, fp);
747       if (idxmax) {
748         mutt_update_attach_index (cur, idxp, idxlen, idxmax, menu);
749         idx = *idxp;
750       }
751       op = OP_VIEW_ATTACH;
752       break;
753       /* functions which are passed through from the pager */
754     case OP_CHECK_TRADITIONAL:
755       if (hdr && hdr->security & PGP_TRADITIONAL_CHECKED) {
756         op = OP_NULL;
757         break;
758       }
759       /* fall through */
760     case OP_ATTACH_COLLAPSE:
761       if (recv)
762         return op;
763     default:
764       op = OP_NULL;
765     }
766   }
767   while (op != OP_NULL);
768
769   return op;
770 }
771
772 static void attach_collapse (BODY * b, short collapse, short init,
773                              short just_one)
774 {
775   short i;
776
777   for (; b; b = b->next) {
778     i = init || b->collapsed;
779     if (i && option (OPTDIGESTCOLLAPSE) && b->type == TYPEMULTIPART
780         && mime_which_token(b->subtype, -1) == MIME_DIGEST)
781       attach_collapse (b->parts, 1, 1, 0);
782     else if (b->type == TYPEMULTIPART || mutt_is_message_type(b))
783       attach_collapse (b->parts, collapse, i, 0);
784     b->collapsed = collapse;
785     if (just_one)
786       return;
787   }
788 }
789
790 void mutt_attach_init (BODY * b)
791 {
792   for (; b; b = b->next) {
793     b->tagged = 0;
794     b->collapsed = 0;
795     if (b->parts)
796       mutt_attach_init (b->parts);
797   }
798 }
799
800 void mutt_view_attachments (HEADER * hdr)
801 {
802   int secured = 0;
803   int need_secured = 0;
804
805   char helpstr[STRING];
806   MUTTMENU *menu;
807   BODY *cur = NULL;
808   MESSAGE *msg;
809   FILE *fp;
810   ATTACHPTR **idx = NULL;
811   short idxlen = 0;
812   short idxmax = 0;
813   int flags = 0;
814   int op = OP_NULL;
815
816   /* make sure we have parsed this message */
817   mutt_parse_mime_message (Context, hdr);
818
819   mutt_message_hook (Context, hdr, M_MESSAGEHOOK);
820
821   if ((msg = mx_open_message (Context, hdr->msgno)) == NULL)
822     return;
823
824   if ((hdr->security & ENCRYPT) ||
825       (mutt_is_application_smime (hdr->content) & SMIMEOPAQUE))
826   {
827     need_secured = 1;
828
829     if ((hdr->security & ENCRYPT) && !crypt_valid_passphrase (hdr->security)) {
830       mx_close_message (&msg);
831       return;
832     }
833     if (hdr->security & APPLICATION_SMIME) {
834       if (hdr->env)
835         crypt_smime_getkeys (hdr->env);
836
837       if (mutt_is_application_smime (hdr->content)) {
838         secured = !crypt_smime_decrypt_mime (msg->fp, &fp,
839                                              hdr->content, &cur);
840
841         /* S/MIME nesting */
842         if ((mutt_is_application_smime (cur) & SMIMEOPAQUE)) {
843           BODY *_cur = cur;
844           FILE *_fp = fp;
845
846           fp = NULL;
847           cur = NULL;
848           secured = !crypt_smime_decrypt_mime (_fp, &fp, _cur, &cur);
849
850           body_list_wipe(&_cur);
851           m_fclose(&_fp);
852         }
853       }
854       else
855         need_secured = 0;
856     }
857     if (hdr->security & APPLICATION_PGP) {
858       if (mutt_is_multipart_encrypted (hdr->content))
859         secured = !crypt_pgp_decrypt_mime (msg->fp, &fp, hdr->content, &cur);
860       else
861         need_secured = 0;
862     }
863
864     if (need_secured && !secured) {
865       mx_close_message (&msg);
866       mutt_error _("Can't decrypt encrypted message!");
867       return;
868     }
869   }
870
871   if (!need_secured) {
872     fp = msg->fp;
873     cur = hdr->content;
874   }
875
876   menu = mutt_new_menu ();
877   menu->menu = MENU_ATTACH;
878   menu->title = _("Attachments");
879   menu->make_entry = attach_entry;
880   menu->tag = mutt_tag_attach;
881   menu->help =
882     mutt_compile_help (helpstr, sizeof (helpstr), MENU_ATTACH, AttachHelp);
883
884   mutt_attach_init (cur);
885   attach_collapse (cur, 0, 1, 0);
886   mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
887
888 #define CHECK_READONLY \
889     if (Context->readonly) {                                            \
890         mutt_flushinp ();                                               \
891         mutt_error _("Mailbox is read-only.");                          \
892         break;                                                          \
893     }
894
895 #define CHECK_ATTACH \
896     if (option(OPTATTACHMSG)) {                                         \
897         mutt_flushinp ();                                               \
898         mutt_error _("Function not permitted in attach-message mode."); \
899         break;                                                          \
900     }
901
902   for (;;) {
903     if (op == OP_NULL)
904       op = mutt_menuLoop(menu);
905     switch (op) {
906     case OP_ATTACH_VIEW_MAILCAP:
907       mutt_view_attachment (fp, idx[menu->current]->content, M_MAILCAP,
908                             hdr, idx, idxlen);
909       menu->redraw = REDRAW_FULL;
910       break;
911
912     case OP_ATTACH_VIEW_TEXT:
913       mutt_view_attachment (fp, idx[menu->current]->content, M_AS_TEXT,
914                             hdr, idx, idxlen);
915       menu->redraw = REDRAW_FULL;
916       break;
917
918     case OP_DISPLAY_HEADERS:
919     case OP_VIEW_ATTACH:
920       op =
921         mutt_attach_display_loop (menu, op, fp, hdr, cur, &idx, &idxlen,
922                                   &idxmax, 1);
923       menu->redraw = REDRAW_FULL;
924       continue;
925
926     case OP_ATTACH_COLLAPSE:
927       if (!idx[menu->current]->content->parts) {
928         mutt_error _("There are no subparts to show!");
929
930         break;
931       }
932       if (!idx[menu->current]->content->collapsed)
933         attach_collapse (idx[menu->current]->content, 1, 0, 1);
934       else
935         attach_collapse (idx[menu->current]->content, 0, 1, 1);
936       mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
937       break;
938
939     case OP_FORGET_PASSPHRASE:
940       crypt_forget_passphrase ();
941       break;
942
943     case OP_EXTRACT_KEYS:
944       crypt_pgp_extract_keys_from_attachment_list (fp, menu->tagprefix,
945                                                    menu->
946                                                    tagprefix ? cur :
947                                                    idx[menu->current]->
948                                                    content);
949       menu->redraw = REDRAW_FULL;
950       break;
951
952     case OP_CHECK_TRADITIONAL:
953       if (crypt_pgp_check_traditional (fp, menu->tagprefix ? cur
954                                           : idx[menu->current]->content,
955                                           menu->tagprefix))
956       {
957         hdr->security = crypt_query (cur);
958         menu->redraw = REDRAW_FULL;
959       }
960       break;
961
962     case OP_PRINT:
963       mutt_print_attachment_list (fp, menu->tagprefix,
964                                   menu->tagprefix ? cur : idx[menu->current]->
965                                   content);
966       break;
967
968     case OP_PIPE:
969       mutt_pipe_attachment_list (fp, menu->tagprefix,
970                                  menu->tagprefix ? cur : idx[menu->current]->
971                                  content, 0);
972       break;
973
974     case OP_SAVE:
975       mutt_save_attachment_list (fp, menu->tagprefix,
976                                  menu->tagprefix ? cur : idx[menu->current]->
977                                  content, hdr, menu);
978
979       if (!menu->tagprefix && option (OPTRESOLVE)
980           && menu->current < menu->max - 1)
981         menu->current++;
982
983       menu->redraw = REDRAW_MOTION_RESYNCH | REDRAW_FULL;
984       break;
985
986     case OP_DELETE:
987       CHECK_READONLY;
988
989       if (Context->magic == M_POP) {
990         mutt_flushinp ();
991         mutt_error _("Can't delete attachment from POP server.");
992
993         break;
994       }
995
996 #ifdef USE_NNTP
997       if (Context->magic == M_NNTP) {
998         mutt_flushinp ();
999         mutt_error _("Can't delete attachment from newsserver.");
1000
1001         break;
1002       }
1003 #endif
1004
1005       if (hdr->security & (~PGP_TRADITIONAL_CHECKED)) {
1006         mutt_message
1007           _("Deletion of attachments from encrypted messages is unsupported.");
1008       }
1009       else {
1010         if (!menu->tagprefix) {
1011           if (idx[menu->current]->parent_type == TYPEMULTIPART) {
1012             idx[menu->current]->content->deleted = 1;
1013             if (option (OPTRESOLVE) && menu->current < menu->max - 1) {
1014               menu->current++;
1015               menu->redraw = REDRAW_MOTION_RESYNCH;
1016             }
1017             else
1018               menu->redraw = REDRAW_CURRENT;
1019           }
1020           else
1021             mutt_message
1022               _("Only deletion of multipart attachments is supported.");
1023         }
1024         else {
1025           int x;
1026
1027           for (x = 0; x < menu->max; x++) {
1028             if (idx[x]->content->tagged) {
1029               if (idx[x]->parent_type == TYPEMULTIPART) {
1030                 idx[x]->content->deleted = 1;
1031                 menu->redraw = REDRAW_INDEX;
1032               }
1033               else
1034                 mutt_message
1035                   _("Only deletion of multipart attachments is supported.");
1036             }
1037           }
1038         }
1039       }
1040       break;
1041
1042     case OP_UNDELETE:
1043       CHECK_READONLY;
1044       if (!menu->tagprefix) {
1045         idx[menu->current]->content->deleted = 0;
1046         if (option (OPTRESOLVE) && menu->current < menu->max - 1) {
1047           menu->current++;
1048           menu->redraw = REDRAW_MOTION_RESYNCH;
1049         }
1050         else
1051           menu->redraw = REDRAW_CURRENT;
1052       }
1053       else {
1054         int x;
1055
1056         for (x = 0; x < menu->max; x++) {
1057           if (idx[x]->content->tagged) {
1058             idx[x]->content->deleted = 0;
1059             menu->redraw = REDRAW_INDEX;
1060           }
1061         }
1062       }
1063       break;
1064
1065     case OP_RESEND:
1066       CHECK_ATTACH;
1067       mutt_attach_resend (fp, hdr, idx, idxlen,
1068                           menu->tagprefix ? NULL : idx[menu->current]->
1069                           content);
1070       menu->redraw = REDRAW_FULL;
1071       break;
1072
1073     case OP_BOUNCE_MESSAGE:
1074       CHECK_ATTACH;
1075       mutt_attach_bounce (fp, hdr, idx, idxlen,
1076                           menu->tagprefix ? NULL : idx[menu->current]->
1077                           content);
1078       menu->redraw = REDRAW_FULL;
1079       break;
1080
1081     case OP_FORWARD_MESSAGE:
1082       CHECK_ATTACH;
1083       mutt_attach_forward (fp, hdr, idx, idxlen,
1084                            menu->tagprefix ? NULL : idx[menu->current]->
1085                            content, 0);
1086       menu->redraw = REDRAW_FULL;
1087       break;
1088
1089 #ifdef USE_NNTP
1090     case OP_FORWARD_TO_GROUP:
1091       CHECK_ATTACH;
1092       mutt_attach_forward (fp, hdr, idx, idxlen,
1093                            menu->tagprefix ? NULL : idx[menu->current]->
1094                            content, SENDNEWS);
1095       menu->redraw = REDRAW_FULL;
1096       break;
1097
1098     case OP_FOLLOWUP:
1099       CHECK_ATTACH;
1100
1101       if (!idx[menu->current]->content->hdr->env->followup_to ||
1102           m_strcasecmp(idx[menu->current]->content->hdr->env->followup_to,
1103                            "poster")
1104           || query_quadoption (OPT_FOLLOWUPTOPOSTER,
1105                                _("Reply by mail as poster prefers?")) !=
1106           M_YES) {
1107         mutt_attach_reply (fp, hdr, idx, idxlen,
1108                            menu->tagprefix ? NULL : idx[menu->current]->
1109                            content, SENDNEWS | SENDREPLY);
1110         menu->redraw = REDRAW_FULL;
1111         break;
1112       }
1113 #endif
1114
1115     case OP_REPLY:
1116     case OP_GROUP_REPLY:
1117     case OP_LIST_REPLY:
1118
1119       CHECK_ATTACH;
1120
1121       flags = SENDREPLY |
1122         (op == OP_GROUP_REPLY ? SENDGROUPREPLY : 0) |
1123         (op == OP_LIST_REPLY ? SENDLISTREPLY : 0);
1124       mutt_attach_reply (fp, hdr, idx, idxlen,
1125                          menu->tagprefix ? NULL : idx[menu->current]->content,
1126                          flags);
1127       menu->redraw = REDRAW_FULL;
1128       break;
1129
1130     case OP_EDIT_TYPE:
1131       mutt_edit_content_type (hdr, idx[menu->current]->content, fp);
1132       mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
1133       break;
1134
1135     case OP_EXIT:
1136       mx_close_message (&msg);
1137       hdr->attach_del = 0;
1138       while (idxmax-- > 0) {
1139         if (!idx[idxmax])
1140           continue;
1141         if (idx[idxmax]->content && idx[idxmax]->content->deleted)
1142           hdr->attach_del = 1;
1143         if (idx[idxmax]->content)
1144           idx[idxmax]->content->aptr = NULL;
1145         p_delete(&idx[idxmax]->tree);
1146         p_delete(&idx[idxmax]);
1147       }
1148       if (hdr->attach_del)
1149         hdr->changed = 1;
1150       p_delete(&idx);
1151       idxmax = 0;
1152
1153       if (need_secured && secured) {
1154         m_fclose(&fp);
1155         body_list_wipe(&cur);
1156       }
1157
1158       mutt_menuDestroy (&menu);
1159       return;
1160     }
1161
1162     op = OP_NULL;
1163   }
1164   /* not reached */
1165 }