wibble.
[apps/madmutt.git] / attach.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
5  *
6  * This file is part of mutt-ng, see http://www.muttng.org/.
7  * It's licensed under the GNU General Public License,
8  * please see the file GPL in the top level source directory.
9  */
10
11 #include <lib-lib/lib-lib.h>
12
13 #include <lib-sys/unix.h>
14 #include <lib-mime/mime.h>
15 #include <lib-ui/curses.h>
16 #include <lib-ui/menu.h>
17 #include <lib-mx/mx.h>
18
19 #include "mutt.h"
20 #include "handler.h"
21 #include "recvattach.h"
22 #include "keymap.h"
23 #include "pager.h"
24 #include "copy.h"
25 #include <lib-crypt/crypt.h>
26
27 int mutt_get_tmp_attachment (BODY * a)
28 {
29   char type[STRING];
30   char tempfile[_POSIX_PATH_MAX];
31   rfc1524_entry *entry = rfc1524_entry_new();
32   FILE *fpin = NULL, *fpout = NULL;
33   struct stat st;
34
35   if (a->unlink)
36     return 0;
37
38   snprintf (type, sizeof (type), "%s/%s", TYPE (a), a->subtype);
39   rfc1524_mailcap_lookup (a, type, entry, 0);
40   rfc1524_expand_filename (entry->nametemplate, a->filename,
41                            tempfile, sizeof (tempfile));
42
43   rfc1524_entry_delete(&entry);
44
45   if (stat (a->filename, &st) == -1)
46     return -1;
47
48   if ((fpin = fopen (a->filename, "r")) && (fpout = safe_fopen (tempfile, "w"))) {      /* __FOPEN_CHECKED__ */
49     mutt_copy_stream (fpin, fpout);
50     m_strreplace(&a->filename, tempfile);
51     a->unlink = 1;
52
53     if (a->stamp >= st.st_mtime)
54       mutt_stamp_attachment (a);
55   }
56   else
57     mutt_perror(fpin ? tempfile : a->filename);
58
59   m_fclose(&fpin);
60   m_fclose(&fpout);
61
62   return a->unlink ? 0 : -1;
63 }
64
65
66 /* return 1 if require full screen redraw, 0 otherwise */
67 int mutt_compose_attachment (BODY * a)
68 {
69   char type[STRING];
70   char command[STRING];
71   char newfile[_POSIX_PATH_MAX] = "";
72   rfc1524_entry *entry = rfc1524_entry_new();
73   short unlink_newfile = 0;
74   int rc = 0;
75
76   snprintf (type, sizeof (type), "%s/%s", TYPE (a), a->subtype);
77   if (rfc1524_mailcap_lookup (a, type, entry, M_COMPOSE)) {
78     if (entry->composecommand || entry->composetypecommand) {
79
80       if (entry->composetypecommand)
81         m_strcpy(command, sizeof(command), entry->composetypecommand);
82       else
83         m_strcpy(command, sizeof(command), entry->composecommand);
84       if (rfc1524_expand_filename (entry->nametemplate,
85                                    a->filename, newfile, sizeof (newfile))) {
86         if (safe_symlink (a->filename, newfile) == -1) {
87           if (mutt_yesorno (_("Can't match nametemplate, continue?"), M_YES)
88               != M_YES)
89             goto bailout;
90         }
91         else
92           unlink_newfile = 1;
93       }
94       else
95         m_strcpy(newfile, sizeof(newfile), a->filename);
96
97       if (rfc1524_expand_command (a, newfile, type,
98                                   command, sizeof (command))) {
99         /* For now, editing requires a file, no piping */
100         mutt_error _("Mailcap compose entry requires %%s");
101       }
102       else {
103         int r;
104
105         mutt_endwin (NULL);
106         if ((r = mutt_system (command)) == -1)
107           mutt_error (_("Error running \"%s\"!"), command);
108
109         if (r != -1 && entry->composetypecommand) {
110           BODY *b;
111           FILE *fp, *tfp;
112
113           if ((fp = safe_fopen (a->filename, "r")) == NULL) {
114             mutt_perror (_("Failure to open file to parse headers."));
115
116             goto bailout;
117           }
118
119           b = mutt_read_mime_header (fp, 0);
120           if (b) {
121             char tempfile[_POSIX_PATH_MAX];
122
123             if (b->parameter) {
124               parameter_list_wipe(&a->parameter);
125               a->parameter = b->parameter;
126               b->parameter = NULL;
127             }
128             if (b->description) {
129               p_delete(&a->description);
130               a->description = b->description;
131               b->description = NULL;
132             }
133             if (b->form_name) {
134               p_delete(&a->form_name);
135               a->form_name = b->form_name;
136               b->form_name = NULL;
137             }
138
139             /* Remove headers by copying out data to another file, then 
140              * copying the file back */
141             fseeko (fp, b->offset, 0);
142             tfp = m_tempfile(tempfile, sizeof(tempfile), NONULL(Tempdir), NULL);
143             if (!tfp) {
144               mutt_perror (_("Failure to open file to strip headers."));
145               goto bailout;
146             }
147             mutt_copy_stream (fp, tfp);
148             m_fclose(&fp);
149             m_fclose(&tfp);
150             mutt_unlink (a->filename);
151             if (mutt_rename_file (tempfile, a->filename) != 0) {
152               mutt_perror (_("Failure to rename file."));
153
154               goto bailout;
155             }
156
157             body_list_wipe(&b);
158           }
159         }
160       }
161     }
162   }
163   else {
164     rfc1524_entry_delete(&entry);
165     mutt_message (_("No mailcap compose entry for %s, creating empty file."),
166                   type);
167     return 1;
168   }
169
170   rc = 1;
171
172 bailout:
173
174   if (unlink_newfile)
175     unlink (newfile);
176
177   rfc1524_entry_delete(&entry);
178   return rc;
179 }
180
181 /* 
182  * Currently, this only works for send mode, as it assumes that the 
183  * BODY->filename actually contains the information.  I'm not sure
184  * we want to deal with editing attachments we've already received,
185  * so this should be ok.
186  *
187  * Returns 1 if editor found, 0 if not (useful to tell calling menu to
188  * redraw)
189  */
190 int mutt_edit_attachment (BODY * a)
191 {
192   char type[STRING];
193   char command[STRING];
194   char newfile[_POSIX_PATH_MAX] = "";
195   rfc1524_entry *entry = rfc1524_entry_new();
196   short unlink_newfile = 0;
197   int rc = 0;
198
199   snprintf (type, sizeof (type), "%s/%s", TYPE (a), a->subtype);
200   if (rfc1524_mailcap_lookup (a, type, entry, M_EDIT)) {
201     if (entry->editcommand) {
202
203       m_strcpy(command, sizeof(command), entry->editcommand);
204       if (rfc1524_expand_filename (entry->nametemplate,
205                                    a->filename, newfile, sizeof (newfile))) {
206           if (safe_symlink (a->filename, newfile) == -1) {
207           if (mutt_yesorno (_("Can't match nametemplate, continue?"), M_YES)
208               != M_YES)
209             goto bailout;
210         }
211         else
212           unlink_newfile = 1;
213       }
214       else
215         m_strcpy(newfile, sizeof(newfile), a->filename);
216
217       if (rfc1524_expand_command (a, newfile, type,
218                                   command, sizeof (command))) {
219         /* For now, editing requires a file, no piping */
220         mutt_error _("Mailcap Edit entry requires %%s");
221         goto bailout;
222       }
223       else {
224         mutt_endwin (NULL);
225         if (mutt_system (command) == -1) {
226           mutt_error (_("Error running \"%s\"!"), command);
227           goto bailout;
228         }
229       }
230     }
231   }
232   else if (a->type == TYPETEXT) {
233     /* On text, default to editor */
234     mutt_edit_file (NONULL (Editor), a->filename);
235   }
236   else {
237     rfc1524_entry_delete(&entry);
238     mutt_error (_("No mailcap edit entry for %s"), type);
239     return 0;
240   }
241
242   rc = 1;
243
244 bailout:
245
246   if (unlink_newfile)
247     unlink (newfile);
248
249   rfc1524_entry_delete(&entry);
250   return rc;
251 }
252
253
254 /* for compatibility with metamail */
255 static int is_mmnoask (const char *buf)
256 {
257   char tmp[LONG_STRING], *p, *q;
258   int lng;
259
260   if ((p = getenv ("MM_NOASK")) != NULL && *p) {
261     if (m_strcmp(p, "1") == 0)
262       return (1);
263
264     m_strcpy(tmp, sizeof(tmp), p);
265     p = tmp;
266
267     while ((p = strtok (p, ",")) != NULL) {
268       if ((q = strrchr (p, '/')) != NULL) {
269         if (*(q + 1) == '*') {
270           if (ascii_strncasecmp (buf, p, q - p) == 0)
271             return (1);
272         }
273         else {
274           if (ascii_strcasecmp (buf, p) == 0)
275             return (1);
276         }
277       }
278       else {
279         lng = m_strlen(p);
280         if (buf[lng] == '/' && m_strncasecmp(buf, p, lng) == 0)
281           return (1);
282       }
283
284       p = NULL;
285     }
286   }
287
288   return (0);
289 }
290
291 void mutt_check_lookup_list (BODY * b, char *type, int len)
292 {
293   string_list_t *t = MimeLookupList;
294   int i;
295
296   for (; t; t = t->next) {
297     i = m_strlen(t->data) - 1;
298     if ((i > 0 && t->data[i - 1] == '/' && t->data[i] == '*' &&
299          ascii_strncasecmp (type, t->data, i) == 0) ||
300         ascii_strcasecmp (type, t->data) == 0) {
301
302       BODY tmp;
303       int n;
304
305       p_clear(&tmp, 1);
306
307       if ((n = mutt_lookup_mime_type (&tmp, b->filename)) != TYPEOTHER) {
308         snprintf (type, len, "%s/%s",
309                   n == TYPEAUDIO ? "audio" :
310                   n == TYPEAPPLICATION ? "application" :
311                   n == TYPEIMAGE ? "image" :
312                   n == TYPEMESSAGE ? "message" :
313                   n == TYPEMODEL ? "model" :
314                   n == TYPEMULTIPART ? "multipart" :
315                   n == TYPETEXT ? "text" :
316                   n == TYPEVIDEO ? "video" : "other", tmp.subtype);
317       }
318       if (tmp.subtype)
319         p_delete(&tmp.subtype);
320       if (tmp.xtype)
321         p_delete(&tmp.xtype);
322     }
323   }
324 }
325
326 int mutt_is_autoview (BODY * b, const char *type)
327 {
328   string_list_t *t = AutoViewList;
329   char _type[SHORT_STRING];
330   int i;
331
332   if (!type)
333     snprintf (_type, sizeof (_type), "%s/%s", TYPE (b), b->subtype);
334   else
335     m_strcpy(_type, sizeof(_type), type);
336
337   mutt_check_lookup_list (b, _type, sizeof (_type));
338   type = _type;
339
340   if (rfc1524_mailcap_isneeded(b)) {
341     if (option (OPTIMPLICITAUTOVIEW))
342       return 1;
343
344     if (is_mmnoask (type))
345       return 1;
346   }
347
348   for (; t; t = t->next) {
349     i = m_strlen(t->data) - 1;
350     if ((i > 0 && t->data[i - 1] == '/' && t->data[i] == '*' &&
351          ascii_strncasecmp (type, t->data, i) == 0) ||
352         ascii_strcasecmp (type, t->data) == 0)
353       return 1;
354   }
355
356   return 0;
357 }
358
359 /* returns -1 on error, 0 or the return code from mutt_do_pager() on success */
360 int mutt_view_attachment (FILE * fp, BODY * a, int flag, HEADER * hdr,
361                           ATTACHPTR ** idx, short idxlen)
362 {
363   char tempfile[_POSIX_PATH_MAX] = "";
364   char pagerfile[_POSIX_PATH_MAX] = "";
365   int is_message;
366   int use_mailcap;
367   int use_pipe = 0;
368   int use_pager = 1;
369   char type[STRING];
370   char command[STRING];
371   char descrip[STRING];
372   char *fname;
373   rfc1524_entry *entry = NULL;
374   int rc = -1;
375   int unlink_tempfile = 0;
376   int pagerfd = -1;
377
378   is_message = mutt_is_message_type (a->type, a->subtype);
379   if (is_message && a->hdr && (a->hdr->security & ENCRYPT) &&
380       !crypt_valid_passphrase (a->hdr->security))
381     return (rc);
382   use_mailcap = (flag == M_MAILCAP ||
383                  (flag == M_REGULAR && rfc1524_mailcap_isneeded(a)));
384   snprintf (type, sizeof (type), "%s/%s", TYPE (a), a->subtype);
385
386   if (use_mailcap) {
387     entry = rfc1524_entry_new();
388     if (!rfc1524_mailcap_lookup (a, type, entry, 0)) {
389       if (flag == M_REGULAR) {
390         /* fallback to view as text */
391         rfc1524_entry_delete(&entry);
392         mutt_error _("No matching mailcap entry found.  Viewing as text.");
393
394         flag = M_AS_TEXT;
395         use_mailcap = 0;
396       }
397       else
398         goto return_error;
399     }
400   }
401
402   if (use_mailcap) {
403     if (!entry->command) {
404       mutt_error _("MIME type not defined.  Cannot view attachment.");
405
406       goto return_error;
407     }
408     m_strcpy(command, sizeof(command), entry->command);
409
410     if (fp) {
411       fname = m_strdup(a->filename);
412       mutt_sanitize_filename (fname, 1);
413     }
414     else
415       fname = a->filename;
416
417     if (rfc1524_expand_filename (entry->nametemplate, fname,
418                                  tempfile, sizeof (tempfile))) {
419       if (fp == NULL && m_strcmp(tempfile, a->filename)) {
420         /* send case: the file is already there */
421         if (safe_symlink (a->filename, tempfile) == -1) {
422           if (mutt_yesorno (_("Can't match nametemplate, continue?"), M_YES)
423               == M_YES)
424             m_strcpy(tempfile, sizeof(tempfile), a->filename);
425           else
426             goto return_error;
427         }
428         else
429           unlink_tempfile = 1;
430       }
431     }
432     else if (fp == NULL)        /* send case */
433       m_strcpy(tempfile, sizeof(tempfile), a->filename);
434
435     if (fp) {
436       /* recv case: we need to save the attachment to a file */
437       p_delete(&fname);
438       if (mutt_save_attachment (fp, a, tempfile, 0, NULL) == -1)
439         goto return_error;
440     }
441
442     use_pipe = rfc1524_expand_command (a, tempfile, type,
443                                        command, sizeof (command));
444     use_pager = entry->copiousoutput;
445   }
446
447   if (use_pager) {
448     /* recv case */
449     pagerfd = m_tempfd(pagerfile, sizeof(pagerfile), NONULL(Tempdir),
450                        fp && !use_mailcap ? a->filename : NULL);
451   }
452
453   if (use_mailcap) {
454     pid_t thepid = 0;
455     int tempfd = -1;
456
457     if (!use_pager)
458       mutt_endwin (NULL);
459
460     if (use_pager || use_pipe) {
461       if (use_pager && pagerfd == -1) {
462         mutt_perror ("open");
463         goto return_error;
464       }
465       if (use_pipe && ((tempfd = open (tempfile, 0)) == -1)) {
466         if (pagerfd != -1)
467           close (pagerfd);
468         mutt_perror ("open");
469         goto return_error;
470       }
471
472       if ((thepid = mutt_create_filter_fd (command, NULL, NULL, NULL,
473                                            use_pipe ? tempfd : -1,
474                                            use_pager ? pagerfd : -1,
475                                            -1)) == -1) {
476         if (pagerfd != -1)
477           close (pagerfd);
478
479         if (tempfd != -1)
480           close (tempfd);
481
482         mutt_error _("Cannot create filter");
483
484         goto return_error;
485       }
486
487       if (use_pager) {
488         if (a->description)
489           snprintf (descrip, sizeof (descrip),
490                     "---Command: %-20.20s Description: %s",
491                     command, a->description);
492         else
493           snprintf (descrip, sizeof (descrip),
494                     "---Command: %-30.30s Attachment: %s", command, type);
495       }
496
497       if ((mutt_wait_filter (thepid) || (entry->needsterminal &&
498                                          option (OPTWAITKEY))) && !use_pager)
499         mutt_any_key_to_continue (NULL);
500
501       close (tempfd);
502       close (pagerfd);
503
504     }
505     else {
506       /* interactive command */
507       if (mutt_system (command) ||
508           (entry->needsterminal && option (OPTWAITKEY)))
509         mutt_any_key_to_continue (NULL);
510     }
511   }
512   else {
513     /* Don't use mailcap; the attachment is viewed in the pager */
514
515     if (flag == M_AS_TEXT) {
516       /* just let me see the raw data */
517       if (mutt_save_attachment (fp, a, pagerfile, 0, NULL))
518         goto return_error;
519     }
520     else {
521       /* Use built-in handler */
522       set_option (OPTVIEWATTACH);       /* disable the "use 'v' to view this part"
523                                          * message in case of error */
524       if (mutt_decode_save_attachment (fp, a, pagerfile, M_DISPLAY, 0)) {
525         unset_option (OPTVIEWATTACH);
526         goto return_error;
527       }
528       unset_option (OPTVIEWATTACH);
529     }
530
531     if (a->description)
532       m_strcpy(descrip, sizeof(descrip), a->description);
533     else if (a->filename)
534       snprintf (descrip, sizeof (descrip), "---Attachment: %s : %s",
535                 a->filename, type);
536     else
537       snprintf (descrip, sizeof (descrip), "---Attachment: %s", type);
538   }
539
540   /* We only reach this point if there have been no errors */
541
542   if (use_pager) {
543     pager_t info;
544     p_clear(&info, 1);
545
546     info.fp  = fp;
547     info.bdy = a;
548     info.ctx = Context;
549     info.idx = idx;
550     info.idxlen = idxlen;
551     info.hdr = hdr;
552
553     rc = mutt_do_pager(descrip, pagerfile,
554                        M_PAGER_ATTACHMENT | (is_message ? M_PAGER_MESSAGE : 0),
555                        &info);
556     *pagerfile = '\0';
557   }
558   else
559     rc = 0;
560
561 return_error:
562
563   if (entry)
564     rfc1524_entry_delete(&entry);
565   if (fp && tempfile[0])
566     mutt_unlink (tempfile);
567   else if (unlink_tempfile)
568     unlink (tempfile);
569
570   if (pagerfile[0])
571     mutt_unlink (pagerfile);
572
573   return rc;
574 }
575
576 /* returns 1 on success, 0 on error */
577 int mutt_pipe_attachment (FILE * fp, BODY * b, const char *path,
578                           char *outfile)
579 {
580   pid_t thepid;
581   int out = -1;
582   int rv = 0;
583
584   if (outfile && *outfile)
585     if ((out = safe_open (outfile, O_CREAT | O_EXCL | O_WRONLY)) < 0) {
586       mutt_perror ("open");
587       return 0;
588     }
589
590   mutt_endwin (NULL);
591
592   if (fp) {
593     /* recv case */
594     STATE s;
595     p_clear(&s, 1);
596
597     if (outfile && *outfile)
598       thepid = mutt_create_filter_fd(path, &s.fpout, NULL, NULL, -1, out, -1);
599     else
600       thepid = mutt_create_filter(path, &s.fpout, NULL, NULL);
601
602     if (thepid < 0) {
603       mutt_perror (_("Can't create filter"));
604
605       goto bail;
606     }
607
608     s.fpin = fp;
609     mutt_decode_attachment (b, &s);
610     m_fclose(&s.fpout);
611   }
612   else {
613     /* send case */
614
615     FILE *ifp, *ofp;
616
617     if ((ifp = fopen (b->filename, "r")) == NULL) {
618       mutt_perror ("fopen");
619       if (outfile && *outfile) {
620         close (out);
621         unlink (outfile);
622       }
623       return 0;
624     }
625
626     if (outfile && *outfile)
627       thepid = mutt_create_filter_fd (path, &ofp, NULL, NULL, -1, out, -1);
628     else
629       thepid = mutt_create_filter (path, &ofp, NULL, NULL);
630
631     if (thepid < 0) {
632       mutt_perror (_("Can't create filter"));
633
634       m_fclose(&ifp);
635       goto bail;
636     }
637
638     mutt_copy_stream (ifp, ofp);
639     m_fclose(&ofp);
640     m_fclose(&ifp);
641   }
642
643   rv = 1;
644
645 bail:
646
647   if (outfile && *outfile)
648     close (out);
649
650   /*
651    * check for error exit from child process
652    */
653   if (mutt_wait_filter (thepid) != 0)
654     rv = 0;
655
656   if (rv == 0 || option (OPTWAITKEY))
657     mutt_any_key_to_continue (NULL);
658   return rv;
659 }
660
661 static FILE *mutt_save_attachment_open (char *path, int flags)
662 {
663   if (flags == M_SAVE_APPEND)
664     return fopen (path, "a");
665   /* be sure not to change the following fopen to safe_fopen
666    * as safe_fopen returns w/ an error if path exists
667    */
668   if (flags == M_SAVE_OVERWRITE)
669     return fopen (path, "w");   /* __FOPEN_CHECKED__ */
670
671   return safe_fopen (path, "w");
672 }
673
674 /* returns 0 on success, -1 on error */
675 int mutt_save_attachment (FILE * fp, BODY * m, char *path, int flags,
676                           HEADER * hdr)
677 {
678   if (fp) {
679
680     /* recv mode */
681
682     if (hdr &&
683         m->hdr &&
684         m->encoding != ENCBASE64 &&
685         m->encoding != ENCQUOTEDPRINTABLE &&
686         mutt_is_message_type (m->type, m->subtype)) {
687       /* message type attachments are written to mail folders. */
688
689       char buf[HUGE_STRING];
690       HEADER *hn;
691       CONTEXT ctx;
692       MESSAGE *msg;
693       int chflags = 0;
694       int r = -1;
695
696       hn = m->hdr;
697       hn->msgno = hdr->msgno;   /* required for MH/maildir */
698       hn->read = 1;
699
700       fseeko (fp, m->offset, 0);
701       if (fgets (buf, sizeof (buf), fp) == NULL)
702         return -1;
703       if (mx_open_mailbox (path, M_APPEND | M_QUIET, &ctx) == NULL)
704         return -1;
705       if ((msg =
706            mx_open_new_message (&ctx, hn,
707                                 is_from (buf, NULL, 0,
708                                          NULL) ? 0 : M_ADD_FROM)) == NULL) {
709         mx_close_mailbox (&ctx, NULL);
710         return -1;
711       }
712       if (ctx.magic == M_MBOX || ctx.magic == M_MMDF)
713         chflags = CH_FROM;
714       chflags |= (ctx.magic == M_MAILDIR ? CH_NOSTATUS : CH_UPDATE);
715       if (_mutt_copy_message (msg->fp, fp, hn, hn->content, 0, chflags) == 0
716           && mx_commit_message (msg, &ctx) == 0)
717         r = 0;
718       else
719         r = -1;
720
721       mx_close_message (&msg);
722       mx_close_mailbox (&ctx, NULL);
723       return r;
724     }
725     else {
726       /* In recv mode, extract from folder and decode */
727
728       STATE s;
729       p_clear(&s, 1);
730
731       if ((s.fpout = mutt_save_attachment_open (path, flags)) == NULL) {
732         mutt_perror ("fopen");
733         return (-1);
734       }
735       fseeko ((s.fpin = fp), m->offset, 0);
736       mutt_decode_attachment (m, &s);
737
738       if (m_fclose(&s.fpout) != 0) {
739         mutt_perror ("fclose");
740         return (-1);
741       }
742     }
743   } else {
744     /* In send mode, just copy file */
745
746     FILE *ofp, *nfp;
747
748     if ((ofp = fopen (m->filename, "r")) == NULL) {
749       mutt_perror ("fopen");
750       return (-1);
751     }
752
753     if ((nfp = mutt_save_attachment_open (path, flags)) == NULL) {
754       mutt_perror ("fopen");
755       m_fclose(&ofp);
756       return (-1);
757     }
758
759     if (mutt_copy_stream (ofp, nfp) == -1) {
760       mutt_error _("Write fault!");
761
762       m_fclose(&ofp);
763       m_fclose(&nfp);
764       return (-1);
765     }
766     m_fclose(&ofp);
767     m_fclose(&nfp);
768   }
769
770   return 0;
771 }
772
773 /* returns 0 on success, -1 on error */
774 int mutt_decode_save_attachment (FILE * fp, BODY * m, char *path,
775                                  int displaying, int flags)
776 {
777   STATE s;
778   unsigned int saved_encoding = 0;
779   BODY *saved_parts = NULL;
780   HEADER *saved_hdr = NULL;
781
782   p_clear(&s, 1);
783   s.flags = displaying;
784
785   if (flags == M_SAVE_APPEND)
786     s.fpout = fopen (path, "a");
787   else if (flags == M_SAVE_OVERWRITE)
788     s.fpout = safe_fopen (path, "w");   /* __FOPEN_CHECKED__ */
789   else
790     s.fpout = safe_fopen (path, "w");
791
792   if (s.fpout == NULL) {
793     mutt_perror ("fopen");
794     return (-1);
795   }
796
797   if (fp == NULL) {
798     /* When called from the compose menu, the attachment isn't parsed,
799      * so we need to do it here. */
800     struct stat st;
801
802     if (stat (m->filename, &st) == -1) {
803       mutt_perror ("stat");
804       m_fclose(&s.fpout);
805       return (-1);
806     }
807
808     if ((s.fpin = fopen (m->filename, "r")) == NULL) {
809       mutt_perror ("fopen");
810       return (-1);
811     }
812
813     saved_encoding = m->encoding;
814     if (!is_multipart (m))
815       m->encoding = ENC8BIT;
816
817     m->length = st.st_size;
818     m->offset = 0;
819     saved_parts = m->parts;
820     saved_hdr = m->hdr;
821     mutt_parse_part (s.fpin, m);
822
823     if (m->noconv || is_multipart (m))
824       s.flags |= M_CHARCONV;
825   }
826   else {
827     s.fpin = fp;
828     s.flags |= M_CHARCONV;
829   }
830
831   mutt_body_handler (m, &s);
832
833   m_fclose(&s.fpout);
834   if (fp == NULL) {
835     m->length = 0;
836     m->encoding = saved_encoding;
837     if (saved_parts) {
838       header_delete(&m->hdr);
839       m->parts = saved_parts;
840       m->hdr = saved_hdr;
841     }
842     m_fclose(&s.fpin);
843   }
844
845   return (0);
846 }
847
848 /* Ok, the difference between send and receive:
849  * recv: BODY->filename is a suggested name, and Context|HEADER points
850  *       to the attachment in mailbox which is encooded
851  * send: BODY->filename points to the un-encoded file which contains the 
852  *       attachment
853  */
854
855 int mutt_print_attachment (FILE * fp, BODY * a)
856 {
857   char newfile[_POSIX_PATH_MAX] = "";
858   char type[STRING];
859   pid_t thepid;
860   FILE *ifp, *fpout;
861   short unlink_newfile = 0;
862
863   snprintf (type, sizeof (type), "%s/%s", TYPE (a), a->subtype);
864
865   if (rfc1524_mailcap_lookup (a, type, NULL, M_PRINT)) {
866     char command[_POSIX_PATH_MAX + STRING];
867     rfc1524_entry *entry;
868     int piped = FALSE;
869
870     entry = rfc1524_entry_new();
871     rfc1524_mailcap_lookup (a, type, entry, M_PRINT);
872     if (rfc1524_expand_filename (entry->nametemplate, a->filename,
873                                  newfile, sizeof (newfile))) {
874       if (!fp) {
875         if (safe_symlink (a->filename, newfile) == -1) {
876           if (mutt_yesorno (_("Can't match nametemplate, continue?"), M_YES)
877               != M_YES) {
878             rfc1524_entry_delete(&entry);
879             return 0;
880           }
881           m_strcpy(newfile, sizeof(newfile), a->filename);
882         }
883         else
884           unlink_newfile = 1;
885       }
886     }
887
888     /* in recv mode, save file to newfile first */
889     if (fp)
890       mutt_save_attachment (fp, a, newfile, 0, NULL);
891
892     m_strcpy(command, sizeof(command), entry->printcommand);
893     piped = rfc1524_expand_command(a, newfile, type, command,
894                                    sizeof(command));
895
896     mutt_endwin (NULL);
897
898     /* interactive program */
899     if (piped) {
900       if ((ifp = fopen (newfile, "r")) == NULL) {
901         mutt_perror ("fopen");
902         rfc1524_entry_delete(&entry);
903         return (0);
904       }
905
906       if ((thepid = mutt_create_filter (command, &fpout, NULL, NULL)) < 0) {
907         mutt_perror (_("Can't create filter"));
908
909         rfc1524_entry_delete(&entry);
910         m_fclose(&ifp);
911         return 0;
912       }
913       mutt_copy_stream (ifp, fpout);
914       m_fclose(&fpout);
915       m_fclose(&ifp);
916       if (mutt_wait_filter (thepid) || option (OPTWAITKEY))
917         mutt_any_key_to_continue (NULL);
918     }
919     else {
920       if (mutt_system (command) || option (OPTWAITKEY))
921         mutt_any_key_to_continue (NULL);
922     }
923
924     if (fp)
925       mutt_unlink (newfile);
926     else if (unlink_newfile)
927       unlink (newfile);
928
929     rfc1524_entry_delete(&entry);
930     return (1);
931   }
932
933   if (!ascii_strcasecmp ("text/plain", type) ||
934       !ascii_strcasecmp ("application/postscript", type)) {
935     return (mutt_pipe_attachment (fp, a, NONULL (PrintCmd), NULL));
936   }
937   else if (mutt_can_decode (a)) {
938     /* decode and print */
939
940     int rc = 0;
941
942     ifp = NULL;
943     fpout = NULL;
944
945     mutt_mktemp (newfile);
946     if (mutt_decode_save_attachment (fp, a, newfile, M_PRINTING, 0) == 0) {
947
948       if ((ifp = fopen (newfile, "r")) == NULL) {
949         mutt_perror ("fopen");
950         goto bail0;
951       }
952
953       mutt_endwin (NULL);
954       if ((thepid =
955            mutt_create_filter (NONULL (PrintCmd), &fpout, NULL, NULL)) < 0) {
956         mutt_perror (_("Can't create filter"));
957
958         goto bail0;
959       }
960
961       mutt_copy_stream (ifp, fpout);
962
963       m_fclose(&fpout);
964       m_fclose(&ifp);
965
966       if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY))
967         mutt_any_key_to_continue (NULL);
968       rc = 1;
969     }
970   bail0:
971     m_fclose(&ifp);
972     m_fclose(&fpout);
973     mutt_unlink (newfile);
974     return rc;
975   }
976   else {
977     mutt_error _("I don't know how to print that!");
978
979     return 0;
980   }
981 }
982
983 int mutt_attach_check (HEADER* hdr) {
984   int found = 0;
985   char buf[LONG_STRING];
986   char *p = NULL;
987   FILE* fp = NULL;
988   regmatch_t pmatch[1];
989
990   if (!hdr || !hdr->content || !((regex_t*) AttachRemindRegexp.rx) ||
991       (fp = safe_fopen (hdr->content->filename, "r")) == NULL)
992     return (0);
993
994   while (!found && fgets (buf, sizeof (buf), fp)) {
995     p = buf;
996     while (p && *p) {
997       if (regexec ((regex_t*) AttachRemindRegexp.rx, p, 1,
998                   pmatch, 0) == 0) {
999         found = 1;
1000         break;
1001       }
1002       p++;
1003     }
1004   }
1005   m_fclose(&fp);
1006
1007   return (found);
1008 }