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