/** * @file * Handling of email attachments * * @authors * Copyright (C) 1996-2000,2002,2013 Michael R. Elkins * Copyright (C) 1999-2004,2006 Thomas Roessler * * @copyright * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ /** * @page mutt_attach Handling of email attachments * * Handling of email attachments */ #include "config.h" #include #include #include #include #include #include #include #include "mutt/mutt.h" #include "config/lib.h" #include "email/lib.h" #include "core/lib.h" #include "mutt_attach.h" #include "context.h" #include "copy.h" #include "curs_lib.h" #include "filter.h" #include "globals.h" #include "handler.h" #include "mailcap.h" #include "muttlib.h" #include "mx.h" #include "ncrypt/ncrypt.h" #include "options.h" #include "pager.h" #include "protos.h" #include "sendlib.h" #include "state.h" /** * mutt_get_tmp_attachment - Get a temporary copy of an attachment * @param a Attachment to copy * @retval 0 Success * @retval -1 Error */ int mutt_get_tmp_attachment(struct Body *a) { char type[256]; struct stat st; if (a->unlink) return 0; struct Buffer *tmpfile = mutt_buffer_pool_get(); struct MailcapEntry *entry = mailcap_entry_new(); snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype); mailcap_lookup(a, type, entry, MUTT_MC_NO_FLAGS); mailcap_expand_filename(entry->nametemplate, a->filename, tmpfile); mailcap_entry_free(&entry); if (stat(a->filename, &st) == -1) { mutt_buffer_pool_release(&tmpfile); return -1; } FILE *fp_in = NULL, *fp_out = NULL; if ((fp_in = fopen(a->filename, "r")) && (fp_out = mutt_file_fopen(mutt_b2s(tmpfile), "w"))) { mutt_file_copy_stream(fp_in, fp_out); mutt_str_replace(&a->filename, mutt_b2s(tmpfile)); a->unlink = true; if (a->stamp >= st.st_mtime) mutt_stamp_attachment(a); } else mutt_perror(fp_in ? mutt_b2s(tmpfile) : a->filename); mutt_file_fclose(&fp_in); mutt_file_fclose(&fp_out); mutt_buffer_pool_release(&tmpfile); return a->unlink ? 0 : -1; } /** * mutt_compose_attachment - Create an attachment * @param a Body of email * @retval 1 if require full screen redraw * @retval 0 otherwise */ int mutt_compose_attachment(struct Body *a) { char type[256]; struct MailcapEntry *entry = mailcap_entry_new(); bool unlink_newfile = false; int rc = 0; struct Buffer *cmd = mutt_buffer_pool_get(); struct Buffer *newfile = mutt_buffer_pool_get(); struct Buffer *tmpfile = mutt_buffer_pool_get(); snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype); if (mailcap_lookup(a, type, entry, MUTT_MC_COMPOSE)) { if (entry->composecommand || entry->composetypecommand) { if (entry->composetypecommand) mutt_buffer_strcpy(cmd, entry->composetypecommand); else mutt_buffer_strcpy(cmd, entry->composecommand); mailcap_expand_filename(entry->nametemplate, a->filename, newfile); mutt_debug(LL_DEBUG1, "oldfile: %s\t newfile: %s\n", a->filename, mutt_b2s(newfile)); if (mutt_file_symlink(a->filename, mutt_b2s(newfile)) == -1) { if (mutt_yesorno(_("Can't match 'nametemplate', continue?"), MUTT_YES) != MUTT_YES) goto bailout; mutt_buffer_strcpy(newfile, a->filename); } else unlink_newfile = true; if (mailcap_expand_command(a, mutt_b2s(newfile), type, cmd)) { /* For now, editing requires a file, no piping */ mutt_error(_("Mailcap compose entry requires %%s")); } else { int r; mutt_endwin(); r = mutt_system(mutt_b2s(cmd)); if (r == -1) mutt_error(_("Error running \"%s\""), mutt_b2s(cmd)); if ((r != -1) && entry->composetypecommand) { struct Body *b = NULL; FILE *fp = mutt_file_fopen(a->filename, "r"); if (!fp) { mutt_perror(_("Failure to open file to parse headers")); goto bailout; } b = mutt_read_mime_header(fp, 0); if (b) { if (!TAILQ_EMPTY(&b->parameter)) { mutt_param_free(&a->parameter); a->parameter = b->parameter; TAILQ_INIT(&b->parameter); } if (b->description) { FREE(&a->description); a->description = b->description; b->description = NULL; } if (b->form_name) { FREE(&a->form_name); a->form_name = b->form_name; b->form_name = NULL; } /* Remove headers by copying out data to another file, then * copying the file back */ fseeko(fp, b->offset, SEEK_SET); mutt_body_free(&b); mutt_buffer_mktemp(tmpfile); FILE *fp_tmp = mutt_file_fopen(mutt_b2s(tmpfile), "w"); if (!fp_tmp) { mutt_perror(_("Failure to open file to strip headers")); mutt_file_fclose(&fp); goto bailout; } mutt_file_copy_stream(fp, fp_tmp); mutt_file_fclose(&fp); mutt_file_fclose(&fp_tmp); mutt_file_unlink(a->filename); if (mutt_file_rename(mutt_b2s(tmpfile), a->filename) != 0) { mutt_perror(_("Failure to rename file")); goto bailout; } } } } } } else { mutt_message(_("No mailcap compose entry for %s, creating empty file"), type); rc = 1; goto bailout; } rc = 1; bailout: if (unlink_newfile) unlink(mutt_b2s(newfile)); mutt_buffer_pool_release(&cmd); mutt_buffer_pool_release(&newfile); mutt_buffer_pool_release(&tmpfile); mailcap_entry_free(&entry); return rc; } /** * mutt_edit_attachment - Edit an attachment * @param a Email containing attachment * @retval 1 if editor found * @retval 0 if not * * Currently, this only works for send mode, as it assumes that the * Body->filename actually contains the information. I'm not sure * we want to deal with editing attachments we've already received, * so this should be ok. * * Returning 0 is useful to tell the calling menu to redraw */ int mutt_edit_attachment(struct Body *a) { char type[256]; struct MailcapEntry *entry = mailcap_entry_new(); bool unlink_newfile = false; int rc = 0; struct Buffer *cmd = mutt_buffer_pool_get(); struct Buffer *newfile = mutt_buffer_pool_get(); snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype); if (mailcap_lookup(a, type, entry, MUTT_MC_EDIT)) { if (entry->editcommand) { mutt_buffer_strcpy(cmd, entry->editcommand); mailcap_expand_filename(entry->nametemplate, a->filename, newfile); mutt_debug(LL_DEBUG1, "oldfile: %s\t newfile: %s\n", a->filename, mutt_b2s(newfile)); if (mutt_file_symlink(a->filename, mutt_b2s(newfile)) == -1) { if (mutt_yesorno(_("Can't match 'nametemplate', continue?"), MUTT_YES) != MUTT_YES) goto bailout; mutt_buffer_strcpy(newfile, a->filename); } else unlink_newfile = true; if (mailcap_expand_command(a, mutt_b2s(newfile), type, cmd)) { /* For now, editing requires a file, no piping */ mutt_error(_("Mailcap Edit entry requires %%s")); goto bailout; } else { mutt_endwin(); if (mutt_system(mutt_b2s(cmd)) == -1) { mutt_error(_("Error running \"%s\""), mutt_b2s(cmd)); goto bailout; } } } } else if (a->type == TYPE_TEXT) { /* On text, default to editor */ mutt_edit_file(NONULL(C_Editor), a->filename); } else { mutt_error(_("No mailcap edit entry for %s"), type); rc = 0; goto bailout; } rc = 1; bailout: if (unlink_newfile) unlink(mutt_b2s(newfile)); mutt_buffer_pool_release(&cmd); mutt_buffer_pool_release(&newfile); mailcap_entry_free(&entry); return rc; } /** * mutt_check_lookup_list - Update the mime type * @param b Message attachment body * @param type Buffer with mime type of attachment in "type/subtype" format * @param len Buffer length */ void mutt_check_lookup_list(struct Body *b, char *type, size_t len) { struct ListNode *np = NULL; STAILQ_FOREACH(np, &MimeLookupList, entries) { const int i = mutt_str_strlen(np->data) - 1; if (((i > 0) && (np->data[i - 1] == '/') && (np->data[i] == '*') && (mutt_str_strncasecmp(type, np->data, i) == 0)) || (mutt_str_strcasecmp(type, np->data) == 0)) { struct Body tmp = { 0 }; enum ContentType n; if ((n = mutt_lookup_mime_type(&tmp, b->filename)) != TYPE_OTHER || (n = mutt_lookup_mime_type(&tmp, b->description)) != TYPE_OTHER) { snprintf(type, len, "%s/%s", (n == TYPE_AUDIO) ? "audio" : (n == TYPE_APPLICATION) ? "application" : (n == TYPE_IMAGE) ? "image" : (n == TYPE_MESSAGE) ? "message" : (n == TYPE_MODEL) ? "model" : (n == TYPE_MULTIPART) ? "multipart" : (n == TYPE_TEXT) ? "text" : (n == TYPE_VIDEO) ? "video" : "other", tmp.subtype); mutt_debug(LL_DEBUG1, "\"%s\" -> %s\n", b->filename, type); } FREE(&tmp.subtype); FREE(&tmp.xtype); } } } /** * mutt_view_attachment - View an attachment * @param fp Source file stream. Can be NULL * @param a The message body containing the attachment * @param mode How the attachment should be viewed, see #ViewAttachMode * @param e Current Email. Can be NULL * @param actx Attachment context * @retval 0 If the viewer is run and exited successfully * @retval -1 Error * @retval num Return value of mutt_do_pager() when it is used * * Display a message attachment using the viewer program configured in mailcap. * If there is no mailcap entry for a file type, view the image as text. * Viewer processes are opened and waited on synchronously so viewing an * attachment this way will block the main neomutt process until the viewer process * exits. */ int mutt_view_attachment(FILE *fp, struct Body *a, enum ViewAttachMode mode, struct Email *e, struct AttachCtx *actx) { bool use_mailcap = false; bool use_pipe = false; bool use_pager = true; char type[256]; char desc[256]; char *fname = NULL; struct MailcapEntry *entry = NULL; int rc = -1; bool unlink_tempfile = false; bool is_message = mutt_is_message_type(a->type, a->subtype); if ((WithCrypto != 0) && is_message && a->email && (a->email->security & SEC_ENCRYPT) && !crypt_valid_passphrase(a->email->security)) { return rc; } struct Buffer *tmpfile = mutt_buffer_pool_get(); struct Buffer *pagerfile = mutt_buffer_pool_get(); struct Buffer *cmd = mutt_buffer_pool_get(); use_mailcap = (mode == MUTT_VA_MAILCAP || (mode == MUTT_VA_REGULAR && mutt_needs_mailcap(a))); snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype); if (use_mailcap) { entry = mailcap_entry_new(); if (!mailcap_lookup(a, type, entry, MUTT_MC_NO_FLAGS)) { if (mode == MUTT_VA_REGULAR) { /* fallback to view as text */ mailcap_entry_free(&entry); mutt_error(_("No matching mailcap entry found. Viewing as text.")); mode = MUTT_VA_AS_TEXT; use_mailcap = false; } else goto return_error; } } if (use_mailcap) { if (!entry->command) { mutt_error(_("MIME type not defined. Can't view attachment.")); goto return_error; } mutt_buffer_strcpy(cmd, entry->command); if (fp) { fname = mutt_str_strdup(a->filename); mutt_file_sanitize_filename(fname, true); } else fname = a->filename; mailcap_expand_filename(entry->nametemplate, fname, tmpfile); /* send case: the file is already there; symlink to it */ if (!fp) { if (mutt_file_symlink(a->filename, mutt_b2s(tmpfile)) == -1) { if (mutt_yesorno(_("Can't match 'nametemplate', continue?"), MUTT_YES) != MUTT_YES) goto return_error; mutt_buffer_strcpy(tmpfile, a->filename); } else unlink_tempfile = true; } /* recv case: we need to save the attachment to a file */ else { FREE(&fname); if (mutt_save_attachment(fp, a, mutt_b2s(tmpfile), MUTT_SAVE_NO_FLAGS, NULL) == -1) goto return_error; mutt_file_chmod(mutt_b2s(tmpfile), S_IRUSR); } use_pipe = mailcap_expand_command(a, mutt_b2s(tmpfile), type, cmd); use_pager = entry->copiousoutput; } if (use_pager) { if (fp && !use_mailcap && a->filename) { /* recv case */ mutt_buffer_strcpy(pagerfile, a->filename); mutt_adv_mktemp(pagerfile); } else mutt_buffer_mktemp(pagerfile); } if (use_mailcap) { pid_t pid = 0; int fd_temp = -1, fd_pager = -1; if (!use_pager) mutt_endwin(); if (use_pager || use_pipe) { if (use_pager && ((fd_pager = mutt_file_open(mutt_b2s(pagerfile), O_CREAT | O_EXCL | O_WRONLY)) == -1)) { mutt_perror("open"); goto return_error; } if (use_pipe && ((fd_temp = open(mutt_b2s(tmpfile), 0)) == -1)) { if (fd_pager != -1) close(fd_pager); mutt_perror("open"); goto return_error; } pid = mutt_create_filter_fd(mutt_b2s(cmd), NULL, NULL, NULL, use_pipe ? fd_temp : -1, use_pager ? fd_pager : -1, -1); if (pid == -1) { if (fd_pager != -1) close(fd_pager); if (fd_temp != -1) close(fd_temp); mutt_error(_("Can't create filter")); goto return_error; } if (use_pager) { if (a->description) { snprintf(desc, sizeof(desc), _("---Command: %-20.20s Description: %s"), mutt_b2s(cmd), a->description); } else { snprintf(desc, sizeof(desc), _("---Command: %-30.30s Attachment: %s"), mutt_b2s(cmd), type); } } if ((mutt_wait_filter(pid) || (entry->needsterminal && C_WaitKey)) && !use_pager) mutt_any_key_to_continue(NULL); if (fd_temp != -1) close(fd_temp); if (fd_pager != -1) close(fd_pager); } else { /* interactive cmd */ int rv = mutt_system(mutt_b2s(cmd)); if (rv == -1) mutt_debug(LL_DEBUG1, "Error running \"%s\"", cmd->data); if ((rv != 0) || (entry->needsterminal && C_WaitKey)) mutt_any_key_to_continue(NULL); } } else { /* Don't use mailcap; the attachment is viewed in the pager */ if (mode == MUTT_VA_AS_TEXT) { /* just let me see the raw data */ if (fp) { /* Viewing from a received message. * * Don't use mutt_save_attachment() because we want to perform charset * conversion since this will be displayed by the internal pager. */ struct State decode_state = { 0 }; decode_state.fp_out = mutt_file_fopen(mutt_b2s(pagerfile), "w"); if (!decode_state.fp_out) { mutt_debug(LL_DEBUG1, "mutt_file_fopen(%s) errno=%d %s\n", mutt_b2s(pagerfile), errno, strerror(errno)); mutt_perror(mutt_b2s(pagerfile)); goto return_error; } decode_state.fp_in = fp; decode_state.flags = MUTT_CHARCONV; mutt_decode_attachment(a, &decode_state); if (fclose(decode_state.fp_out) == EOF) mutt_debug(LL_DEBUG1, "fclose(%s) errno=%d %s\n", mutt_b2s(pagerfile), errno, strerror(errno)); } else { /* in compose mode, just copy the file. we can't use * mutt_decode_attachment() since it assumes the content-encoding has * already been applied */ if (mutt_save_attachment(fp, a, mutt_b2s(pagerfile), MUTT_SAVE_NO_FLAGS, NULL)) goto return_error; } } else { /* Use built-in handler */ OptViewAttach = true; /* disable the "use 'v' to view this part" * message in case of error */ if (mutt_decode_save_attachment(fp, a, mutt_b2s(pagerfile), MUTT_DISPLAY, MUTT_SAVE_NO_FLAGS)) { OptViewAttach = false; goto return_error; } OptViewAttach = false; } if (a->description) mutt_str_strfcpy(desc, a->description, sizeof(desc)); else if (a->filename) snprintf(desc, sizeof(desc), _("---Attachment: %s: %s"), a->filename, type); else snprintf(desc, sizeof(desc), _("---Attachment: %s"), type); } /* We only reach this point if there have been no errors */ if (use_pager) { struct Pager info = { 0 }; info.fp = fp; info.body = a; info.ctx = Context; info.actx = actx; info.email = e; rc = mutt_do_pager(desc, mutt_b2s(pagerfile), MUTT_PAGER_ATTACHMENT | (is_message ? MUTT_PAGER_MESSAGE : MUTT_PAGER_NO_FLAGS), &info); mutt_buffer_reset(pagerfile); } else rc = 0; return_error: if (!entry || !entry->xneomuttkeep) { if (fp && !mutt_buffer_is_empty(tmpfile)) { /* add temporary file to TempAttachmentsList to be deleted on timeout hook */ mutt_add_temp_attachment(mutt_b2s(tmpfile)); } else if (unlink_tempfile) { unlink(mutt_b2s(tmpfile)); } } mailcap_entry_free(&entry); if (!mutt_buffer_is_empty(pagerfile)) mutt_file_unlink(mutt_b2s(pagerfile)); mutt_buffer_pool_release(&tmpfile); mutt_buffer_pool_release(&pagerfile); mutt_buffer_pool_release(&cmd); return rc; } /** * mutt_pipe_attachment - Pipe an attachment to a command * @param fp File to pipe into the command * @param b Attachment * @param path Path to command * @param outfile File to save output to * @retval 1 Success * @retval 0 Error */ int mutt_pipe_attachment(FILE *fp, struct Body *b, const char *path, char *outfile) { pid_t pid; int out = -1; int rc = 0; if (outfile && *outfile) { out = mutt_file_open(outfile, O_CREAT | O_EXCL | O_WRONLY); if (out < 0) { mutt_perror("open"); return 0; } } mutt_endwin(); if (fp) { /* recv case */ struct State s = { 0 }; /* perform charset conversion on text attachments when piping */ s.flags = MUTT_CHARCONV; if (outfile && *outfile) pid = mutt_create_filter_fd(path, &s.fp_out, NULL, NULL, -1, out, -1); else pid = mutt_create_filter(path, &s.fp_out, NULL, NULL); if (pid < 0) { mutt_perror(_("Can't create filter")); goto bail; } s.fp_in = fp; mutt_decode_attachment(b, &s); mutt_file_fclose(&s.fp_out); } else { /* send case */ FILE *fp_in = fopen(b->filename, "r"); if (!fp_in) { mutt_perror("fopen"); if (outfile && *outfile) { close(out); unlink(outfile); } return 0; } FILE *fp_out = NULL; if (outfile && *outfile) pid = mutt_create_filter_fd(path, &fp_out, NULL, NULL, -1, out, -1); else pid = mutt_create_filter(path, &fp_out, NULL, NULL); if (pid < 0) { mutt_perror(_("Can't create filter")); mutt_file_fclose(&fp_in); goto bail; } mutt_file_copy_stream(fp_in, fp_out); mutt_file_fclose(&fp_out); mutt_file_fclose(&fp_in); } rc = 1; bail: if (outfile && *outfile) close(out); /* check for error exit from child process */ if (mutt_wait_filter(pid) != 0) rc = 0; if ((rc == 0) || C_WaitKey) mutt_any_key_to_continue(NULL); return rc; } /** * save_attachment_open - Open a file to write an attachment to * @param path Path to file to open * @param opt Save option, see #SaveAttach * @retval ptr File handle to attachment file */ static FILE *save_attachment_open(const char *path, enum SaveAttach opt) { if (opt == MUTT_SAVE_APPEND) return fopen(path, "a"); if (opt == MUTT_SAVE_OVERWRITE) return fopen(path, "w"); return mutt_file_fopen(path, "w"); } /** * mutt_save_attachment - Save an attachment * @param fp Source file stream. Can be NULL * @param m Email Body * @param path Where to save the attachment * @param opt Save option, see #SaveAttach * @param e Current Email. Can be NULL * @retval 0 Success * @retval -1 Error */ int mutt_save_attachment(FILE *fp, struct Body *m, const char *path, enum SaveAttach opt, struct Email *e) { if (!m) return -1; if (fp) { /* recv mode */ if (e && m->email && (m->encoding != ENC_BASE64) && (m->encoding != ENC_QUOTED_PRINTABLE) && mutt_is_message_type(m->type, m->subtype)) { /* message type attachments are written to mail folders. */ char buf[8192]; struct Message *msg = NULL; CopyHeaderFlags chflags = CH_NO_FLAGS; int rc = -1; struct Email *e_new = m->email; e_new->msgno = e->msgno; /* required for MH/maildir */ e_new->read = true; if (fseeko(fp, m->offset, SEEK_SET) < 0) return -1; if (!fgets(buf, sizeof(buf), fp)) return -1; struct Mailbox *m_att = mx_path_resolve(path); struct Context *ctx = mx_mbox_open(m_att, MUTT_APPEND | MUTT_QUIET); if (!ctx) { mailbox_free(&m_att); return -1; } msg = mx_msg_open_new(ctx->mailbox, e_new, is_from(buf, NULL, 0, NULL) ? MUTT_MSG_NO_FLAGS : MUTT_ADD_FROM); if (!msg) { mx_mbox_close(&ctx); return -1; } if ((ctx->mailbox->magic == MUTT_MBOX) || (ctx->mailbox->magic == MUTT_MMDF)) chflags = CH_FROM | CH_UPDATE_LEN; chflags |= ((ctx->mailbox->magic == MUTT_MAILDIR) ? CH_NOSTATUS : CH_UPDATE); if ((mutt_copy_message_fp(msg->fp, fp, e_new, MUTT_CM_NO_FLAGS, chflags, 0) == 0) && (mx_msg_commit(ctx->mailbox, msg) == 0)) { rc = 0; } else { rc = -1; } mx_msg_close(ctx->mailbox, &msg); mx_mbox_close(&ctx); return rc; } else { /* In recv mode, extract from folder and decode */ struct State s = { 0 }; s.fp_out = save_attachment_open(path, opt); if (!s.fp_out) { mutt_perror("fopen"); return -1; } fseeko((s.fp_in = fp), m->offset, SEEK_SET); mutt_decode_attachment(m, &s); if (mutt_file_fsync_close(&s.fp_out) != 0) { mutt_perror("fclose"); return -1; } } } else { if (!m->filename) return -1; /* In send mode, just copy file */ FILE *fp_old = fopen(m->filename, "r"); if (!fp_old) { mutt_perror("fopen"); return -1; } FILE *fp_new = save_attachment_open(path, opt); if (!fp_new) { mutt_perror("fopen"); mutt_file_fclose(&fp_old); return -1; } if (mutt_file_copy_stream(fp_old, fp_new) == -1) { mutt_error(_("Write fault")); mutt_file_fclose(&fp_old); mutt_file_fclose(&fp_new); return -1; } mutt_file_fclose(&fp_old); if (mutt_file_fsync_close(&fp_new) != 0) { mutt_error(_("Write fault")); return -1; } } return 0; } /** * mutt_decode_save_attachment - Decode, then save an attachment * @param fp File to read from (OPTIONAL) * @param m Attachment * @param path Path to save the Attachment to * @param displaying Flags, e.g. #MUTT_DISPLAY * @param opt Save option, see #SaveAttach * @retval 0 Success * @retval -1 Error */ int mutt_decode_save_attachment(FILE *fp, struct Body *m, const char *path, int displaying, enum SaveAttach opt) { struct State s = { 0 }; unsigned int saved_encoding = 0; struct Body *saved_parts = NULL; struct Email *e_saved = NULL; int rc = 0; s.flags = displaying; if (opt == MUTT_SAVE_APPEND) s.fp_out = fopen(path, "a"); else if (opt == MUTT_SAVE_OVERWRITE) s.fp_out = fopen(path, "w"); else s.fp_out = mutt_file_fopen(path, "w"); if (!s.fp_out) { mutt_perror("fopen"); return -1; } if (!fp) { /* When called from the compose menu, the attachment isn't parsed, * so we need to do it here. */ struct stat st; if (stat(m->filename, &st) == -1) { mutt_perror("stat"); mutt_file_fclose(&s.fp_out); return -1; } s.fp_in = fopen(m->filename, "r"); if (!s.fp_in) { mutt_perror("fopen"); return -1; } saved_encoding = m->encoding; if (!is_multipart(m)) m->encoding = ENC_8BIT; m->length = st.st_size; m->offset = 0; saved_parts = m->parts; e_saved = m->email; mutt_parse_part(s.fp_in, m); if (m->noconv || is_multipart(m)) s.flags |= MUTT_CHARCONV; } else { s.fp_in = fp; s.flags |= MUTT_CHARCONV; } mutt_body_handler(m, &s); if (mutt_file_fsync_close(&s.fp_out) != 0) { mutt_perror("fclose"); rc = -1; } if (!fp) { m->length = 0; m->encoding = saved_encoding; if (saved_parts) { email_free(&m->email); m->parts = saved_parts; m->email = e_saved; } mutt_file_fclose(&s.fp_in); } return rc; } /** * mutt_print_attachment - Print out an attachment * @param fp File to write to * @param a Attachment * @retval 1 Success * @retval 0 Error * * Ok, the difference between send and receive: * recv: Body->filename is a suggested name, and Mailbox|Email points * to the attachment in mailbox which is encoded * send: Body->filename points to the un-encoded file which contains the * attachment */ int mutt_print_attachment(FILE *fp, struct Body *a) { char type[256]; pid_t pid; FILE *fp_in = NULL, *fp_out = NULL; bool unlink_newfile = false; struct Buffer *newfile = mutt_buffer_pool_get(); struct Buffer *cmd = mutt_buffer_pool_get(); int rc = 0; snprintf(type, sizeof(type), "%s/%s", TYPE(a), a->subtype); if (mailcap_lookup(a, type, NULL, MUTT_MC_PRINT)) { int piped = false; mutt_debug(LL_DEBUG2, "Using mailcap\n"); struct MailcapEntry *entry = mailcap_entry_new(); mailcap_lookup(a, type, entry, MUTT_MC_PRINT); mailcap_expand_filename(entry->nametemplate, a->filename, newfile); /* send mode: symlink from existing file to the newfile */ if (!fp) { if (mutt_file_symlink(a->filename, mutt_b2s(newfile)) == -1) { if (mutt_yesorno(_("Can't match 'nametemplate', continue?"), MUTT_YES) != MUTT_YES) goto mailcap_cleanup; mutt_buffer_strcpy(newfile, a->filename); } else unlink_newfile = true; } /* in recv mode, save file to newfile first */ else { if (mutt_save_attachment(fp, a, mutt_b2s(newfile), 0, NULL) == -1) goto mailcap_cleanup; } mutt_buffer_strcpy(cmd, entry->printcommand); piped = mailcap_expand_command(a, mutt_b2s(newfile), type, cmd); mutt_endwin(); /* interactive program */ if (piped) { fp_in = fopen(mutt_b2s(newfile), "r"); if (!fp_in) { mutt_perror("fopen"); mailcap_entry_free(&entry); goto mailcap_cleanup; } pid = mutt_create_filter(mutt_b2s(cmd), &fp_out, NULL, NULL); if (pid < 0) { mutt_perror(_("Can't create filter")); mailcap_entry_free(&entry); mutt_file_fclose(&fp_in); goto mailcap_cleanup; } mutt_file_copy_stream(fp_in, fp_out); mutt_file_fclose(&fp_out); mutt_file_fclose(&fp_in); if (mutt_wait_filter(pid) || C_WaitKey) mutt_any_key_to_continue(NULL); } else { int rc2 = mutt_system(mutt_b2s(cmd)); if (rc2 == -1) mutt_debug(LL_DEBUG1, "Error running \"%s\"", cmd->data); if ((rc2 != 0) || C_WaitKey) mutt_any_key_to_continue(NULL); } rc = 1; mailcap_cleanup: if (fp) mutt_file_unlink(mutt_b2s(newfile)); else if (unlink_newfile) unlink(mutt_b2s(newfile)); mailcap_entry_free(&entry); goto out; } if ((mutt_str_strcasecmp("text/plain", type) == 0) || (mutt_str_strcasecmp("application/postscript", type) == 0)) { rc = (mutt_pipe_attachment(fp, a, NONULL(C_PrintCommand), NULL)); goto out; } else if (mutt_can_decode(a)) { /* decode and print */ fp_in = NULL; fp_out = NULL; mutt_buffer_mktemp(newfile); if (mutt_decode_save_attachment(fp, a, mutt_b2s(newfile), MUTT_PRINTING, 0) == 0) { mutt_debug(LL_DEBUG2, "successfully decoded %s type attachment to %s\n", type, mutt_b2s(newfile)); fp_in = fopen(mutt_b2s(newfile), "r"); if (!fp_in) { mutt_perror("fopen"); goto decode_cleanup; } mutt_debug(LL_DEBUG2, "successfully opened %s read-only\n", mutt_b2s(newfile)); mutt_endwin(); pid = mutt_create_filter(NONULL(C_PrintCommand), &fp_out, NULL, NULL); if (pid < 0) { mutt_perror(_("Can't create filter")); goto decode_cleanup; } mutt_debug(LL_DEBUG2, "Filter created\n"); mutt_file_copy_stream(fp_in, fp_out); mutt_file_fclose(&fp_out); mutt_file_fclose(&fp_in); if ((mutt_wait_filter(pid) != 0) || C_WaitKey) mutt_any_key_to_continue(NULL); rc = 1; } decode_cleanup: mutt_file_fclose(&fp_in); mutt_file_fclose(&fp_out); mutt_file_unlink(mutt_b2s(newfile)); } else { mutt_error(_("I don't know how to print that")); rc = 0; } out: mutt_buffer_pool_release(&newfile); mutt_buffer_pool_release(&cmd); return rc; } /** * mutt_add_temp_attachment - Add file to list of temporary attachments * @param filename filename with full path */ void mutt_add_temp_attachment(const char *filename) { mutt_list_insert_tail(&TempAttachmentsList, mutt_str_strdup(filename)); } /** * mutt_unlink_temp_attachments - Delete all temporary attachments */ void mutt_unlink_temp_attachments(void) { struct ListNode *np = NULL; STAILQ_FOREACH(np, &TempAttachmentsList, entries) { mutt_file_chmod_add(np->data, S_IWUSR); mutt_file_unlink(np->data); } mutt_list_free(&TempAttachmentsList); }