--- /dev/null
+/*
+ * 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.
+ *
+ * Copyright © 2006 Pierre Habouzit
+ */
+/*
+ * Copyright notice from original mutt:
+ * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
+ *
+ * This file is part of mutt-ng, see http://www.muttng.org/.
+ * It's licensed under the GNU General Public License,
+ * please see the file GPL in the top level source directory.
+ */
+
+/*
+ * rfc1524 defines a format for the Multimedia Mail Configuration, which
+ * is the standard mailcap file format under Unix which specifies what
+ * external programs should be used to view/compose/edit multimedia files
+ * based on content type.
+ *
+ * This file contains various functions for implementing a fair subset of
+ * rfc1524.
+ */
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <lib-lib/mem.h>
+#include <lib-lib/str.h>
+#include <lib-lib/ascii.h>
+#include <lib-lib/macros.h>
+#include <lib-lib/file.h>
+
+#include <lib-sys/unix.h>
+
+#include "mutt.h"
+#include "mime.h"
+#include "attach.h"
+
+/* The command semantics include the following:
+ * %s is the filename that contains the mail body data
+ * %t is the content type, like text/plain
+ * %{parameter} is replaced by the parameter value from the content-type field
+ * \% is %
+ * Unsupported rfc1524 parameters: these would probably require some doing
+ * by mutt, and can probably just be done by piping the message to metamail
+ * %n is the integer number of sub-parts in the multipart
+ * %F is "content-type filename" repeated for each sub-part
+ *
+ * In addition, this function returns a 0 if the command works on a file,
+ * and 1 if the command works on a pipe.
+ */
+int rfc1524_expand_command(BODY *a, const char *filename, const char *mtype,
+ char *command, int clen)
+{
+ int x = 0, y = 0;
+ int needspipe = TRUE;
+ char buf[LONG_STRING];
+ char type[LONG_STRING];
+
+ m_strcpy(type, sizeof(type), mtype);
+
+ if (option(OPTMAILCAPSANITIZE))
+ mutt_sanitize_filename(type, 0);
+
+ while (command[x] && x < clen && y < ssizeof(buf)) {
+ switch (command[x]) {
+ case '\\':
+ x++;
+ buf[y++] = command[x++];
+ break;
+
+ case '%':
+ x++;
+ if (command[x] == '{') {
+ char param[STRING];
+ char pval[STRING];
+ int z = 0;
+
+ x++;
+ while (command[x] && command[x] != '}' && z < ssizeof (param))
+ param[z++] = command[x++];
+ param[z] = '\0';
+
+ m_strcpy(pval, sizeof(pval),
+ mutt_get_parameter(param, a->parameter));
+
+ if (option(OPTMAILCAPSANITIZE))
+ mutt_sanitize_filename(pval, 0);
+
+ y += mutt_quote_filename(buf + y, sizeof(buf) - y, pval);
+ }
+
+ if (command[x] == 's' && filename) {
+ y += mutt_quote_filename(buf + y, sizeof(buf) - y, filename);
+ needspipe = FALSE;
+ }
+
+ if (command[x] == 't') {
+ y += mutt_quote_filename(buf + y, sizeof(buf) - y, type);
+ }
+ x++;
+ break;
+
+ default:
+ buf[y++] = command[x++];
+ break;
+ }
+ }
+
+ buf[y] = '\0';
+ m_strcpy(command, clen, buf);
+
+ return needspipe;
+}
+
+static char *parse_field(char *p, char **field, char **value)
+{
+ p = vskipspaces(p);
+ *field = p;
+ *value = NULL;
+
+ for (;;) {
+ switch (*p) {
+ case ';':
+ *p++ = '\0';
+ case '\0':
+ m_strrtrim(*field);
+ m_strrtrim(*value);
+ return p;
+
+ case '\\':
+ p += 1 + (p[1] != 0);
+ break;
+
+ case '=':
+ if (!*value) {
+ *p++ = '\0';
+ p = *value = vskipspaces(p);
+ continue;
+ }
+ /* falltrhrough */
+
+ default:
+ p++;
+ break;
+ }
+ }
+}
+
+static inline void
+parse_field_error(const char *type, const char *filename, int line)
+{
+ mutt_error(_("Improperly formated entry for type %s in \"%s\" line %d"),
+ type, filename, line);
+}
+
+/* rfc1524 mailcap file is of the format:
+ * base/type; command; extradefs
+ * type can be * for matching all
+ * base with no /type is an implicit wild
+ * command contains a %s for the filename to pass, default to pipe on stdin
+ * extradefs are of the form:
+ * def1="definition"; def2="define \;";
+ * line wraps with a \ at the end of the line
+ * # for comments
+ */
+static int
+rfc1524_mailcap_parse(BODY *a, const char *filename, const char *type,
+ rfc1524_entry *entry, int opt)
+{
+ FILE *fp;
+ char *buf = NULL;
+ ssize_t buflen;
+ char *ch;
+ char *field, *value;
+ int found = FALSE;
+ int copiousoutput;
+ int composecommand;
+ int editcommand;
+ int printcommand;
+ int btlen;
+ int line = 0;
+
+ /* find length of basetype */
+ if ((ch = strchr (type, '/')) == NULL)
+ return FALSE;
+ btlen = ch - type;
+
+ fp = fopen(filename, "r");
+ if (!fp)
+ goto error;
+
+ while (!found && (buf = mutt_read_line(buf, &buflen, fp, &line)) != NULL) {
+ /* ignore comments */
+ if (*buf == '#')
+ continue;
+
+ /* check type */
+ ch = parse_field(buf, &field, &value);
+ if (ascii_strcasecmp(field, type)
+ && (ascii_strncasecmp(field, type, btlen)
+ || (buf[btlen] != 0 && m_strcmp(buf + btlen, "/*"))))
+ continue;
+
+ /* next field is the viewcommand */
+ ch = parse_field(ch, &field, &value);
+ if (entry)
+ entry->command = m_strdup(field);
+
+ /* parse the optional fields */
+ found = TRUE;
+ copiousoutput = FALSE;
+ composecommand = FALSE;
+ editcommand = FALSE;
+ printcommand = FALSE;
+
+ while (*ch) {
+ ch = parse_field(ch, &field, &value);
+
+ switch (mime_which_token(field, -1)) {
+#define DO_CASE(token, field, expr) \
+ case token: \
+ if (value) { \
+ if (entry) \
+ m_strreplace(&entry->field, value); \
+ expr; \
+ } else { \
+ parse_field_error(type, filename, line); \
+ } \
+ break;
+
+ case MIME_NEEDSTERMINAL:
+ if (entry)
+ entry->needsterminal = TRUE;
+ break;
+
+ case MIME_COPIOUSOUTPUT:
+ copiousoutput = TRUE;
+ if (entry)
+ entry->copiousoutput = TRUE;
+ break;
+
+ DO_CASE(MIME_COMPOSETYPED, composecommand, composecommand = TRUE);
+ DO_CASE(MIME_COMPOSE, composecommand, composecommand = TRUE);
+ DO_CASE(MIME_PRINT, printcommand, printcommand = TRUE);
+ DO_CASE(MIME_EDIT, editcommand, editcommand = TRUE);
+ DO_CASE(MIME_NAMETEMPLATE, nametemplate, );
+ DO_CASE(MIME_X_CONVERT, convert, );
+
+ case MIME_TEST:
+ /*
+ * This routine executes the given test command to determine
+ * if this is the right entry. a non-zero exit code means
+ * test failed
+ */
+ if (value) {
+ ssize_t len = m_strlen(value) + STRING;
+ char *testcmd = p_new(char, len);
+
+ strcpy(testcmd, value);
+ rfc1524_expand_command(a, a->filename, type, testcmd, len);
+ found = !mutt_system(testcmd);
+ p_delete(&testcmd);
+ } else {
+ parse_field_error(type, filename, line);
+ }
+ break;
+
+ default:
+ break;
+#undef DO_CASE
+ }
+ }
+
+ if (opt == M_AUTOVIEW) {
+ if (!copiousoutput)
+ found = FALSE;
+ }
+ else if (opt == M_COMPOSE) {
+ if (!composecommand)
+ found = FALSE;
+ }
+ else if (opt == M_EDIT) {
+ if (!editcommand)
+ found = FALSE;
+ }
+ else if (opt == M_PRINT) {
+ if (!printcommand)
+ found = FALSE;
+ }
+
+ if (!found && entry) {
+ rfc1524_entry_wipe(entry);
+ rfc1524_entry_init(entry);
+ }
+ } /* while (!found && (buf = mutt_read_line ())) */
+ fclose (fp);
+
+ error:
+ p_delete(&buf);
+ return found;
+}
+
+
+/************** READ MARK **********************/
+
+/*
+ * rfc1524_mailcap_lookup attempts to find the given type in the
+ * list of mailcap files. On success, this returns the entry information
+ * in *entry, and returns 1. On failure (not found), returns 0.
+ * If entry == NULL just return 1 if the given type is found.
+ */
+int rfc1524_mailcap_lookup (BODY * a, char *type, rfc1524_entry * entry,
+ int opt)
+{
+ char path[_POSIX_PATH_MAX];
+ int x;
+ int found = FALSE;
+ char *curr = MailcapPath;
+
+ /* rfc1524 specifies that a path of mailcap files should be searched.
+ * joy. They say
+ * $HOME/.mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap, etc
+ * and overriden by the MAILCAPS environment variable, and, just to be nice,
+ * we'll make it specifiable in .muttrc
+ */
+ if (!curr || !*curr) {
+ mutt_error _("No mailcap path specified");
+
+ return 0;
+ }
+
+ mutt_check_lookup_list (a, type, SHORT_STRING);
+
+ while (!found && *curr) {
+ x = 0;
+ while (*curr && *curr != ':' && x < ssizeof (path) - 1) {
+ path[x++] = *curr;
+ curr++;
+ }
+ if (*curr)
+ curr++;
+
+ if (!x)
+ continue;
+
+ path[x] = '\0';
+ mutt_expand_path (path, sizeof (path));
+
+ found = rfc1524_mailcap_parse (a, path, type, entry, opt);
+ }
+
+ if (entry && !found)
+ mutt_error (_("mailcap entry for type %s not found"), type);
+
+ return found;
+}
+
+
+/* This routine will create a _temporary_ filename matching the
+ * name template given if this needs to be done.
+ *
+ * Please note that only the last path element of the
+ * template and/or the old file name will be used for the
+ * comparison and the temporary file name.
+ *
+ * Returns 0 if oldfile is fine as is.
+ * Returns 1 if newfile specified
+ */
+
+int rfc1524_expand_filename (char *nametemplate,
+ char *oldfile, char *newfile, ssize_t nflen)
+{
+ int i, j, k, ps, r;
+ char *s;
+ short lmatch = 0, rmatch = 0;
+ char left[_POSIX_PATH_MAX];
+ char right[_POSIX_PATH_MAX];
+
+ newfile[0] = 0;
+
+ /* first, ignore leading path components.
+ */
+
+ if (nametemplate && (s = strrchr (nametemplate, '/')))
+ nametemplate = s + 1;
+
+ if (oldfile && (s = strrchr (oldfile, '/')))
+ oldfile = s + 1;
+
+ if (!nametemplate) {
+ if (oldfile)
+ m_strcpy(newfile, nflen, oldfile);
+ }
+ else if (!oldfile) {
+ mutt_expand_fmt (newfile, nflen, nametemplate, "mutt");
+ }
+ else { /* oldfile && nametemplate */
+
+
+ /* first, compare everything left from the "%s"
+ * (if there is one).
+ */
+
+ lmatch = 1;
+ ps = 0;
+ for (i = 0; nametemplate[i]; i++) {
+ if (nametemplate[i] == '%' && nametemplate[i + 1] == 's') {
+ ps = 1;
+ break;
+ }
+
+ /* note that the following will _not_ read beyond oldfile's end. */
+
+ if (lmatch && nametemplate[i] != oldfile[i])
+ lmatch = 0;
+ }
+
+ if (ps) {
+
+ /* If we had a "%s", check the rest. */
+
+ /* now, for the right part: compare everything right from
+ * the "%s" to the final part of oldfile.
+ *
+ * The logic here is as follows:
+ *
+ * - We start reading from the end.
+ * - There must be a match _right_ from the "%s",
+ * thus the i + 2.
+ * - If there was a left hand match, this stuff
+ * must not be counted again. That's done by the
+ * condition (j >= (lmatch ? i : 0)).
+ */
+
+ rmatch = 1;
+
+ for (r = 0, j = m_strlen(oldfile) - 1, k =
+ m_strlen(nametemplate) - 1;
+ j >= (lmatch ? i : 0) && k >= i + 2; j--, k--) {
+ if (nametemplate[k] != oldfile[j]) {
+ rmatch = 0;
+ break;
+ }
+ }
+
+ /* Now, check if we had a full match. */
+
+ if (k >= i + 2)
+ rmatch = 0;
+
+ if (lmatch)
+ *left = 0;
+ else
+ m_strncpy(left, sizeof(left), nametemplate, i);
+
+ if (rmatch)
+ *right = 0;
+ else
+ m_strcpy(right, sizeof(right), nametemplate + i + 2);
+
+ snprintf (newfile, nflen, "%s%s%s", left, oldfile, right);
+ }
+ else {
+ /* no "%s" in the name template. */
+ m_strcpy(newfile, nflen, nametemplate);
+ }
+ }
+
+ mutt_adv_mktemp (NULL, newfile, nflen);
+
+ return !(rmatch && lmatch);
+}
+