2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
7 * This program is distributed in the hope that it will be useful, but
8 * WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 * Copyright © 2006 Pierre Habouzit
20 * Copyright notice from original mutt:
21 * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
23 * This file is part of mutt-ng, see http://www.muttng.org/.
24 * It's licensed under the GNU General Public License,
25 * please see the file GPL in the top level source directory.
29 * rfc1524 defines a format for the Multimedia Mail Configuration, which
30 * is the standard mailcap file format under Unix which specifies what
31 * external programs should be used to view/compose/edit multimedia files
32 * based on content type.
34 * This file contains various functions for implementing a fair subset of
51 #include <lib-lib/mem.h>
52 #include <lib-lib/str.h>
53 #include <lib-lib/ascii.h>
54 #include <lib-lib/macros.h>
55 #include <lib-lib/file.h>
57 #include <lib-sys/unix.h>
63 void rfc1524_entry_wipe(rfc1524_entry *p)
65 p_delete(&p->command);
66 p_delete(&p->testcommand);
67 p_delete(&p->composecommand);
68 p_delete(&p->composetypecommand);
69 p_delete(&p->editcommand);
70 p_delete(&p->printcommand);
71 p_delete(&p->nametemplate);
72 p_delete(&p->convert);
75 /* returns 1 if Mutt can't display this type of data, 0 otherwise */
76 int rfc1524_mailcap_isneeded(BODY * m)
86 return !(mutt_is_application_pgp(m) || mutt_is_application_smime(m));
89 tok = mime_which_token(m->subtype, -1);
91 || tok == MIME_RFC822_HEADERS
92 || tok == MIME_ENRICHED)
100 /* The command semantics include the following:
101 * %s is the filename that contains the mail body data
102 * %t is the content type, like text/plain
103 * %{parameter} is replaced by the parameter value from the content-type field
105 * Unsupported rfc1524 parameters: these would probably require some doing
106 * by mutt, and can probably just be done by piping the message to metamail
107 * %n is the integer number of sub-parts in the multipart
108 * %F is "content-type filename" repeated for each sub-part
110 * In addition, this function returns a 0 if the command works on a file,
111 * and 1 if the command works on a pipe.
113 int rfc1524_expand_command(BODY *a, const char *filename, const char *mtype,
114 char *command, int clen)
117 int needspipe = TRUE;
118 char buf[LONG_STRING];
119 char type[LONG_STRING];
121 m_strcpy(type, sizeof(type), mtype);
123 if (option(OPTMAILCAPSANITIZE))
124 mutt_sanitize_filename(type, 0);
126 while (command[x] && x < clen && y < ssizeof(buf)) {
127 switch (command[x]) {
130 buf[y++] = command[x++];
135 if (command[x] == '{') {
141 while (command[x] && command[x] != '}' && z < ssizeof (param))
142 param[z++] = command[x++];
145 m_strcpy(pval, sizeof(pval),
146 parameter_getval(a->parameter, param));
148 if (option(OPTMAILCAPSANITIZE))
149 mutt_sanitize_filename(pval, 0);
151 y += mutt_quote_filename(buf + y, sizeof(buf) - y, pval);
154 if (command[x] == 's' && filename) {
155 y += mutt_quote_filename(buf + y, sizeof(buf) - y, filename);
159 if (command[x] == 't') {
160 y += mutt_quote_filename(buf + y, sizeof(buf) - y, type);
166 buf[y++] = command[x++];
172 m_strcpy(command, clen, buf);
177 static char *parse_field(char *p, char **field, char **value)
193 p += 1 + (p[1] != 0);
199 p = *value = vskipspaces(p);
212 parse_field_error(const char *type, const char *filename, int line)
214 mutt_error(_("Improperly formated entry for type %s in \"%s\" line %d"),
215 type, filename, line);
218 /* rfc1524 mailcap file is of the format:
219 * base/type; command; extradefs
220 * type can be * for matching all
221 * base with no /type is an implicit wild
222 * command contains a %s for the filename to pass, default to pipe on stdin
223 * extradefs are of the form:
224 * def1="definition"; def2="define \;";
225 * line wraps with a \ at the end of the line
229 rfc1524_mailcap_parse(BODY *a, const char *filename, const char *type,
230 rfc1524_entry *entry, int opt)
245 /* find length of basetype */
246 if ((ch = strchr (type, '/')) == NULL)
250 fp = fopen(filename, "r");
254 while (!found && (buf = mutt_read_line(buf, &buflen, fp, &line)) != NULL) {
255 /* ignore comments */
260 ch = parse_field(buf, &field, &value);
261 if (ascii_strcasecmp(field, type)
262 && (ascii_strncasecmp(field, type, btlen)
263 || (buf[btlen] != 0 && m_strcmp(buf + btlen, "/*"))))
266 /* next field is the viewcommand */
267 ch = parse_field(ch, &field, &value);
269 entry->command = m_strdup(field);
271 /* parse the optional fields */
273 copiousoutput = FALSE;
274 composecommand = FALSE;
276 printcommand = FALSE;
279 ch = parse_field(ch, &field, &value);
281 switch (mime_which_token(field, -1)) {
282 #define DO_CASE(token, field, expr) \
286 m_strreplace(&entry->field, value); \
289 parse_field_error(type, filename, line); \
293 case MIME_NEEDSTERMINAL:
295 entry->needsterminal = TRUE;
298 case MIME_COPIOUSOUTPUT:
299 copiousoutput = TRUE;
301 entry->copiousoutput = TRUE;
304 DO_CASE(MIME_COMPOSETYPED, composecommand, composecommand = TRUE);
305 DO_CASE(MIME_COMPOSE, composecommand, composecommand = TRUE);
306 DO_CASE(MIME_PRINT, printcommand, printcommand = TRUE);
307 DO_CASE(MIME_EDIT, editcommand, editcommand = TRUE);
308 DO_CASE(MIME_NAMETEMPLATE, nametemplate, );
309 DO_CASE(MIME_X_CONVERT, convert, );
313 * This routine executes the given test command to determine
314 * if this is the right entry. a non-zero exit code means
318 ssize_t len = m_strlen(value) + STRING;
319 char *testcmd = p_new(char, len);
321 strcpy(testcmd, value);
322 rfc1524_expand_command(a, a->filename, type, testcmd, len);
323 found = !mutt_system(testcmd);
326 parse_field_error(type, filename, line);
336 if (opt == M_AUTOVIEW) {
340 else if (opt == M_COMPOSE) {
344 else if (opt == M_EDIT) {
348 else if (opt == M_PRINT) {
353 if (!found && entry) {
354 rfc1524_entry_wipe(entry);
355 rfc1524_entry_init(entry);
357 } /* while (!found && (buf = mutt_read_line ())) */
366 /************** READ MARK **********************/
369 * rfc1524_mailcap_lookup attempts to find the given type in the
370 * list of mailcap files. On success, this returns the entry information
371 * in *entry, and returns 1. On failure (not found), returns 0.
372 * If entry == NULL just return 1 if the given type is found.
374 int rfc1524_mailcap_lookup (BODY * a, char *type, rfc1524_entry * entry,
377 char path[_POSIX_PATH_MAX];
380 char *curr = MailcapPath;
382 /* rfc1524 specifies that a path of mailcap files should be searched.
384 * $HOME/.mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap, etc
385 * and overriden by the MAILCAPS environment variable, and, just to be nice,
386 * we'll make it specifiable in .muttrc
388 if (!curr || !*curr) {
389 mutt_error _("No mailcap path specified");
394 mutt_check_lookup_list (a, type, SHORT_STRING);
396 while (!found && *curr) {
398 while (*curr && *curr != ':' && x < ssizeof (path) - 1) {
409 mutt_expand_path (path, sizeof (path));
411 found = rfc1524_mailcap_parse (a, path, type, entry, opt);
415 mutt_error (_("mailcap entry for type %s not found"), type);
421 /* This routine will create a _temporary_ filename matching the
422 * name template given if this needs to be done.
424 * Please note that only the last path element of the
425 * template and/or the old file name will be used for the
426 * comparison and the temporary file name.
428 * Returns 0 if oldfile is fine as is.
429 * Returns 1 if newfile specified
432 int rfc1524_expand_filename (char *nametemplate,
433 char *oldfile, char *newfile, ssize_t nflen)
437 short lmatch = 0, rmatch = 0;
438 char left[_POSIX_PATH_MAX];
439 char right[_POSIX_PATH_MAX];
443 /* first, ignore leading path components.
446 if (nametemplate && (s = strrchr (nametemplate, '/')))
447 nametemplate = s + 1;
449 if (oldfile && (s = strrchr (oldfile, '/')))
454 m_strcpy(newfile, nflen, oldfile);
457 mutt_expand_fmt (newfile, nflen, nametemplate, "mutt");
459 else { /* oldfile && nametemplate */
462 /* first, compare everything left from the "%s"
468 for (i = 0; nametemplate[i]; i++) {
469 if (nametemplate[i] == '%' && nametemplate[i + 1] == 's') {
474 /* note that the following will _not_ read beyond oldfile's end. */
476 if (lmatch && nametemplate[i] != oldfile[i])
482 /* If we had a "%s", check the rest. */
484 /* now, for the right part: compare everything right from
485 * the "%s" to the final part of oldfile.
487 * The logic here is as follows:
489 * - We start reading from the end.
490 * - There must be a match _right_ from the "%s",
492 * - If there was a left hand match, this stuff
493 * must not be counted again. That's done by the
494 * condition (j >= (lmatch ? i : 0)).
499 for (r = 0, j = m_strlen(oldfile) - 1, k =
500 m_strlen(nametemplate) - 1;
501 j >= (lmatch ? i : 0) && k >= i + 2; j--, k--) {
502 if (nametemplate[k] != oldfile[j]) {
508 /* Now, check if we had a full match. */
516 m_strncpy(left, sizeof(left), nametemplate, i);
521 m_strcpy(right, sizeof(right), nametemplate + i + 2);
523 snprintf (newfile, nflen, "%s%s%s", left, oldfile, right);
526 /* no "%s" in the name template. */
527 m_strcpy(newfile, nflen, nametemplate);
531 mutt_adv_mktemp (NULL, newfile, nflen);
533 return !(rmatch && lmatch);