summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/mailcap.c660
1 files changed, 660 insertions, 0 deletions
diff --git a/lib/mailcap.c b/lib/mailcap.c
new file mode 100644
index 000000000..8e0d65f97
--- /dev/null
+++ b/lib/mailcap.c
@@ -0,0 +1,660 @@
+/* GNU Mailutils -- a suite of utilities for electronic mail
+ Copyright (C) 2005 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 2, 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <mailutils/mailutils.h>
+#include <xalloc.h>
+#include <fnmatch.h>
+#define obstack_chunk_alloc malloc
+#define obstack_chunk_free free
+#include <obstack.h>
+#include <sys/wait.h>
+
+/* 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
+{
+ stream_t input;
+ header_t hdr;
+ char *content_type_buffer;
+ char *content_type;
+ list_t values;
+ char *temp_file;
+ int unlink_temp_file;
+
+ char *no_ask_str;
+ list_t no_ask_types;
+ int debug_level;
+ int flags;
+};
+
+#define DEBUG(c,l,f) if ((c)->debug_level > (l)) printf f
+
+static void
+mime_context_fill (struct mime_context *ctx, const char *file,
+ stream_t input, header_t hdr, const char *no_ask,
+ int interactive, int dry_run, int debug_level)
+{
+ char *p, *sp;
+
+ memset (ctx, 0, sizeof *ctx);
+ ctx->input = input;
+ ctx->hdr = hdr;
+ header_aget_value (hdr, MU_HEADER_CONTENT_TYPE, &ctx->content_type_buffer);
+ ctx->content_type = strtok_r (ctx->content_type_buffer, ";", &sp);
+ ctx->temp_file = file ? strdup (file) : NULL;
+ ctx->unlink_temp_file = 0;
+
+ if (interactive)
+ ctx->flags |= FLAGS_INTERACTIVE;
+ if (dry_run)
+ ctx->flags |= FLAGS_DRY_RUN;
+ ctx->debug_level = debug_level;
+
+ list_create (&ctx->values);
+ while ((p = strtok_r (NULL, ";", &sp)))
+ list_append (ctx->values, p);
+
+ if (no_ask)
+ {
+ ctx->no_ask_str = xstrdup (no_ask);
+ list_create (&ctx->no_ask_types);
+ for (p = strtok_r (ctx->no_ask_str, ",", &sp); p;
+ p = strtok_r (NULL, ",", &sp))
+ list_append (ctx->no_ask_types, p);
+ }
+}
+
+static void
+mime_context_release (struct mime_context *ctx)
+{
+ free (ctx->content_type_buffer);
+ if (ctx->unlink_temp_file)
+ unlink (ctx->temp_file);
+ free (ctx->temp_file);
+ list_destroy (&ctx->values);
+ free (ctx->no_ask_str);
+ list_destroy (&ctx->no_ask_types);
+}
+
+static int
+mime_context_do_not_ask (struct mime_context *ctx)
+{
+ int rc = 0;
+
+ if (ctx->no_ask_types)
+ {
+ iterator_t itr;
+ list_get_iterator (ctx->no_ask_types, &itr);
+ for (iterator_first (itr); !rc && !iterator_is_done (itr);
+ iterator_next (itr))
+ {
+ char *p;
+ iterator_current (itr, (void**)&p);
+ rc = fnmatch (p, ctx->content_type, FNM_CASEFOLD) == 0;
+ }
+ iterator_destroy (&itr);
+ }
+ return rc;
+}
+
+int
+dry_run_p (struct mime_context *ctx)
+{
+ return ctx->flags & FLAGS_DRY_RUN;
+}
+
+int
+interactive_p (struct mime_context *ctx)
+{
+ return ctx->flags & FLAGS_INTERACTIVE;
+}
+
+static void
+mime_context_get_content_type (struct mime_context *ctx, char **ptr)
+{
+ *ptr = ctx->content_type;
+}
+
+static void
+mime_context_get_input (struct mime_context *ctx, stream_t *pinput)
+{
+ *pinput = ctx->input;
+}
+
+static void
+mime_context_get_content_type_value (struct mime_context *ctx,
+ char *name, size_t len,
+ char **ptr, size_t *plen)
+{
+ iterator_t itr = NULL;
+
+ list_get_iterator (ctx->values, &itr);
+ for (iterator_first (itr); !iterator_is_done (itr); iterator_next (itr))
+ {
+ char *item, *p;
+
+ iterator_current (itr, (void**) &item);
+ p = strchr (item, '=');
+ if (p - item == len && strncasecmp (item, name, len) == 0)
+ {
+ *ptr = ++p;
+ *plen = strlen (*ptr);
+ if (**ptr == '"')
+ {
+ ++*ptr;
+ *plen -= 2;
+ }
+ break;
+ }
+ }
+ iterator_destroy (&itr);
+}
+
+static void
+mime_context_write_input (struct mime_context *ctx, int fd)
+{
+ stream_t input;
+ char buf[512];
+ size_t n;
+ int status;
+
+ mime_context_get_input (ctx, &input);
+ stream_seek (input, 0, SEEK_SET);
+ while ((status = stream_sequential_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 = mu_tempfile (NULL, &ctx->temp_file);
+ if (fd == -1)
+ return -1;
+ mime_context_write_input (ctx, fd);
+ close (fd);
+ ctx->unlink_temp_file = 1;
+ }
+ *ptr = ctx->temp_file;
+ return 0;
+}
+
+
+static struct obstack expand_stack;
+
+static int
+expand_string (struct mime_context *ct, char **pstr)
+{
+ char *p, *s;
+ int rc = 0;
+
+ for (p = *pstr; *p; )
+ {
+ switch (p[0])
+ {
+ case '%':
+ switch (p[1])
+ {
+ case 's':
+ mime_context_get_temp_file (ct, &s);
+ obstack_grow (&expand_stack, s, strlen (s));
+ rc = 1;
+ p += 2;
+ break;
+
+ case 't':
+ mime_context_get_content_type (ct, &s);
+ obstack_grow (&expand_stack, s, strlen (s));
+ p += 2;
+ break;
+
+ case '{':
+ {
+ size_t n;
+ char *q = ++p;
+ while (*p && *p != '}')
+ p++;
+ mime_context_get_content_type_value (ct, q, p-q, &s, &n);
+ obstack_grow (&expand_stack, s, n);
+ if (*p)
+ p++;
+ break;
+ }
+
+ case 'F':
+ case 'n':
+ p++;
+ break;
+
+ default:
+ obstack_1grow (&expand_stack, p[0]);
+ }
+ break;
+
+ case '\\':
+ if (p[1])
+ {
+ obstack_1grow (&expand_stack, p[1]);
+ p += 2;
+ }
+ else
+ {
+ obstack_1grow (&expand_stack, p[0]);
+ p++;
+ }
+ break;
+
+ case '"':
+ if (p[1] == p[0])
+ {
+ obstack_1grow (&expand_stack, '%');
+ p++;
+ }
+ else
+ {
+ obstack_1grow (&expand_stack, p[0]);
+ p++;
+ }
+ break;
+
+ default:
+ obstack_1grow (&expand_stack, p[0]);
+ p++;
+ }
+ }
+ obstack_1grow (&expand_stack, 0);
+ free (*pstr);
+ *pstr = obstack_finish (&expand_stack);
+ return rc;
+}
+
+static int
+confirm_action (struct mime_context *ctx, const char *str)
+{
+ char repl[128], *p;
+ int len;
+ char *type;
+
+ mime_context_get_content_type (ctx, &type);
+ if (dry_run_p (ctx) || !interactive_p (ctx) || mime_context_do_not_ask (ctx))
+ 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);
+}
+
+static void
+dump_mailcap_entry (mu_mailcap_entry_t entry)
+{
+ char buffer[256];
+ size_t i, count;
+
+ mu_mailcap_entry_get_typefield (entry, buffer,
+ sizeof (buffer), NULL);
+ printf ("typefield: %s\n", buffer);
+
+ /* view-command. */
+ mu_mailcap_entry_get_viewcommand (entry, buffer,
+ sizeof (buffer), NULL);
+ printf ("view-command: %s\n", buffer);
+
+ /* fields. */
+ mu_mailcap_entry_fields_count (entry, &count);
+ for (i = 1; i <= count; i++)
+ {
+ int status = mu_mailcap_entry_get_field (entry, i, buffer,
+ sizeof (buffer), NULL);
+ if (status)
+ {
+ mu_error (_("cannot retrieve field %lu: %s"),
+ (unsigned long) i,
+ mu_strerror (status));
+ break;
+ }
+ printf ("fields[%d]: %s\n", i, buffer);
+ }
+ 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 */
+ int argc;
+ char **argv;
+
+ if (need_shell_p (cmd))
+ {
+ argc = 3;
+ argv = xmalloc ((argc + 1) * sizeof *argv);
+ argv[0] = getenv ("SHELL");
+ argv[1] = "-c";
+ argv[2] = cmd;
+ argv[3] = NULL;
+ }
+ else
+ argcv_get (cmd, "", NULL, &argc, &argv);
+
+ /* 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)
+{
+ size_t size;
+ int status = 0;
+
+ if (mu_mailcap_entry_get_test (entry, NULL, 0, &size) == 0)
+ {
+ int argc;
+ char **argv;
+ char *str = xmalloc (size + 1);
+ mu_mailcap_entry_get_test (entry, str, size + 1, NULL);
+
+ expand_string (ctx, &str);
+ argcv_get (str, "", NULL, &argc, &argv);
+ free (str);
+
+ if (mu_spawnvp (argv[0], argv, &status))
+ status = 1;
+ argcv_free (argc, argv);
+ }
+ return status;
+}
+
+int
+run_mailcap (mu_mailcap_entry_t entry, struct mime_context *ctx)
+{
+ char *view_command;
+ size_t size;
+ int flag;
+ int status;
+ int fd;
+ int *pfd = NULL;
+ int outfd = -1;
+ pid_t pid, pager_pid;
+
+ if (ctx->debug_level > 1)
+ dump_mailcap_entry (entry);
+
+ if (run_test (entry, ctx))
+ return -1;
+
+ if (interactive_p (ctx))
+ {
+ mu_mailcap_entry_get_viewcommand (entry, NULL, 0, &size);
+ size++;
+ view_command = xmalloc (size);
+ mu_mailcap_entry_get_viewcommand (entry, view_command, size, NULL);
+ }
+ else
+ {
+ if (mu_mailcap_entry_get_value (entry, "print", NULL, 0, &size))
+ return 1;
+ size++;
+ view_command = xmalloc (size);
+ mu_mailcap_entry_get_value (entry, "print", view_command, size, NULL);
+ }
+
+ /* NOTE: We don't create temporary file for %s, we just use
+ mimeview_file instead */
+ if (expand_string (ctx, &view_command))
+ pfd = NULL;
+ else
+ pfd = &fd;
+ DEBUG (ctx, 0, (_("Executing %s...\n"), view_command));
+
+ if (!confirm_action (ctx, view_command))
+ {
+ free (view_command);
+ return 1;
+ }
+
+ flag = 0;
+ if (interactive_p (ctx)
+ && mu_mailcap_entry_copiousoutput (entry, &flag) == 0 && flag)
+ pager_pid = create_filter (get_pager (), -1, &outfd);
+
+ pid = create_filter (view_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 (ctx->debug_level)
+ print_exit_status (status);
+ }
+ free (view_command);
+ return 0;
+}
+
+static int
+find_entry (const char *file, struct mime_context *ctx)
+{
+ mu_mailcap_t mailcap;
+ int status;
+ stream_t stream;
+ int rc = 1;
+
+ DEBUG (ctx, 2, (_("Trying %s...\n"), file));
+ status = file_stream_create (&stream, file, MU_STREAM_READ);
+ if (status)
+ {
+ mu_error ("cannot create file stream %s: %s",
+ file, mu_strerror (status));
+ return -1;
+ }
+
+ status = stream_open (stream);
+ if (status)
+ {
+ stream_destroy (&stream, stream_get_owner (stream));
+ if (status != ENOENT)
+ mu_error ("cannot open file stream %s: %s",
+ file, mu_strerror (status));
+ return -1;
+ }
+
+ status = mu_mailcap_create (&mailcap, stream);
+ if (status == 0)
+ {
+ size_t i, count = 0;
+ char *type;
+
+ mime_context_get_content_type (ctx, &type);
+
+ mu_mailcap_entries_count (mailcap, &count);
+ for (i = 1; i <= count; i++)
+ {
+ mu_mailcap_entry_t entry;
+ char buffer[256];
+
+ if (mu_mailcap_get_entry (mailcap, i, &entry))
+ continue;
+
+ /* typefield. */
+ mu_mailcap_entry_get_typefield (entry,
+ buffer, sizeof (buffer), NULL);
+
+ if (fnmatch (buffer, type, FNM_CASEFOLD) == 0)
+ {
+ DEBUG (ctx, 2, (_("Found in %s\n"), file));
+ if (run_mailcap (entry, ctx) == 0)
+ {
+ rc = 0;
+ break;
+ }
+ }
+ }
+ mu_mailcap_destroy (&mailcap);
+ }
+ else
+ {
+ mu_error ("cannot create mailcap for %s: %s",
+ file, mu_strerror (status));
+ }
+ return rc;
+}
+
+int
+display_stream_mailcap (const char *ident, stream_t stream, header_t hdr,
+ const char *no_ask, int interactive, int dry_run,
+ int debug_level)
+{
+ char *p, *sp;
+ char *mailcap_path;
+ struct mime_context ctx;
+ int rc = 1;
+
+ mailcap_path = getenv ("MAILCAP");
+ if (!mailcap_path)
+ {
+ char *home = mu_get_homedir ();
+ asprintf (&mailcap_path, "%s/.mailcap:%s", home, DEFAULT_MAILCAP);
+ free (home);
+ }
+ else
+ mailcap_path = strdup (mailcap_path);
+
+ obstack_init (&expand_stack);
+ mime_context_fill (&ctx, ident, stream, hdr, no_ask, interactive, dry_run,
+ debug_level);
+
+ for (p = strtok_r (mailcap_path, ":", &sp); p; p = strtok_r (NULL, ":", &sp))
+ {
+ if ((rc = find_entry (p, &ctx)) == 0)
+ break;
+ }
+
+ mime_context_release (&ctx);
+ obstack_free (&expand_stack, NULL);
+ free (mailcap_path);
+ return rc;
+}

Return to:

Send suggestions and report system problems to the System administrator.