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