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