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