begin to rework mailcap parsing a "bit".
[apps/madmutt.git] / lib-mime / rfc1524.c
diff --git a/lib-mime/rfc1524.c b/lib-mime/rfc1524.c
new file mode 100644 (file)
index 0000000..c2c8058
--- /dev/null
@@ -0,0 +1,498 @@
+/*
+ *  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);
+}
+