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