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