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