07905f9f3987f279957f0cc4b69386ab7bc1718d
[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
15 #include <lib-ui/curses.h>
16 #include <lib-ui/enter.h>
17 #include <lib-ui/menu.h>
18
19 #include "mutt.h"
20 #include "handler.h"
21 #include "recvattach.h"
22 #include "attach.h"
23 #include "mx.h"
24 #include "copy.h"
25 #include <lib-crypt/crypt.h>
26
27
28 static const char *Mailbox_is_read_only = N_("Mailbox is read-only.");
29 static char LastSaveFolder[_POSIX_PATH_MAX] = "";
30
31 #define CHECK_READONLY if (Context->readonly) \
32 {\
33     mutt_flushinp (); \
34     mutt_error _(Mailbox_is_read_only); \
35     break; \
36 }
37
38 #define SW              (option(OPTMBOXPANE)?SidebarWidth:0)
39
40 static struct mapping_t AttachHelp[] = {
41   {N_("Exit"), OP_EXIT},
42   {N_("Save"), OP_SAVE},
43   {N_("Pipe"), OP_PIPE},
44   {N_("Print"), OP_PRINT},
45   {N_("Help"), OP_HELP},
46   {NULL, OP_NULL}
47 };
48
49 static int mutt_extract_path (char *filename, char *path)
50 {
51   char *tmp = p_new(char, _POSIX_PATH_MAX);
52   char *help_ptr;
53
54   help_ptr = tmp;
55
56   while (*filename != '\0') {
57     if (*filename == '/') {
58       *help_ptr++ = *filename++;
59       *help_ptr++ = '\0';
60       strcat (path, tmp);
61       help_ptr = tmp;
62     }
63     *help_ptr++ = *filename++;
64   }
65   p_delete(&tmp);
66   return 0;
67 }
68
69 void mutt_update_tree (ATTACHPTR ** idx, short idxlen)
70 {
71   char buf[STRING];
72   char *s;
73   int x;
74
75   for (x = 0; x < idxlen; x++) {
76     idx[x]->num = x;
77     if (2 * (idx[x]->level + 2) < ssizeof (buf)) {
78       if (idx[x]->level) {
79         s = buf + 2 * (idx[x]->level - 1);
80         *s++ = (idx[x]->content->next) ? M_TREE_LTEE : M_TREE_LLCORNER;
81         *s++ = M_TREE_HLINE;
82         *s++ = M_TREE_RARROW;
83       }
84       else
85         s = buf;
86       *s = 0;
87     }
88
89     if (idx[x]->tree) {
90       if (m_strcmp(idx[x]->tree, buf) != 0)
91         m_strreplace(&idx[x]->tree, buf);
92     }
93     else
94       idx[x]->tree = m_strdup(buf);
95
96     if (2 * (idx[x]->level + 2) < ssizeof (buf) && idx[x]->level) {
97       s = buf + 2 * (idx[x]->level - 1);
98       *s++ = (idx[x]->content->next) ? '\005' : '\006';
99       *s++ = '\006';
100     }
101   }
102 }
103
104 ATTACHPTR **mutt_gen_attach_list (BODY * m,
105                                   int parent_type,
106                                   ATTACHPTR ** idx,
107                                   short *idxlen,
108                                   short *idxmax, int level, int compose)
109 {
110   ATTACHPTR *new;
111   int i;
112
113   for (; m; m = m->next) {
114     if (*idxlen == *idxmax) {
115       p_realloc(&idx, (*idxmax) += 5);
116       for (i = *idxlen; i < *idxmax; i++)
117         idx[i] = NULL;
118     }
119
120     if (m->type == TYPEMULTIPART && m->parts
121         && (compose
122             || (parent_type == -1
123                 && ascii_strcasecmp ("alternative", m->subtype)))
124         && (!mutt_is_multipart_encrypted (m))
125       ) {
126       idx =
127         mutt_gen_attach_list (m->parts, m->type, idx, idxlen, idxmax, level,
128                               compose);
129     }
130     else {
131       if (!idx[*idxlen])
132         idx[*idxlen] = p_new(ATTACHPTR, 1);
133
134       new = idx[(*idxlen)++];
135       new->content = m;
136       m->aptr = new;
137       new->parent_type = parent_type;
138       new->level = level;
139
140       /* We don't support multipart messages in the compose menu yet */
141       if (!compose && !m->collapsed
142       &&  ((m->type == TYPEMULTIPART && !mutt_is_multipart_encrypted(m))
143       ||  mutt_is_message_type (m->type, m->subtype)))
144       {
145         idx = mutt_gen_attach_list (m->parts, m->type, idx, idxlen, idxmax,
146                                     level + 1, compose);
147       }
148     }
149   }
150
151   if (level == 0)
152     mutt_update_tree (idx, *idxlen);
153
154   return (idx);
155 }
156
157 /* %c = character set: convert?
158  * %C = character set
159  * %D = deleted flag
160  * %d = description
161  * %e = MIME content-transfer-encoding
162  * %f = filename
163  * %I = content-disposition, either I (inline) or A (attachment)
164  * %t = tagged flag
165  * %T = tree chars
166  * %m = major MIME type
167  * %M = MIME subtype
168  * %n = attachment number
169  * %s = size
170  * %u = unlink 
171  */
172 const char *mutt_attach_fmt (char *dest,
173                              ssize_t destlen,
174                              char op,
175                              const char *src,
176                              const char *prefix,
177                              const char *ifstring,
178                              const char *elsestring,
179                              unsigned long data, format_flag flags)
180 {
181   char fmt[16];
182   char tmp[SHORT_STRING];
183   char charset[SHORT_STRING];
184   ATTACHPTR *aptr = (ATTACHPTR *) data;
185   int optional = (flags & M_FORMAT_OPTIONAL);
186   ssize_t l;
187
188   switch (op) {
189   case 'C':
190     if (!optional) {
191       if (mutt_is_text_part (aptr->content) &&
192           mutt_get_body_charset (charset, sizeof (charset), aptr->content))
193         mutt_format_s (dest, destlen, prefix, charset);
194       else
195         mutt_format_s (dest, destlen, prefix, "");
196     }
197     else if (!mutt_is_text_part (aptr->content) ||
198              !mutt_get_body_charset (charset, sizeof (charset),
199                                      aptr->content))
200       optional = 0;
201     break;
202   case 'c':
203     /* XXX */
204     if (!optional) {
205       snprintf (fmt, sizeof (fmt), "%%%sc", prefix);
206       snprintf (dest, destlen, fmt, aptr->content->type != TYPETEXT ||
207                 aptr->content->noconv ? 'n' : 'c');
208     }
209     else if (aptr->content->type != TYPETEXT || aptr->content->noconv)
210       optional = 0;
211     break;
212   case 'd':
213     if (!optional) {
214       if (aptr->content->description) {
215         mutt_format_s (dest, destlen, prefix, aptr->content->description);
216         break;
217       }
218       if (mutt_is_message_type (aptr->content->type, aptr->content->subtype)
219           && MsgFmt && aptr->content->hdr) {
220         char s[SHORT_STRING];
221
222         _mutt_make_string (s, sizeof (s), MsgFmt, NULL, aptr->content->hdr,
223                            M_FORMAT_FORCESUBJ | M_FORMAT_MAKEPRINT |
224                            M_FORMAT_ARROWCURSOR);
225         if (*s) {
226           mutt_format_s (dest, destlen, prefix, s);
227           break;
228         }
229       }
230       if (!aptr->content->filename) {
231         mutt_format_s (dest, destlen, prefix, "<no description>");
232         break;
233       }
234     }
235     else if (aptr->content->description ||
236              (mutt_is_message_type
237               (aptr->content->type, aptr->content->subtype)
238               && MsgFmt && aptr->content->hdr))
239       break;
240     /* FALLS THROUGH TO 'f' */
241   case 'f':
242     if (!optional) {
243       if (aptr->content->filename && *aptr->content->filename == '/') {
244         char path[_POSIX_PATH_MAX];
245
246         m_strcpy(path, sizeof(path), aptr->content->filename);
247         mutt_pretty_mailbox (path);
248         mutt_format_s (dest, destlen, prefix, path);
249       }
250       else
251         mutt_format_s (dest, destlen, prefix,
252                        NONULL (aptr->content->filename));
253     }
254     else if (!aptr->content->filename)
255       optional = 0;
256     break;
257   case 'D':
258     if (!optional)
259       snprintf (dest, destlen, "%c", aptr->content->deleted ? 'D' : ' ');
260     else if (!aptr->content->deleted)
261       optional = 0;
262     break;
263   case 'e':
264     if (!optional)
265       mutt_format_s (dest, destlen, prefix,
266                      ENCODING (aptr->content->encoding));
267     break;
268   case 'I':
269     if (!optional) {
270       snprintf (dest, destlen, "%c",
271                 (aptr->content->disposition == DISPINLINE) ? 'I' : 'A');
272     }
273     break;
274   case 'm':
275     if (!optional)
276       mutt_format_s (dest, destlen, prefix, TYPE (aptr->content));
277     break;
278   case 'M':
279     if (!optional)
280       mutt_format_s (dest, destlen, prefix, aptr->content->subtype);
281     else if (!aptr->content->subtype)
282       optional = 0;
283     break;
284   case 'n':
285     if (!optional) {
286       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
287       snprintf (dest, destlen, fmt, aptr->num + 1);
288     }
289     break;
290   case 'Q':
291     if (optional)
292       optional = aptr->content->attach_qualifies;
293     else {
294       snprintf (fmt, sizeof (fmt), "%%%sc", prefix);
295       mutt_format_s (dest, destlen, fmt, "Q");
296     }
297     break;
298   case 's':
299     if (flags & M_FORMAT_STAT_FILE) {
300       struct stat st;
301
302       stat (aptr->content->filename, &st);
303       l = st.st_size;
304     }
305     else
306       l = aptr->content->length;
307
308     if (!optional) {
309       mutt_pretty_size (tmp, sizeof (tmp), l);
310       mutt_format_s (dest, destlen, prefix, tmp);
311     }
312     else if (l == 0)
313       optional = 0;
314
315     break;
316   case 't':
317     if (!optional)
318       snprintf (dest, destlen, "%c", aptr->content->tagged ? '*' : ' ');
319     else if (!aptr->content->tagged)
320       optional = 0;
321     break;
322   case 'T':
323     if (!optional)
324       mutt_format_s_tree (dest, destlen, prefix, NONULL (aptr->tree));
325     else if (!aptr->tree)
326       optional = 0;
327     break;
328   case 'u':
329     if (!optional)
330       snprintf (dest, destlen, "%c", aptr->content->unlink ? '-' : ' ');
331     else if (!aptr->content->unlink)
332       optional = 0;
333     break;
334   case 'X':
335     if (optional)
336       optional = (aptr->content->attach_count + aptr->content->attach_qualifies) != 0;
337     else {
338       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
339       snprintf (dest, destlen, fmt, aptr->content->attach_count + aptr->content->attach_qualifies);
340     }
341     break;
342   default:
343     *dest = 0;
344   }
345
346   if (optional)
347     mutt_FormatString (dest, destlen, ifstring, mutt_attach_fmt, data, 0);
348   else if (flags & M_FORMAT_OPTIONAL)
349     mutt_FormatString (dest, destlen, elsestring, mutt_attach_fmt, data, 0);
350   return (src);
351 }
352
353 static void attach_entry (char *b, ssize_t blen, MUTTMENU * menu, int num)
354 {
355   int w=(COLS-SW)>blen?blen:(COLS-SW);
356   mutt_FormatString (b, w, 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, sizeof(buf), *directory,
394                        mutt_basename(body->filename));
395     else
396       m_strcpy(buf, sizeof(buf), body->filename);
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       m_strcpy(buf, sizeof(buf), tmpbuf);
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       m_strcpy(LastSaveFolder, sizeof(LastSaveFolder), path);
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       m_strcpy(tfile, sizeof(tfile), buf);
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           m_strcpy(buf, sizeof(buf), NONULL(top->filename));
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   p_delete(&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 afilter)
551 {
552   char tfile[_POSIX_PATH_MAX];
553   char warning[STRING + _POSIX_PATH_MAX];
554
555   if (afilter) {
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 (afilter) {
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 (afilter && 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 afilter, STATE * state)
607 {
608   for (; top; top = top->next) {
609     if (!tag || top->tagged) {
610       if (!afilter && !option (OPTATTACHSPLIT))
611         pipe_attachment (fp, top, state);
612       else
613         mutt_query_pipe_attachment (command, fp, top, afilter);
614     }
615     else if (top->parts)
616       pipe_attachment_list (command, fp, tag, top->parts, afilter, state);
617     if (!tag)
618       break;
619   }
620 }
621
622 void mutt_pipe_attachment_list (FILE * fp, int tag, BODY * top, int afilter)
623 {
624   STATE state;
625   char buf[SHORT_STRING];
626   pid_t thepid;
627
628   if (fp)
629     afilter = 0;                 /* sanity check: we can't filter in the recv case yet */
630
631   buf[0] = 0;
632   p_clear(&state, 1);
633
634   if (mutt_get_field ((afilter ? _("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 (!afilter && !option (OPTATTACHSPLIT)) {
641     mutt_endwin (NULL);
642     thepid = mutt_create_filter (buf, &state.fpout, NULL, NULL);
643     pipe_attachment_list (buf, fp, tag, top, afilter, &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, afilter, &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     p_clear(&state, 1);
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 static 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 (hdr && hdr->security & PGP_TRADITIONAL_CHECKED) {
825         op = OP_NULL;
826         break;
827       }
828       /* fall through */
829     case OP_ATTACH_COLLAPSE:
830       if (recv)
831         return op;
832     default:
833       op = OP_NULL;
834     }
835   }
836   while (op != OP_NULL);
837
838 #if 0
839   if (option (OPTWEED) != old_optweed)
840     toggle_option (OPTWEED);
841 #endif
842   return op;
843 }
844
845 static void attach_collapse (BODY * b, short collapse, short init,
846                              short just_one)
847 {
848   short i;
849
850   for (; b; b = b->next) {
851     i = init || b->collapsed;
852     if (i && option (OPTDIGESTCOLLAPSE) && b->type == TYPEMULTIPART
853         && !ascii_strcasecmp (b->subtype, "digest"))
854       attach_collapse (b->parts, 1, 1, 0);
855     else if (b->type == TYPEMULTIPART
856              || mutt_is_message_type (b->type, b->subtype))
857       attach_collapse (b->parts, collapse, i, 0);
858     b->collapsed = collapse;
859     if (just_one)
860       return;
861   }
862 }
863
864 void mutt_attach_init (BODY * b)
865 {
866   for (; b; b = b->next) {
867     b->tagged = 0;
868     b->collapsed = 0;
869     if (b->parts)
870       mutt_attach_init (b->parts);
871   }
872 }
873
874 static const char *Function_not_permitted =
875 N_("Function not permitted in attach-message mode.");
876
877 #define CHECK_ATTACH if(option(OPTATTACHMSG)) \
878                      {\
879                         mutt_flushinp (); \
880                         mutt_error _(Function_not_permitted); \
881                         break; \
882                      }
883
884
885
886
887 void mutt_view_attachments (HEADER * hdr)
888 {
889   int secured = 0;
890   int need_secured = 0;
891
892   char helpstr[SHORT_STRING];
893   MUTTMENU *menu;
894   BODY *cur = NULL;
895   MESSAGE *msg;
896   FILE *fp;
897   ATTACHPTR **idx = NULL;
898   short idxlen = 0;
899   short idxmax = 0;
900   int flags = 0;
901   int op = OP_NULL;
902
903   /* make sure we have parsed this message */
904   mutt_parse_mime_message (Context, hdr);
905
906   mutt_message_hook (Context, hdr, M_MESSAGEHOOK);
907
908   if ((msg = mx_open_message (Context, hdr->msgno)) == NULL)
909     return;
910
911
912   if ((hdr->security & ENCRYPT) ||
913       (mutt_is_application_smime (hdr->content) & SMIMEOPAQUE))
914   {
915     need_secured = 1;
916
917     if ((hdr->security & ENCRYPT) && !crypt_valid_passphrase (hdr->security)) {
918       mx_close_message (&msg);
919       return;
920     }
921     if (hdr->security & APPLICATION_SMIME) {
922       if (hdr->env)
923         crypt_smime_getkeys (hdr->env);
924
925       if (mutt_is_application_smime (hdr->content)) {
926         secured = !crypt_smime_decrypt_mime (msg->fp, &fp,
927                                              hdr->content, &cur);
928
929         /* S/MIME nesting */
930         if ((mutt_is_application_smime (cur) & SMIMEOPAQUE)) {
931           BODY *_cur = cur;
932           FILE *_fp = fp;
933
934           fp = NULL;
935           cur = NULL;
936           secured = !crypt_smime_decrypt_mime (_fp, &fp, _cur, &cur);
937
938           body_list_wipe(&_cur);
939           safe_fclose (&_fp);
940         }
941       }
942       else
943         need_secured = 0;
944     }
945     if (hdr->security & APPLICATION_PGP) {
946       if (mutt_is_multipart_encrypted (hdr->content))
947         secured = !crypt_pgp_decrypt_mime (msg->fp, &fp, hdr->content, &cur);
948       else
949         need_secured = 0;
950     }
951
952     if (need_secured && !secured) {
953       mx_close_message (&msg);
954       mutt_error _("Can't decrypt encrypted message!");
955
956       return;
957     }
958   }
959
960   if (!need_secured) {
961     fp = msg->fp;
962     cur = hdr->content;
963   }
964
965   menu = mutt_new_menu ();
966   menu->menu = MENU_ATTACH;
967   menu->title = _("Attachments");
968   menu->make_entry = attach_entry;
969   menu->tag = mutt_tag_attach;
970   menu->help =
971     mutt_compile_help (helpstr, sizeof (helpstr), MENU_ATTACH, AttachHelp);
972
973   mutt_attach_init (cur);
974   attach_collapse (cur, 0, 1, 0);
975   mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
976
977   for (;;) {
978     if (op == OP_NULL)
979       op = mutt_menuLoop (menu);
980     switch (op) {
981     case OP_ATTACH_VIEW_MAILCAP:
982       mutt_view_attachment (fp, idx[menu->current]->content, M_MAILCAP,
983                             hdr, idx, idxlen);
984       menu->redraw = REDRAW_FULL;
985       break;
986
987     case OP_ATTACH_VIEW_TEXT:
988       mutt_view_attachment (fp, idx[menu->current]->content, M_AS_TEXT,
989                             hdr, idx, idxlen);
990       menu->redraw = REDRAW_FULL;
991       break;
992
993     case OP_DISPLAY_HEADERS:
994     case OP_VIEW_ATTACH:
995       op =
996         mutt_attach_display_loop (menu, op, fp, hdr, cur, &idx, &idxlen,
997                                   &idxmax, 1);
998       menu->redraw = REDRAW_FULL;
999       continue;
1000
1001     case OP_ATTACH_COLLAPSE:
1002       if (!idx[menu->current]->content->parts) {
1003         mutt_error _("There are no subparts to show!");
1004
1005         break;
1006       }
1007       if (!idx[menu->current]->content->collapsed)
1008         attach_collapse (idx[menu->current]->content, 1, 0, 1);
1009       else
1010         attach_collapse (idx[menu->current]->content, 0, 1, 1);
1011       mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
1012       break;
1013
1014     case OP_FORGET_PASSPHRASE:
1015       crypt_forget_passphrase ();
1016       break;
1017
1018     case OP_EXTRACT_KEYS:
1019       crypt_pgp_extract_keys_from_attachment_list (fp, menu->tagprefix,
1020                                                    menu->
1021                                                    tagprefix ? cur :
1022                                                    idx[menu->current]->
1023                                                    content);
1024       menu->redraw = REDRAW_FULL;
1025       break;
1026
1027     case OP_CHECK_TRADITIONAL:
1028       if (crypt_pgp_check_traditional (fp, menu->tagprefix ? cur
1029                                           : idx[menu->current]->content,
1030                                           menu->tagprefix))
1031       {
1032         hdr->security = crypt_query (cur);
1033         menu->redraw = REDRAW_FULL;
1034       }
1035       break;
1036
1037     case OP_PRINT:
1038       mutt_print_attachment_list (fp, menu->tagprefix,
1039                                   menu->tagprefix ? cur : idx[menu->current]->
1040                                   content);
1041       break;
1042
1043     case OP_PIPE:
1044       mutt_pipe_attachment_list (fp, menu->tagprefix,
1045                                  menu->tagprefix ? cur : idx[menu->current]->
1046                                  content, 0);
1047       break;
1048
1049     case OP_SAVE:
1050       mutt_save_attachment_list (fp, menu->tagprefix,
1051                                  menu->tagprefix ? cur : idx[menu->current]->
1052                                  content, hdr, menu);
1053
1054       if (!menu->tagprefix && option (OPTRESOLVE)
1055           && menu->current < menu->max - 1)
1056         menu->current++;
1057
1058       menu->redraw = REDRAW_MOTION_RESYNCH | REDRAW_FULL;
1059       break;
1060
1061     case OP_DELETE:
1062       CHECK_READONLY;
1063
1064       if (Context->magic == M_POP) {
1065         mutt_flushinp ();
1066         mutt_error _("Can't delete attachment from POP server.");
1067
1068         break;
1069       }
1070
1071 #ifdef USE_NNTP
1072       if (Context->magic == M_NNTP) {
1073         mutt_flushinp ();
1074         mutt_error _("Can't delete attachment from newsserver.");
1075
1076         break;
1077       }
1078 #endif
1079
1080       if (hdr->security & (~PGP_TRADITIONAL_CHECKED)) {
1081         mutt_message
1082           _("Deletion of attachments from encrypted messages is unsupported.");
1083       }
1084       else {
1085         if (!menu->tagprefix) {
1086           if (idx[menu->current]->parent_type == TYPEMULTIPART) {
1087             idx[menu->current]->content->deleted = 1;
1088             if (option (OPTRESOLVE) && menu->current < menu->max - 1) {
1089               menu->current++;
1090               menu->redraw = REDRAW_MOTION_RESYNCH;
1091             }
1092             else
1093               menu->redraw = REDRAW_CURRENT;
1094           }
1095           else
1096             mutt_message
1097               _("Only deletion of multipart attachments is supported.");
1098         }
1099         else {
1100           int x;
1101
1102           for (x = 0; x < menu->max; x++) {
1103             if (idx[x]->content->tagged) {
1104               if (idx[x]->parent_type == TYPEMULTIPART) {
1105                 idx[x]->content->deleted = 1;
1106                 menu->redraw = REDRAW_INDEX;
1107               }
1108               else
1109                 mutt_message
1110                   _("Only deletion of multipart attachments is supported.");
1111             }
1112           }
1113         }
1114       }
1115       break;
1116
1117     case OP_UNDELETE:
1118       CHECK_READONLY;
1119       if (!menu->tagprefix) {
1120         idx[menu->current]->content->deleted = 0;
1121         if (option (OPTRESOLVE) && menu->current < menu->max - 1) {
1122           menu->current++;
1123           menu->redraw = REDRAW_MOTION_RESYNCH;
1124         }
1125         else
1126           menu->redraw = REDRAW_CURRENT;
1127       }
1128       else {
1129         int x;
1130
1131         for (x = 0; x < menu->max; x++) {
1132           if (idx[x]->content->tagged) {
1133             idx[x]->content->deleted = 0;
1134             menu->redraw = REDRAW_INDEX;
1135           }
1136         }
1137       }
1138       break;
1139
1140     case OP_RESEND:
1141       CHECK_ATTACH;
1142       mutt_attach_resend (fp, hdr, idx, idxlen,
1143                           menu->tagprefix ? NULL : idx[menu->current]->
1144                           content);
1145       menu->redraw = REDRAW_FULL;
1146       break;
1147
1148     case OP_BOUNCE_MESSAGE:
1149       CHECK_ATTACH;
1150       mutt_attach_bounce (fp, hdr, idx, idxlen,
1151                           menu->tagprefix ? NULL : idx[menu->current]->
1152                           content);
1153       menu->redraw = REDRAW_FULL;
1154       break;
1155
1156     case OP_FORWARD_MESSAGE:
1157       CHECK_ATTACH;
1158       mutt_attach_forward (fp, hdr, idx, idxlen,
1159                            menu->tagprefix ? NULL : idx[menu->current]->
1160                            content, 0);
1161       menu->redraw = REDRAW_FULL;
1162       break;
1163
1164 #ifdef USE_NNTP
1165     case OP_FORWARD_TO_GROUP:
1166       CHECK_ATTACH;
1167       mutt_attach_forward (fp, hdr, idx, idxlen,
1168                            menu->tagprefix ? NULL : idx[menu->current]->
1169                            content, SENDNEWS);
1170       menu->redraw = REDRAW_FULL;
1171       break;
1172
1173     case OP_FOLLOWUP:
1174       CHECK_ATTACH;
1175
1176       if (!idx[menu->current]->content->hdr->env->followup_to ||
1177           m_strcasecmp(idx[menu->current]->content->hdr->env->followup_to,
1178                            "poster")
1179           || query_quadoption (OPT_FOLLOWUPTOPOSTER,
1180                                _("Reply by mail as poster prefers?")) !=
1181           M_YES) {
1182         mutt_attach_reply (fp, hdr, idx, idxlen,
1183                            menu->tagprefix ? NULL : idx[menu->current]->
1184                            content, SENDNEWS | SENDREPLY);
1185         menu->redraw = REDRAW_FULL;
1186         break;
1187       }
1188 #endif
1189
1190     case OP_REPLY:
1191     case OP_GROUP_REPLY:
1192     case OP_LIST_REPLY:
1193
1194       CHECK_ATTACH;
1195
1196       flags = SENDREPLY |
1197         (op == OP_GROUP_REPLY ? SENDGROUPREPLY : 0) |
1198         (op == OP_LIST_REPLY ? SENDLISTREPLY : 0);
1199       mutt_attach_reply (fp, hdr, idx, idxlen,
1200                          menu->tagprefix ? NULL : idx[menu->current]->content,
1201                          flags);
1202       menu->redraw = REDRAW_FULL;
1203       break;
1204
1205     case OP_EDIT_TYPE:
1206       mutt_edit_content_type (hdr, idx[menu->current]->content, fp);
1207       mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
1208       break;
1209
1210     case OP_EXIT:
1211       mx_close_message (&msg);
1212       hdr->attach_del = 0;
1213       while (idxmax-- > 0) {
1214         if (!idx[idxmax])
1215           continue;
1216         if (idx[idxmax]->content && idx[idxmax]->content->deleted)
1217           hdr->attach_del = 1;
1218         if (idx[idxmax]->content)
1219           idx[idxmax]->content->aptr = NULL;
1220         p_delete(&idx[idxmax]->tree);
1221         p_delete(&idx[idxmax]);
1222       }
1223       if (hdr->attach_del)
1224         hdr->changed = 1;
1225       p_delete(&idx);
1226       idxmax = 0;
1227
1228       if (need_secured && secured) {
1229         fclose (fp);
1230         body_list_wipe(&cur);
1231       }
1232
1233       mutt_menuDestroy (&menu);
1234       return;
1235     }
1236
1237     op = OP_NULL;
1238   }
1239
1240   /* not reached */
1241 }