/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 2005-2021 Free Software Foundation, Inc.
GNU Mailutils 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 3, or (at your option)
any later version.
GNU Mailutils 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 GNU Mailutils. If not, see . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
/* Default mailcap path, the $HOME/.mailcap: entry is prepended to it */
#define DEFAULT_MAILCAP \
"/usr/local/etc/mailcap:"\
"/usr/etc/mailcap:"\
"/etc/mailcap:"\
"/etc/mail/mailcap:"\
"/usr/public/lib/mailcap"
#define FLAGS_DRY_RUN 0x0001
#define FLAGS_INTERACTIVE 0x0002
struct mime_context
{
mu_stream_t input;
mu_header_t hdr;
mu_content_type_t content_type;
char *temp_file;
int unlink_temp_file;
char *no_ask_types;
int dh;
int flags;
};
static int
mime_context_fill (struct mime_context *ctx, const char *file,
mu_stream_t input, mu_header_t hdr, const char *no_ask,
int interactive, int dry_run, mu_debug_handle_t dh)
{
int rc;
char *buffer;
memset (ctx, 0, sizeof *ctx);
ctx->input = input;
ctx->hdr = hdr;
rc = mu_header_aget_value_unfold (hdr, MU_HEADER_CONTENT_TYPE, &buffer);
if (rc)
return 1;
rc = mu_content_type_parse (buffer, NULL, &ctx->content_type);
free (buffer);
if (rc)
return 1;
ctx->temp_file = file ? mu_strdup (file) : NULL;
ctx->unlink_temp_file = 0;
if (interactive)
ctx->flags |= FLAGS_INTERACTIVE;
if (dry_run)
ctx->flags |= FLAGS_DRY_RUN;
ctx->dh = dh;
ctx->no_ask_types = no_ask ? mu_strdup (no_ask) : NULL;
return 0;
}
static void
mime_context_release (struct mime_context *ctx)
{
mu_content_type_destroy (&ctx->content_type);
if (ctx->unlink_temp_file)
unlink (ctx->temp_file);
free (ctx->temp_file);
free (ctx->no_ask_types);
}
static int
dry_run_p (struct mime_context *ctx)
{
return ctx->flags & FLAGS_DRY_RUN;
}
static int
interactive_p (struct mime_context *ctx)
{
return ctx->flags & FLAGS_INTERACTIVE;
}
static void
mime_context_get_input (struct mime_context *ctx, mu_stream_t *pinput)
{
*pinput = ctx->input;
}
/* FIXME: Rewrite via mu_stream_copy */
static void
mime_context_write_input (struct mime_context *ctx, int fd)
{
mu_stream_t input;
char buf[512];
size_t n;
int status;
mime_context_get_input (ctx, &input);
status = mu_stream_seek (input, 0, SEEK_SET, NULL);
if (status && status != ENOSYS)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_seek", NULL, status);
abort (); /* FIXME */
}
while ((status = mu_stream_read (input, buf, sizeof buf, &n)) == 0
&& n)
write (fd, buf, n);
}
static int
mime_context_get_temp_file (struct mime_context *ctx, char **ptr)
{
if (!ctx->temp_file)
{
int fd;
if (mu_tempfile (NULL, 0, &fd, &ctx->temp_file))
return -1;
mime_context_write_input (ctx, fd);
close (fd);
ctx->unlink_temp_file = 1;
}
*ptr = ctx->temp_file;
return 0;
}
static mu_opool_t expand_pool;
static int
expand_string (struct mime_context *ct, char const *input, char **pstr)
{
char const *p;
char *s;
int rc = 0;
for (p = input; *p; )
{
switch (p[0])
{
case '%':
switch (p[1])
{
case 's':
mime_context_get_temp_file (ct, &s);
mu_opool_appendz (expand_pool, s);
rc = 1;
p += 2;
break;
case 't':
mu_opool_appendz (expand_pool, ct->content_type->type);
mu_opool_append_char (expand_pool, '/');
mu_opool_appendz (expand_pool, ct->content_type->subtype);
p += 2;
break;
case '{':
{
size_t n;
char const *q;
char *namebuf;
struct mu_mime_param *param;
p += 2;
q = p;
while (*p && *p != '}')
p++;
n = p - q;
namebuf = mu_alloc (n + 1);
memcpy (namebuf, q, n);
namebuf[n] = 0;
param = mu_assoc_get (ct->content_type->param, namebuf);
if (param)
/* FIXME: cset? */
mu_opool_appendz (expand_pool, param->value);
free (namebuf);
if (*p)
p++;
break;
}
case 'F':
case 'n':
p++;
break;
default:
mu_opool_append_char (expand_pool, p[0]);
}
break;
case '\\':
if (p[1])
{
mu_opool_append_char (expand_pool, p[1]);
p += 2;
}
else
{
mu_opool_append_char (expand_pool, p[0]);
p++;
}
break;
case '"':
if (p[1] == p[0])
{
mu_opool_append_char (expand_pool, '%');
p++;
}
else
{
mu_opool_append_char (expand_pool, p[0]);
p++;
}
break;
default:
mu_opool_append_char (expand_pool, p[0]);
p++;
}
}
mu_opool_append_char (expand_pool, 0);
*pstr = mu_opool_finish (expand_pool, NULL);
return rc;
}
static int
confirm_action (struct mime_context *ctx, const char *str)
{
char repl[128], *p;
int len;
if (!interactive_p (ctx)
|| mu_mailcap_content_type_match (ctx->no_ask_types, ',',
ctx->content_type) == 0)
return 1;
printf (_("Run `%s'?"), str);
fflush (stdout);
p = fgets (repl, sizeof repl, stdin);
if (!p)
return 0;
len = strlen (p);
if (len > 0 && p[len-1] == '\n')
p[len--] = 0;
return mu_true_answer_p (p);
}
struct list_closure
{
unsigned long n;
};
static int
list_field (char const *name, char const *value, void *data)
{
struct list_closure *fc = data;
printf ("\tfields[%lu]: ", fc->n++);
if (value)
printf ("%s=%s", name, value);
else
printf ("%s", name);
printf ("\n");
return 0;
}
static void
dump_mailcap_entry (mu_mailcap_entry_t entry)
{
char const *value;
struct list_closure lc;
mu_mailcap_entry_sget_type (entry, &value);
printf ("typefield: %s\n", value);
/* view-command. */
mu_mailcap_entry_sget_command (entry, &value);
printf ("view-command: %s\n", value);
/* fields. */
lc.n = 1;
mu_mailcap_entry_fields_foreach (entry, list_field, &lc);
printf ("\n");
}
/* Return 1 if CMD needs to be executed via sh -c */
static int
need_shell_p (const char *cmd)
{
for (; *cmd; cmd++)
if (strchr ("<>|&", *cmd))
return 1;
return 0;
}
static pid_t
create_filter (char *cmd, int outfd, int *infd)
{
pid_t pid;
int lp[2];
if (infd)
pipe (lp);
pid = fork ();
if (pid == -1)
{
if (infd)
{
close (lp[0]);
close (lp[1]);
}
mu_error ("fork: %s", mu_strerror (errno));
return -1;
}
if (pid == 0)
{
/* Child process */
struct mu_wordsplit ws;
char **argv;
if (need_shell_p (cmd))
{
char *x_argv[4];
argv = x_argv;
argv[0] = getenv ("SHELL");
argv[1] = "-c";
argv[2] = cmd;
argv[3] = NULL;
}
else
{
if (mu_wordsplit (cmd, &ws, MU_WRDSF_DEFFLAGS))
{
mu_error (_("%s failed: %s"), "mu_wordsplit",
mu_wordsplit_strerror (&ws));
_exit (127);
}
argv = ws.ws_wordv;
}
/* Create input channel: */
if (infd)
{
if (lp[0] != 0)
dup2 (lp[0], 0);
close (lp[1]);
}
/* Create output channel */
if (outfd != -1 && outfd != 1)
dup2 (outfd, 1);
execvp (argv[0], argv);
mu_error (_("cannot execute `%s': %s"), cmd, mu_strerror (errno));
_exit (127);
}
/* Master process */
if (infd)
{
*infd = lp[1];
close (lp[0]);
}
return pid;
}
static void
print_exit_status (int status)
{
if (WIFEXITED (status))
printf (_("Command exited with status %d\n"), WEXITSTATUS(status));
else if (WIFSIGNALED (status))
printf(_("Command terminated on signal %d\n"), WTERMSIG(status));
else
printf (_("Command terminated\n"));
}
static char *
get_pager ()
{
char *pager = getenv ("MIMEVIEW_PAGER");
if (!pager)
{
pager = getenv ("METAMAIL_PAGER");
if (!pager)
{
pager = getenv ("PAGER");
if (!pager)
pager = "more";
}
}
return pager;
}
static int
run_test (mu_mailcap_entry_t entry, struct mime_context *ctx)
{
int status = 0;
char const *value;
if (mu_mailcap_entry_sget_field (entry, MU_MAILCAP_TEST, &value) == 0)
{
char *str;
char *argv[] = { "/bin/sh", "-c", NULL, NULL };
expand_string (ctx, value, &str);
argv[2] = str;
if (mu_spawnvp (argv[0], argv, &status))
status = 1;
}
return status;
}
static int
run_mailcap (mu_mailcap_entry_t entry, struct mime_context *ctx)
{
char const *view_command;
char *command;
int status;
int fd;
int *pfd = NULL;
int outfd = -1;
pid_t pid;
struct mu_locus_range lr = MU_LOCUS_RANGE_INITIALIZER;
if (mu_mailcap_entry_get_locus (entry, &lr) == 0)
{
mu_stream_lprintf (mu_strout, &lr, "trying entry\n");
mu_locus_range_deinit (&lr);
}
if (mu_debug_level_p (ctx->dh, MU_DEBUG_TRACE2))
dump_mailcap_entry (entry);
if (run_test (entry, ctx))
return -1;
if (interactive_p (ctx))
status = mu_mailcap_entry_sget_command (entry, &view_command);
else
status = mu_mailcap_entry_sget_field (entry, MU_MAILCAP_PRINT, &view_command);
if (status)
return 1;
/* NOTE: We don't create temporary file for %s, we just use
mimeview_file instead */
if (expand_string (ctx, view_command, &command))
pfd = NULL;
else
pfd = &fd;
mu_debug (ctx->dh, MU_DEBUG_TRACE0, (_("executing %s...\n"), command));
if (!confirm_action (ctx, command))
return 1;
if (dry_run_p (ctx))
return 0;
if (interactive_p (ctx)
&& mu_mailcap_entry_sget_field (entry, MU_MAILCAP_COPIOUSOUTPUT, NULL) == 0)
create_filter (get_pager (), -1, &outfd);
pid = create_filter (command, outfd, pfd);
if (pid > 0)
{
if (pfd)
{
mime_context_write_input (ctx, fd);
close (fd);
}
while (waitpid (pid, &status, 0) < 0)
if (errno != EINTR)
{
mu_error ("waitpid: %s", mu_strerror (errno));
break;
}
if (mu_debug_level_p (ctx->dh, MU_DEBUG_TRACE0))
print_exit_status (status);
}
return 0;
}
static int
entry_selector (mu_mailcap_entry_t entry, void *data)
{
struct mime_context *ctx = data;
char const *pattern;
if (mu_mailcap_entry_sget_type (entry, &pattern))
return 1;
return mu_mailcap_content_type_match (pattern, 0, ctx->content_type);
}
int
display_stream_mailcap (const char *ident, mu_stream_t stream, mu_header_t hdr,
const char *no_ask, int interactive, int dry_run,
mu_debug_handle_t dh)
{
char *mailcap_path, *mailcap_path_tmp = NULL;
struct mu_wordsplit ws;
struct mime_context ctx;
int rc = 1;
if (mime_context_fill (&ctx, ident, stream, hdr,
no_ask, interactive, dry_run, dh))
return 1;
mailcap_path = getenv ("MAILCAP");
if (!mailcap_path)
{
char *home = mu_get_homedir ();
mailcap_path_tmp = mu_make_file_name_suf (home, ".mailcap:",
DEFAULT_MAILCAP);
free (home);
if (!mailcap_path_tmp)
return 1;
mailcap_path = mailcap_path_tmp;
}
mu_opool_create (&expand_pool, MU_OPOOL_ENOMEMABRT);
ws.ws_delim = ":";
if (mu_wordsplit (mailcap_path, &ws,
MU_WRDSF_DELIM|MU_WRDSF_SQUEEZE_DELIMS|
MU_WRDSF_NOVAR|MU_WRDSF_NOCMD))
{
mu_error (_("cannot split line `%s': %s"), mailcap_path,
mu_wordsplit_strerror (&ws));
}
else
{
mu_mailcap_finder_t finder;
int flags = MU_MAILCAP_FLAG_DEFAULT;
struct mu_mailcap_error_closure *errcp = NULL;
struct mu_mailcap_selector_closure selcl;
mu_mailcap_entry_t entry;
int rc;
if (mu_debug_level_p (ctx.dh, MU_DEBUG_TRACE1)
|| mu_debug_level_p (ctx.dh, MU_DEBUG_TRACE2))
flags |= MU_MAILCAP_FLAG_LOCUS;
if (mu_debug_level_p (ctx.dh, MU_DEBUG_ERROR))
errcp = &mu_mailcap_default_error_closure;
memset (&selcl, 0, sizeof (selcl));
selcl.selector = entry_selector;
selcl.data = &ctx;
rc = mu_mailcap_finder_create (&finder, flags,
&selcl, errcp, ws.ws_wordv);
mu_wordsplit_free (&ws);
while ((rc = mu_mailcap_finder_next_match (finder, &entry)) == 0)
{
if (run_mailcap (entry, &ctx) == 0)
break;
}
mu_mailcap_finder_destroy (&finder);
}
mu_opool_destroy (&expand_pool);
free (mailcap_path_tmp);
mime_context_release (&ctx);
return rc;
}