/* * Copyright (C) 1996-2000,2002,2007,2010 Michael R. Elkins * Copyright (C) 1999-2006 Thomas Roessler * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #if HAVE_CONFIG_H # include "config.h" #endif #include "mutt.h" #include "mutt_curses.h" #include "mutt_menu.h" #include "rfc1524.h" #include "mime.h" #include "mailbox.h" #include "attach.h" #include "mapping.h" #include "mx.h" #include "mutt_crypt.h" #include "rfc3676.h" #include #include #include #include #include #include #include static void mutt_update_recvattach_menu (ATTACH_CONTEXT *actx, MUTTMENU *menu, int init); static const char *Mailbox_is_read_only = N_("Mailbox is read-only."); #define CHECK_READONLY \ if (Context->readonly) \ { \ mutt_flushinp (); \ mutt_error _(Mailbox_is_read_only); \ break; \ } #define CURATTACH actx->idx[actx->v2r[menu->current]] static const struct mapping_t AttachHelp[] = { { N_("Exit"), OP_EXIT }, { N_("Save"), OP_SAVE }, { N_("Pipe"), OP_PIPE }, { N_("Print"), OP_PRINT }, { N_("Help"), OP_HELP }, { NULL, 0 } }; static void mutt_update_v2r (ATTACH_CONTEXT *actx) { int vindex, rindex, curlevel; vindex = rindex = 0; while (rindex < actx->idxlen) { actx->v2r[vindex++] = rindex; if (actx->idx[rindex]->content->collapsed) { curlevel = actx->idx[rindex]->level; do rindex++; while ((rindex < actx->idxlen) && (actx->idx[rindex]->level > curlevel)); } else rindex++; } actx->vcount = vindex; } void mutt_update_tree (ATTACH_CONTEXT *actx) { char buf[STRING]; char *s; int rindex, vindex; mutt_update_v2r (actx); for (vindex = 0; vindex < actx->vcount; vindex++) { rindex = actx->v2r[vindex]; actx->idx[rindex]->num = vindex; if (2 * (actx->idx[rindex]->level + 2) < sizeof (buf)) { if (actx->idx[rindex]->level) { s = buf + 2 * (actx->idx[rindex]->level - 1); *s++ = (actx->idx[rindex]->content->next) ? MUTT_TREE_LTEE : MUTT_TREE_LLCORNER; *s++ = MUTT_TREE_HLINE; *s++ = MUTT_TREE_RARROW; } else s = buf; *s = 0; } if (actx->idx[rindex]->tree) { if (mutt_strcmp (actx->idx[rindex]->tree, buf) != 0) mutt_str_replace (&actx->idx[rindex]->tree, buf); } else actx->idx[rindex]->tree = safe_strdup (buf); if (2 * (actx->idx[rindex]->level + 2) < sizeof (buf) && actx->idx[rindex]->level) { s = buf + 2 * (actx->idx[rindex]->level - 1); *s++ = (actx->idx[rindex]->content->next) ? '\005' : '\006'; *s++ = '\006'; } } } /* %c = character set: convert? * %C = character set * %D = deleted flag * %d = description * %e = MIME content-transfer-encoding * %F = filename for content-disposition header * %f = filename * %I = content-disposition, either I (inline) or A (attachment) * %t = tagged flag * %T = tree chars * %m = major MIME type * %M = MIME subtype * %n = attachment number * %s = size * %u = unlink */ const char *mutt_attach_fmt (char *dest, size_t destlen, size_t col, int cols, char op, const char *src, const char *prefix, const char *ifstring, const char *elsestring, void *data, format_flag flags) { char fmt[16]; char tmp[SHORT_STRING]; char charset[SHORT_STRING]; ATTACHPTR *aptr = (ATTACHPTR *) data; int optional = (flags & MUTT_FORMAT_OPTIONAL); size_t l; switch (op) { case 'C': if (!optional) { if (mutt_is_text_part (aptr->content) && mutt_get_body_charset (charset, sizeof (charset), aptr->content)) mutt_format_s (dest, destlen, prefix, charset); else mutt_format_s (dest, destlen, prefix, ""); } else if (!mutt_is_text_part (aptr->content) || !mutt_get_body_charset (charset, sizeof (charset), aptr->content)) optional = 0; break; case 'c': /* XXX */ if (!optional) { snprintf (fmt, sizeof (fmt), "%%%sc", prefix); snprintf (dest, destlen, fmt, aptr->content->type != TYPETEXT || aptr->content->noconv ? 'n' : 'c'); } else if (aptr->content->type != TYPETEXT || aptr->content->noconv) optional = 0; break; case 'd': if (!optional) { if (aptr->content->description) { mutt_format_s (dest, destlen, prefix, aptr->content->description); break; } if (mutt_is_message_type(aptr->content->type, aptr->content->subtype) && MsgFmt && aptr->content->hdr) { char s[SHORT_STRING]; _mutt_make_string (s, sizeof (s), MsgFmt, NULL, aptr->content->hdr, MUTT_FORMAT_FORCESUBJ | MUTT_FORMAT_ARROWCURSOR); if (*s) { mutt_format_s (dest, destlen, prefix, s); break; } } if (!aptr->content->d_filename && !aptr->content->filename) { mutt_format_s (dest, destlen, prefix, ""); break; } } else if (aptr->content->description || (mutt_is_message_type (aptr->content->type, aptr->content->subtype) && MsgFmt && aptr->content->hdr)) break; /* fall through */ case 'F': if (!optional) { if (aptr->content->d_filename) { mutt_format_s (dest, destlen, prefix, aptr->content->d_filename); break; } } else if (!aptr->content->d_filename && !aptr->content->filename) { optional = 0; break; } /* fall through */ case 'f': if (!optional) { if (aptr->content->filename && *aptr->content->filename == '/') { BUFFER *path; path = mutt_buffer_pool_get (); mutt_buffer_strcpy (path, aptr->content->filename); mutt_buffer_pretty_mailbox (path); mutt_format_s (dest, destlen, prefix, mutt_b2s (path)); mutt_buffer_pool_release (&path); } else mutt_format_s (dest, destlen, prefix, NONULL (aptr->content->filename)); } else if (!aptr->content->filename) optional = 0; break; case 'D': if (!optional) snprintf (dest, destlen, "%c", aptr->content->deleted ? 'D' : ' '); else if (!aptr->content->deleted) optional = 0; break; case 'e': if (!optional) mutt_format_s (dest, destlen, prefix, ENCODING (aptr->content->encoding)); break; case 'I': if (!optional) { const char dispchar[] = { 'I', 'A', 'F', '-' }; char ch; if (aptr->content->disposition < sizeof(dispchar)) ch = dispchar[aptr->content->disposition]; else { dprint(1, (debugfile, "ERROR: invalid content-disposition %d\n", aptr->content->disposition)); ch = '!'; } snprintf (dest, destlen, "%c", ch); } break; case 'm': if (!optional) mutt_format_s (dest, destlen, prefix, TYPE (aptr->content)); break; case 'M': if (!optional) mutt_format_s (dest, destlen, prefix, aptr->content->subtype); else if (!aptr->content->subtype) optional = 0; break; case 'n': if (!optional) { snprintf (fmt, sizeof (fmt), "%%%sd", prefix); snprintf (dest, destlen, fmt, aptr->num + 1); } break; case 'Q': if (optional) optional = aptr->content->attach_qualifies; else { snprintf (fmt, sizeof (fmt), "%%%sc", prefix); mutt_format_s (dest, destlen, fmt, "Q"); } break; case 's': if (flags & MUTT_FORMAT_STAT_FILE) { struct stat st; stat (aptr->content->filename, &st); l = st.st_size; } else l = aptr->content->length; if (!optional) { mutt_pretty_size (tmp, sizeof(tmp), l); mutt_format_s (dest, destlen, prefix, tmp); } else if (l == 0) optional = 0; break; case 't': if (!optional) snprintf (dest, destlen, "%c", aptr->content->tagged ? '*' : ' '); else if (!aptr->content->tagged) optional = 0; break; case 'T': if (!optional) mutt_format_s_tree (dest, destlen, prefix, NONULL (aptr->tree)); else if (!aptr->tree) optional = 0; break; case 'u': if (!optional) snprintf (dest, destlen, "%c", aptr->content->unlink ? '-' : ' '); else if (!aptr->content->unlink) optional = 0; break; case 'X': if (optional) optional = (aptr->content->attach_count + aptr->content->attach_qualifies) != 0; else { snprintf (fmt, sizeof (fmt), "%%%sd", prefix); snprintf (dest, destlen, fmt, aptr->content->attach_count + aptr->content->attach_qualifies); } break; default: *dest = 0; } if (optional) mutt_FormatString (dest, destlen, col, cols, ifstring, mutt_attach_fmt, data, 0); else if (flags & MUTT_FORMAT_OPTIONAL) mutt_FormatString (dest, destlen, col, cols, elsestring, mutt_attach_fmt, data, 0); return (src); } static void attach_entry (char *b, size_t blen, MUTTMENU *menu, int num) { ATTACH_CONTEXT *actx = (ATTACH_CONTEXT *)menu->data; mutt_FormatString (b, blen, 0, MuttIndexWindow->cols, NONULL (AttachFormat), mutt_attach_fmt, actx->idx[actx->v2r[num]], MUTT_FORMAT_ARROWCURSOR); } int mutt_tag_attach (MUTTMENU *menu, int n, int m) { ATTACH_CONTEXT *actx = (ATTACH_CONTEXT *)menu->data; BODY *cur = actx->idx[actx->v2r[n]]->content; int ot = cur->tagged; cur->tagged = (m >= 0 ? m : !cur->tagged); return cur->tagged - ot; } int mutt_is_message_type (int type, const char *subtype) { if (type != TYPEMESSAGE) return 0; subtype = NONULL(subtype); return (ascii_strcasecmp (subtype, "rfc822") == 0 || ascii_strcasecmp (subtype, "news") == 0 || ascii_strcasecmp (subtype, "global") == 0); } /* * This prepends "./" to attachment names that start with a special character, * to prevent mutt_expand_path() from expanding and saving the attachment * in an unexpected location. */ static void prepend_curdir (BUFFER *dst) { BUFFER *tmp = NULL; if (!dst || !mutt_buffer_len (dst)) return; if (!strchr ("~=+@<>!-^", *dst->data)) return; tmp = mutt_buffer_pool_get (); mutt_buffer_addstr (tmp, "./"); mutt_buffer_addstr (tmp, mutt_b2s (dst)); mutt_buffer_strcpy (dst, mutt_b2s (tmp)); mutt_buffer_pool_release (&tmp); } /* This is a proxy between the mutt_save_attachment_list() calls and * mutt_save_attachment(). It exists to support unstuffing * format=flowed text attachments, and for quadoption prompting * charset conversion of text attachments. * * Direct modification of mutt_save_attachment() wasn't easily possible * because: * 1) other callers of mutt_save_attachment() should not have unstuffing * performed, such as replying/forwarding attachments. * 2) the attachment saving can append to a file, making the * unstuffing inside difficult with current functions. * 3) we can't unstuff before-hand because decoding hasn't occurred. * * So, I apologize for this horrific proxy, but it was the most * straightforward method. * * Returns: * 0 on success * -1 on an error inside mutt_save_attachment() * -2 if the convert prompt is aborted. */ static int save_attachment_helper (FILE *fp, BODY *m, const char *path, int flags, HEADER *hdr) { int rc = -1, charset_conv = 0; BUFFER *prompt; if (mutt_is_text_part (m)) { const char *charset; char fromcode[SHORT_STRING]; char tocode[SHORT_STRING]; charset = mutt_get_parameter ("charset", m->parameter); if (!charset && AssumedCharset) charset = mutt_get_default_charset (); if (charset && Charset) { mutt_canonical_charset (fromcode, sizeof (fromcode), charset); mutt_canonical_charset (tocode, sizeof (tocode), Charset); if (mutt_strcmp (fromcode, tocode) && /* don't prompt converting ascii to extended-ascii: */ (mutt_strcmp (fromcode, "us-ascii") || (mutt_strcmp (tocode, "utf-8") && mutt_strncmp (tocode, "iso-8859-", 9)))) { prompt = mutt_buffer_pool_get (); mutt_buffer_printf (prompt, /* L10N: Prompt to convert text attachments from the sent charset to the local $charset when saving them in the receive attachment menu. */ _("Convert attachment from %s to %s?"), fromcode, tocode); charset_conv = query_quadoption (OPT_ATTACH_SAVE_CHARCONV, mutt_b2s (prompt)); mutt_buffer_pool_release (&prompt); if (charset_conv < 0) return -2; } } } if (mutt_rfc3676_is_format_flowed (m)) { BUFFER *tempfile; BODY fakebody; tempfile = mutt_buffer_pool_get (); mutt_buffer_mktemp (tempfile); /* Pass flags=0 to force safe_fopen("w") */ rc = mutt_save_attachment (fp, m, mutt_b2s (tempfile), 0, hdr, charset_conv); if (rc) goto cleanup; mutt_rfc3676_space_unstuff_attachment (m, mutt_b2s (tempfile)); /* Now "really" save it. Send mode does this without touching anything, * so force send-mode. */ memset (&fakebody, 0, sizeof (BODY)); fakebody.filename = tempfile->data; rc = mutt_save_attachment (NULL, &fakebody, path, flags, hdr, 0); mutt_unlink (mutt_b2s (tempfile)); cleanup: mutt_buffer_pool_release (&tempfile); } else { rc = mutt_save_attachment (fp, m, path, flags, hdr, charset_conv); } return rc; } static int mutt_query_save_attachment (FILE *fp, BODY *body, HEADER *hdr, char **directory) { char *prompt; BUFFER *buf = NULL, *tfile = NULL; int is_message; int append = 0; int rc = -1; buf = mutt_buffer_pool_get (); tfile = mutt_buffer_pool_get (); if (body->filename) { if (directory && *directory) mutt_buffer_concat_path (buf, *directory, mutt_basename (body->filename)); else mutt_buffer_strcpy (buf, body->filename); } else if (body->hdr && body->encoding != ENCBASE64 && body->encoding != ENCQUOTEDPRINTABLE && mutt_is_message_type(body->type, body->subtype)) { mutt_default_save (buf->data, buf->dsize, body->hdr); mutt_buffer_fix_dptr (buf); } prepend_curdir (buf); prompt = _("Save to file: "); while (prompt) { if ((mutt_buffer_get_field (prompt, buf, MUTT_FILE | MUTT_CLEAR) != 0) || !mutt_buffer_len (buf)) goto cleanup; prompt = NULL; mutt_buffer_expand_path (buf); is_message = (fp && body->hdr && body->encoding != ENCBASE64 && body->encoding != ENCQUOTEDPRINTABLE && mutt_is_message_type (body->type, body->subtype)); if (is_message) { struct stat st; /* check to make sure that this file is really the one the user wants */ if ((rc = mutt_save_confirm (mutt_b2s (buf), &st)) == 1) { prompt = _("Save to file: "); continue; } else if (rc == -1) goto cleanup; mutt_buffer_strcpy (tfile, mutt_b2s (buf)); } else { if ((rc = mutt_check_overwrite (body->filename, mutt_b2s (buf), tfile, &append, directory)) == -1) goto cleanup; else if (rc == 1) { prompt = _("Save to file: "); continue; } } mutt_message _("Saving..."); if ((rc = save_attachment_helper (fp, body, mutt_b2s (tfile), append, (hdr || !is_message) ? hdr : body->hdr)) == 0) { mutt_message _("Attachment saved."); goto cleanup; } /* charset convert prompt abort: */ else if (rc == -2) { rc = -1; goto cleanup; } /* some kind of save_attachment failure: */ else { prompt = _("Save to file: "); continue; } } cleanup: mutt_buffer_pool_release (&buf); mutt_buffer_pool_release (&tfile); return rc; } void mutt_save_attachment_list (ATTACH_CONTEXT *actx, FILE *fp, int tag, BODY *top, HEADER *hdr, MUTTMENU *menu) { BUFFER *buf = NULL, *tfile = NULL, *orig_cwd = NULL; char *directory = NULL; int i, rc = 1, restore_cwd = 0; int last = menu ? menu->current : -1; FILE *fpout; buf = mutt_buffer_pool_get (); tfile = mutt_buffer_pool_get (); if (AttachSaveDir) { orig_cwd = mutt_buffer_pool_get (); mutt_getcwd (orig_cwd); if (chdir (AttachSaveDir) == 0) restore_cwd = 1; else { struct stat sb; char msg[STRING]; if (stat (AttachSaveDir, &sb) == -1 && errno == ENOENT) { snprintf (msg, sizeof (msg), _("%s does not exist. Create it?"), AttachSaveDir); if (mutt_yesorno (msg, MUTT_YES) == MUTT_YES) { if (mutt_mkdir (AttachSaveDir, 0700) != 0) { mutt_error ( _("Can't create %s: %s."), AttachSaveDir, strerror (errno)); mutt_sleep (1); } else if (chdir (AttachSaveDir) == 0) restore_cwd = 1; } } } if (!restore_cwd) { /* L10N: Printed if the value of $attach_save_dir can not be chdir'ed to before saving. This could be a permission issue, or if the value doesn't point to a directory, or the value didn't exist but couldn't be created. */ mutt_error (_("Unable to save attachments to %s. Using cwd"), AttachSaveDir); mutt_sleep (1); } } for (i = 0; !tag || i < actx->idxlen; i++) { if (tag) { fp = actx->idx[i]->fp; top = actx->idx[i]->content; } if (!tag || top->tagged) { if (!option (OPTATTACHSPLIT)) { int append = MUTT_SAVE_APPEND; if (!mutt_buffer_len (buf)) { append = 0; mutt_buffer_strcpy (buf, mutt_basename (NONULL (top->filename))); prepend_curdir (buf); if ((mutt_buffer_get_field (_("Save to file: "), buf, MUTT_FILE | MUTT_CLEAR) != 0) || !mutt_buffer_len (buf)) goto cleanup; mutt_buffer_expand_path (buf); if (mutt_check_overwrite (top->filename, mutt_b2s (buf), tfile, &append, NULL)) goto cleanup; } rc = save_attachment_helper (fp, top, mutt_b2s (tfile), append, hdr); if (rc < 0) goto cleanup; if (AttachSep && (fpout = fopen (mutt_b2s (tfile), "a")) != NULL) { fprintf(fpout, "%s", AttachSep); safe_fclose (&fpout); } } else { if (tag && menu && top->aptr) { menu->oldcurrent = menu->current; menu->current = top->aptr->num; menu_check_recenter (menu); menu->redraw |= REDRAW_MOTION; menu_redraw (menu); } if (mutt_query_save_attachment (fp, top, hdr, &directory) < 0) break; } } if (!tag) break; } FREE (&directory); if (tag && menu) { menu->oldcurrent = menu->current; menu->current = last; menu_check_recenter (menu); menu->redraw |= REDRAW_MOTION; } if (!option (OPTATTACHSPLIT) && (rc == 0)) mutt_message _("Attachment saved."); cleanup: /* if restore_cwd is set, the orig_cwd buffer is non-null. However, * the getcwd() could have failed, so check it has a value. */ if (restore_cwd && mutt_buffer_len (orig_cwd)) chdir (mutt_b2s (orig_cwd)); mutt_buffer_pool_release (&buf); mutt_buffer_pool_release (&tfile); mutt_buffer_pool_release (&orig_cwd); } static void mutt_query_pipe_attachment (const char *command, FILE *fp, BODY *body, int filter) { BUFFER *tfile = NULL, *warning = NULL; tfile = mutt_buffer_pool_get (); warning = mutt_buffer_pool_get (); if (filter) { mutt_buffer_printf (warning, _("WARNING! You are about to overwrite %s, continue?"), body->filename); if (mutt_yesorno (mutt_b2s (warning), MUTT_NO) != MUTT_YES) { mutt_window_clearline (MuttMessageWindow, 0); goto cleanup; } mutt_buffer_mktemp (tfile); } if (mutt_pipe_attachment (fp, body, command, mutt_b2s (tfile))) { if (filter) { mutt_unlink (body->filename); mutt_rename_file (mutt_b2s (tfile), body->filename); mutt_update_encoding (body); mutt_message _("Attachment filtered."); } } else { if (filter && mutt_buffer_len (tfile)) mutt_unlink (mutt_b2s (tfile)); } cleanup: mutt_buffer_pool_release (&tfile); mutt_buffer_pool_release (&warning); } static void pipe_attachment (FILE *fp, BODY *b, STATE *state) { FILE *unstuff_fp = NULL, *ifp = NULL; int is_flowed = 0, unlink_unstuff = 0; BUFFER *unstuff_tempfile = NULL; if (mutt_rfc3676_is_format_flowed (b)) { is_flowed = 1; unstuff_tempfile = mutt_buffer_pool_get (); mutt_buffer_mktemp (unstuff_tempfile); } if (fp) { state->fpin = fp; if (is_flowed) { FILE *filter_fp; unstuff_fp = safe_fopen (mutt_b2s (unstuff_tempfile), "w"); if (unstuff_fp == NULL) { mutt_perror ("safe_fopen"); goto bail; } unlink_unstuff = 1; filter_fp = state->fpout; state->fpout = unstuff_fp; mutt_decode_attachment (b, state); safe_fclose (&unstuff_fp); state->fpout = filter_fp; unstuff_fp = safe_fopen (mutt_b2s (unstuff_tempfile), "r"); if (unstuff_fp == NULL) { mutt_perror ("safe_fopen"); goto bail; } mutt_copy_stream (unstuff_fp, filter_fp); safe_fclose (&unstuff_fp); } else mutt_decode_attachment (b, state); } else { const char *infile; if (is_flowed) { if (mutt_save_attachment (fp, b, mutt_b2s (unstuff_tempfile), 0, NULL, 0) == -1) goto bail; unlink_unstuff = 1; mutt_rfc3676_space_unstuff_attachment (b, mutt_b2s (unstuff_tempfile)); infile = mutt_b2s (unstuff_tempfile); } else infile = b->filename; if ((ifp = fopen (infile, "r")) == NULL) { mutt_perror ("fopen"); goto bail; } mutt_copy_stream (ifp, state->fpout); safe_fclose (&ifp); } if (AttachSep) state_puts (AttachSep, state); bail: safe_fclose (&unstuff_fp); safe_fclose (&ifp); if (unlink_unstuff) mutt_unlink (mutt_b2s (unstuff_tempfile)); mutt_buffer_pool_release (&unstuff_tempfile); } static void pipe_attachment_list (const char *command, ATTACH_CONTEXT *actx, FILE *fp, int tag, BODY *top, int filter, STATE *state) { int i; for (i = 0; !tag || i < actx->idxlen; i++) { if (tag) { fp = actx->idx[i]->fp; top = actx->idx[i]->content; } if (!tag || top->tagged) { if (!filter && !option (OPTATTACHSPLIT)) pipe_attachment (fp, top, state); else mutt_query_pipe_attachment (command, fp, top, filter); } if (!tag) break; } } void mutt_pipe_attachment_list (ATTACH_CONTEXT *actx, FILE *fp, int tag, BODY *top, int filter) { STATE state; BUFFER *buf = NULL; pid_t thepid; if (fp) filter = 0; /* sanity check: we can't filter in the recv case yet */ buf = mutt_buffer_pool_get (); memset (&state, 0, sizeof (STATE)); /* perform charset conversion on text attachments when piping */ state.flags = MUTT_CHARCONV; if (mutt_buffer_get_field ((filter ? _("Filter through: ") : _("Pipe to: ")), buf, MUTT_CMD) != 0) goto cleanup; if (!mutt_buffer_len (buf)) goto cleanup; /* norel because buf is a command */ mutt_buffer_expand_path_norel (buf); if (!filter && !option (OPTATTACHSPLIT)) { mutt_endwin (NULL); thepid = mutt_create_filter (mutt_b2s (buf), &state.fpout, NULL, NULL); pipe_attachment_list (mutt_b2s (buf), actx, fp, tag, top, filter, &state); safe_fclose (&state.fpout); if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY)) mutt_any_key_to_continue (NULL); } else pipe_attachment_list (mutt_b2s (buf), actx, fp, tag, top, filter, &state); cleanup: mutt_buffer_pool_release (&buf); } static int can_print (ATTACH_CONTEXT *actx, BODY *top, int tag) { char type [STRING]; int i; for (i = 0; !tag || i < actx->idxlen; i++) { if (tag) top = actx->idx[i]->content; snprintf (type, sizeof (type), "%s/%s", TYPE (top), top->subtype); if (!tag || top->tagged) { if (!rfc1524_mailcap_lookup (top, type, sizeof(type), NULL, MUTT_PRINT)) { if (ascii_strcasecmp ("text/plain", top->subtype) && ascii_strcasecmp ("application/postscript", top->subtype)) { if (!mutt_can_decode (top)) { mutt_error (_("I don't know how to print %s attachments!"), type); return (0); } } } } if (!tag) break; } return (1); } static void print_attachment_list (ATTACH_CONTEXT *actx, FILE *fp, int tag, BODY *top, STATE *state) { int i; char type [STRING]; for (i = 0; !tag || i < actx->idxlen; i++) { if (tag) { fp = actx->idx[i]->fp; top = actx->idx[i]->content; } if (!tag || top->tagged) { snprintf (type, sizeof (type), "%s/%s", TYPE (top), top->subtype); if (!option (OPTATTACHSPLIT) && !rfc1524_mailcap_lookup (top, type, sizeof(type), NULL, MUTT_PRINT)) { if (!ascii_strcasecmp ("text/plain", top->subtype) || !ascii_strcasecmp ("application/postscript", top->subtype)) pipe_attachment (fp, top, state); else if (mutt_can_decode (top)) { /* decode and print */ BUFFER *newfile = NULL; FILE *ifp; newfile = mutt_buffer_pool_get (); mutt_buffer_mktemp (newfile); if (mutt_decode_save_attachment (fp, top, mutt_b2s (newfile), MUTT_PRINTING, 0) == 0) { if ((ifp = fopen (mutt_b2s (newfile), "r")) != NULL) { mutt_copy_stream (ifp, state->fpout); safe_fclose (&ifp); if (AttachSep) state_puts (AttachSep, state); } } mutt_unlink (mutt_b2s (newfile)); mutt_buffer_pool_release (&newfile); } } else mutt_print_attachment (fp, top); } if (!tag) break; } } void mutt_print_attachment_list (ATTACH_CONTEXT *actx, FILE *fp, int tag, BODY *top) { STATE state; pid_t thepid; if (query_quadoption (OPT_PRINT, tag ? _("Print tagged attachment(s)?") : _("Print attachment?")) != MUTT_YES) return; if (!option (OPTATTACHSPLIT)) { if (!can_print (actx, top, tag)) return; mutt_endwin (NULL); memset (&state, 0, sizeof (STATE)); thepid = mutt_create_filter (NONULL (PrintCmd), &state.fpout, NULL, NULL); print_attachment_list (actx, fp, tag, top, &state); safe_fclose (&state.fpout); if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY)) mutt_any_key_to_continue (NULL); } else print_attachment_list (actx, fp, tag, top, &state); } static void recvattach_extract_pgp_keys (ATTACH_CONTEXT *actx, MUTTMENU *menu) { int i; if (!menu->tagprefix) crypt_pgp_extract_keys_from_attachment_list (CURATTACH->fp, 0, CURATTACH->content); else { for (i = 0; i < actx->idxlen; i++) if (actx->idx[i]->content->tagged) crypt_pgp_extract_keys_from_attachment_list (actx->idx[i]->fp, 0, actx->idx[i]->content); } } static int recvattach_pgp_check_traditional (ATTACH_CONTEXT *actx, MUTTMENU *menu) { int i, rv = 0; if (!menu->tagprefix) rv = crypt_pgp_check_traditional (CURATTACH->fp, CURATTACH->content, 1); else { for (i = 0; i < actx->idxlen; i++) if (actx->idx[i]->content->tagged) rv = rv || crypt_pgp_check_traditional (actx->idx[i]->fp, actx->idx[i]->content, 1); } return rv; } static void recvattach_edit_content_type (ATTACH_CONTEXT *actx, MUTTMENU *menu, HEADER *hdr) { int i; if (mutt_edit_content_type (hdr, CURATTACH->content, CURATTACH->fp) == 1) { /* The mutt_update_recvattach_menu() will overwrite any changes * made to a decrypted CURATTACH->content, so warn the user. */ if (CURATTACH->decrypted) { mutt_message _("Structural changes to decrypted attachments are not supported"); mutt_sleep (1); } /* Editing the content type can rewrite the body structure. */ for (i = 0; i < actx->idxlen; i++) actx->idx[i]->content = NULL; mutt_actx_free_entries (actx); mutt_update_recvattach_menu (actx, menu, 1); } } int mutt_attach_display_loop (MUTTMENU *menu, int op, HEADER *hdr, ATTACH_CONTEXT *actx, int recv) { do { switch (op) { case OP_DISPLAY_HEADERS: toggle_option (OPTWEED); /* fall through */ case OP_VIEW_ATTACH: op = mutt_view_attachment (CURATTACH->fp, CURATTACH->content, MUTT_REGULAR, hdr, actx); break; case OP_NEXT_ENTRY: case OP_MAIN_NEXT_UNDELETED: /* hack */ if (menu->current < menu->max - 1) { menu->current++; op = OP_VIEW_ATTACH; } else op = OP_NULL; break; case OP_PREV_ENTRY: case OP_MAIN_PREV_UNDELETED: /* hack */ if (menu->current > 0) { menu->current--; op = OP_VIEW_ATTACH; } else op = OP_NULL; break; case OP_EDIT_TYPE: /* when we edit the content-type, we should redisplay the attachment immediately */ if (recv) recvattach_edit_content_type (actx, menu, hdr); else mutt_edit_content_type (hdr, CURATTACH->content, CURATTACH->fp); menu->redraw |= REDRAW_INDEX; op = OP_VIEW_ATTACH; break; /* functions which are passed through from the pager */ case OP_CHECK_TRADITIONAL: if (!(WithCrypto & APPLICATION_PGP) || (hdr && hdr->security & PGP_TRADITIONAL_CHECKED)) { op = OP_NULL; break; } /* fall through */ case OP_ATTACH_COLLAPSE: if (recv) return op; /* fall through */ default: op = OP_NULL; } } while (op != OP_NULL); return op; } void mutt_generate_recvattach_list (ATTACH_CONTEXT *actx, HEADER *hdr, BODY *parts, FILE *fp, int parent_type, int level, int decrypted) { ATTACHPTR *new; BODY *m; BODY *new_body = NULL; FILE *new_fp = NULL; int type, need_secured, secured; for (m = parts; m; m = m->next) { need_secured = secured = 0; if ((WithCrypto & APPLICATION_SMIME) && (type = mutt_is_application_smime (m))) { need_secured = 1; if (type & ENCRYPT) { if (!crypt_valid_passphrase (APPLICATION_SMIME)) goto decrypt_failed; if (hdr->env) crypt_smime_getkeys (hdr->env); } secured = !crypt_smime_decrypt_mime (fp, &new_fp, m, &new_body); /* If the decrypt/verify-opaque doesn't generate mime output, an * empty text/plain type will still be returned by * mutt_read_mime_header(). We can't distinguish an actual part * from a failure, so only use a text/plain that results from a single * top-level part. */ if (secured && new_body->type == TYPETEXT && !ascii_strcasecmp ("plain", new_body->subtype) && (parts != m || m->next)) { mutt_free_body (&new_body); safe_fclose (&new_fp); goto decrypt_failed; } if (secured && (type & ENCRYPT)) hdr->security |= SMIMEENCRYPT; } if ((WithCrypto & APPLICATION_PGP) && (mutt_is_multipart_encrypted (m) || mutt_is_malformed_multipart_pgp_encrypted (m))) { need_secured = 1; if (!crypt_valid_passphrase (APPLICATION_PGP)) goto decrypt_failed; secured = !crypt_pgp_decrypt_mime (fp, &new_fp, m, &new_body); if (secured) hdr->security |= PGPENCRYPT; } if (need_secured && secured) { mutt_actx_add_fp (actx, new_fp); mutt_actx_add_body (actx, new_body); mutt_generate_recvattach_list (actx, hdr, new_body, new_fp, parent_type, level, 1); continue; } decrypt_failed: /* Fall through and show the original parts if decryption fails */ if (need_secured && !secured) mutt_error _("Can't decrypt encrypted message!"); /* Strip out the top level multipart */ if (m->type == TYPEMULTIPART && m->parts && !need_secured && (parent_type == -1 && ascii_strcasecmp ("alternative", m->subtype))) { mutt_generate_recvattach_list (actx, hdr, m->parts, fp, m->type, level, decrypted); } else { new = (ATTACHPTR *) safe_calloc (1, sizeof (ATTACHPTR)); mutt_actx_add_attach (actx, new); new->content = m; new->fp = fp; m->aptr = new; new->parent_type = parent_type; new->level = level; new->decrypted = decrypted; if (m->type == TYPEMULTIPART) mutt_generate_recvattach_list (actx, hdr, m->parts, fp, m->type, level + 1, decrypted); else if (mutt_is_message_type (m->type, m->subtype)) { mutt_generate_recvattach_list (actx, m->hdr, m->parts, fp, m->type, level + 1, decrypted); hdr->security |= m->hdr->security; } } } } void mutt_attach_init (ATTACH_CONTEXT *actx) { int i; for (i = 0; i < actx->idxlen; i++) { actx->idx[i]->content->tagged = 0; if (option (OPTDIGESTCOLLAPSE) && actx->idx[i]->content->type == TYPEMULTIPART && !ascii_strcasecmp (actx->idx[i]->content->subtype, "digest")) actx->idx[i]->content->collapsed = 1; else actx->idx[i]->content->collapsed = 0; } } static void mutt_update_recvattach_menu (ATTACH_CONTEXT *actx, MUTTMENU *menu, int init) { if (init) { mutt_generate_recvattach_list (actx, actx->hdr, actx->hdr->content, actx->root_fp, -1, 0, 0); mutt_attach_init (actx); menu->data = actx; } mutt_update_tree (actx); menu->max = actx->vcount; if (menu->current >= menu->max) menu->current = menu->max - 1; menu_check_recenter (menu); menu->redraw |= REDRAW_INDEX; } static void attach_collapse (ATTACH_CONTEXT *actx, MUTTMENU *menu) { int rindex, curlevel; CURATTACH->content->collapsed = !CURATTACH->content->collapsed; /* When expanding, expand all the children too */ if (CURATTACH->content->collapsed) return; curlevel = CURATTACH->level; rindex = actx->v2r[menu->current] + 1; while ((rindex < actx->idxlen) && (actx->idx[rindex]->level > curlevel)) { if (option (OPTDIGESTCOLLAPSE) && actx->idx[rindex]->content->type == TYPEMULTIPART && !ascii_strcasecmp (actx->idx[rindex]->content->subtype, "digest")) actx->idx[rindex]->content->collapsed = 1; else actx->idx[rindex]->content->collapsed = 0; rindex++; } } static const char *Function_not_permitted = N_("Function not permitted in attach-message mode."); #define CHECK_ATTACH \ if (option(OPTATTACHMSG)) \ { \ mutt_flushinp (); \ mutt_error _(Function_not_permitted); \ break; \ } void mutt_view_attachments (HEADER *hdr) { char helpstr[LONG_STRING]; MUTTMENU *menu; BODY *cur = NULL; MESSAGE *msg; ATTACH_CONTEXT *actx; int flags = 0; int op = OP_NULL; int i; /* make sure we have parsed this message */ mutt_parse_mime_message (Context, hdr); mutt_message_hook (Context, hdr, MUTT_MESSAGEHOOK); if ((msg = mx_open_message (Context, hdr->msgno, 0)) == NULL) return; menu = mutt_new_menu (MENU_ATTACH); menu->title = _("Attachments"); menu->make_entry = attach_entry; menu->tag = mutt_tag_attach; menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_ATTACH, AttachHelp); mutt_push_current_menu (menu); actx = safe_calloc (sizeof(ATTACH_CONTEXT), 1); actx->hdr = hdr; actx->root_fp = msg->fp; mutt_update_recvattach_menu (actx, menu, 1); FOREVER { if (op == OP_NULL) op = mutt_menuLoop (menu); switch (op) { case OP_ATTACH_VIEW_MAILCAP: mutt_view_attachment (CURATTACH->fp, CURATTACH->content, MUTT_MAILCAP, hdr, actx); menu->redraw = REDRAW_FULL; break; case OP_ATTACH_VIEW_TEXT: mutt_view_attachment (CURATTACH->fp, CURATTACH->content, MUTT_AS_TEXT, hdr, actx); menu->redraw = REDRAW_FULL; break; case OP_ATTACH_VIEW_PAGER: mutt_view_attachment (CURATTACH->fp, CURATTACH->content, MUTT_VIEW_PAGER, hdr, actx); menu->redraw = REDRAW_FULL; break; case OP_DISPLAY_HEADERS: case OP_VIEW_ATTACH: op = mutt_attach_display_loop (menu, op, hdr, actx, 1); menu->redraw = REDRAW_FULL; continue; case OP_ATTACH_COLLAPSE: if (!CURATTACH->content->parts) { mutt_error _("There are no subparts to show!"); break; } attach_collapse (actx, menu); mutt_update_recvattach_menu (actx, menu, 0); break; case OP_FORGET_PASSPHRASE: crypt_forget_passphrase (); break; case OP_EXTRACT_KEYS: if ((WithCrypto & APPLICATION_PGP)) { recvattach_extract_pgp_keys (actx, menu); menu->redraw = REDRAW_FULL; } break; case OP_CHECK_TRADITIONAL: if ((WithCrypto & APPLICATION_PGP) && recvattach_pgp_check_traditional (actx, menu)) { hdr->security = crypt_query (cur); menu->redraw = REDRAW_FULL; } break; case OP_PRINT: mutt_print_attachment_list (actx, CURATTACH->fp, menu->tagprefix, CURATTACH->content); break; case OP_PIPE: mutt_pipe_attachment_list (actx, CURATTACH->fp, menu->tagprefix, CURATTACH->content, 0); break; case OP_SAVE: mutt_save_attachment_list (actx, CURATTACH->fp, menu->tagprefix, CURATTACH->content, hdr, menu); if (!menu->tagprefix && option (OPTRESOLVE) && menu->current < menu->max - 1) menu->current++; menu->redraw = REDRAW_MOTION_RESYNCH | REDRAW_FULL; break; case OP_DELETE: CHECK_READONLY; #ifdef USE_POP if (Context->magic == MUTT_POP) { mutt_flushinp (); mutt_error _("Can't delete attachment from POP server."); break; } #endif if (WithCrypto && (hdr->security & ENCRYPT)) { mutt_message _( "Deletion of attachments from encrypted messages is unsupported."); break; } if (WithCrypto && (hdr->security & (SIGN | PARTSIGN))) { mutt_message _( "Deletion of attachments from signed messages may invalidate the signature."); } if (!menu->tagprefix) { if (CURATTACH->parent_type == TYPEMULTIPART) { CURATTACH->content->deleted = 1; if (option (OPTRESOLVE) && menu->current < menu->max - 1) { menu->current++; menu->redraw = REDRAW_MOTION_RESYNCH; } else menu->redraw = REDRAW_CURRENT; } else mutt_message _( "Only deletion of multipart attachments is supported."); } else { int x; for (x = 0; x < menu->max; x++) { if (actx->idx[x]->content->tagged) { if (actx->idx[x]->parent_type == TYPEMULTIPART) { actx->idx[x]->content->deleted = 1; menu->redraw = REDRAW_INDEX; } else mutt_message _( "Only deletion of multipart attachments is supported."); } } } break; case OP_UNDELETE: CHECK_READONLY; if (!menu->tagprefix) { CURATTACH->content->deleted = 0; if (option (OPTRESOLVE) && menu->current < menu->max - 1) { menu->current++; menu->redraw = REDRAW_MOTION_RESYNCH; } else menu->redraw = REDRAW_CURRENT; } else { int x; for (x = 0; x < menu->max; x++) { if (actx->idx[x]->content->tagged) { actx->idx[x]->content->deleted = 0; menu->redraw = REDRAW_INDEX; } } } break; case OP_RESEND: CHECK_ATTACH; mutt_attach_resend (CURATTACH->fp, hdr, actx, menu->tagprefix ? NULL : CURATTACH->content); menu->redraw = REDRAW_FULL; break; case OP_BOUNCE_MESSAGE: CHECK_ATTACH; mutt_attach_bounce (CURATTACH->fp, hdr, actx, menu->tagprefix ? NULL : CURATTACH->content); menu->redraw = REDRAW_FULL; break; case OP_FORWARD_MESSAGE: CHECK_ATTACH; mutt_attach_forward (CURATTACH->fp, hdr, actx, menu->tagprefix ? NULL : CURATTACH->content); menu->redraw = REDRAW_FULL; break; case OP_COMPOSE_TO_SENDER: CHECK_ATTACH; mutt_attach_mail_sender (CURATTACH->fp, hdr, actx, menu->tagprefix ? NULL : CURATTACH->content); menu->redraw = REDRAW_FULL; break; case OP_REPLY: case OP_GROUP_REPLY: case OP_GROUP_CHAT_REPLY: case OP_LIST_REPLY: CHECK_ATTACH; flags = SENDREPLY | SENDBACKGROUNDEDIT | (op == OP_GROUP_REPLY ? SENDGROUPREPLY : 0) | (op == OP_GROUP_CHAT_REPLY ? SENDGROUPCHATREPLY : 0) | (op == OP_LIST_REPLY ? SENDLISTREPLY : 0); mutt_attach_reply (CURATTACH->fp, hdr, actx, menu->tagprefix ? NULL : CURATTACH->content, flags); menu->redraw = REDRAW_FULL; break; case OP_EDIT_TYPE: recvattach_edit_content_type (actx, menu, hdr); menu->redraw |= REDRAW_INDEX; break; case OP_EXIT: mx_close_message (Context, &msg); hdr->attach_del = 0; for (i = 0; i < actx->idxlen; i++) if (actx->idx[i]->content && actx->idx[i]->content->deleted) { hdr->attach_del = 1; break; } if (hdr->attach_del) hdr->changed = 1; mutt_free_attach_context (&actx); mutt_pop_current_menu (menu); mutt_menuDestroy (&menu); return; } op = OP_NULL; } /* not reached */ }