small detail in m_dirname.
[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 &&
388                   body->hdr &&
389                   body->encoding != ENCBASE64 &&
390                   body->encoding != ENCQUOTEDPRINTABLE &&
391                   mutt_is_message_type(body));
392
393     if (is_message) {
394       struct stat st;
395
396       /* check to make sure that this file is really the one the user wants */
397       if ((rc = mutt_save_confirm (buf, &st)) == 1) {
398         prompt = _("Save to file: ");
399         continue;
400       }
401       else if (rc == -1)
402         return -1;
403       m_strcpy(tfile, sizeof(tfile), buf);
404     }
405     else {
406       if ((rc =
407            mutt_check_overwrite (body->filename, buf, tfile, sizeof (tfile),
408                                  &append, directory)) == -1)
409         return -1;
410       else if (rc == 1) {
411         prompt = _("Save to file: ");
412         continue;
413       }
414     }
415
416     mutt_message _("Saving...");
417
418     if (mutt_save_attachment
419         (fp, body, tfile, append,
420          (hdr || !is_message) ? hdr : body->hdr) == 0) {
421       mutt_message _("Attachment saved.");
422
423       return 0;
424     }
425     else {
426       prompt = _("Save to file: ");
427       continue;
428     }
429   }
430   return 0;
431 }
432
433 void mutt_save_attachment_list (FILE * fp, int tag, BODY * top, HEADER * hdr,
434                                 MUTTMENU * menu)
435 {
436   char buf[_POSIX_PATH_MAX], tfile[_POSIX_PATH_MAX];
437   char *directory = NULL;
438   int rc = 1;
439   int last = menu ? menu->current : -1;
440   FILE *fpout;
441
442   buf[0] = 0;
443
444   for (; top; top = top->next) {
445     if (!tag || top->tagged) {
446       if (!option (OPTATTACHSPLIT)) {
447         if (!buf[0]) {
448           int append = 0;
449
450           m_strcpy(buf, sizeof(buf), NONULL(top->filename));
451           if (mutt_get_field (_("Save to file: "), buf, sizeof (buf),
452                               M_FILE | M_CLEAR) != 0 || !buf[0])
453             return;
454           mutt_expand_path (buf, sizeof (buf));
455           if (mutt_check_overwrite (top->filename, buf, tfile,
456                                     sizeof (tfile), &append, NULL))
457             return;
458           rc = mutt_save_attachment (fp, top, tfile, append, hdr);
459           if (rc == 0 && AttachSep && (fpout = fopen (tfile, "a")) != NULL) {
460             fprintf (fpout, "%s", AttachSep);
461             m_fclose(&fpout);
462           }
463         }
464         else {
465           rc = mutt_save_attachment (fp, top, tfile, M_SAVE_APPEND, hdr);
466           if (rc == 0 && AttachSep && (fpout = fopen (tfile, "a")) != NULL) {
467             fprintf (fpout, "%s", AttachSep);
468             m_fclose(&fpout);
469           }
470         }
471       }
472       else {
473         if (tag && menu && top->aptr) {
474           menu->oldcurrent = menu->current;
475           menu->current = top->aptr->num;
476           menu_check_recenter (menu);
477           menu->redraw |= REDRAW_MOTION;
478
479           menu_redraw (menu);
480         }
481         if (mutt_query_save_attachment (fp, top, hdr, &directory) == -1)
482           break;
483       }
484     }
485     else if (top->parts)
486       mutt_save_attachment_list (fp, 1, top->parts, hdr, menu);
487     if (!tag)
488       break;
489   }
490
491   p_delete(&directory);
492
493   if (tag && menu) {
494     menu->oldcurrent = menu->current;
495     menu->current = last;
496     menu_check_recenter (menu);
497     menu->redraw |= REDRAW_MOTION;
498   }
499
500   if (!option (OPTATTACHSPLIT) && (rc == 0))
501     mutt_message _("Attachment saved.");
502 }
503
504 static void
505 mutt_query_pipe_attachment (char *command, FILE * fp, BODY * body, int afilter)
506 {
507   char tfile[_POSIX_PATH_MAX];
508   char warning[STRING + _POSIX_PATH_MAX];
509
510   if (afilter) {
511     snprintf (warning, sizeof (warning),
512               _("WARNING!  You are about to overwrite %s, continue?"),
513               body->filename);
514     if (mutt_yesorno (warning, M_NO) != M_YES) {
515       CLEARLINE (LINES - 1);
516       return;
517     }
518     mutt_mktemp (tfile);
519   }
520   else
521     tfile[0] = 0;
522
523   if (mutt_pipe_attachment (fp, body, command, tfile)) {
524     if (afilter) {
525       mutt_unlink (body->filename);
526       mutt_rename_file (tfile, body->filename);
527       mutt_update_encoding (body);
528       mutt_message _("Attachment filtered.");
529     }
530   }
531   else {
532     if (afilter && tfile[0])
533       mutt_unlink (tfile);
534   }
535 }
536
537 static void pipe_attachment (FILE * fp, BODY * b, STATE * state)
538 {
539   FILE *ifp;
540
541   if (fp) {
542     state->fpin = fp;
543     mutt_decode_attachment (b, state);
544     if (AttachSep)
545       state_puts (AttachSep, state);
546   }
547   else {
548     if ((ifp = fopen (b->filename, "r")) == NULL) {
549       mutt_perror ("fopen");
550       return;
551     }
552     mutt_copy_stream (ifp, state->fpout);
553     m_fclose(&ifp);
554     if (AttachSep)
555       state_puts (AttachSep, state);
556   }
557 }
558
559 static void
560 pipe_attachment_list (char *command, FILE * fp, int tag, BODY * top,
561                       int afilter, STATE * state)
562 {
563   for (; top; top = top->next) {
564     if (!tag || top->tagged) {
565       if (!afilter && !option (OPTATTACHSPLIT))
566         pipe_attachment (fp, top, state);
567       else
568         mutt_query_pipe_attachment (command, fp, top, afilter);
569     }
570     else if (top->parts)
571       pipe_attachment_list (command, fp, tag, top->parts, afilter, state);
572     if (!tag)
573       break;
574   }
575 }
576
577 void mutt_pipe_attachment_list (FILE * fp, int tag, BODY * top, int afilter)
578 {
579   STATE state;
580   char buf[SHORT_STRING];
581   pid_t thepid;
582
583   if (fp)
584     afilter = 0;                 /* sanity check: we can't filter in the recv case yet */
585
586   buf[0] = 0;
587   p_clear(&state, 1);
588
589   if (mutt_get_field ((afilter ? _("Filter through: ") : _("Pipe to: ")),
590                       buf, sizeof (buf), M_CMD) != 0 || !buf[0])
591     return;
592
593   mutt_expand_path (buf, sizeof (buf));
594
595   if (!afilter && !option (OPTATTACHSPLIT)) {
596     mutt_endwin (NULL);
597     thepid = mutt_create_filter (buf, &state.fpout, NULL, NULL);
598     pipe_attachment_list (buf, fp, tag, top, afilter, &state);
599     m_fclose(&state.fpout);
600     if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY))
601       mutt_any_key_to_continue (NULL);
602   }
603   else
604     pipe_attachment_list (buf, fp, tag, top, afilter, &state);
605 }
606
607 static int can_print (BODY * top, int tag)
608 {
609   char type[STRING];
610
611   for (; top; top = top->next) {
612     snprintf (type, sizeof (type), "%s/%s", TYPE (top), top->subtype);
613     if (!tag || top->tagged) {
614       if (!rfc1524_mailcap_lookup (top, type, NULL, M_PRINT)) {
615         if (ascii_strcasecmp ("text/plain", top->subtype) &&
616             ascii_strcasecmp ("application/postscript", top->subtype)) {
617           if (!mutt_can_decode (top)) {
618             mutt_error (_("I dont know how to print %s attachments!"), type);
619             return (0);
620           }
621         }
622       }
623     }
624     else if (top->parts)
625       return (can_print (top->parts, tag));
626     if (!tag)
627       break;
628   }
629   return (1);
630 }
631
632 static void print_attachment_list (FILE * fp, int tag, BODY * top,
633                                    STATE * state)
634 {
635   char type[STRING];
636
637
638   for (; top; top = top->next) {
639     if (!tag || top->tagged) {
640       snprintf (type, sizeof (type), "%s/%s", TYPE (top), top->subtype);
641       if (!option (OPTATTACHSPLIT)
642           && !rfc1524_mailcap_lookup (top, type, NULL, M_PRINT)) {
643         if (!ascii_strcasecmp ("text/plain", top->subtype)
644             || !ascii_strcasecmp ("application/postscript", top->subtype))
645           pipe_attachment (fp, top, state);
646         else if (mutt_can_decode (top)) {
647           /* decode and print */
648
649           char newfile[_POSIX_PATH_MAX] = "";
650           FILE *ifp;
651
652           mutt_mktemp (newfile);
653           if (mutt_decode_save_attachment (fp, top, newfile, M_PRINTING, 0) ==
654               0) {
655             if ((ifp = fopen (newfile, "r")) != NULL) {
656               mutt_copy_stream (ifp, state->fpout);
657               m_fclose(&ifp);
658               if (AttachSep)
659                 state_puts (AttachSep, state);
660             }
661           }
662           mutt_unlink (newfile);
663         }
664       }
665       else
666         mutt_print_attachment (fp, top);
667     }
668     else if (top->parts)
669       print_attachment_list (fp, tag, top->parts, state);
670     if (!tag)
671       return;
672   }
673 }
674
675 void mutt_print_attachment_list (FILE * fp, int tag, BODY * top)
676 {
677   STATE state;
678
679   pid_t thepid;
680
681   if (query_quadoption
682       (OPT_PRINT,
683        tag ? _("Print tagged attachment(s)?") : _("Print attachment?")) !=
684       M_YES)
685     return;
686
687   if (!option (OPTATTACHSPLIT)) {
688     if (!can_print (top, tag))
689       return;
690     mutt_endwin (NULL);
691     p_clear(&state, 1);
692     thepid = mutt_create_filter (NONULL (PrintCmd), &state.fpout, NULL, NULL);
693     print_attachment_list (fp, tag, top, &state);
694     m_fclose(&state.fpout);
695     if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY))
696       mutt_any_key_to_continue (NULL);
697   }
698   else
699     print_attachment_list (fp, tag, top, &state);
700 }
701
702 static void
703 mutt_update_attach_index (BODY * cur, ATTACHPTR *** idxp,
704                           short *idxlen, short *idxmax, MUTTMENU * menu)
705 {
706   ATTACHPTR **idx = *idxp;
707
708   while (--(*idxlen) >= 0)
709     idx[(*idxlen)]->content = NULL;
710   *idxlen = 0;
711
712   idx = *idxp = mutt_gen_attach_list (cur, -1, idx, idxlen, idxmax, 0, 0);
713
714   menu->max = *idxlen;
715   menu->data = *idxp;
716
717   if (menu->current >= menu->max)
718     menu->current = menu->max - 1;
719   menu_check_recenter (menu);
720   menu->redraw |= REDRAW_INDEX;
721
722 }
723
724
725 int
726 mutt_attach_display_loop (MUTTMENU * menu, int op, FILE * fp, HEADER * hdr,
727                           BODY * cur, ATTACHPTR *** idxp, short *idxlen,
728                           short *idxmax, int recv)
729 {
730   ATTACHPTR **idx = *idxp;
731
732   do {
733     switch (op) {
734     case OP_DISPLAY_HEADERS:
735       toggle_option (OPTWEED);
736       /* fall through */
737
738     case OP_VIEW_ATTACH:
739       op = mutt_view_attachment (fp, idx[menu->current]->content, M_REGULAR,
740                                  hdr, idx, *idxlen);
741       break;
742
743     case OP_NEXT_ENTRY:
744     case OP_MAIN_NEXT_UNDELETED:       /* hack */
745       if (menu->current < menu->max - 1) {
746         menu->current++;
747         op = OP_VIEW_ATTACH;
748       }
749       else
750         op = OP_NULL;
751       break;
752     case OP_PREV_ENTRY:
753     case OP_MAIN_PREV_UNDELETED:       /* hack */
754       if (menu->current > 0) {
755         menu->current--;
756         op = OP_VIEW_ATTACH;
757       }
758       else
759         op = OP_NULL;
760       break;
761     case OP_EDIT_TYPE:
762       /* when we edit the content-type, we should redisplay the attachment
763          immediately */
764       mutt_edit_content_type (hdr, idx[menu->current]->content, fp);
765       if (idxmax) {
766         mutt_update_attach_index (cur, idxp, idxlen, idxmax, menu);
767         idx = *idxp;
768       }
769       op = OP_VIEW_ATTACH;
770       break;
771       /* functions which are passed through from the pager */
772     case OP_CHECK_TRADITIONAL:
773       if (hdr && hdr->security & PGP_TRADITIONAL_CHECKED) {
774         op = OP_NULL;
775         break;
776       }
777       /* fall through */
778     case OP_ATTACH_COLLAPSE:
779       if (recv)
780         return op;
781     default:
782       op = OP_NULL;
783     }
784   }
785   while (op != OP_NULL);
786
787   return op;
788 }
789
790 static void attach_collapse (BODY * b, short collapse, short init,
791                              short just_one)
792 {
793   short i;
794
795   for (; b; b = b->next) {
796     i = init || b->collapsed;
797     if (i && option (OPTDIGESTCOLLAPSE) && b->type == TYPEMULTIPART
798         && mime_which_token(b->subtype, -1) == MIME_DIGEST)
799       attach_collapse (b->parts, 1, 1, 0);
800     else if (b->type == TYPEMULTIPART || mutt_is_message_type(b))
801       attach_collapse (b->parts, collapse, i, 0);
802     b->collapsed = collapse;
803     if (just_one)
804       return;
805   }
806 }
807
808 void mutt_attach_init (BODY * b)
809 {
810   for (; b; b = b->next) {
811     b->tagged = 0;
812     b->collapsed = 0;
813     if (b->parts)
814       mutt_attach_init (b->parts);
815   }
816 }
817
818 void mutt_view_attachments (HEADER * hdr)
819 {
820   int secured = 0;
821   int need_secured = 0;
822
823   char helpstr[SHORT_STRING];
824   MUTTMENU *menu;
825   BODY *cur = NULL;
826   MESSAGE *msg;
827   FILE *fp;
828   ATTACHPTR **idx = NULL;
829   short idxlen = 0;
830   short idxmax = 0;
831   int flags = 0;
832   int op = OP_NULL;
833
834   /* make sure we have parsed this message */
835   mutt_parse_mime_message (Context, hdr);
836
837   mutt_message_hook (Context, hdr, M_MESSAGEHOOK);
838
839   if ((msg = mx_open_message (Context, hdr->msgno)) == NULL)
840     return;
841
842
843   if ((hdr->security & ENCRYPT) ||
844       (mutt_is_application_smime (hdr->content) & SMIMEOPAQUE))
845   {
846     need_secured = 1;
847
848     if ((hdr->security & ENCRYPT) && !crypt_valid_passphrase (hdr->security)) {
849       mx_close_message (&msg);
850       return;
851     }
852     if (hdr->security & APPLICATION_SMIME) {
853       if (hdr->env)
854         crypt_smime_getkeys (hdr->env);
855
856       if (mutt_is_application_smime (hdr->content)) {
857         secured = !crypt_smime_decrypt_mime (msg->fp, &fp,
858                                              hdr->content, &cur);
859
860         /* S/MIME nesting */
861         if ((mutt_is_application_smime (cur) & SMIMEOPAQUE)) {
862           BODY *_cur = cur;
863           FILE *_fp = fp;
864
865           fp = NULL;
866           cur = NULL;
867           secured = !crypt_smime_decrypt_mime (_fp, &fp, _cur, &cur);
868
869           body_list_wipe(&_cur);
870           m_fclose(&_fp);
871         }
872       }
873       else
874         need_secured = 0;
875     }
876     if (hdr->security & APPLICATION_PGP) {
877       if (mutt_is_multipart_encrypted (hdr->content))
878         secured = !crypt_pgp_decrypt_mime (msg->fp, &fp, hdr->content, &cur);
879       else
880         need_secured = 0;
881     }
882
883     if (need_secured && !secured) {
884       mx_close_message (&msg);
885       mutt_error _("Can't decrypt encrypted message!");
886
887       return;
888     }
889   }
890
891   if (!need_secured) {
892     fp = msg->fp;
893     cur = hdr->content;
894   }
895
896   menu = mutt_new_menu ();
897   menu->menu = MENU_ATTACH;
898   menu->title = _("Attachments");
899   menu->make_entry = attach_entry;
900   menu->tag = mutt_tag_attach;
901   menu->help =
902     mutt_compile_help (helpstr, sizeof (helpstr), MENU_ATTACH, AttachHelp);
903
904   mutt_attach_init (cur);
905   attach_collapse (cur, 0, 1, 0);
906   mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
907
908 #define CHECK_READONLY \
909     if (Context->readonly) {                                            \
910         mutt_flushinp ();                                               \
911         mutt_error _("Mailbox is read-only.");                          \
912         break;                                                          \
913     }
914
915 #define CHECK_ATTACH \
916     if (option(OPTATTACHMSG)) {                                         \
917         mutt_flushinp ();                                               \
918         mutt_error _("Function not permitted in attach-message mode."); \
919         break;                                                          \
920     }
921
922   for (;;) {
923     if (op == OP_NULL)
924       op = mutt_menuLoop(menu);
925     switch (op) {
926     case OP_ATTACH_VIEW_MAILCAP:
927       mutt_view_attachment (fp, idx[menu->current]->content, M_MAILCAP,
928                             hdr, idx, idxlen);
929       menu->redraw = REDRAW_FULL;
930       break;
931
932     case OP_ATTACH_VIEW_TEXT:
933       mutt_view_attachment (fp, idx[menu->current]->content, M_AS_TEXT,
934                             hdr, idx, idxlen);
935       menu->redraw = REDRAW_FULL;
936       break;
937
938     case OP_DISPLAY_HEADERS:
939     case OP_VIEW_ATTACH:
940       op =
941         mutt_attach_display_loop (menu, op, fp, hdr, cur, &idx, &idxlen,
942                                   &idxmax, 1);
943       menu->redraw = REDRAW_FULL;
944       continue;
945
946     case OP_ATTACH_COLLAPSE:
947       if (!idx[menu->current]->content->parts) {
948         mutt_error _("There are no subparts to show!");
949
950         break;
951       }
952       if (!idx[menu->current]->content->collapsed)
953         attach_collapse (idx[menu->current]->content, 1, 0, 1);
954       else
955         attach_collapse (idx[menu->current]->content, 0, 1, 1);
956       mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
957       break;
958
959     case OP_FORGET_PASSPHRASE:
960       crypt_forget_passphrase ();
961       break;
962
963     case OP_EXTRACT_KEYS:
964       crypt_pgp_extract_keys_from_attachment_list (fp, menu->tagprefix,
965                                                    menu->
966                                                    tagprefix ? cur :
967                                                    idx[menu->current]->
968                                                    content);
969       menu->redraw = REDRAW_FULL;
970       break;
971
972     case OP_CHECK_TRADITIONAL:
973       if (crypt_pgp_check_traditional (fp, menu->tagprefix ? cur
974                                           : idx[menu->current]->content,
975                                           menu->tagprefix))
976       {
977         hdr->security = crypt_query (cur);
978         menu->redraw = REDRAW_FULL;
979       }
980       break;
981
982     case OP_PRINT:
983       mutt_print_attachment_list (fp, menu->tagprefix,
984                                   menu->tagprefix ? cur : idx[menu->current]->
985                                   content);
986       break;
987
988     case OP_PIPE:
989       mutt_pipe_attachment_list (fp, menu->tagprefix,
990                                  menu->tagprefix ? cur : idx[menu->current]->
991                                  content, 0);
992       break;
993
994     case OP_SAVE:
995       mutt_save_attachment_list (fp, menu->tagprefix,
996                                  menu->tagprefix ? cur : idx[menu->current]->
997                                  content, hdr, menu);
998
999       if (!menu->tagprefix && option (OPTRESOLVE)
1000           && menu->current < menu->max - 1)
1001         menu->current++;
1002
1003       menu->redraw = REDRAW_MOTION_RESYNCH | REDRAW_FULL;
1004       break;
1005
1006     case OP_DELETE:
1007       CHECK_READONLY;
1008
1009       if (Context->magic == M_POP) {
1010         mutt_flushinp ();
1011         mutt_error _("Can't delete attachment from POP server.");
1012
1013         break;
1014       }
1015
1016 #ifdef USE_NNTP
1017       if (Context->magic == M_NNTP) {
1018         mutt_flushinp ();
1019         mutt_error _("Can't delete attachment from newsserver.");
1020
1021         break;
1022       }
1023 #endif
1024
1025       if (hdr->security & (~PGP_TRADITIONAL_CHECKED)) {
1026         mutt_message
1027           _("Deletion of attachments from encrypted messages is unsupported.");
1028       }
1029       else {
1030         if (!menu->tagprefix) {
1031           if (idx[menu->current]->parent_type == TYPEMULTIPART) {
1032             idx[menu->current]->content->deleted = 1;
1033             if (option (OPTRESOLVE) && menu->current < menu->max - 1) {
1034               menu->current++;
1035               menu->redraw = REDRAW_MOTION_RESYNCH;
1036             }
1037             else
1038               menu->redraw = REDRAW_CURRENT;
1039           }
1040           else
1041             mutt_message
1042               _("Only deletion of multipart attachments is supported.");
1043         }
1044         else {
1045           int x;
1046
1047           for (x = 0; x < menu->max; x++) {
1048             if (idx[x]->content->tagged) {
1049               if (idx[x]->parent_type == TYPEMULTIPART) {
1050                 idx[x]->content->deleted = 1;
1051                 menu->redraw = REDRAW_INDEX;
1052               }
1053               else
1054                 mutt_message
1055                   _("Only deletion of multipart attachments is supported.");
1056             }
1057           }
1058         }
1059       }
1060       break;
1061
1062     case OP_UNDELETE:
1063       CHECK_READONLY;
1064       if (!menu->tagprefix) {
1065         idx[menu->current]->content->deleted = 0;
1066         if (option (OPTRESOLVE) && menu->current < menu->max - 1) {
1067           menu->current++;
1068           menu->redraw = REDRAW_MOTION_RESYNCH;
1069         }
1070         else
1071           menu->redraw = REDRAW_CURRENT;
1072       }
1073       else {
1074         int x;
1075
1076         for (x = 0; x < menu->max; x++) {
1077           if (idx[x]->content->tagged) {
1078             idx[x]->content->deleted = 0;
1079             menu->redraw = REDRAW_INDEX;
1080           }
1081         }
1082       }
1083       break;
1084
1085     case OP_RESEND:
1086       CHECK_ATTACH;
1087       mutt_attach_resend (fp, hdr, idx, idxlen,
1088                           menu->tagprefix ? NULL : idx[menu->current]->
1089                           content);
1090       menu->redraw = REDRAW_FULL;
1091       break;
1092
1093     case OP_BOUNCE_MESSAGE:
1094       CHECK_ATTACH;
1095       mutt_attach_bounce (fp, hdr, idx, idxlen,
1096                           menu->tagprefix ? NULL : idx[menu->current]->
1097                           content);
1098       menu->redraw = REDRAW_FULL;
1099       break;
1100
1101     case OP_FORWARD_MESSAGE:
1102       CHECK_ATTACH;
1103       mutt_attach_forward (fp, hdr, idx, idxlen,
1104                            menu->tagprefix ? NULL : idx[menu->current]->
1105                            content, 0);
1106       menu->redraw = REDRAW_FULL;
1107       break;
1108
1109 #ifdef USE_NNTP
1110     case OP_FORWARD_TO_GROUP:
1111       CHECK_ATTACH;
1112       mutt_attach_forward (fp, hdr, idx, idxlen,
1113                            menu->tagprefix ? NULL : idx[menu->current]->
1114                            content, SENDNEWS);
1115       menu->redraw = REDRAW_FULL;
1116       break;
1117
1118     case OP_FOLLOWUP:
1119       CHECK_ATTACH;
1120
1121       if (!idx[menu->current]->content->hdr->env->followup_to ||
1122           m_strcasecmp(idx[menu->current]->content->hdr->env->followup_to,
1123                            "poster")
1124           || query_quadoption (OPT_FOLLOWUPTOPOSTER,
1125                                _("Reply by mail as poster prefers?")) !=
1126           M_YES) {
1127         mutt_attach_reply (fp, hdr, idx, idxlen,
1128                            menu->tagprefix ? NULL : idx[menu->current]->
1129                            content, SENDNEWS | SENDREPLY);
1130         menu->redraw = REDRAW_FULL;
1131         break;
1132       }
1133 #endif
1134
1135     case OP_REPLY:
1136     case OP_GROUP_REPLY:
1137     case OP_LIST_REPLY:
1138
1139       CHECK_ATTACH;
1140
1141       flags = SENDREPLY |
1142         (op == OP_GROUP_REPLY ? SENDGROUPREPLY : 0) |
1143         (op == OP_LIST_REPLY ? SENDLISTREPLY : 0);
1144       mutt_attach_reply (fp, hdr, idx, idxlen,
1145                          menu->tagprefix ? NULL : idx[menu->current]->content,
1146                          flags);
1147       menu->redraw = REDRAW_FULL;
1148       break;
1149
1150     case OP_EDIT_TYPE:
1151       mutt_edit_content_type (hdr, idx[menu->current]->content, fp);
1152       mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
1153       break;
1154
1155     case OP_EXIT:
1156       mx_close_message (&msg);
1157       hdr->attach_del = 0;
1158       while (idxmax-- > 0) {
1159         if (!idx[idxmax])
1160           continue;
1161         if (idx[idxmax]->content && idx[idxmax]->content->deleted)
1162           hdr->attach_del = 1;
1163         if (idx[idxmax]->content)
1164           idx[idxmax]->content->aptr = NULL;
1165         p_delete(&idx[idxmax]->tree);
1166         p_delete(&idx[idxmax]);
1167       }
1168       if (hdr->attach_del)
1169         hdr->changed = 1;
1170       p_delete(&idx);
1171       idxmax = 0;
1172
1173       if (need_secured && secured) {
1174         m_fclose(&fp);
1175         body_list_wipe(&cur);
1176       }
1177
1178       mutt_menuDestroy (&menu);
1179       return;
1180     }
1181
1182     op = OP_NULL;
1183   }
1184   /* not reached */
1185 }