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