mailcap bug 1113: Don't leak values of duplicate fields
[elinks.git] / src / mime / backend / mailcap.c
blob099fe630c4b08f176210c13ea338331076329acd
1 /* RFC1524 (mailcap file) implementation */
3 /* This file contains various functions for implementing a fair subset of
4 * rfc1524.
6 * The rfc1524 defines a format for the Multimedia Mail Configuration, which is
7 * the standard mailcap file format under Unix which specifies what external
8 * programs should be used to view/compose/edit multimedia files based on
9 * content type.
11 * Copyright (C) 1996-2000 Michael R. Elkins <me@cs.hmc.edu>
12 * Copyright (c) 2002-2004 The ELinks project
14 * This file was hijacked from the Mutt project <URL:http://www.mutt.org>
15 * (version 1.4) on Saturday the 7th December 2002. It has been heavily
16 * elinksified. */
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
22 #include <ctype.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
27 #include "elinks.h"
29 #include "config/options.h"
30 #include "intl/gettext/libintl.h"
31 #include "main/module.h"
32 #include "mime/backend/common.h"
33 #include "mime/backend/mailcap.h"
34 #include "mime/mime.h"
35 #include "osdep/osdep.h" /* For exe() */
36 #include "session/session.h"
37 #include "util/file.h"
38 #include "util/hash.h"
39 #include "util/lists.h"
40 #include "util/memory.h"
41 #include "util/string.h"
43 struct mailcap_hash_item {
44 /* The entries associated with the type */
45 LIST_OF(struct mailcap_entry) entries;
47 /* The content type of all @entries. Must be last! */
48 unsigned char type[1];
51 struct mailcap_entry {
52 LIST_HEAD(struct mailcap_entry);
54 /* To verify if command qualifies. Cannot contain %s formats. */
55 unsigned char *testcommand;
57 /* Used to inform the user of the type or handler. */
58 unsigned char *description;
60 /* Used to determine between an exact match and a wildtype match. Lower
61 * is better. Increased for each sourced file. */
62 unsigned int priority;
64 /* Whether the program "blocks" the term. */
65 unsigned int needsterminal:1;
67 /* If "| ${PAGER}" should be added. It would of course be better to
68 * pipe the output into a buffer and let ELinks display it but this
69 * will have to do for now. */
70 unsigned int copiousoutput:1;
72 /* The 'raw' unformatted (view)command from the mailcap files. */
73 unsigned char command[1];
76 enum mailcap_option {
77 MAILCAP_TREE,
79 MAILCAP_ENABLE,
80 MAILCAP_PATH,
81 MAILCAP_ASK,
82 MAILCAP_DESCRIPTION,
83 MAILCAP_PRIORITIZE,
85 MAILCAP_OPTIONS
88 static union option_info mailcap_options[] = {
89 INIT_OPT_TREE("mime", N_("Mailcap"),
90 "mailcap", 0,
91 N_("Options for mailcap support.")),
93 INIT_OPT_BOOL("mime.mailcap", N_("Enable"),
94 "enable", 0, 1,
95 N_("Enable mailcap support.")),
97 INIT_OPT_STRING("mime.mailcap", N_("Path"),
98 "path", 0, DEFAULT_MAILCAP_PATH,
99 N_("Mailcap search path. Colon-separated list of files. "
100 "Leave as \"\" to use MAILCAP environment variable instead.")),
102 INIT_OPT_BOOL("mime.mailcap", N_("Ask before opening"),
103 "ask", 0, 1,
104 N_("Ask before using the handlers defined by mailcap.")),
106 INIT_OPT_INT("mime.mailcap", N_("Type query string"),
107 "description", 0, 0, 2, 0,
108 N_("Type of description to show in \"what to do with "
109 "this file\" query dialog:\n"
110 "0 is show \"mailcap\"\n"
111 "1 is show program to be run\n"
112 "2 is show mailcap description field if any;\n"
113 " \"mailcap\" otherwise")),
115 INIT_OPT_BOOL("mime.mailcap", N_("Prioritize entries by file"),
116 "prioritize", 0, 1,
117 N_("Prioritize entries by the order of the files in "
118 "the mailcap path. This means that wildcard entries "
119 "(like: image/*) will also be checked before deciding "
120 "the handler.")),
122 NULL_OPTION_INFO,
125 #define get_opt_mailcap(which) mailcap_options[(which)].option
126 #define get_mailcap(which) get_opt_mailcap(which).value
127 #define get_mailcap_ask() get_mailcap(MAILCAP_ASK).number
128 #define get_mailcap_description() get_mailcap(MAILCAP_DESCRIPTION).number
129 #define get_mailcap_enable() get_mailcap(MAILCAP_ENABLE).number
130 #define get_mailcap_prioritize() get_mailcap(MAILCAP_PRIORITIZE).number
131 #define get_mailcap_path() get_mailcap(MAILCAP_PATH).string
133 /* State variables */
134 static struct hash *mailcap_map = NULL;
137 static inline void
138 done_mailcap_entry(struct mailcap_entry *entry)
140 if (!entry) return;
141 mem_free_if(entry->testcommand);
142 mem_free_if(entry->description);
143 mem_free(entry);
146 /* Takes care of all initialization of mailcap entries.
147 * Clear memory to make freeing it safer later and we get
148 * needsterminal and copiousoutput initialized for free. */
149 static inline struct mailcap_entry *
150 init_mailcap_entry(unsigned char *command, int priority)
152 struct mailcap_entry *entry;
153 int commandlen = strlen(command);
155 entry = mem_calloc(1, sizeof(*entry) + commandlen);
156 if (!entry) return NULL;
158 memcpy(entry->command, command, commandlen);
160 entry->priority = priority;
162 return entry;
165 static inline void
166 add_mailcap_entry(struct mailcap_entry *entry, unsigned char *type, int typelen)
168 struct mailcap_hash_item *mitem;
169 struct hash_item *item;
171 /* Time to get the entry into the mailcap_map */
172 /* First check if the type is already checked in */
173 item = get_hash_item(mailcap_map, type, typelen);
174 if (!item) {
175 mitem = mem_alloc(sizeof(*mitem) + typelen);
176 if (!mitem) {
177 done_mailcap_entry(entry);
178 return;
181 safe_strncpy(mitem->type, type, typelen + 1);
183 init_list(mitem->entries);
185 item = add_hash_item(mailcap_map, mitem->type, typelen, mitem);
186 if (!item) {
187 mem_free(mitem);
188 done_mailcap_entry(entry);
189 return;
191 } else if (item->value) {
192 mitem = item->value;
193 } else {
194 done_mailcap_entry(entry);
195 return;
198 add_to_list_end(mitem->entries, entry);
201 /* Parsing of a RFC1524 mailcap file */
202 /* The format is:
204 * base/type; command; extradefs
206 * type can be * for matching all; base with no /type is an implicit
207 * wildcard; command contains a %s for the filename to pass, default to pipe on
208 * stdin; extradefs are of the form:
210 * def1="definition"; def2="define \;";
212 * line wraps with a \ at the end of the line, # for comments. */
213 /* TODO handle default pipe. Maybe by prepending "cat |" to the command. */
215 /* Returns a NULL terminated RFC 1524 field, while modifying @next to point
216 * to the next field. */
217 static unsigned char *
218 get_mailcap_field(unsigned char **next)
220 unsigned char *field;
221 unsigned char *fieldend;
223 if (!next || !*next) return NULL;
225 field = *next;
226 skip_space(field);
227 fieldend = field;
229 /* End field at the next occurence of ';' but not escaped '\;' */
230 do {
231 /* Handle both if ';' is the first char or if it's escaped */
232 if (*fieldend == ';')
233 fieldend++;
235 fieldend = strchr(fieldend, ';');
236 } while (fieldend && *(fieldend-1) == '\\');
238 if (fieldend) {
239 *fieldend = '\0';
240 *next = fieldend;
241 fieldend--;
242 (*next)++;
243 skip_space(*next);
244 } else {
245 *next = NULL;
246 fieldend = field + strlen(field) - 1;
249 /* Remove trailing whitespace */
250 while (field <= fieldend && isspace(*fieldend))
251 *fieldend-- = '\0';
253 return field;
256 /* Parses specific fields (ex: the '=TestCommand' part of 'test=TestCommand').
257 * Expects that @field is pointing right after the specifier (ex: 'test'
258 * above). Allocates and returns a NULL terminated token, or NULL if parsing
259 * fails. */
261 static unsigned char *
262 get_mailcap_field_text(unsigned char *field)
264 skip_space(field);
266 if (*field == '=') {
267 field++;
268 skip_space(field);
270 return stracpy(field);
273 return NULL;
276 /* Parse optional extra definitions. Zero return value means syntax error */
277 static inline int
278 parse_optional_fields(struct mailcap_entry *entry, unsigned char *line)
280 while (0xf131d5) {
281 unsigned char *field = get_mailcap_field(&line);
283 if (!field) break;
285 if (!c_strncasecmp(field, "needsterminal", 13)) {
286 entry->needsterminal = 1;
288 } else if (!c_strncasecmp(field, "copiousoutput", 13)) {
289 entry->copiousoutput = 1;
291 } else if (!c_strncasecmp(field, "test", 4)) {
292 /* Don't leak memory if a corrupted mailcap
293 * file has multiple test commands in the same
294 * line. */
295 mem_free_set(&entry->testcommand,
296 get_mailcap_field_text(field + 4));
297 if (!entry->testcommand)
298 return 0;
300 /* Find out wether testing requires filename */
301 for (field = entry->testcommand; *field; field++)
302 if (*field == '%' && *(field+1) == 's') {
303 mem_free(entry->testcommand);
304 return 0;
307 } else if (!c_strncasecmp(field, "description", 11)) {
308 mem_free_set(&entry->description,
309 get_mailcap_field_text(field + 11));
310 if (!entry->description)
311 return 0;
315 return 1;
318 /* Parses whole mailcap files line-by-line adding entries to the map
319 * assigning them the given @priority */
320 static void
321 parse_mailcap_file(unsigned char *filename, unsigned int priority)
323 FILE *file = fopen(filename, "rb");
324 unsigned char *line = NULL;
325 size_t linelen = MAX_STR_LEN;
326 int lineno = 1;
328 if (!file) return;
330 while ((line = file_read_line(line, &linelen, file, &lineno))) {
331 struct mailcap_entry *entry;
332 unsigned char *linepos;
333 unsigned char *command;
334 unsigned char *basetypeend;
335 unsigned char *type;
336 int typelen;
338 /* Ignore comments */
339 if (*line == '#') continue;
341 linepos = line;
343 /* Get type */
344 type = get_mailcap_field(&linepos);
345 if (!type) continue;
347 /* Next field is the viewcommand */
348 command = get_mailcap_field(&linepos);
349 if (!command) continue;
351 entry = init_mailcap_entry(command, priority);
352 if (!entry) continue;
354 if (!parse_optional_fields(entry, linepos)) {
355 done_mailcap_entry(entry);
356 usrerror(gettext("Badly formated mailcap entry "
357 "for type %s in \"%s\" line %d"),
358 type, filename, lineno);
359 continue;
362 basetypeend = strchr(type, '/');
363 typelen = strlen(type);
365 if (!basetypeend) {
366 unsigned char implicitwild[64];
368 if (typelen + 3 > sizeof(implicitwild)) {
369 done_mailcap_entry(entry);
370 continue;
373 memcpy(implicitwild, type, typelen);
374 implicitwild[typelen++] = '/';
375 implicitwild[typelen++] = '*';
376 implicitwild[typelen++] = '\0';
377 add_mailcap_entry(entry, implicitwild, typelen);
378 continue;
381 add_mailcap_entry(entry, type, typelen);
384 fclose(file);
385 mem_free_if(line); /* Alloced by file_read_line() */
389 /* When initializing the mailcap map/hash read, parse and build a hash mapping
390 * content type to handlers. Map is built from a list of mailcap files.
392 * The RFC1524 specifies that a path of mailcap files should be used.
393 * o First we check to see if the user supplied any in mime.mailcap.path
394 * o Then we check the MAILCAP environment variable.
395 * o Finally fall back to reasonable default
398 static struct hash *
399 init_mailcap_map(void)
401 unsigned char *path;
402 unsigned int priority = 0;
404 mailcap_map = init_hash8();
405 if (!mailcap_map) return NULL;
407 /* Try to setup mailcap_path */
408 path = get_mailcap_path();
409 if (!path || !*path) path = getenv("MAILCAP");
410 if (!path) path = DEFAULT_MAILCAP_PATH;
412 while (*path) {
413 unsigned char *filename = get_next_path_filename(&path, ':');
415 if (!filename) continue;
416 parse_mailcap_file(filename, priority++);
417 mem_free(filename);
420 return mailcap_map;
423 static void
424 done_mailcap(struct module *module)
426 struct hash_item *item;
427 int i;
429 if (!mailcap_map) return;
431 foreach_hash_item (item, *mailcap_map, i) {
432 struct mailcap_hash_item *mitem = item->value;
434 if (!mitem) continue;
436 while (!list_empty(mitem->entries)) {
437 struct mailcap_entry *entry = mitem->entries.next;
439 del_from_list(entry);
440 done_mailcap_entry(entry);
443 mem_free(mitem);
446 free_hash(&mailcap_map);
449 #ifndef TEST_MAILCAP
451 static int
452 change_hook_mailcap(struct session *ses, struct option *current, struct option *changed)
454 if (changed == &get_opt_mailcap(MAILCAP_PATH)
455 || (changed == &get_opt_mailcap(MAILCAP_ENABLE)
456 && !get_mailcap_enable())) {
457 done_mailcap(&mailcap_mime_module);
460 return 0;
463 static void
464 init_mailcap(struct module *module)
466 static const struct change_hook_info mimetypes_change_hooks[] = {
467 { "mime.mailcap", change_hook_mailcap },
468 { NULL, NULL },
471 register_change_hooks(mimetypes_change_hooks);
473 if (get_cmd_opt_bool("anonymous"))
474 get_mailcap_enable() = 0;
477 #else
478 #define init_mailcap NULL
479 #endif /* TEST_MAILCAP */
481 /* The command semantics include the following:
483 * %s is the filename that contains the mail body data
484 * %t is the content type, like text/plain
485 * %{parameter} is replaced by the parameter value from the content-type
486 * field
487 * \% is %
489 * Unsupported RFC1524 parameters: these would probably require some doing
490 * by Mutt, and can probably just be done by piping the message to metamail:
492 * %n is the integer number of sub-parts in the multipart
493 * %F is "content-type filename" repeated for each sub-part
494 * Only % is supported by subst_file() which is equivalent to %s. */
496 /* The formatting is postponed until the command is needed. This means
497 * @type can be NULL. If '%t' is used in command we bail out. */
498 static unsigned char *
499 format_command(unsigned char *command, unsigned char *type, int copiousoutput)
501 struct string cmd;
503 if (!init_string(&cmd)) return NULL;
505 while (*command) {
506 unsigned char *start = command;
508 while (*command && *command != '%' && *command != '\\' && *command != '\'')
509 command++;
511 if (start < command)
512 add_bytes_to_string(&cmd, start, command - start);
514 switch (*command) {
515 case '\'': /* Debian's '%s' */
516 command++;
517 if (!strncmp(command, "%s'", 3)) {
518 command += 3;
519 add_char_to_string(&cmd, '%');
520 } else {
521 add_char_to_string(&cmd, '\'');
523 break;
525 case '%':
526 command++;
527 if (!*command) {
528 done_string(&cmd);
529 return NULL;
531 } else if (*command == 's') {
532 add_char_to_string(&cmd, '%');
534 } else if (*command == 't') {
535 if (!type) {
536 done_string(&cmd);
537 return NULL;
540 add_to_string(&cmd, type);
542 command++;
543 break;
545 case '\\':
546 command++;
547 if (*command) {
548 add_char_to_string(&cmd, *command);
549 command++;
551 default:
552 break;
556 if (copiousoutput) {
557 unsigned char *pager = getenv("PAGER");
559 if (!pager && file_exists(DEFAULT_PAGER_PATH)) {
560 pager = DEFAULT_PAGER_PATH;
561 } else if (!pager && file_exists(DEFAULT_LESS_PATH)) {
562 pager = DEFAULT_LESS_PATH;
563 } else if (!pager && file_exists(DEFAULT_MORE_PATH)) {
564 pager = DEFAULT_MORE_PATH;
567 if (pager) {
568 add_char_to_string(&cmd, '|');
569 add_to_string(&cmd, pager);
573 return cmd.source;
576 /* Returns first usable mailcap_entry from a list where @entry is the head.
577 * Use of @filename is not supported (yet). */
578 static struct mailcap_entry *
579 check_entries(struct mailcap_hash_item *item)
581 struct mailcap_entry *entry;
583 foreach (entry, item->entries) {
584 unsigned char *test;
586 /* Accept current if no test is needed */
587 if (!entry->testcommand)
588 return entry;
590 /* We have to run the test command */
591 test = format_command(entry->testcommand, NULL, 0);
592 if (test) {
593 int exitcode = exe(test);
595 mem_free(test);
596 if (!exitcode)
597 return entry;
601 return NULL;
604 /* Attempts to find the given type in the mailcap association map. On success,
605 * this returns the associated command, else NULL. Type is a string with
606 * syntax '<base>/<type>' (ex: 'text/plain')
608 * First the given type is looked up. Then the given <base>-type with added
609 * wildcard '*' (ex: 'text/<star>'). For each lookup all the associated
610 * entries are checked/tested.
612 * The lookup supports testing on files. If no file is given (NULL) any tests
613 * that need a file will be taken as failed. */
615 static struct mailcap_entry *
616 get_mailcap_entry(unsigned char *type)
618 struct mailcap_entry *entry;
619 struct hash_item *item;
621 item = get_hash_item(mailcap_map, type, strlen(type));
623 /* Check list of entries */
624 entry = (item && item->value) ? check_entries(item->value) : NULL;
626 if (!entry || get_mailcap_prioritize()) {
627 /* The type lookup has either failed or we need to check
628 * the priorities so get the wild card handler */
629 struct mailcap_entry *wildcard = NULL;
630 unsigned char *wildpos = strchr(type, '/');
632 if (wildpos) {
633 int wildlen = wildpos - type + 1; /* include '/' */
634 unsigned char *wildtype = memacpy(type, wildlen + 2);
636 if (!wildtype) return NULL;
638 wildtype[wildlen++] = '*';
639 wildtype[wildlen] = '\0';
641 item = get_hash_item(mailcap_map, wildtype, wildlen);
642 mem_free(wildtype);
644 if (item && item->value)
645 wildcard = check_entries(item->value);
648 /* Use @wildcard if its priority is better or @entry is NULL */
649 if (wildcard && (!entry || (wildcard->priority < entry->priority)))
650 entry = wildcard;
653 return entry;
656 static struct mime_handler *
657 get_mime_handler_mailcap(unsigned char *type, int options)
659 struct mailcap_entry *entry;
660 struct mime_handler *handler;
661 unsigned char *program;
662 int block;
664 if (!get_mailcap_enable()
665 || (!mailcap_map && !init_mailcap_map()))
666 return NULL;
668 entry = get_mailcap_entry(type);
669 if (!entry) return NULL;
671 program = format_command(entry->command, type, entry->copiousoutput);
672 if (!program) return NULL;
674 block = (entry->needsterminal || entry->copiousoutput);
675 handler = init_mime_handler(program, entry->description,
676 mailcap_mime_module.name,
677 get_mailcap_ask(), block);
678 mem_free(program);
680 return handler;
684 const struct mime_backend mailcap_mime_backend = {
685 /* get_content_type: */ NULL,
686 /* get_mime_handler: */ get_mime_handler_mailcap,
689 /* Setup the exported module. */
690 struct module mailcap_mime_module = struct_module(
691 /* name: */ N_("Mailcap"),
692 /* options: */ mailcap_options,
693 /* hooks: */ NULL,
694 /* submodules: */ NULL,
695 /* data: */ NULL,
696 /* init: */ init_mailcap,
697 /* done: */ done_mailcap
700 #ifdef TEST_MAILCAP
702 #include "util/test.h"
704 /* Some ugly shortcuts for getting defined symbols to work. */
705 int default_mime_backend,
706 install_signal_handler,
707 mimetypes_mime_backend,
708 program;
709 LIST_OF(struct terminal) terminals;
712 main(int argc, char *argv[])
714 unsigned char *format = "description,ask,block,program";
715 int has_gotten = 0;
716 int i;
718 for (i = 1; i < argc; i++) {
719 char *arg = argv[i];
721 if (strncmp(arg, "--", 2))
722 break;
724 arg += 2;
726 if (get_test_opt(&arg, "path", &i, argc, argv, "a string")) {
727 get_mailcap_path() = arg;
728 done_mailcap(NULL);
730 } else if (get_test_opt(&arg, "format", &i, argc, argv, "a string")) {
731 format = arg;
733 } else if (get_test_opt(&arg, "get", &i, argc, argv, "a string")) {
734 struct mime_handler *handler;
736 if (has_gotten)
737 printf("\n");
738 has_gotten = 1;
739 printf("type: %s\n", arg);
740 handler = get_mime_handler_mailcap(arg, 0);
741 if (!handler) continue;
743 if (strstr(format, "description"))
744 printf("description: %s\n", handler->description);
746 if (strstr(format, "ask"))
747 printf("ask: %d\n", handler->ask);
749 if (strstr(format, "block"))
750 printf("block: %d\n", handler->block);
752 if (strstr(format, "program"))
753 printf("program: %s\n", handler->program);
755 } else {
756 die("Unknown argument '%s'", arg - 2);
760 done_mailcap(NULL);
762 return 0;
765 #endif /* TEST_MAILCAP */