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