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