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