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