From 4e6c7e6e3f5a8906c5229390b45c2c9b3aa090df Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Sat, 12 Mar 2005 19:18:55 +0000 Subject: Routines for displaying MIME messages. --- lib/mailcap.c | 660 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 660 insertions(+) create mode 100644 lib/mailcap.c 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 +#endif +#include +#include +#include +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free +#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 +{ + 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; +} -- cgit v1.2.1