b76c43a298fa37f25323ea12121f726f1196da80
[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 #include <lib-lib/lib-lib.h>
12
13 #include <lib-mime/mime.h>
14
15 #include <lib-ui/curses.h>
16 #include <lib-ui/enter.h>
17 #include <lib-ui/menu.h>
18 #include <lib-mx/mx.h>
19
20 #include <lib-sys/unix.h>
21
22 #include "mutt.h"
23 #include "handler.h"
24 #include "recvattach.h"
25 #include "attach.h"
26 #include "copy.h"
27 #include <lib-crypt/crypt.h>
28
29
30 static const char *Mailbox_is_read_only = N_("Mailbox is read-only.");
31 static char LastSaveFolder[_POSIX_PATH_MAX] = "";
32
33 #define CHECK_READONLY if (Context->readonly) \
34 {\
35     mutt_flushinp (); \
36     mutt_error _(Mailbox_is_read_only); \
37     break; \
38 }
39
40 #define SW              (option(OPTMBOXPANE)?SidebarWidth:0)
41
42 static struct mapping_t AttachHelp[] = {
43   {N_("Exit"), OP_EXIT},
44   {N_("Save"), OP_SAVE},
45   {N_("Pipe"), OP_PIPE},
46   {N_("Print"), OP_PRINT},
47   {N_("Help"), OP_HELP},
48   {NULL, OP_NULL}
49 };
50
51 static int mutt_extract_path (char *filename, char *path, ssize_t pathlen)
52 {
53   char *tmp = p_new(char, _POSIX_PATH_MAX);
54   char *help_ptr;
55
56   help_ptr = tmp;
57
58   while (*filename != '\0') {
59     if (*filename == '/') {
60       *help_ptr++ = *filename++;
61       *help_ptr++ = '\0';
62       m_strcat(path, pathlen, tmp);
63       help_ptr = tmp;
64     }
65     *help_ptr++ = *filename++;
66   }
67   p_delete(&tmp);
68   return 0;
69 }
70
71 void mutt_update_tree (ATTACHPTR ** idx, short idxlen)
72 {
73   char buf[STRING];
74   char *s;
75   int x;
76
77   for (x = 0; x < idxlen; x++) {
78     idx[x]->num = x;
79     if (2 * (idx[x]->level + 2) < ssizeof (buf)) {
80       if (idx[x]->level) {
81         s = buf + 2 * (idx[x]->level - 1);
82         *s++ = (idx[x]->content->next) ? M_TREE_LTEE : M_TREE_LLCORNER;
83         *s++ = M_TREE_HLINE;
84         *s++ = M_TREE_RARROW;
85       }
86       else
87         s = buf;
88       *s = 0;
89     }
90
91     if (idx[x]->tree) {
92       if (m_strcmp(idx[x]->tree, buf) != 0)
93         m_strreplace(&idx[x]->tree, buf);
94     }
95     else
96       idx[x]->tree = m_strdup(buf);
97
98     if (2 * (idx[x]->level + 2) < ssizeof (buf) && idx[x]->level) {
99       s = buf + 2 * (idx[x]->level - 1);
100       *s++ = (idx[x]->content->next) ? '\005' : '\006';
101       *s++ = '\006';
102     }
103   }
104 }
105
106 ATTACHPTR **mutt_gen_attach_list (BODY * m,
107                                   int parent_type,
108                                   ATTACHPTR ** idx,
109                                   short *idxlen,
110                                   short *idxmax, int level, int compose)
111 {
112   ATTACHPTR *new;
113   int i;
114
115   for (; m; m = m->next) {
116     if (*idxlen == *idxmax) {
117       p_realloc(&idx, (*idxmax) += 5);
118       for (i = *idxlen; i < *idxmax; i++)
119         idx[i] = NULL;
120     }
121
122     if (m->type == TYPEMULTIPART && m->parts
123         && (compose
124             || (parent_type == -1
125                 && ascii_strcasecmp ("alternative", m->subtype)))
126         && (!mutt_is_multipart_encrypted (m))
127       ) {
128       idx =
129         mutt_gen_attach_list (m->parts, m->type, idx, idxlen, idxmax, level,
130                               compose);
131     }
132     else {
133       if (!idx[*idxlen])
134         idx[*idxlen] = p_new(ATTACHPTR, 1);
135
136       new = idx[(*idxlen)++];
137       new->content = m;
138       m->aptr = new;
139       new->parent_type = parent_type;
140       new->level = level;
141
142       /* We don't support multipart messages in the compose menu yet */
143       if (!compose && !m->collapsed
144       &&  ((m->type == TYPEMULTIPART && !mutt_is_multipart_encrypted(m))
145       ||  mutt_is_message_type(m)))
146       {
147         idx = mutt_gen_attach_list (m->parts, m->type, idx, idxlen, idxmax,
148                                     level + 1, compose);
149       }
150     }
151   }
152
153   if (level == 0)
154     mutt_update_tree (idx, *idxlen);
155
156   return (idx);
157 }
158
159 /* %c = character set: convert?
160  * %C = character set
161  * %D = deleted flag
162  * %d = description
163  * %e = MIME content-transfer-encoding
164  * %f = filename
165  * %I = content-disposition, either I (inline) or A (attachment)
166  * %t = tagged flag
167  * %T = tree chars
168  * %m = major MIME type
169  * %M = MIME subtype
170  * %n = attachment number
171  * %s = size
172  * %u = unlink 
173  */
174 const char *mutt_attach_fmt (char *dest,
175                              ssize_t destlen,
176                              char op,
177                              const char *src,
178                              const char *prefix,
179                              const char *ifstring,
180                              const char *elsestring,
181                              unsigned long data, format_flag flags)
182 {
183   char fmt[16];
184   char tmp[SHORT_STRING];
185   char charset[SHORT_STRING];
186   ATTACHPTR *aptr = (ATTACHPTR *) data;
187   int optional = (flags & M_FORMAT_OPTIONAL);
188   ssize_t l;
189
190   switch (op) {
191   case 'C':
192     if (!optional) {
193       if (mutt_is_text_part (aptr->content) &&
194           mutt_get_body_charset (charset, sizeof (charset), aptr->content))
195         mutt_format_s (dest, destlen, prefix, charset);
196       else
197         mutt_format_s (dest, destlen, prefix, "");
198     }
199     else if (!mutt_is_text_part (aptr->content) ||
200              !mutt_get_body_charset (charset, sizeof (charset),
201                                      aptr->content))
202       optional = 0;
203     break;
204   case 'c':
205     /* XXX */
206     if (!optional) {
207       snprintf (fmt, sizeof (fmt), "%%%sc", prefix);
208       snprintf (dest, destlen, fmt, aptr->content->type != TYPETEXT ||
209                 aptr->content->noconv ? 'n' : 'c');
210     }
211     else if (aptr->content->type != TYPETEXT || aptr->content->noconv)
212       optional = 0;
213     break;
214   case 'd':
215     if (!optional) {
216       if (aptr->content->description) {
217         mutt_format_s (dest, destlen, prefix, aptr->content->description);
218         break;
219       }
220       if (mutt_is_message_type(aptr->content) && MsgFmt && aptr->content->hdr)
221       {
222         char s[SHORT_STRING];
223
224         _mutt_make_string (s, sizeof (s), MsgFmt, NULL, aptr->content->hdr,
225                            M_FORMAT_FORCESUBJ | M_FORMAT_MAKEPRINT |
226                            M_FORMAT_ARROWCURSOR);
227         if (*s) {
228           mutt_format_s (dest, destlen, prefix, s);
229           break;
230         }
231       }
232       if (!aptr->content->filename) {
233         mutt_format_s (dest, destlen, prefix, "<no description>");
234         break;
235       }
236     }
237     else if (aptr->content->description ||
238              (mutt_is_message_type(aptr->content)
239               && MsgFmt && aptr->content->hdr))
240       break;
241     /* FALLS THROUGH TO 'f' */
242   case 'f':
243     if (!optional) {
244       if (aptr->content->filename && *aptr->content->filename == '/') {
245         char path[_POSIX_PATH_MAX];
246
247         m_strcpy(path, sizeof(path), aptr->content->filename);
248         mutt_pretty_mailbox (path);
249         mutt_format_s (dest, destlen, prefix, path);
250       }
251       else
252         mutt_format_s (dest, destlen, prefix,
253                        NONULL (aptr->content->filename));
254     }
255     else if (!aptr->content->filename)
256       optional = 0;
257     break;
258   case 'D':
259     if (!optional)
260       snprintf (dest, destlen, "%c", aptr->content->deleted ? 'D' : ' ');
261     else if (!aptr->content->deleted)
262       optional = 0;
263     break;
264   case 'e':
265     if (!optional)
266       mutt_format_s (dest, destlen, prefix,
267                      ENCODING (aptr->content->encoding));
268     break;
269   case 'I':
270     if (!optional) {
271       snprintf (dest, destlen, "%c",
272                 (aptr->content->disposition == DISPINLINE) ? 'I' : 'A');
273     }
274     break;
275   case 'm':
276     if (!optional)
277       mutt_format_s (dest, destlen, prefix, TYPE (aptr->content));
278     break;
279   case 'M':
280     if (!optional)
281       mutt_format_s (dest, destlen, prefix, aptr->content->subtype);
282     else if (!aptr->content->subtype)
283       optional = 0;
284     break;
285   case 'n':
286     if (!optional) {
287       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
288       snprintf (dest, destlen, fmt, aptr->num + 1);
289     }
290     break;
291   case 'Q':
292     if (optional)
293       optional = aptr->content->attach_qualifies;
294     else {
295       snprintf (fmt, sizeof (fmt), "%%%sc", prefix);
296       mutt_format_s (dest, destlen, fmt, "Q");
297     }
298     break;
299   case 's':
300     if (flags & M_FORMAT_STAT_FILE) {
301       struct stat st;
302
303       stat (aptr->content->filename, &st);
304       l = st.st_size;
305     }
306     else
307       l = aptr->content->length;
308
309     if (!optional) {
310       mutt_pretty_size (tmp, sizeof (tmp), l);
311       mutt_format_s (dest, destlen, prefix, tmp);
312     }
313     else if (l == 0)
314       optional = 0;
315
316     break;
317   case 't':
318     if (!optional)
319       snprintf (dest, destlen, "%c", aptr->content->tagged ? '*' : ' ');
320     else if (!aptr->content->tagged)
321       optional = 0;
322     break;
323   case 'T':
324     if (!optional)
325       mutt_format_s_tree (dest, destlen, prefix, NONULL (aptr->tree));
326     else if (!aptr->tree)
327       optional = 0;
328     break;
329   case 'u':
330     if (!optional)
331       snprintf (dest, destlen, "%c", aptr->content->unlink ? '-' : ' ');
332     else if (!aptr->content->unlink)
333       optional = 0;
334     break;
335   case 'X':
336     if (optional)
337       optional = (aptr->content->attach_count + aptr->content->attach_qualifies) != 0;
338     else {
339       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
340       snprintf (dest, destlen, fmt, aptr->content->attach_count + aptr->content->attach_qualifies);
341     }
342     break;
343   default:
344     *dest = 0;
345   }
346
347   if (optional)
348     mutt_FormatString (dest, destlen, ifstring, mutt_attach_fmt, data, 0);
349   else if (flags & M_FORMAT_OPTIONAL)
350     mutt_FormatString (dest, destlen, elsestring, mutt_attach_fmt, data, 0);
351   return (src);
352 }
353
354 static void attach_entry (char *b, ssize_t blen, MUTTMENU * menu, int num)
355 {
356   int w=(COLS-SW)>blen?blen:(COLS-SW);
357   mutt_FormatString (b, w, NONULL (AttachFormat), mutt_attach_fmt,
358                      (unsigned long) (((ATTACHPTR **) menu->data)[num]),
359                      M_FORMAT_ARROWCURSOR);
360 }
361
362 int mutt_tag_attach (MUTTMENU * menu, int n, int m)
363 {
364   BODY *cur = ((ATTACHPTR **) menu->data)[n]->content;
365   int ot = cur->tagged;
366
367   cur->tagged = (m >= 0 ? m : !cur->tagged);
368   return cur->tagged - ot;
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, sizeof(buf), *directory,
385                        mutt_basename(body->filename));
386     else
387       m_strcpy(buf, sizeof(buf), body->filename);
388   }
389   else if (body->hdr &&
390            body->encoding != ENCBASE64 &&
391            body->encoding != ENCQUOTEDPRINTABLE &&
392            mutt_is_message_type(body))
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       m_strcpy(buf, sizeof(buf), tmpbuf);
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, sizeof(path));
417       m_strcpy(LastSaveFolder, sizeof(LastSaveFolder), path);
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));
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       m_strcpy(tfile, sizeof(tfile), buf);
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           m_strcpy(buf, sizeof(buf), NONULL(top->filename));
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             m_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             m_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   p_delete(&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 afilter)
542 {
543   char tfile[_POSIX_PATH_MAX];
544   char warning[STRING + _POSIX_PATH_MAX];
545
546   if (afilter) {
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 (afilter) {
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 (afilter && 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     m_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 afilter, STATE * state)
598 {
599   for (; top; top = top->next) {
600     if (!tag || top->tagged) {
601       if (!afilter && !option (OPTATTACHSPLIT))
602         pipe_attachment (fp, top, state);
603       else
604         mutt_query_pipe_attachment (command, fp, top, afilter);
605     }
606     else if (top->parts)
607       pipe_attachment_list (command, fp, tag, top->parts, afilter, state);
608     if (!tag)
609       break;
610   }
611 }
612
613 void mutt_pipe_attachment_list (FILE * fp, int tag, BODY * top, int afilter)
614 {
615   STATE state;
616   char buf[SHORT_STRING];
617   pid_t thepid;
618
619   if (fp)
620     afilter = 0;                 /* sanity check: we can't filter in the recv case yet */
621
622   buf[0] = 0;
623   p_clear(&state, 1);
624
625   if (mutt_get_field ((afilter ? _("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 (!afilter && !option (OPTATTACHSPLIT)) {
632     mutt_endwin (NULL);
633     thepid = mutt_create_filter (buf, &state.fpout, NULL, NULL);
634     pipe_attachment_list (buf, fp, tag, top, afilter, &state);
635     m_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, afilter, &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               m_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     p_clear(&state, 1);
728     thepid = mutt_create_filter (NONULL (PrintCmd), &state.fpout, NULL, NULL);
729     print_attachment_list (fp, tag, top, &state);
730     m_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 static 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   do {
769     switch (op) {
770     case OP_DISPLAY_HEADERS:
771       toggle_option (OPTWEED);
772       /* fall through */
773
774     case OP_VIEW_ATTACH:
775       op = mutt_view_attachment (fp, idx[menu->current]->content, M_REGULAR,
776                                  hdr, idx, *idxlen);
777       break;
778
779     case OP_NEXT_ENTRY:
780     case OP_MAIN_NEXT_UNDELETED:       /* hack */
781       if (menu->current < menu->max - 1) {
782         menu->current++;
783         op = OP_VIEW_ATTACH;
784       }
785       else
786         op = OP_NULL;
787       break;
788     case OP_PREV_ENTRY:
789     case OP_MAIN_PREV_UNDELETED:       /* hack */
790       if (menu->current > 0) {
791         menu->current--;
792         op = OP_VIEW_ATTACH;
793       }
794       else
795         op = OP_NULL;
796       break;
797     case OP_EDIT_TYPE:
798       /* when we edit the content-type, we should redisplay the attachment
799          immediately */
800       mutt_edit_content_type (hdr, idx[menu->current]->content, fp);
801       if (idxmax) {
802         mutt_update_attach_index (cur, idxp, idxlen, idxmax, menu);
803         idx = *idxp;
804       }
805       op = OP_VIEW_ATTACH;
806       break;
807       /* functions which are passed through from the pager */
808     case OP_CHECK_TRADITIONAL:
809       if (hdr && hdr->security & PGP_TRADITIONAL_CHECKED) {
810         op = OP_NULL;
811         break;
812       }
813       /* fall through */
814     case OP_ATTACH_COLLAPSE:
815       if (recv)
816         return op;
817     default:
818       op = OP_NULL;
819     }
820   }
821   while (op != OP_NULL);
822
823   return op;
824 }
825
826 static void attach_collapse (BODY * b, short collapse, short init,
827                              short just_one)
828 {
829   short i;
830
831   for (; b; b = b->next) {
832     i = init || b->collapsed;
833     if (i && option (OPTDIGESTCOLLAPSE) && b->type == TYPEMULTIPART
834         && mime_which_token(b->subtype, -1) == MIME_DIGEST)
835       attach_collapse (b->parts, 1, 1, 0);
836     else if (b->type == TYPEMULTIPART || mutt_is_message_type(b))
837       attach_collapse (b->parts, collapse, i, 0);
838     b->collapsed = collapse;
839     if (just_one)
840       return;
841   }
842 }
843
844 void mutt_attach_init (BODY * b)
845 {
846   for (; b; b = b->next) {
847     b->tagged = 0;
848     b->collapsed = 0;
849     if (b->parts)
850       mutt_attach_init (b->parts);
851   }
852 }
853
854 void mutt_view_attachments (HEADER * hdr)
855 {
856   int secured = 0;
857   int need_secured = 0;
858
859   char helpstr[SHORT_STRING];
860   MUTTMENU *menu;
861   BODY *cur = NULL;
862   MESSAGE *msg;
863   FILE *fp;
864   ATTACHPTR **idx = NULL;
865   short idxlen = 0;
866   short idxmax = 0;
867   int flags = 0;
868   int op = OP_NULL;
869
870   /* make sure we have parsed this message */
871   mutt_parse_mime_message (Context, hdr);
872
873   mutt_message_hook (Context, hdr, M_MESSAGEHOOK);
874
875   if ((msg = mx_open_message (Context, hdr->msgno)) == NULL)
876     return;
877
878
879   if ((hdr->security & ENCRYPT) ||
880       (mutt_is_application_smime (hdr->content) & SMIMEOPAQUE))
881   {
882     need_secured = 1;
883
884     if ((hdr->security & ENCRYPT) && !crypt_valid_passphrase (hdr->security)) {
885       mx_close_message (&msg);
886       return;
887     }
888     if (hdr->security & APPLICATION_SMIME) {
889       if (hdr->env)
890         crypt_smime_getkeys (hdr->env);
891
892       if (mutt_is_application_smime (hdr->content)) {
893         secured = !crypt_smime_decrypt_mime (msg->fp, &fp,
894                                              hdr->content, &cur);
895
896         /* S/MIME nesting */
897         if ((mutt_is_application_smime (cur) & SMIMEOPAQUE)) {
898           BODY *_cur = cur;
899           FILE *_fp = fp;
900
901           fp = NULL;
902           cur = NULL;
903           secured = !crypt_smime_decrypt_mime (_fp, &fp, _cur, &cur);
904
905           body_list_wipe(&_cur);
906           m_fclose(&_fp);
907         }
908       }
909       else
910         need_secured = 0;
911     }
912     if (hdr->security & APPLICATION_PGP) {
913       if (mutt_is_multipart_encrypted (hdr->content))
914         secured = !crypt_pgp_decrypt_mime (msg->fp, &fp, hdr->content, &cur);
915       else
916         need_secured = 0;
917     }
918
919     if (need_secured && !secured) {
920       mx_close_message (&msg);
921       mutt_error _("Can't decrypt encrypted message!");
922
923       return;
924     }
925   }
926
927   if (!need_secured) {
928     fp = msg->fp;
929     cur = hdr->content;
930   }
931
932   menu = mutt_new_menu ();
933   menu->menu = MENU_ATTACH;
934   menu->title = _("Attachments");
935   menu->make_entry = attach_entry;
936   menu->tag = mutt_tag_attach;
937   menu->help =
938     mutt_compile_help (helpstr, sizeof (helpstr), MENU_ATTACH, AttachHelp);
939
940   mutt_attach_init (cur);
941   attach_collapse (cur, 0, 1, 0);
942   mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
943
944 #define CHECK_ATTACH \
945     if (option(OPTATTACHMSG)) {                                         \
946         mutt_flushinp ();                                               \
947         mutt_error _("Function not permitted in attach-message mode."); \
948         break;                                                          \
949     }
950
951   for (;;) {
952     if (op == OP_NULL)
953       op = mutt_menuLoop(menu);
954     switch (op) {
955     case OP_ATTACH_VIEW_MAILCAP:
956       mutt_view_attachment (fp, idx[menu->current]->content, M_MAILCAP,
957                             hdr, idx, idxlen);
958       menu->redraw = REDRAW_FULL;
959       break;
960
961     case OP_ATTACH_VIEW_TEXT:
962       mutt_view_attachment (fp, idx[menu->current]->content, M_AS_TEXT,
963                             hdr, idx, idxlen);
964       menu->redraw = REDRAW_FULL;
965       break;
966
967     case OP_DISPLAY_HEADERS:
968     case OP_VIEW_ATTACH:
969       op =
970         mutt_attach_display_loop (menu, op, fp, hdr, cur, &idx, &idxlen,
971                                   &idxmax, 1);
972       menu->redraw = REDRAW_FULL;
973       continue;
974
975     case OP_ATTACH_COLLAPSE:
976       if (!idx[menu->current]->content->parts) {
977         mutt_error _("There are no subparts to show!");
978
979         break;
980       }
981       if (!idx[menu->current]->content->collapsed)
982         attach_collapse (idx[menu->current]->content, 1, 0, 1);
983       else
984         attach_collapse (idx[menu->current]->content, 0, 1, 1);
985       mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
986       break;
987
988     case OP_FORGET_PASSPHRASE:
989       crypt_forget_passphrase ();
990       break;
991
992     case OP_EXTRACT_KEYS:
993       crypt_pgp_extract_keys_from_attachment_list (fp, menu->tagprefix,
994                                                    menu->
995                                                    tagprefix ? cur :
996                                                    idx[menu->current]->
997                                                    content);
998       menu->redraw = REDRAW_FULL;
999       break;
1000
1001     case OP_CHECK_TRADITIONAL:
1002       if (crypt_pgp_check_traditional (fp, menu->tagprefix ? cur
1003                                           : idx[menu->current]->content,
1004                                           menu->tagprefix))
1005       {
1006         hdr->security = crypt_query (cur);
1007         menu->redraw = REDRAW_FULL;
1008       }
1009       break;
1010
1011     case OP_PRINT:
1012       mutt_print_attachment_list (fp, menu->tagprefix,
1013                                   menu->tagprefix ? cur : idx[menu->current]->
1014                                   content);
1015       break;
1016
1017     case OP_PIPE:
1018       mutt_pipe_attachment_list (fp, menu->tagprefix,
1019                                  menu->tagprefix ? cur : idx[menu->current]->
1020                                  content, 0);
1021       break;
1022
1023     case OP_SAVE:
1024       mutt_save_attachment_list (fp, menu->tagprefix,
1025                                  menu->tagprefix ? cur : idx[menu->current]->
1026                                  content, hdr, menu);
1027
1028       if (!menu->tagprefix && option (OPTRESOLVE)
1029           && menu->current < menu->max - 1)
1030         menu->current++;
1031
1032       menu->redraw = REDRAW_MOTION_RESYNCH | REDRAW_FULL;
1033       break;
1034
1035     case OP_DELETE:
1036       CHECK_READONLY;
1037
1038       if (Context->magic == M_POP) {
1039         mutt_flushinp ();
1040         mutt_error _("Can't delete attachment from POP server.");
1041
1042         break;
1043       }
1044
1045 #ifdef USE_NNTP
1046       if (Context->magic == M_NNTP) {
1047         mutt_flushinp ();
1048         mutt_error _("Can't delete attachment from newsserver.");
1049
1050         break;
1051       }
1052 #endif
1053
1054       if (hdr->security & (~PGP_TRADITIONAL_CHECKED)) {
1055         mutt_message
1056           _("Deletion of attachments from encrypted messages is unsupported.");
1057       }
1058       else {
1059         if (!menu->tagprefix) {
1060           if (idx[menu->current]->parent_type == TYPEMULTIPART) {
1061             idx[menu->current]->content->deleted = 1;
1062             if (option (OPTRESOLVE) && menu->current < menu->max - 1) {
1063               menu->current++;
1064               menu->redraw = REDRAW_MOTION_RESYNCH;
1065             }
1066             else
1067               menu->redraw = REDRAW_CURRENT;
1068           }
1069           else
1070             mutt_message
1071               _("Only deletion of multipart attachments is supported.");
1072         }
1073         else {
1074           int x;
1075
1076           for (x = 0; x < menu->max; x++) {
1077             if (idx[x]->content->tagged) {
1078               if (idx[x]->parent_type == TYPEMULTIPART) {
1079                 idx[x]->content->deleted = 1;
1080                 menu->redraw = REDRAW_INDEX;
1081               }
1082               else
1083                 mutt_message
1084                   _("Only deletion of multipart attachments is supported.");
1085             }
1086           }
1087         }
1088       }
1089       break;
1090
1091     case OP_UNDELETE:
1092       CHECK_READONLY;
1093       if (!menu->tagprefix) {
1094         idx[menu->current]->content->deleted = 0;
1095         if (option (OPTRESOLVE) && menu->current < menu->max - 1) {
1096           menu->current++;
1097           menu->redraw = REDRAW_MOTION_RESYNCH;
1098         }
1099         else
1100           menu->redraw = REDRAW_CURRENT;
1101       }
1102       else {
1103         int x;
1104
1105         for (x = 0; x < menu->max; x++) {
1106           if (idx[x]->content->tagged) {
1107             idx[x]->content->deleted = 0;
1108             menu->redraw = REDRAW_INDEX;
1109           }
1110         }
1111       }
1112       break;
1113
1114     case OP_RESEND:
1115       CHECK_ATTACH;
1116       mutt_attach_resend (fp, hdr, idx, idxlen,
1117                           menu->tagprefix ? NULL : idx[menu->current]->
1118                           content);
1119       menu->redraw = REDRAW_FULL;
1120       break;
1121
1122     case OP_BOUNCE_MESSAGE:
1123       CHECK_ATTACH;
1124       mutt_attach_bounce (fp, hdr, idx, idxlen,
1125                           menu->tagprefix ? NULL : idx[menu->current]->
1126                           content);
1127       menu->redraw = REDRAW_FULL;
1128       break;
1129
1130     case OP_FORWARD_MESSAGE:
1131       CHECK_ATTACH;
1132       mutt_attach_forward (fp, hdr, idx, idxlen,
1133                            menu->tagprefix ? NULL : idx[menu->current]->
1134                            content, 0);
1135       menu->redraw = REDRAW_FULL;
1136       break;
1137
1138 #ifdef USE_NNTP
1139     case OP_FORWARD_TO_GROUP:
1140       CHECK_ATTACH;
1141       mutt_attach_forward (fp, hdr, idx, idxlen,
1142                            menu->tagprefix ? NULL : idx[menu->current]->
1143                            content, SENDNEWS);
1144       menu->redraw = REDRAW_FULL;
1145       break;
1146
1147     case OP_FOLLOWUP:
1148       CHECK_ATTACH;
1149
1150       if (!idx[menu->current]->content->hdr->env->followup_to ||
1151           m_strcasecmp(idx[menu->current]->content->hdr->env->followup_to,
1152                            "poster")
1153           || query_quadoption (OPT_FOLLOWUPTOPOSTER,
1154                                _("Reply by mail as poster prefers?")) !=
1155           M_YES) {
1156         mutt_attach_reply (fp, hdr, idx, idxlen,
1157                            menu->tagprefix ? NULL : idx[menu->current]->
1158                            content, SENDNEWS | SENDREPLY);
1159         menu->redraw = REDRAW_FULL;
1160         break;
1161       }
1162 #endif
1163
1164     case OP_REPLY:
1165     case OP_GROUP_REPLY:
1166     case OP_LIST_REPLY:
1167
1168       CHECK_ATTACH;
1169
1170       flags = SENDREPLY |
1171         (op == OP_GROUP_REPLY ? SENDGROUPREPLY : 0) |
1172         (op == OP_LIST_REPLY ? SENDLISTREPLY : 0);
1173       mutt_attach_reply (fp, hdr, idx, idxlen,
1174                          menu->tagprefix ? NULL : idx[menu->current]->content,
1175                          flags);
1176       menu->redraw = REDRAW_FULL;
1177       break;
1178
1179     case OP_EDIT_TYPE:
1180       mutt_edit_content_type (hdr, idx[menu->current]->content, fp);
1181       mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
1182       break;
1183
1184     case OP_EXIT:
1185       mx_close_message (&msg);
1186       hdr->attach_del = 0;
1187       while (idxmax-- > 0) {
1188         if (!idx[idxmax])
1189           continue;
1190         if (idx[idxmax]->content && idx[idxmax]->content->deleted)
1191           hdr->attach_del = 1;
1192         if (idx[idxmax]->content)
1193           idx[idxmax]->content->aptr = NULL;
1194         p_delete(&idx[idxmax]->tree);
1195         p_delete(&idx[idxmax]);
1196       }
1197       if (hdr->attach_del)
1198         hdr->changed = 1;
1199       p_delete(&idx);
1200       idxmax = 0;
1201
1202       if (need_secured && secured) {
1203         m_fclose(&fp);
1204         body_list_wipe(&cur);
1205       }
1206
1207       mutt_menuDestroy (&menu);
1208       return;
1209     }
1210
1211     op = OP_NULL;
1212   }
1213   /* not reached */
1214 }