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