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