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