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