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