/*
* Copyright (C) 1996-2000,2002,2007,2010 Michael R. Elkins <me@mutt.org>
* Copyright (C) 1999-2006 Thomas Roessler <roessler@does-not-exist.org>
*
* 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 <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
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, "<no description>");
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 */
}