]> granicus.if.org Git - neomutt/blob - mutt_attach.c
Fix mutt_write_mime_body() application/pgp-encrypted handling
[neomutt] / mutt_attach.c
1 /**
2  * @file
3  * Handling of email attachments
4  *
5  * @authors
6  * Copyright (C) 1996-2000,2002,2013 Michael R. Elkins <me@mutt.org>
7  * Copyright (C) 1999-2004,2006 Thomas Roessler <roessler@does-not-exist.org>
8  *
9  * @copyright
10  * This program is free software: you can redistribute it and/or modify it under
11  * the terms of the GNU General Public License as published by the Free Software
12  * Foundation, either version 2 of the License, or (at your option) any later
13  * version.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License along with
21  * this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23
24 /**
25  * @page mutt_attach Handling of email attachments
26  *
27  * Handling of email attachments
28  */
29
30 #include "config.h"
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <stdbool.h>
34 #include <stdio.h>
35 #include <string.h>
36 #include <sys/stat.h>
37 #include <unistd.h>
38 #include "mutt/mutt.h"
39 #include "config/lib.h"
40 #include "email/lib.h"
41 #include "core/lib.h"
42 #include "mutt_attach.h"
43 #include "context.h"
44 #include "copy.h"
45 #include "curs_lib.h"
46 #include "filter.h"
47 #include "globals.h"
48 #include "handler.h"
49 #include "mailcap.h"
50 #include "muttlib.h"
51 #include "mx.h"
52 #include "ncrypt/ncrypt.h"
53 #include "options.h"
54 #include "pager.h"
55 #include "protos.h"
56 #include "sendlib.h"
57 #include "state.h"
58
59 /**
60  * mutt_get_tmp_attachment - Get a temporary copy of an attachment
61  * @param a Attachment to copy
62  * @retval  0 Success
63  * @retval -1 Error
64  */
65 int mutt_get_tmp_attachment(struct Body *a)
66 {
67   char type[256];
68   struct stat st;
69
70   if (a->unlink)
71     return 0;
72
73   struct Buffer *tmpfile = mutt_buffer_pool_get();
74   struct MailcapEntry *entry = mailcap_entry_new();
75   snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype);
76   mailcap_lookup(a, type, entry, MUTT_MC_NO_FLAGS);
77   mailcap_expand_filename(entry->nametemplate, a->filename, tmpfile);
78
79   mailcap_entry_free(&entry);
80
81   if (stat(a->filename, &st) == -1)
82   {
83     mutt_buffer_pool_release(&tmpfile);
84     return -1;
85   }
86
87   FILE *fp_in = NULL, *fp_out = NULL;
88   if ((fp_in = fopen(a->filename, "r")) &&
89       (fp_out = mutt_file_fopen(mutt_b2s(tmpfile), "w")))
90   {
91     mutt_file_copy_stream(fp_in, fp_out);
92     mutt_str_replace(&a->filename, mutt_b2s(tmpfile));
93     a->unlink = true;
94
95     if (a->stamp >= st.st_mtime)
96       mutt_stamp_attachment(a);
97   }
98   else
99     mutt_perror(fp_in ? mutt_b2s(tmpfile) : a->filename);
100
101   mutt_file_fclose(&fp_in);
102   mutt_file_fclose(&fp_out);
103
104   mutt_buffer_pool_release(&tmpfile);
105
106   return a->unlink ? 0 : -1;
107 }
108
109 /**
110  * mutt_compose_attachment - Create an attachment
111  * @param a Body of email
112  * @retval 1 if require full screen redraw
113  * @retval 0 otherwise
114  */
115 int mutt_compose_attachment(struct Body *a)
116 {
117   char type[256];
118   struct MailcapEntry *entry = mailcap_entry_new();
119   bool unlink_newfile = false;
120   int rc = 0;
121   struct Buffer *cmd = mutt_buffer_pool_get();
122   struct Buffer *newfile = mutt_buffer_pool_get();
123   struct Buffer *tmpfile = mutt_buffer_pool_get();
124
125   snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype);
126   if (mailcap_lookup(a, type, entry, MUTT_MC_COMPOSE))
127   {
128     if (entry->composecommand || entry->composetypecommand)
129     {
130       if (entry->composetypecommand)
131         mutt_buffer_strcpy(cmd, entry->composetypecommand);
132       else
133         mutt_buffer_strcpy(cmd, entry->composecommand);
134
135       mailcap_expand_filename(entry->nametemplate, a->filename, newfile);
136       mutt_debug(LL_DEBUG1, "oldfile: %s\t newfile: %s\n", a->filename, mutt_b2s(newfile));
137       if (mutt_file_symlink(a->filename, mutt_b2s(newfile)) == -1)
138       {
139         if (mutt_yesorno(_("Can't match 'nametemplate', continue?"), MUTT_YES) != MUTT_YES)
140           goto bailout;
141         mutt_buffer_strcpy(newfile, a->filename);
142       }
143       else
144         unlink_newfile = true;
145
146       if (mailcap_expand_command(a, mutt_b2s(newfile), type, cmd))
147       {
148         /* For now, editing requires a file, no piping */
149         mutt_error(_("Mailcap compose entry requires %%s"));
150       }
151       else
152       {
153         int r;
154
155         mutt_endwin();
156         r = mutt_system(mutt_b2s(cmd));
157         if (r == -1)
158           mutt_error(_("Error running \"%s\""), mutt_b2s(cmd));
159
160         if ((r != -1) && entry->composetypecommand)
161         {
162           struct Body *b = NULL;
163
164           FILE *fp = mutt_file_fopen(a->filename, "r");
165           if (!fp)
166           {
167             mutt_perror(_("Failure to open file to parse headers"));
168             goto bailout;
169           }
170
171           b = mutt_read_mime_header(fp, 0);
172           if (b)
173           {
174             if (!TAILQ_EMPTY(&b->parameter))
175             {
176               mutt_param_free(&a->parameter);
177               a->parameter = b->parameter;
178               TAILQ_INIT(&b->parameter);
179             }
180             if (b->description)
181             {
182               FREE(&a->description);
183               a->description = b->description;
184               b->description = NULL;
185             }
186             if (b->form_name)
187             {
188               FREE(&a->form_name);
189               a->form_name = b->form_name;
190               b->form_name = NULL;
191             }
192
193             /* Remove headers by copying out data to another file, then
194              * copying the file back */
195             fseeko(fp, b->offset, SEEK_SET);
196             mutt_body_free(&b);
197             mutt_buffer_mktemp(tmpfile);
198             FILE *fp_tmp = mutt_file_fopen(mutt_b2s(tmpfile), "w");
199             if (!fp_tmp)
200             {
201               mutt_perror(_("Failure to open file to strip headers"));
202               mutt_file_fclose(&fp);
203               goto bailout;
204             }
205             mutt_file_copy_stream(fp, fp_tmp);
206             mutt_file_fclose(&fp);
207             mutt_file_fclose(&fp_tmp);
208             mutt_file_unlink(a->filename);
209             if (mutt_file_rename(mutt_b2s(tmpfile), a->filename) != 0)
210             {
211               mutt_perror(_("Failure to rename file"));
212               goto bailout;
213             }
214           }
215         }
216       }
217     }
218   }
219   else
220   {
221     mutt_message(_("No mailcap compose entry for %s, creating empty file"), type);
222     rc = 1;
223     goto bailout;
224   }
225
226   rc = 1;
227
228 bailout:
229
230   if (unlink_newfile)
231     unlink(mutt_b2s(newfile));
232
233   mutt_buffer_pool_release(&cmd);
234   mutt_buffer_pool_release(&newfile);
235   mutt_buffer_pool_release(&tmpfile);
236
237   mailcap_entry_free(&entry);
238   return rc;
239 }
240
241 /**
242  * mutt_edit_attachment - Edit an attachment
243  * @param a Email containing attachment
244  * @retval 1 if editor found
245  * @retval 0 if not
246  *
247  * Currently, this only works for send mode, as it assumes that the
248  * Body->filename actually contains the information.  I'm not sure
249  * we want to deal with editing attachments we've already received,
250  * so this should be ok.
251  *
252  * Returning 0 is useful to tell the calling menu to redraw
253  */
254 int mutt_edit_attachment(struct Body *a)
255 {
256   char type[256];
257   struct MailcapEntry *entry = mailcap_entry_new();
258   bool unlink_newfile = false;
259   int rc = 0;
260   struct Buffer *cmd = mutt_buffer_pool_get();
261   struct Buffer *newfile = mutt_buffer_pool_get();
262
263   snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype);
264   if (mailcap_lookup(a, type, entry, MUTT_MC_EDIT))
265   {
266     if (entry->editcommand)
267     {
268       mutt_buffer_strcpy(cmd, entry->editcommand);
269       mailcap_expand_filename(entry->nametemplate, a->filename, newfile);
270       mutt_debug(LL_DEBUG1, "oldfile: %s\t newfile: %s\n", a->filename, mutt_b2s(newfile));
271       if (mutt_file_symlink(a->filename, mutt_b2s(newfile)) == -1)
272       {
273         if (mutt_yesorno(_("Can't match 'nametemplate', continue?"), MUTT_YES) != MUTT_YES)
274           goto bailout;
275         mutt_buffer_strcpy(newfile, a->filename);
276       }
277       else
278         unlink_newfile = true;
279
280       if (mailcap_expand_command(a, mutt_b2s(newfile), type, cmd))
281       {
282         /* For now, editing requires a file, no piping */
283         mutt_error(_("Mailcap Edit entry requires %%s"));
284         goto bailout;
285       }
286       else
287       {
288         mutt_endwin();
289         if (mutt_system(mutt_b2s(cmd)) == -1)
290         {
291           mutt_error(_("Error running \"%s\""), mutt_b2s(cmd));
292           goto bailout;
293         }
294       }
295     }
296   }
297   else if (a->type == TYPE_TEXT)
298   {
299     /* On text, default to editor */
300     mutt_edit_file(NONULL(C_Editor), a->filename);
301   }
302   else
303   {
304     mutt_error(_("No mailcap edit entry for %s"), type);
305     rc = 0;
306     goto bailout;
307   }
308
309   rc = 1;
310
311 bailout:
312
313   if (unlink_newfile)
314     unlink(mutt_b2s(newfile));
315
316   mutt_buffer_pool_release(&cmd);
317   mutt_buffer_pool_release(&newfile);
318
319   mailcap_entry_free(&entry);
320   return rc;
321 }
322
323 /**
324  * mutt_check_lookup_list - Update the mime type
325  * @param b    Message attachment body
326  * @param type Buffer with mime type of attachment in "type/subtype" format
327  * @param len  Buffer length
328  */
329 void mutt_check_lookup_list(struct Body *b, char *type, size_t len)
330 {
331   struct ListNode *np = NULL;
332   STAILQ_FOREACH(np, &MimeLookupList, entries)
333   {
334     const int i = mutt_str_strlen(np->data) - 1;
335     if (((i > 0) && (np->data[i - 1] == '/') && (np->data[i] == '*') &&
336          (mutt_str_strncasecmp(type, np->data, i) == 0)) ||
337         (mutt_str_strcasecmp(type, np->data) == 0))
338     {
339       struct Body tmp = { 0 };
340       enum ContentType n;
341       if ((n = mutt_lookup_mime_type(&tmp, b->filename)) != TYPE_OTHER ||
342           (n = mutt_lookup_mime_type(&tmp, b->description)) != TYPE_OTHER)
343       {
344         snprintf(type, len, "%s/%s",
345                  (n == TYPE_AUDIO) ?
346                      "audio" :
347                      (n == TYPE_APPLICATION) ?
348                      "application" :
349                      (n == TYPE_IMAGE) ?
350                      "image" :
351                      (n == TYPE_MESSAGE) ?
352                      "message" :
353                      (n == TYPE_MODEL) ?
354                      "model" :
355                      (n == TYPE_MULTIPART) ?
356                      "multipart" :
357                      (n == TYPE_TEXT) ? "text" : (n == TYPE_VIDEO) ? "video" : "other",
358                  tmp.subtype);
359         mutt_debug(LL_DEBUG1, "\"%s\" -> %s\n", b->filename, type);
360       }
361       FREE(&tmp.subtype);
362       FREE(&tmp.xtype);
363     }
364   }
365 }
366
367 /**
368  * mutt_view_attachment - View an attachment
369  * @param fp     Source file stream. Can be NULL
370  * @param a      The message body containing the attachment
371  * @param mode   How the attachment should be viewed, see #ViewAttachMode
372  * @param e      Current Email. Can be NULL
373  * @param actx   Attachment context
374  * @retval 0   If the viewer is run and exited successfully
375  * @retval -1  Error
376  * @retval num Return value of mutt_do_pager() when it is used
377  *
378  * Display a message attachment using the viewer program configured in mailcap.
379  * If there is no mailcap entry for a file type, view the image as text.
380  * Viewer processes are opened and waited on synchronously so viewing an
381  * attachment this way will block the main neomutt process until the viewer process
382  * exits.
383  */
384 int mutt_view_attachment(FILE *fp, struct Body *a, enum ViewAttachMode mode,
385                          struct Email *e, struct AttachCtx *actx)
386 {
387   bool use_mailcap = false;
388   bool use_pipe = false;
389   bool use_pager = true;
390   char type[256];
391   char desc[256];
392   char *fname = NULL;
393   struct MailcapEntry *entry = NULL;
394   int rc = -1;
395   bool unlink_tempfile = false;
396
397   bool is_message = mutt_is_message_type(a->type, a->subtype);
398   if ((WithCrypto != 0) && is_message && a->email &&
399       (a->email->security & SEC_ENCRYPT) && !crypt_valid_passphrase(a->email->security))
400   {
401     return rc;
402   }
403
404   struct Buffer *tmpfile = mutt_buffer_pool_get();
405   struct Buffer *pagerfile = mutt_buffer_pool_get();
406   struct Buffer *cmd = mutt_buffer_pool_get();
407
408   use_mailcap =
409       (mode == MUTT_VA_MAILCAP || (mode == MUTT_VA_REGULAR && mutt_needs_mailcap(a)));
410   snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype);
411
412   if (use_mailcap)
413   {
414     entry = mailcap_entry_new();
415     if (!mailcap_lookup(a, type, entry, MUTT_MC_NO_FLAGS))
416     {
417       if (mode == MUTT_VA_REGULAR)
418       {
419         /* fallback to view as text */
420         mailcap_entry_free(&entry);
421         mutt_error(_("No matching mailcap entry found.  Viewing as text."));
422         mode = MUTT_VA_AS_TEXT;
423         use_mailcap = false;
424       }
425       else
426         goto return_error;
427     }
428   }
429
430   if (use_mailcap)
431   {
432     if (!entry->command)
433     {
434       mutt_error(_("MIME type not defined.  Can't view attachment."));
435       goto return_error;
436     }
437     mutt_buffer_strcpy(cmd, entry->command);
438
439     if (fp)
440     {
441       fname = mutt_str_strdup(a->filename);
442       mutt_file_sanitize_filename(fname, true);
443     }
444     else
445       fname = a->filename;
446
447     mailcap_expand_filename(entry->nametemplate, fname, tmpfile);
448     /* send case: the file is already there; symlink to it */
449     if (!fp)
450     {
451       if (mutt_file_symlink(a->filename, mutt_b2s(tmpfile)) == -1)
452       {
453         if (mutt_yesorno(_("Can't match 'nametemplate', continue?"), MUTT_YES) != MUTT_YES)
454           goto return_error;
455         mutt_buffer_strcpy(tmpfile, a->filename);
456       }
457       else
458         unlink_tempfile = true;
459     }
460     /* recv case: we need to save the attachment to a file */
461     else
462     {
463       FREE(&fname);
464       if (mutt_save_attachment(fp, a, mutt_b2s(tmpfile), MUTT_SAVE_NO_FLAGS, NULL) == -1)
465         goto return_error;
466       mutt_file_chmod(mutt_b2s(tmpfile), S_IRUSR);
467     }
468
469     use_pipe = mailcap_expand_command(a, mutt_b2s(tmpfile), type, cmd);
470     use_pager = entry->copiousoutput;
471   }
472
473   if (use_pager)
474   {
475     if (fp && !use_mailcap && a->filename)
476     {
477       /* recv case */
478       mutt_buffer_strcpy(pagerfile, a->filename);
479       mutt_adv_mktemp(pagerfile);
480     }
481     else
482       mutt_buffer_mktemp(pagerfile);
483   }
484
485   if (use_mailcap)
486   {
487     pid_t pid = 0;
488     int fd_temp = -1, fd_pager = -1;
489
490     if (!use_pager)
491       mutt_endwin();
492
493     if (use_pager || use_pipe)
494     {
495       if (use_pager && ((fd_pager = mutt_file_open(mutt_b2s(pagerfile),
496                                                    O_CREAT | O_EXCL | O_WRONLY)) == -1))
497       {
498         mutt_perror("open");
499         goto return_error;
500       }
501       if (use_pipe && ((fd_temp = open(mutt_b2s(tmpfile), 0)) == -1))
502       {
503         if (fd_pager != -1)
504           close(fd_pager);
505         mutt_perror("open");
506         goto return_error;
507       }
508
509       pid = mutt_create_filter_fd(mutt_b2s(cmd), NULL, NULL, NULL,
510                                   use_pipe ? fd_temp : -1,
511                                   use_pager ? fd_pager : -1, -1);
512       if (pid == -1)
513       {
514         if (fd_pager != -1)
515           close(fd_pager);
516
517         if (fd_temp != -1)
518           close(fd_temp);
519
520         mutt_error(_("Can't create filter"));
521         goto return_error;
522       }
523
524       if (use_pager)
525       {
526         if (a->description)
527         {
528           snprintf(desc, sizeof(desc), _("---Command: %-20.20s Description: %s"),
529                    mutt_b2s(cmd), a->description);
530         }
531         else
532         {
533           snprintf(desc, sizeof(desc), _("---Command: %-30.30s Attachment: %s"),
534                    mutt_b2s(cmd), type);
535         }
536       }
537
538       if ((mutt_wait_filter(pid) || (entry->needsterminal && C_WaitKey)) && !use_pager)
539         mutt_any_key_to_continue(NULL);
540
541       if (fd_temp != -1)
542         close(fd_temp);
543       if (fd_pager != -1)
544         close(fd_pager);
545     }
546     else
547     {
548       /* interactive cmd */
549       int rv = mutt_system(mutt_b2s(cmd));
550       if (rv == -1)
551         mutt_debug(LL_DEBUG1, "Error running \"%s\"", cmd->data);
552
553       if ((rv != 0) || (entry->needsterminal && C_WaitKey))
554         mutt_any_key_to_continue(NULL);
555     }
556   }
557   else
558   {
559     /* Don't use mailcap; the attachment is viewed in the pager */
560
561     if (mode == MUTT_VA_AS_TEXT)
562     {
563       /* just let me see the raw data */
564       if (fp)
565       {
566         /* Viewing from a received message.
567          *
568          * Don't use mutt_save_attachment() because we want to perform charset
569          * conversion since this will be displayed by the internal pager.  */
570         struct State decode_state = { 0 };
571
572         decode_state.fp_out = mutt_file_fopen(mutt_b2s(pagerfile), "w");
573         if (!decode_state.fp_out)
574         {
575           mutt_debug(LL_DEBUG1, "mutt_file_fopen(%s) errno=%d %s\n",
576                      mutt_b2s(pagerfile), errno, strerror(errno));
577           mutt_perror(mutt_b2s(pagerfile));
578           goto return_error;
579         }
580         decode_state.fp_in = fp;
581         decode_state.flags = MUTT_CHARCONV;
582         mutt_decode_attachment(a, &decode_state);
583         if (fclose(decode_state.fp_out) == EOF)
584           mutt_debug(LL_DEBUG1, "fclose(%s) errno=%d %s\n", mutt_b2s(pagerfile),
585                      errno, strerror(errno));
586       }
587       else
588       {
589         /* in compose mode, just copy the file.  we can't use
590          * mutt_decode_attachment() since it assumes the content-encoding has
591          * already been applied */
592         if (mutt_save_attachment(fp, a, mutt_b2s(pagerfile), MUTT_SAVE_NO_FLAGS, NULL))
593           goto return_error;
594       }
595     }
596     else
597     {
598       /* Use built-in handler */
599       OptViewAttach = true; /* disable the "use 'v' to view this part"
600                              * message in case of error */
601       if (mutt_decode_save_attachment(fp, a, mutt_b2s(pagerfile), MUTT_DISPLAY, MUTT_SAVE_NO_FLAGS))
602       {
603         OptViewAttach = false;
604         goto return_error;
605       }
606       OptViewAttach = false;
607     }
608
609     if (a->description)
610       mutt_str_strfcpy(desc, a->description, sizeof(desc));
611     else if (a->filename)
612       snprintf(desc, sizeof(desc), _("---Attachment: %s: %s"), a->filename, type);
613     else
614       snprintf(desc, sizeof(desc), _("---Attachment: %s"), type);
615   }
616
617   /* We only reach this point if there have been no errors */
618
619   if (use_pager)
620   {
621     struct Pager info = { 0 };
622     info.fp = fp;
623     info.body = a;
624     info.ctx = Context;
625     info.actx = actx;
626     info.email = e;
627
628     rc = mutt_do_pager(desc, mutt_b2s(pagerfile),
629                        MUTT_PAGER_ATTACHMENT | (is_message ? MUTT_PAGER_MESSAGE : MUTT_PAGER_NO_FLAGS),
630                        &info);
631     mutt_buffer_reset(pagerfile);
632   }
633   else
634     rc = 0;
635
636 return_error:
637
638   if (!entry || !entry->xneomuttkeep)
639   {
640     if (fp && !mutt_buffer_is_empty(tmpfile))
641     {
642       /* add temporary file to TempAttachmentsList to be deleted on timeout hook */
643       mutt_add_temp_attachment(mutt_b2s(tmpfile));
644     }
645     else if (unlink_tempfile)
646     {
647       unlink(mutt_b2s(tmpfile));
648     }
649   }
650
651   mailcap_entry_free(&entry);
652
653   if (!mutt_buffer_is_empty(pagerfile))
654     mutt_file_unlink(mutt_b2s(pagerfile));
655
656   mutt_buffer_pool_release(&tmpfile);
657   mutt_buffer_pool_release(&pagerfile);
658   mutt_buffer_pool_release(&cmd);
659
660   return rc;
661 }
662
663 /**
664  * mutt_pipe_attachment - Pipe an attachment to a command
665  * @param fp      File to pipe into the command
666  * @param b       Attachment
667  * @param path    Path to command
668  * @param outfile File to save output to
669  * @retval 1 Success
670  * @retval 0 Error
671  */
672 int mutt_pipe_attachment(FILE *fp, struct Body *b, const char *path, char *outfile)
673 {
674   pid_t pid;
675   int out = -1;
676   int rc = 0;
677
678   if (outfile && *outfile)
679   {
680     out = mutt_file_open(outfile, O_CREAT | O_EXCL | O_WRONLY);
681     if (out < 0)
682     {
683       mutt_perror("open");
684       return 0;
685     }
686   }
687
688   mutt_endwin();
689
690   if (fp)
691   {
692     /* recv case */
693
694     struct State s = { 0 };
695
696     /* perform charset conversion on text attachments when piping */
697     s.flags = MUTT_CHARCONV;
698
699     if (outfile && *outfile)
700       pid = mutt_create_filter_fd(path, &s.fp_out, NULL, NULL, -1, out, -1);
701     else
702       pid = mutt_create_filter(path, &s.fp_out, NULL, NULL);
703
704     if (pid < 0)
705     {
706       mutt_perror(_("Can't create filter"));
707       goto bail;
708     }
709
710     s.fp_in = fp;
711     mutt_decode_attachment(b, &s);
712     mutt_file_fclose(&s.fp_out);
713   }
714   else
715   {
716     /* send case */
717     FILE *fp_in = fopen(b->filename, "r");
718     if (!fp_in)
719     {
720       mutt_perror("fopen");
721       if (outfile && *outfile)
722       {
723         close(out);
724         unlink(outfile);
725       }
726       return 0;
727     }
728
729     FILE *fp_out = NULL;
730     if (outfile && *outfile)
731       pid = mutt_create_filter_fd(path, &fp_out, NULL, NULL, -1, out, -1);
732     else
733       pid = mutt_create_filter(path, &fp_out, NULL, NULL);
734
735     if (pid < 0)
736     {
737       mutt_perror(_("Can't create filter"));
738       mutt_file_fclose(&fp_in);
739       goto bail;
740     }
741
742     mutt_file_copy_stream(fp_in, fp_out);
743     mutt_file_fclose(&fp_out);
744     mutt_file_fclose(&fp_in);
745   }
746
747   rc = 1;
748
749 bail:
750
751   if (outfile && *outfile)
752     close(out);
753
754   /* check for error exit from child process */
755   if (mutt_wait_filter(pid) != 0)
756     rc = 0;
757
758   if ((rc == 0) || C_WaitKey)
759     mutt_any_key_to_continue(NULL);
760   return rc;
761 }
762
763 /**
764  * save_attachment_open - Open a file to write an attachment to
765  * @param path Path to file to open
766  * @param opt  Save option, see #SaveAttach
767  * @retval ptr File handle to attachment file
768  */
769 static FILE *save_attachment_open(const char *path, enum SaveAttach opt)
770 {
771   if (opt == MUTT_SAVE_APPEND)
772     return fopen(path, "a");
773   if (opt == MUTT_SAVE_OVERWRITE)
774     return fopen(path, "w");
775
776   return mutt_file_fopen(path, "w");
777 }
778
779 /**
780  * mutt_save_attachment - Save an attachment
781  * @param fp   Source file stream. Can be NULL
782  * @param m    Email Body
783  * @param path Where to save the attachment
784  * @param opt  Save option, see #SaveAttach
785  * @param e    Current Email. Can be NULL
786  * @retval  0 Success
787  * @retval -1 Error
788  */
789 int mutt_save_attachment(FILE *fp, struct Body *m, const char *path,
790                          enum SaveAttach opt, struct Email *e)
791 {
792   if (!m)
793     return -1;
794
795   if (fp)
796   {
797     /* recv mode */
798
799     if (e && m->email && (m->encoding != ENC_BASE64) &&
800         (m->encoding != ENC_QUOTED_PRINTABLE) && mutt_is_message_type(m->type, m->subtype))
801     {
802       /* message type attachments are written to mail folders. */
803
804       char buf[8192];
805       struct Message *msg = NULL;
806       CopyHeaderFlags chflags = CH_NO_FLAGS;
807       int rc = -1;
808
809       struct Email *e_new = m->email;
810       e_new->msgno = e->msgno; /* required for MH/maildir */
811       e_new->read = true;
812
813       if (fseeko(fp, m->offset, SEEK_SET) < 0)
814         return -1;
815       if (!fgets(buf, sizeof(buf), fp))
816         return -1;
817       struct Mailbox *m_att = mx_path_resolve(path);
818       struct Context *ctx = mx_mbox_open(m_att, MUTT_APPEND | MUTT_QUIET);
819       if (!ctx)
820       {
821         mailbox_free(&m_att);
822         return -1;
823       }
824       msg = mx_msg_open_new(ctx->mailbox, e_new,
825                             is_from(buf, NULL, 0, NULL) ? MUTT_MSG_NO_FLAGS : MUTT_ADD_FROM);
826       if (!msg)
827       {
828         mx_mbox_close(&ctx);
829         return -1;
830       }
831       if ((ctx->mailbox->magic == MUTT_MBOX) || (ctx->mailbox->magic == MUTT_MMDF))
832         chflags = CH_FROM | CH_UPDATE_LEN;
833       chflags |= ((ctx->mailbox->magic == MUTT_MAILDIR) ? CH_NOSTATUS : CH_UPDATE);
834       if ((mutt_copy_message_fp(msg->fp, fp, e_new, MUTT_CM_NO_FLAGS, chflags) == 0) &&
835           (mx_msg_commit(ctx->mailbox, msg) == 0))
836       {
837         rc = 0;
838       }
839       else
840       {
841         rc = -1;
842       }
843
844       mx_msg_close(ctx->mailbox, &msg);
845       mx_mbox_close(&ctx);
846       return rc;
847     }
848     else
849     {
850       /* In recv mode, extract from folder and decode */
851
852       struct State s = { 0 };
853
854       s.fp_out = save_attachment_open(path, opt);
855       if (!s.fp_out)
856       {
857         mutt_perror("fopen");
858         return -1;
859       }
860       fseeko((s.fp_in = fp), m->offset, SEEK_SET);
861       mutt_decode_attachment(m, &s);
862
863       if (mutt_file_fsync_close(&s.fp_out) != 0)
864       {
865         mutt_perror("fclose");
866         return -1;
867       }
868     }
869   }
870   else
871   {
872     if (!m->filename)
873       return -1;
874
875     /* In send mode, just copy file */
876
877     FILE *fp_old = fopen(m->filename, "r");
878     if (!fp_old)
879     {
880       mutt_perror("fopen");
881       return -1;
882     }
883
884     FILE *fp_new = save_attachment_open(path, opt);
885     if (!fp_new)
886     {
887       mutt_perror("fopen");
888       mutt_file_fclose(&fp_old);
889       return -1;
890     }
891
892     if (mutt_file_copy_stream(fp_old, fp_new) == -1)
893     {
894       mutt_error(_("Write fault"));
895       mutt_file_fclose(&fp_old);
896       mutt_file_fclose(&fp_new);
897       return -1;
898     }
899     mutt_file_fclose(&fp_old);
900     if (mutt_file_fsync_close(&fp_new) != 0)
901     {
902       mutt_error(_("Write fault"));
903       return -1;
904     }
905   }
906
907   return 0;
908 }
909
910 /**
911  * mutt_decode_save_attachment - Decode, then save an attachment
912  * @param fp         File to read from (OPTIONAL)
913  * @param m          Attachment
914  * @param path       Path to save the Attachment to
915  * @param displaying Flags, e.g. #MUTT_DISPLAY
916  * @param opt        Save option, see #SaveAttach
917  * @retval 0  Success
918  * @retval -1 Error
919  */
920 int mutt_decode_save_attachment(FILE *fp, struct Body *m, const char *path,
921                                 int displaying, enum SaveAttach opt)
922 {
923   struct State s = { 0 };
924   unsigned int saved_encoding = 0;
925   struct Body *saved_parts = NULL;
926   struct Email *e_saved = NULL;
927   int rc = 0;
928
929   s.flags = displaying;
930
931   if (opt == MUTT_SAVE_APPEND)
932     s.fp_out = fopen(path, "a");
933   else if (opt == MUTT_SAVE_OVERWRITE)
934     s.fp_out = fopen(path, "w");
935   else
936     s.fp_out = mutt_file_fopen(path, "w");
937
938   if (!s.fp_out)
939   {
940     mutt_perror("fopen");
941     return -1;
942   }
943
944   if (!fp)
945   {
946     /* When called from the compose menu, the attachment isn't parsed,
947      * so we need to do it here. */
948     struct stat st;
949
950     if (stat(m->filename, &st) == -1)
951     {
952       mutt_perror("stat");
953       mutt_file_fclose(&s.fp_out);
954       return -1;
955     }
956
957     s.fp_in = fopen(m->filename, "r");
958     if (!s.fp_in)
959     {
960       mutt_perror("fopen");
961       return -1;
962     }
963
964     saved_encoding = m->encoding;
965     if (!is_multipart(m))
966       m->encoding = ENC_8BIT;
967
968     m->length = st.st_size;
969     m->offset = 0;
970     saved_parts = m->parts;
971     e_saved = m->email;
972     mutt_parse_part(s.fp_in, m);
973
974     if (m->noconv || is_multipart(m))
975       s.flags |= MUTT_CHARCONV;
976   }
977   else
978   {
979     s.fp_in = fp;
980     s.flags |= MUTT_CHARCONV;
981   }
982
983   mutt_body_handler(m, &s);
984
985   if (mutt_file_fsync_close(&s.fp_out) != 0)
986   {
987     mutt_perror("fclose");
988     rc = -1;
989   }
990   if (!fp)
991   {
992     m->length = 0;
993     m->encoding = saved_encoding;
994     if (saved_parts)
995     {
996       email_free(&m->email);
997       m->parts = saved_parts;
998       m->email = e_saved;
999     }
1000     mutt_file_fclose(&s.fp_in);
1001   }
1002
1003   return rc;
1004 }
1005
1006 /**
1007  * mutt_print_attachment - Print out an attachment
1008  * @param fp File to write to
1009  * @param a  Attachment
1010  * @retval 1 Success
1011  * @retval 0 Error
1012  *
1013  * Ok, the difference between send and receive:
1014  * recv: Body->filename is a suggested name, and Mailbox|Email points
1015  *       to the attachment in mailbox which is encoded
1016  * send: Body->filename points to the un-encoded file which contains the
1017  *       attachment
1018  */
1019 int mutt_print_attachment(FILE *fp, struct Body *a)
1020 {
1021   char type[256];
1022   pid_t pid;
1023   FILE *fp_in = NULL, *fp_out = NULL;
1024   bool unlink_newfile = false;
1025   struct Buffer *newfile = mutt_buffer_pool_get();
1026   struct Buffer *cmd = mutt_buffer_pool_get();
1027
1028   int rc = 0;
1029
1030   snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype);
1031
1032   if (mailcap_lookup(a, type, NULL, MUTT_MC_PRINT))
1033   {
1034     int piped = false;
1035
1036     mutt_debug(LL_DEBUG2, "Using mailcap\n");
1037
1038     struct MailcapEntry *entry = mailcap_entry_new();
1039     mailcap_lookup(a, type, entry, MUTT_MC_PRINT);
1040     mailcap_expand_filename(entry->nametemplate, a->filename, newfile);
1041     /* send mode: symlink from existing file to the newfile */
1042     if (!fp)
1043     {
1044       if (mutt_file_symlink(a->filename, mutt_b2s(newfile)) == -1)
1045       {
1046         if (mutt_yesorno(_("Can't match 'nametemplate', continue?"), MUTT_YES) != MUTT_YES)
1047           goto mailcap_cleanup;
1048         mutt_buffer_strcpy(newfile, a->filename);
1049       }
1050       else
1051         unlink_newfile = true;
1052     }
1053     /* in recv mode, save file to newfile first */
1054     else
1055     {
1056       if (mutt_save_attachment(fp, a, mutt_b2s(newfile), 0, NULL) == -1)
1057         goto mailcap_cleanup;
1058     }
1059
1060     mutt_buffer_strcpy(cmd, entry->printcommand);
1061     piped = mailcap_expand_command(a, mutt_b2s(newfile), type, cmd);
1062
1063     mutt_endwin();
1064
1065     /* interactive program */
1066     if (piped)
1067     {
1068       fp_in = fopen(mutt_b2s(newfile), "r");
1069       if (!fp_in)
1070       {
1071         mutt_perror("fopen");
1072         mailcap_entry_free(&entry);
1073         goto mailcap_cleanup;
1074       }
1075
1076       pid = mutt_create_filter(mutt_b2s(cmd), &fp_out, NULL, NULL);
1077       if (pid < 0)
1078       {
1079         mutt_perror(_("Can't create filter"));
1080         mailcap_entry_free(&entry);
1081         mutt_file_fclose(&fp_in);
1082         goto mailcap_cleanup;
1083       }
1084       mutt_file_copy_stream(fp_in, fp_out);
1085       mutt_file_fclose(&fp_out);
1086       mutt_file_fclose(&fp_in);
1087       if (mutt_wait_filter(pid) || C_WaitKey)
1088         mutt_any_key_to_continue(NULL);
1089     }
1090     else
1091     {
1092       int rc2 = mutt_system(mutt_b2s(cmd));
1093       if (rc2 == -1)
1094         mutt_debug(LL_DEBUG1, "Error running \"%s\"", cmd->data);
1095
1096       if ((rc2 != 0) || C_WaitKey)
1097         mutt_any_key_to_continue(NULL);
1098     }
1099
1100     rc = 1;
1101
1102   mailcap_cleanup:
1103     if (fp)
1104       mutt_file_unlink(mutt_b2s(newfile));
1105     else if (unlink_newfile)
1106       unlink(mutt_b2s(newfile));
1107
1108     mailcap_entry_free(&entry);
1109     goto out;
1110   }
1111
1112   if ((mutt_str_strcasecmp("text/plain", type) == 0) ||
1113       (mutt_str_strcasecmp("application/postscript", type) == 0))
1114   {
1115     rc = (mutt_pipe_attachment(fp, a, NONULL(C_PrintCommand), NULL));
1116     goto out;
1117   }
1118   else if (mutt_can_decode(a))
1119   {
1120     /* decode and print */
1121
1122     fp_in = NULL;
1123     fp_out = NULL;
1124
1125     mutt_buffer_mktemp(newfile);
1126     if (mutt_decode_save_attachment(fp, a, mutt_b2s(newfile), MUTT_PRINTING, 0) == 0)
1127     {
1128       mutt_debug(LL_DEBUG2, "successfully decoded %s type attachment to %s\n",
1129                  type, mutt_b2s(newfile));
1130
1131       fp_in = fopen(mutt_b2s(newfile), "r");
1132       if (!fp_in)
1133       {
1134         mutt_perror("fopen");
1135         goto decode_cleanup;
1136       }
1137
1138       mutt_debug(LL_DEBUG2, "successfully opened %s read-only\n", mutt_b2s(newfile));
1139
1140       mutt_endwin();
1141       pid = mutt_create_filter(NONULL(C_PrintCommand), &fp_out, NULL, NULL);
1142       if (pid < 0)
1143       {
1144         mutt_perror(_("Can't create filter"));
1145         goto decode_cleanup;
1146       }
1147
1148       mutt_debug(LL_DEBUG2, "Filter created\n");
1149
1150       mutt_file_copy_stream(fp_in, fp_out);
1151
1152       mutt_file_fclose(&fp_out);
1153       mutt_file_fclose(&fp_in);
1154
1155       if ((mutt_wait_filter(pid) != 0) || C_WaitKey)
1156         mutt_any_key_to_continue(NULL);
1157       rc = 1;
1158     }
1159   decode_cleanup:
1160     mutt_file_fclose(&fp_in);
1161     mutt_file_fclose(&fp_out);
1162     mutt_file_unlink(mutt_b2s(newfile));
1163   }
1164   else
1165   {
1166     mutt_error(_("I don't know how to print that"));
1167     rc = 0;
1168   }
1169
1170 out:
1171   mutt_buffer_pool_release(&newfile);
1172   mutt_buffer_pool_release(&cmd);
1173
1174   return rc;
1175 }
1176
1177 /**
1178  * mutt_add_temp_attachment - Add file to list of temporary attachments
1179  * @param filename filename with full path
1180  */
1181 void mutt_add_temp_attachment(const char *filename)
1182 {
1183   mutt_list_insert_tail(&TempAttachmentsList, mutt_str_strdup(filename));
1184 }
1185
1186 /**
1187  * mutt_unlink_temp_attachments - Delete all temporary attachments
1188  */
1189 void mutt_unlink_temp_attachments(void)
1190 {
1191   struct ListNode *np = NULL;
1192
1193   STAILQ_FOREACH(np, &TempAttachmentsList, entries)
1194   {
1195     mutt_file_chmod_add(np->data, S_IWUSR);
1196     mutt_file_unlink(np->data);
1197   }
1198
1199   mutt_list_free(&TempAttachmentsList);
1200 }