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