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