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