dead code
[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       return
488     tempfd = m_tempfd(tfile, sizeof(tfile), NONULL(mod_core.tmpdir), NULL);
489   }
490
491   if (mutt_pipe_attachment(fp, body, command, tempfd)) {
492     if (afilter) {
493       mutt_unlink (body->filename);
494       mutt_rename_file (tfile, body->filename);
495       mutt_update_encoding (body);
496       mutt_message _("Attachment filtered.");
497     }
498   } else if (afilter) {
499       close(tempfd);
500       mutt_unlink (tfile);
501   }
502 }
503
504 static void pipe_attachment (FILE * fp, BODY * b, STATE * state)
505 {
506   FILE *ifp;
507
508   if (fp) {
509     state->fpin = fp;
510     mutt_decode_attachment (b, state);
511     if (AttachSep)
512       state_puts (AttachSep, state);
513   } else {
514     if ((ifp = fopen (b->filename, "r")) == NULL) {
515       mutt_perror ("fopen");
516       return;
517     }
518     mutt_copy_stream (ifp, state->fpout);
519     m_fclose(&ifp);
520     if (AttachSep)
521       state_puts (AttachSep, state);
522   }
523 }
524
525 static void
526 pipe_attachment_list (char *command, FILE * fp, int tag, BODY * top,
527                       int afilter, STATE * state)
528 {
529   for (; top; top = top->next) {
530     if (!tag || top->tagged) {
531       if (!afilter && !option (OPTATTACHSPLIT))
532         pipe_attachment (fp, top, state);
533       else
534         mutt_query_pipe_attachment (command, fp, top, afilter);
535     }
536     else if (top->parts)
537       pipe_attachment_list (command, fp, tag, top->parts, afilter, state);
538     if (!tag)
539       break;
540   }
541 }
542
543 void mutt_pipe_attachment_list (FILE * fp, int tag, BODY * top, int afilter)
544 {
545   STATE state;
546   char buf[STRING];
547   pid_t thepid;
548
549   if (fp)
550     afilter = 0;                 /* sanity check: we can't filter in the recv case yet */
551
552   buf[0] = '\0';
553   p_clear(&state, 1);
554
555   if (mutt_get_field ((afilter ? _("Filter through: ") : _("Pipe to: ")),
556                       buf, sizeof (buf), M_CMD) != 0 || !buf[0])
557     return;
558
559   mutt_expand_path (buf, sizeof (buf));
560
561   if (!afilter && !option (OPTATTACHSPLIT)) {
562     mutt_endwin (NULL);
563     thepid = mutt_create_filter (buf, &state.fpout, NULL, NULL);
564     pipe_attachment_list (buf, fp, tag, top, afilter, &state);
565     m_fclose(&state.fpout);
566     if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY))
567       mutt_any_key_to_continue (NULL);
568   } else {
569     pipe_attachment_list (buf, fp, tag, top, afilter, &state);
570   }
571 }
572
573 static int can_print(BODY * top, int tag)
574 {
575   for (; top; top = top->next) {
576     char type[STRING];
577     int tok = mime_which_token(top->subtype, -1);
578
579     snprintf(type, sizeof(type), "%s/%s", TYPE(top), top->subtype);
580
581     if (!tag || top->tagged) {
582       if (!rfc1524_mailcap_lookup(top, type, NULL, M_PRINT)
583       && !(top->type == TYPETEXT && tok == MIME_PLAIN)
584       && !(top->type == TYPEAPPLICATION && tok == MIME_POSTSCRIPT)
585       && !mutt_can_decode(top))
586       {
587         mutt_error(_("I dont know how to print %s attachments!"), type);
588         return 0;
589       }
590     }
591     else if (top->parts)
592       return (can_print(top->parts, tag));
593     if (!tag)
594       break;
595   }
596   return 1;
597 }
598
599 static void print_attachment_list (FILE * fp, int tag, BODY * top,
600                                    STATE * state)
601 {
602   char type[STRING];
603
604   for (; top; top = top->next) {
605     if (!tag || top->tagged) {
606       int tok = mime_which_token(top->subtype, -1);
607
608       snprintf (type, sizeof (type), "%s/%s", TYPE (top), top->subtype);
609       if (!option (OPTATTACHSPLIT)
610           && !rfc1524_mailcap_lookup (top, type, NULL, M_PRINT)) {
611         if ((top->type == TYPETEXT && tok == MIME_PLAIN)
612         ||  (top->type == TYPEAPPLICATION && tok == MIME_POSTSCRIPT)) {
613           pipe_attachment (fp, top, state);
614         } else
615         if (mutt_can_decode (top)) {
616           /* decode and print */
617
618           char newfile[_POSIX_PATH_MAX] = "";
619           FILE *ifp;
620           int newfile_fd;
621
622           newfile_fd = m_tempfd(newfile, sizeof(newfile), NONULL(mod_core.tmpdir), NULL);
623           if (mutt_decode_save_attachment (fp, top, newfile_fd, M_PRINTING) == 0) {
624             if ((ifp = fopen (newfile, "r")) != NULL) {
625               mutt_copy_stream (ifp, state->fpout);
626               m_fclose(&ifp);
627               if (AttachSep)
628                 state_puts (AttachSep, state);
629             }
630           }
631           mutt_unlink (newfile);
632         }
633       } else {
634         mutt_print_attachment(fp, top);
635       }
636     }
637     else if (top->parts)
638       print_attachment_list (fp, tag, top->parts, state);
639     if (!tag)
640       return;
641   }
642 }
643
644 void mutt_print_attachment_list (FILE * fp, int tag, BODY * top)
645 {
646   STATE state;
647
648   pid_t thepid;
649
650   if (query_quadoption(OPT_PRINT,
651        tag ? _("Print tagged attachment(s)?") : _("Print attachment?")) !=
652       M_YES)
653     return;
654
655   if (!option (OPTATTACHSPLIT)) {
656     if (!can_print (top, tag))
657       return;
658     mutt_endwin (NULL);
659     p_clear(&state, 1);
660     thepid = mutt_create_filter (NONULL (PrintCmd), &state.fpout, NULL, NULL);
661     print_attachment_list (fp, tag, top, &state);
662     m_fclose(&state.fpout);
663     if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY))
664       mutt_any_key_to_continue (NULL);
665   }
666   else
667     print_attachment_list (fp, tag, top, &state);
668 }
669
670 static void
671 mutt_update_attach_index (BODY * cur, ATTACHPTR *** idxp,
672                           short *idxlen, short *idxmax, MUTTMENU * menu)
673 {
674   ATTACHPTR **idx = *idxp;
675
676   while (--(*idxlen) >= 0)
677     idx[(*idxlen)]->content = NULL;
678   *idxlen = 0;
679
680   idx = *idxp = mutt_gen_attach_list (cur, -1, idx, idxlen, idxmax, 0, 0);
681
682   menu->max = *idxlen;
683   menu->data = *idxp;
684
685   if (menu->current >= menu->max)
686     menu->current = menu->max - 1;
687   menu_check_recenter (menu);
688   menu->redraw |= REDRAW_INDEX;
689 }
690
691 int
692 mutt_attach_display_loop (MUTTMENU * menu, int op, FILE * fp, HEADER * hdr,
693                           BODY * cur, ATTACHPTR *** idxp, short *idxlen,
694                           short *idxmax, int recv)
695 {
696   ATTACHPTR **idx = *idxp;
697
698   do {
699     switch (op) {
700     case OP_DISPLAY_HEADERS:
701       toggle_option (OPTWEED);
702       /* fall through */
703
704     case OP_VIEW_ATTACH:
705       op = mutt_view_attachment (fp, idx[menu->current]->content, M_REGULAR,
706                                  hdr, idx, *idxlen);
707       break;
708
709     case OP_NEXT_ENTRY:
710     case OP_MAIN_NEXT_UNDELETED:       /* hack */
711       if (menu->current < menu->max - 1) {
712         menu->current++;
713         op = OP_VIEW_ATTACH;
714       }
715       else
716         op = OP_NULL;
717       break;
718     case OP_PREV_ENTRY:
719     case OP_MAIN_PREV_UNDELETED:       /* hack */
720       if (menu->current > 0) {
721         menu->current--;
722         op = OP_VIEW_ATTACH;
723       }
724       else
725         op = OP_NULL;
726       break;
727     case OP_EDIT_TYPE:
728       /* when we edit the content-type, we should redisplay the attachment
729          immediately */
730       mutt_edit_content_type (hdr, idx[menu->current]->content, fp);
731       if (idxmax) {
732         mutt_update_attach_index (cur, idxp, idxlen, idxmax, menu);
733         idx = *idxp;
734       }
735       op = OP_VIEW_ATTACH;
736       break;
737       /* functions which are passed through from the pager */
738     case OP_CHECK_TRADITIONAL:
739       if (hdr && hdr->security & PGP_TRADITIONAL_CHECKED) {
740         op = OP_NULL;
741         break;
742       }
743       /* fall through */
744     case OP_ATTACH_COLLAPSE:
745       if (recv)
746         return op;
747     default:
748       op = OP_NULL;
749     }
750   }
751   while (op != OP_NULL);
752
753   return op;
754 }
755
756 static void attach_collapse (BODY * b, short collapse, short init,
757                              short just_one)
758 {
759   short i;
760
761   for (; b; b = b->next) {
762     i = init || b->collapsed;
763     if (i && option (OPTDIGESTCOLLAPSE) && b->type == TYPEMULTIPART
764         && mime_which_token(b->subtype, -1) == MIME_DIGEST)
765       attach_collapse (b->parts, 1, 1, 0);
766     else if (b->type == TYPEMULTIPART || mutt_is_message_type(b))
767       attach_collapse (b->parts, collapse, i, 0);
768     b->collapsed = collapse;
769     if (just_one)
770       return;
771   }
772 }
773
774 void mutt_attach_init (BODY * b)
775 {
776   for (; b; b = b->next) {
777     b->tagged = 0;
778     b->collapsed = 0;
779     if (b->parts)
780       mutt_attach_init (b->parts);
781   }
782 }
783
784 void mutt_view_attachments (HEADER * hdr)
785 {
786   int secured = 0;
787   int need_secured = 0;
788
789   MUTTMENU *menu;
790   BODY *cur = NULL;
791   MESSAGE *msg;
792   FILE *fp;
793   ATTACHPTR **idx = NULL;
794   short idxlen = 0;
795   short idxmax = 0;
796   int flags = 0;
797   int op = OP_NULL;
798
799   /* make sure we have parsed this message */
800   mutt_parse_mime_message (Context, hdr);
801
802   mutt_message_hook (Context, hdr, M_MESSAGEHOOK);
803
804   if ((msg = mx_open_message (Context, hdr->msgno)) == NULL)
805     return;
806
807   if ((hdr->security & ENCRYPT) ||
808       (mutt_is_application_smime (hdr->content) & SMIMEOPAQUE))
809   {
810     need_secured = 1;
811
812     if (hdr->security & APPLICATION_SMIME) {
813       if (mutt_is_application_smime (hdr->content)) {
814         secured = !crypt_smime_decrypt_mime (msg->fp, &fp,
815                                              hdr->content, &cur);
816
817         /* S/MIME nesting */
818         if ((mutt_is_application_smime (cur) & SMIMEOPAQUE)) {
819           BODY *_cur = cur;
820           FILE *_fp = fp;
821
822           fp = NULL;
823           cur = NULL;
824           secured = !crypt_smime_decrypt_mime (_fp, &fp, _cur, &cur);
825
826           body_list_wipe(&_cur);
827           m_fclose(&_fp);
828         }
829       }
830       else
831         need_secured = 0;
832     }
833     if (hdr->security & APPLICATION_PGP) {
834       if (mutt_is_multipart_encrypted (hdr->content))
835         secured = !crypt_pgp_decrypt_mime (msg->fp, &fp, hdr->content, &cur);
836       else
837         need_secured = 0;
838     }
839
840     if (need_secured && !secured) {
841       mx_close_message (&msg);
842       mutt_error _("Can't decrypt encrypted message!");
843       return;
844     }
845   }
846
847   if (!need_secured) {
848     fp = msg->fp;
849     cur = hdr->content;
850   }
851
852   menu = mutt_new_menu ();
853   menu->menu = MENU_ATTACH;
854   menu->title = _("Attachments");
855   menu->make_entry = attach_entry;
856   menu->tag = mutt_tag_attach;
857
858   mutt_attach_init (cur);
859   attach_collapse (cur, 0, 1, 0);
860   mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
861
862 #define CHECK_READONLY \
863     if (Context->readonly) {                                            \
864         mutt_flushinp ();                                               \
865         mutt_error _("Mailbox is read-only.");                          \
866         break;                                                          \
867     }
868
869 #define CHECK_ATTACH \
870     if (option(OPTATTACHMSG)) {                                         \
871         mutt_flushinp ();                                               \
872         mutt_error _("Function not permitted in attach-message mode."); \
873         break;                                                          \
874     }
875
876   for (;;) {
877     if (op == OP_NULL)
878       op = mutt_menuLoop(menu);
879     switch (op) {
880     case OP_ATTACH_VIEW_MAILCAP:
881       mutt_view_attachment (fp, idx[menu->current]->content, M_MAILCAP,
882                             hdr, idx, idxlen);
883       menu->redraw = REDRAW_FULL;
884       break;
885
886     case OP_ATTACH_VIEW_TEXT:
887       mutt_view_attachment (fp, idx[menu->current]->content, M_AS_TEXT,
888                             hdr, idx, idxlen);
889       menu->redraw = REDRAW_FULL;
890       break;
891
892     case OP_DISPLAY_HEADERS:
893     case OP_VIEW_ATTACH:
894       op =
895         mutt_attach_display_loop (menu, op, fp, hdr, cur, &idx, &idxlen,
896                                   &idxmax, 1);
897       menu->redraw = REDRAW_FULL;
898       continue;
899
900     case OP_ATTACH_COLLAPSE:
901       if (!idx[menu->current]->content->parts) {
902         mutt_error _("There are no subparts to show!");
903
904         break;
905       }
906       if (!idx[menu->current]->content->collapsed)
907         attach_collapse (idx[menu->current]->content, 1, 0, 1);
908       else
909         attach_collapse (idx[menu->current]->content, 0, 1, 1);
910       mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
911       break;
912
913     case OP_EXTRACT_KEYS:
914       crypt_pgp_extract_keys_from_attachment_list (fp, menu->tagprefix,
915                                                    menu->
916                                                    tagprefix ? cur :
917                                                    idx[menu->current]->
918                                                    content);
919       menu->redraw = REDRAW_FULL;
920       break;
921
922     case OP_CHECK_TRADITIONAL:
923       if (crypt_pgp_check_traditional (fp, menu->tagprefix ? cur
924                                           : idx[menu->current]->content,
925                                           menu->tagprefix))
926       {
927         hdr->security = crypt_query (cur);
928         menu->redraw = REDRAW_FULL;
929       }
930       break;
931
932     case OP_PRINT:
933       mutt_print_attachment_list (fp, menu->tagprefix,
934                                   menu->tagprefix ? cur : idx[menu->current]->
935                                   content);
936       break;
937
938     case OP_PIPE:
939       mutt_pipe_attachment_list (fp, menu->tagprefix,
940                                  menu->tagprefix ? cur : idx[menu->current]->
941                                  content, 0);
942       break;
943
944     case OP_SAVE:
945       mutt_save_attachment_list (fp, menu->tagprefix,
946                                  menu->tagprefix ? cur : idx[menu->current]->
947                                  content, hdr, menu);
948
949       if (!menu->tagprefix && option (OPTRESOLVE)
950           && menu->current < menu->max - 1)
951         menu->current++;
952
953       menu->redraw = REDRAW_MOTION_RESYNCH | REDRAW_FULL;
954       break;
955
956     case OP_DELETE:
957       CHECK_READONLY;
958
959       if (Context->magic == M_POP) {
960         mutt_flushinp ();
961         mutt_error _("Can't delete attachment from POP server.");
962
963         break;
964       }
965
966       if (hdr->security & (~PGP_TRADITIONAL_CHECKED)) {
967         mutt_message
968           _("Deletion of attachments from encrypted messages is unsupported.");
969       }
970       else {
971         if (!menu->tagprefix) {
972           if (idx[menu->current]->parent_type == TYPEMULTIPART) {
973             idx[menu->current]->content->deleted = 1;
974             if (option (OPTRESOLVE) && menu->current < menu->max - 1) {
975               menu->current++;
976               menu->redraw = REDRAW_MOTION_RESYNCH;
977             }
978             else
979               menu->redraw = REDRAW_CURRENT;
980           }
981           else
982             mutt_message
983               _("Only deletion of multipart attachments is supported.");
984         }
985         else {
986           int x;
987
988           for (x = 0; x < menu->max; x++) {
989             if (idx[x]->content->tagged) {
990               if (idx[x]->parent_type == TYPEMULTIPART) {
991                 idx[x]->content->deleted = 1;
992                 menu->redraw = REDRAW_INDEX;
993               }
994               else
995                 mutt_message
996                   _("Only deletion of multipart attachments is supported.");
997             }
998           }
999         }
1000       }
1001       break;
1002
1003     case OP_UNDELETE:
1004       CHECK_READONLY;
1005       if (!menu->tagprefix) {
1006         idx[menu->current]->content->deleted = 0;
1007         if (option (OPTRESOLVE) && menu->current < menu->max - 1) {
1008           menu->current++;
1009           menu->redraw = REDRAW_MOTION_RESYNCH;
1010         }
1011         else
1012           menu->redraw = REDRAW_CURRENT;
1013       }
1014       else {
1015         int x;
1016
1017         for (x = 0; x < menu->max; x++) {
1018           if (idx[x]->content->tagged) {
1019             idx[x]->content->deleted = 0;
1020             menu->redraw = REDRAW_INDEX;
1021           }
1022         }
1023       }
1024       break;
1025
1026     case OP_RESEND:
1027       CHECK_ATTACH;
1028       mutt_attach_resend (fp, hdr, idx, idxlen,
1029                           menu->tagprefix ? NULL : idx[menu->current]->
1030                           content);
1031       menu->redraw = REDRAW_FULL;
1032       break;
1033
1034     case OP_BOUNCE_MESSAGE:
1035       CHECK_ATTACH;
1036       mutt_attach_bounce (fp, hdr, idx, idxlen,
1037                           menu->tagprefix ? NULL : idx[menu->current]->
1038                           content);
1039       menu->redraw = REDRAW_FULL;
1040       break;
1041
1042     case OP_FORWARD_MESSAGE:
1043       CHECK_ATTACH;
1044       mutt_attach_forward (fp, hdr, idx, idxlen,
1045                            menu->tagprefix ? NULL : idx[menu->current]->
1046                            content, 0);
1047       menu->redraw = REDRAW_FULL;
1048       break;
1049
1050     case OP_REPLY:
1051     case OP_GROUP_REPLY:
1052     case OP_LIST_REPLY:
1053
1054       CHECK_ATTACH;
1055
1056       flags = SENDREPLY |
1057         (op == OP_GROUP_REPLY ? SENDGROUPREPLY : 0) |
1058         (op == OP_LIST_REPLY ? SENDLISTREPLY : 0);
1059       mutt_attach_reply (fp, hdr, idx, idxlen,
1060                          menu->tagprefix ? NULL : idx[menu->current]->content,
1061                          flags);
1062       menu->redraw = REDRAW_FULL;
1063       break;
1064
1065     case OP_EDIT_TYPE:
1066       mutt_edit_content_type (hdr, idx[menu->current]->content, fp);
1067       mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
1068       break;
1069
1070     case OP_EXIT:
1071       mx_close_message (&msg);
1072       hdr->attach_del = 0;
1073       while (idxmax-- > 0) {
1074         if (!idx[idxmax])
1075           continue;
1076         if (idx[idxmax]->content && idx[idxmax]->content->deleted)
1077           hdr->attach_del = 1;
1078         if (idx[idxmax]->content)
1079           idx[idxmax]->content->aptr = NULL;
1080         p_delete(&idx[idxmax]->tree);
1081         p_delete(&idx[idxmax]);
1082       }
1083       if (hdr->attach_del)
1084         hdr->changed = 1;
1085       p_delete(&idx);
1086       idxmax = 0;
1087
1088       if (need_secured && secured) {
1089         m_fclose(&fp);
1090         body_list_wipe(&cur);
1091       }
1092
1093       mutt_menuDestroy (&menu);
1094       return;
1095     }
1096
1097     op = OP_NULL;
1098   }
1099 #undef CHECK_READONLY
1100 #undef CHECK_ATTACH
1101   /* not reached */
1102 }