e123884f1096a0068b76cce3deebaa286062997c
[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
20 #include "mutt.h"
21 #include "enter.h"
22 #include "handler.h"
23 #include "recvattach.h"
24 #include "mutt_curses.h"
25 #include "mutt_menu.h"
26 #include "rfc1524.h"
27 #include "mime.h"
28 #include "attach.h"
29 #include "mapping.h"
30 #include "mx.h"
31 #include "copy.h"
32 #include "mutt_crypt.h"
33
34
35 #include <ctype.h>
36 #include <stdlib.h>
37 #include <unistd.h>
38 #include <sys/wait.h>
39 #include <sys/stat.h>
40 #include <string.h>
41 #include <errno.h>
42
43 static const char *Mailbox_is_read_only = N_("Mailbox is read-only.");
44 static char LastSaveFolder[_POSIX_PATH_MAX] = "";
45
46 #define CHECK_READONLY if (Context->readonly) \
47 {\
48     mutt_flushinp (); \
49     mutt_error _(Mailbox_is_read_only); \
50     break; \
51 }
52
53 #define SW              (option(OPTMBOXPANE)?SidebarWidth:0)
54
55 static struct mapping_t AttachHelp[] = {
56   {N_("Exit"), OP_EXIT},
57   {N_("Save"), OP_SAVE},
58   {N_("Pipe"), OP_PIPE},
59   {N_("Print"), OP_PRINT},
60   {N_("Help"), OP_HELP},
61   {NULL, OP_NULL}
62 };
63
64 static int mutt_extract_path (char *filename, char *path)
65 {
66   char *tmp = p_new(char, _POSIX_PATH_MAX);
67   char *help_ptr;
68
69   help_ptr = tmp;
70
71   while (*filename != '\0') {
72     if (*filename == '/') {
73       *help_ptr++ = *filename++;
74       *help_ptr++ = '\0';
75       strcat (path, tmp);
76       help_ptr = tmp;
77     }
78     *help_ptr++ = *filename++;
79   }
80   p_delete(&tmp);
81   return 0;
82 }
83
84 void mutt_update_tree (ATTACHPTR ** idx, short idxlen)
85 {
86   char buf[STRING];
87   char *s;
88   int x;
89
90   for (x = 0; x < idxlen; x++) {
91     idx[x]->num = x;
92     if (2 * (idx[x]->level + 2) < sizeof (buf)) {
93       if (idx[x]->level) {
94         s = buf + 2 * (idx[x]->level - 1);
95         *s++ = (idx[x]->content->next) ? M_TREE_LTEE : M_TREE_LLCORNER;
96         *s++ = M_TREE_HLINE;
97         *s++ = M_TREE_RARROW;
98       }
99       else
100         s = buf;
101       *s = 0;
102     }
103
104     if (idx[x]->tree) {
105       if (m_strcmp(idx[x]->tree, buf) != 0)
106         str_replace (&idx[x]->tree, buf);
107     }
108     else
109       idx[x]->tree = m_strdup(buf);
110
111     if (2 * (idx[x]->level + 2) < sizeof (buf) && idx[x]->level) {
112       s = buf + 2 * (idx[x]->level - 1);
113       *s++ = (idx[x]->content->next) ? '\005' : '\006';
114       *s++ = '\006';
115     }
116   }
117 }
118
119 ATTACHPTR **mutt_gen_attach_list (BODY * m,
120                                   int parent_type,
121                                   ATTACHPTR ** idx,
122                                   short *idxlen,
123                                   short *idxmax, int level, int compose)
124 {
125   ATTACHPTR *new;
126   int i;
127
128   for (; m; m = m->next) {
129     if (*idxlen == *idxmax) {
130       p_realloc(&idx, (*idxmax) += 5);
131       for (i = *idxlen; i < *idxmax; i++)
132         idx[i] = NULL;
133     }
134
135     if (m->type == TYPEMULTIPART && m->parts
136         && (compose
137             || (parent_type == -1
138                 && ascii_strcasecmp ("alternative", m->subtype)))
139         && (!(WithCrypto & APPLICATION_PGP)
140             || !mutt_is_multipart_encrypted (m))
141       ) {
142       idx =
143         mutt_gen_attach_list (m->parts, m->type, idx, idxlen, idxmax, level,
144                               compose);
145     }
146     else {
147       if (!idx[*idxlen])
148         idx[*idxlen] = p_new(ATTACHPTR, 1);
149
150       new = idx[(*idxlen)++];
151       new->content = m;
152       m->aptr = new;
153       new->parent_type = parent_type;
154       new->level = level;
155
156       /* We don't support multipart messages in the compose menu yet */
157       if (!compose && !m->collapsed &&
158           ((m->type == TYPEMULTIPART && (!(WithCrypto & APPLICATION_PGP)
159                                          || !mutt_is_multipart_encrypted (m))
160            )
161            || mutt_is_message_type (m->type, m->subtype))) {
162         idx =
163           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         strfcpy (path, aptr->content->filename, sizeof (path));
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, size_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, *directory, mutt_basename (body->filename),
412                         sizeof (buf));
413     else
414       strfcpy (buf, body->filename, sizeof (buf));
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       strfcpy (buf, tmpbuf, sizeof (buf));
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       strfcpy (LastSaveFolder, path, sizeof (LastSaveFolder));
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       strfcpy (tfile, buf, sizeof (tfile));
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           strfcpy (buf, NONULL (top->filename), sizeof (buf));
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 filter)
569 {
570   char tfile[_POSIX_PATH_MAX];
571   char warning[STRING + _POSIX_PATH_MAX];
572
573   if (filter) {
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 (filter) {
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 (filter && 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 filter, STATE * state)
625 {
626   for (; top; top = top->next) {
627     if (!tag || top->tagged) {
628       if (!filter && !option (OPTATTACHSPLIT))
629         pipe_attachment (fp, top, state);
630       else
631         mutt_query_pipe_attachment (command, fp, top, filter);
632     }
633     else if (top->parts)
634       pipe_attachment_list (command, fp, tag, top->parts, filter, state);
635     if (!tag)
636       break;
637   }
638 }
639
640 void mutt_pipe_attachment_list (FILE * fp, int tag, BODY * top, int filter)
641 {
642   STATE state;
643   char buf[SHORT_STRING];
644   pid_t thepid;
645
646   if (fp)
647     filter = 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 ((filter ? _("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 (!filter && !option (OPTATTACHSPLIT)) {
659     mutt_endwin (NULL);
660     thepid = mutt_create_filter (buf, &state.fpout, NULL, NULL);
661     pipe_attachment_list (buf, fp, tag, top, filter, &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, filter, &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 (!(WithCrypto & APPLICATION_PGP)
843           || (hdr && hdr->security & PGP_TRADITIONAL_CHECKED)) {
844         op = OP_NULL;
845         break;
846       }
847       /* fall through */
848     case OP_ATTACH_COLLAPSE:
849       if (recv)
850         return op;
851     default:
852       op = OP_NULL;
853     }
854   }
855   while (op != OP_NULL);
856
857 #if 0
858   if (option (OPTWEED) != old_optweed)
859     toggle_option (OPTWEED);
860 #endif
861   return op;
862 }
863
864 static void attach_collapse (BODY * b, short collapse, short init,
865                              short just_one)
866 {
867   short i;
868
869   for (; b; b = b->next) {
870     i = init || b->collapsed;
871     if (i && option (OPTDIGESTCOLLAPSE) && b->type == TYPEMULTIPART
872         && !ascii_strcasecmp (b->subtype, "digest"))
873       attach_collapse (b->parts, 1, 1, 0);
874     else if (b->type == TYPEMULTIPART
875              || mutt_is_message_type (b->type, b->subtype))
876       attach_collapse (b->parts, collapse, i, 0);
877     b->collapsed = collapse;
878     if (just_one)
879       return;
880   }
881 }
882
883 void mutt_attach_init (BODY * b)
884 {
885   for (; b; b = b->next) {
886     b->tagged = 0;
887     b->collapsed = 0;
888     if (b->parts)
889       mutt_attach_init (b->parts);
890   }
891 }
892
893 static const char *Function_not_permitted =
894 N_("Function not permitted in attach-message mode.");
895
896 #define CHECK_ATTACH if(option(OPTATTACHMSG)) \
897                      {\
898                         mutt_flushinp (); \
899                         mutt_error _(Function_not_permitted); \
900                         break; \
901                      }
902
903
904
905
906 void mutt_view_attachments (HEADER * hdr)
907 {
908   int secured = 0;
909   int need_secured = 0;
910
911   char helpstr[SHORT_STRING];
912   MUTTMENU *menu;
913   BODY *cur = NULL;
914   MESSAGE *msg;
915   FILE *fp;
916   ATTACHPTR **idx = NULL;
917   short idxlen = 0;
918   short idxmax = 0;
919   int flags = 0;
920   int op = OP_NULL;
921
922   /* make sure we have parsed this message */
923   mutt_parse_mime_message (Context, hdr);
924
925   mutt_message_hook (Context, hdr, M_MESSAGEHOOK);
926
927   if ((msg = mx_open_message (Context, hdr->msgno)) == NULL)
928     return;
929
930
931   if (WithCrypto && ((hdr->security & ENCRYPT) ||
932                      (mutt_is_application_smime (hdr->content) &
933                       SMIMEOPAQUE))) {
934     need_secured = 1;
935
936     if ((hdr->security & ENCRYPT) && !crypt_valid_passphrase (hdr->security)) {
937       mx_close_message (&msg);
938       return;
939     }
940     if ((WithCrypto & APPLICATION_SMIME)
941         && (hdr->security & APPLICATION_SMIME)) {
942       if (hdr->env)
943         crypt_smime_getkeys (hdr->env);
944
945       if (mutt_is_application_smime (hdr->content)) {
946         secured = !crypt_smime_decrypt_mime (msg->fp, &fp,
947                                              hdr->content, &cur);
948
949         /* S/MIME nesting */
950         if ((mutt_is_application_smime (cur) & SMIMEOPAQUE)) {
951           BODY *_cur = cur;
952           FILE *_fp = fp;
953
954           fp = NULL;
955           cur = NULL;
956           secured = !crypt_smime_decrypt_mime (_fp, &fp, _cur, &cur);
957
958           mutt_free_body (&_cur);
959           safe_fclose (&_fp);
960         }
961       }
962       else
963         need_secured = 0;
964     }
965     if ((WithCrypto & APPLICATION_PGP) && (hdr->security & APPLICATION_PGP)) {
966       if (mutt_is_multipart_encrypted (hdr->content))
967         secured = !crypt_pgp_decrypt_mime (msg->fp, &fp, hdr->content, &cur);
968       else
969         need_secured = 0;
970     }
971
972     if (need_secured && !secured) {
973       mx_close_message (&msg);
974       mutt_error _("Can't decrypt encrypted message!");
975
976       return;
977     }
978   }
979
980   if (!WithCrypto || !need_secured) {
981     fp = msg->fp;
982     cur = hdr->content;
983   }
984
985   menu = mutt_new_menu ();
986   menu->menu = MENU_ATTACH;
987   menu->title = _("Attachments");
988   menu->make_entry = attach_entry;
989   menu->tag = mutt_tag_attach;
990   menu->help =
991     mutt_compile_help (helpstr, sizeof (helpstr), MENU_ATTACH, AttachHelp);
992
993   mutt_attach_init (cur);
994   attach_collapse (cur, 0, 1, 0);
995   mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
996
997   FOREVER {
998     if (op == OP_NULL)
999       op = mutt_menuLoop (menu);
1000     switch (op) {
1001     case OP_ATTACH_VIEW_MAILCAP:
1002       mutt_view_attachment (fp, idx[menu->current]->content, M_MAILCAP,
1003                             hdr, idx, idxlen);
1004       menu->redraw = REDRAW_FULL;
1005       break;
1006
1007     case OP_ATTACH_VIEW_TEXT:
1008       mutt_view_attachment (fp, idx[menu->current]->content, M_AS_TEXT,
1009                             hdr, idx, idxlen);
1010       menu->redraw = REDRAW_FULL;
1011       break;
1012
1013     case OP_DISPLAY_HEADERS:
1014     case OP_VIEW_ATTACH:
1015       op =
1016         mutt_attach_display_loop (menu, op, fp, hdr, cur, &idx, &idxlen,
1017                                   &idxmax, 1);
1018       menu->redraw = REDRAW_FULL;
1019       continue;
1020
1021     case OP_ATTACH_COLLAPSE:
1022       if (!idx[menu->current]->content->parts) {
1023         mutt_error _("There are no subparts to show!");
1024
1025         break;
1026       }
1027       if (!idx[menu->current]->content->collapsed)
1028         attach_collapse (idx[menu->current]->content, 1, 0, 1);
1029       else
1030         attach_collapse (idx[menu->current]->content, 0, 1, 1);
1031       mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
1032       break;
1033
1034     case OP_FORGET_PASSPHRASE:
1035       crypt_forget_passphrase ();
1036       break;
1037
1038     case OP_EXTRACT_KEYS:
1039       if ((WithCrypto & APPLICATION_PGP)) {
1040         crypt_pgp_extract_keys_from_attachment_list (fp, menu->tagprefix,
1041                                                      menu->
1042                                                      tagprefix ? cur :
1043                                                      idx[menu->current]->
1044                                                      content);
1045         menu->redraw = REDRAW_FULL;
1046       }
1047       break;
1048
1049     case OP_CHECK_TRADITIONAL:
1050       if ((WithCrypto & APPLICATION_PGP)
1051           && crypt_pgp_check_traditional (fp, menu->tagprefix ? cur
1052                                           : idx[menu->current]->content,
1053                                           menu->tagprefix)) {
1054         hdr->security = crypt_query (cur);
1055         menu->redraw = REDRAW_FULL;
1056       }
1057       break;
1058
1059     case OP_PRINT:
1060       mutt_print_attachment_list (fp, menu->tagprefix,
1061                                   menu->tagprefix ? cur : idx[menu->current]->
1062                                   content);
1063       break;
1064
1065     case OP_PIPE:
1066       mutt_pipe_attachment_list (fp, menu->tagprefix,
1067                                  menu->tagprefix ? cur : idx[menu->current]->
1068                                  content, 0);
1069       break;
1070
1071     case OP_SAVE:
1072       mutt_save_attachment_list (fp, menu->tagprefix,
1073                                  menu->tagprefix ? cur : idx[menu->current]->
1074                                  content, hdr, menu);
1075
1076       if (!menu->tagprefix && option (OPTRESOLVE)
1077           && menu->current < menu->max - 1)
1078         menu->current++;
1079
1080       menu->redraw = REDRAW_MOTION_RESYNCH | REDRAW_FULL;
1081       break;
1082
1083     case OP_DELETE:
1084       CHECK_READONLY;
1085
1086 #ifdef USE_POP
1087       if (Context->magic == M_POP) {
1088         mutt_flushinp ();
1089         mutt_error _("Can't delete attachment from POP server.");
1090
1091         break;
1092       }
1093 #endif
1094
1095 #ifdef USE_NNTP
1096       if (Context->magic == M_NNTP) {
1097         mutt_flushinp ();
1098         mutt_error _("Can't delete attachment from newsserver.");
1099
1100         break;
1101       }
1102 #endif
1103
1104       if (WithCrypto && (hdr->security & (~PGP_TRADITIONAL_CHECKED))) {
1105         mutt_message
1106           _
1107           ("Deletion of attachments from encrypted messages is unsupported.");
1108       }
1109       else {
1110         if (!menu->tagprefix) {
1111           if (idx[menu->current]->parent_type == TYPEMULTIPART) {
1112             idx[menu->current]->content->deleted = 1;
1113             if (option (OPTRESOLVE) && menu->current < menu->max - 1) {
1114               menu->current++;
1115               menu->redraw = REDRAW_MOTION_RESYNCH;
1116             }
1117             else
1118               menu->redraw = REDRAW_CURRENT;
1119           }
1120           else
1121             mutt_message
1122               _("Only deletion of multipart attachments is supported.");
1123         }
1124         else {
1125           int x;
1126
1127           for (x = 0; x < menu->max; x++) {
1128             if (idx[x]->content->tagged) {
1129               if (idx[x]->parent_type == TYPEMULTIPART) {
1130                 idx[x]->content->deleted = 1;
1131                 menu->redraw = REDRAW_INDEX;
1132               }
1133               else
1134                 mutt_message
1135                   _("Only deletion of multipart attachments is supported.");
1136             }
1137           }
1138         }
1139       }
1140       break;
1141
1142     case OP_UNDELETE:
1143       CHECK_READONLY;
1144       if (!menu->tagprefix) {
1145         idx[menu->current]->content->deleted = 0;
1146         if (option (OPTRESOLVE) && menu->current < menu->max - 1) {
1147           menu->current++;
1148           menu->redraw = REDRAW_MOTION_RESYNCH;
1149         }
1150         else
1151           menu->redraw = REDRAW_CURRENT;
1152       }
1153       else {
1154         int x;
1155
1156         for (x = 0; x < menu->max; x++) {
1157           if (idx[x]->content->tagged) {
1158             idx[x]->content->deleted = 0;
1159             menu->redraw = REDRAW_INDEX;
1160           }
1161         }
1162       }
1163       break;
1164
1165     case OP_RESEND:
1166       CHECK_ATTACH;
1167       mutt_attach_resend (fp, hdr, idx, idxlen,
1168                           menu->tagprefix ? NULL : idx[menu->current]->
1169                           content);
1170       menu->redraw = REDRAW_FULL;
1171       break;
1172
1173     case OP_BOUNCE_MESSAGE:
1174       CHECK_ATTACH;
1175       mutt_attach_bounce (fp, hdr, idx, idxlen,
1176                           menu->tagprefix ? NULL : idx[menu->current]->
1177                           content);
1178       menu->redraw = REDRAW_FULL;
1179       break;
1180
1181     case OP_FORWARD_MESSAGE:
1182       CHECK_ATTACH;
1183       mutt_attach_forward (fp, hdr, idx, idxlen,
1184                            menu->tagprefix ? NULL : idx[menu->current]->
1185                            content, 0);
1186       menu->redraw = REDRAW_FULL;
1187       break;
1188
1189 #ifdef USE_NNTP
1190     case OP_FORWARD_TO_GROUP:
1191       CHECK_ATTACH;
1192       mutt_attach_forward (fp, hdr, idx, idxlen,
1193                            menu->tagprefix ? NULL : idx[menu->current]->
1194                            content, SENDNEWS);
1195       menu->redraw = REDRAW_FULL;
1196       break;
1197
1198     case OP_FOLLOWUP:
1199       CHECK_ATTACH;
1200
1201       if (!idx[menu->current]->content->hdr->env->followup_to ||
1202           str_casecmp (idx[menu->current]->content->hdr->env->followup_to,
1203                            "poster")
1204           || query_quadoption (OPT_FOLLOWUPTOPOSTER,
1205                                _("Reply by mail as poster prefers?")) !=
1206           M_YES) {
1207         mutt_attach_reply (fp, hdr, idx, idxlen,
1208                            menu->tagprefix ? NULL : idx[menu->current]->
1209                            content, SENDNEWS | SENDREPLY);
1210         menu->redraw = REDRAW_FULL;
1211         break;
1212       }
1213 #endif
1214
1215     case OP_REPLY:
1216     case OP_GROUP_REPLY:
1217     case OP_LIST_REPLY:
1218
1219       CHECK_ATTACH;
1220
1221       flags = SENDREPLY |
1222         (op == OP_GROUP_REPLY ? SENDGROUPREPLY : 0) |
1223         (op == OP_LIST_REPLY ? SENDLISTREPLY : 0);
1224       mutt_attach_reply (fp, hdr, idx, idxlen,
1225                          menu->tagprefix ? NULL : idx[menu->current]->content,
1226                          flags);
1227       menu->redraw = REDRAW_FULL;
1228       break;
1229
1230     case OP_EDIT_TYPE:
1231       mutt_edit_content_type (hdr, idx[menu->current]->content, fp);
1232       mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
1233       break;
1234
1235     case OP_EXIT:
1236       mx_close_message (&msg);
1237       hdr->attach_del = 0;
1238       while (idxmax-- > 0) {
1239         if (!idx[idxmax])
1240           continue;
1241         if (idx[idxmax]->content && idx[idxmax]->content->deleted)
1242           hdr->attach_del = 1;
1243         if (idx[idxmax]->content)
1244           idx[idxmax]->content->aptr = NULL;
1245         p_delete(&idx[idxmax]->tree);
1246         p_delete(&idx[idxmax]);
1247       }
1248       if (hdr->attach_del)
1249         hdr->changed = 1;
1250       p_delete(&idx);
1251       idxmax = 0;
1252
1253       if (WithCrypto && need_secured && secured) {
1254         fclose (fp);
1255         mutt_free_body (&cur);
1256       }
1257
1258       mutt_menuDestroy (&menu);
1259       return;
1260     }
1261
1262     op = OP_NULL;
1263   }
1264
1265   /* not reached */
1266 }