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