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