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