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