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