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