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