bug 764: Initialize the right member of union option_value
[elinks.git] / src / mime / backend / mailcap.c
blob12bc40f84b078fe23662b9c1013e9b3f86696c29
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 entry->testcommand = get_mailcap_field_text(field + 4);
293 if (!entry->testcommand)
294 return 0;
296 /* Find out wether testing requires filename */
297 for (field = entry->testcommand; *field; field++)
298 if (*field == '%' && *(field+1) == 's') {
299 mem_free(entry->testcommand);
300 return 0;
303 } else if (!c_strncasecmp(field, "description", 11)) {
304 entry->description = get_mailcap_field_text(field + 11);
305 if (!entry->description)
306 return 0;
310 return 1;
313 /* Parses whole mailcap files line-by-line adding entries to the map
314 * assigning them the given @priority */
315 static void
316 parse_mailcap_file(unsigned char *filename, unsigned int priority)
318 FILE *file = fopen(filename, "rb");
319 unsigned char *line = NULL;
320 size_t linelen = MAX_STR_LEN;
321 int lineno = 1;
323 if (!file) return;
325 while ((line = file_read_line(line, &linelen, file, &lineno))) {
326 struct mailcap_entry *entry;
327 unsigned char *linepos;
328 unsigned char *command;
329 unsigned char *basetypeend;
330 unsigned char *type;
331 int typelen;
333 /* Ignore comments */
334 if (*line == '#') continue;
336 linepos = line;
338 /* Get type */
339 type = get_mailcap_field(&linepos);
340 if (!type) continue;
342 /* Next field is the viewcommand */
343 command = get_mailcap_field(&linepos);
344 if (!command) continue;
346 entry = init_mailcap_entry(command, priority);
347 if (!entry) continue;
349 if (!parse_optional_fields(entry, linepos)) {
350 done_mailcap_entry(entry);
351 usrerror(gettext("Badly formated mailcap entry "
352 "for type %s in \"%s\" line %d"),
353 type, filename, lineno);
354 continue;
357 basetypeend = strchr(type, '/');
358 typelen = strlen(type);
360 if (!basetypeend) {
361 unsigned char implicitwild[64];
363 if (typelen + 3 > sizeof(implicitwild)) {
364 done_mailcap_entry(entry);
365 continue;
368 memcpy(implicitwild, type, typelen);
369 implicitwild[typelen++] = '/';
370 implicitwild[typelen++] = '*';
371 implicitwild[typelen++] = '\0';
372 add_mailcap_entry(entry, implicitwild, typelen);
373 continue;
376 add_mailcap_entry(entry, type, typelen);
379 fclose(file);
380 mem_free_if(line); /* Alloced by file_read_line() */
384 /* When initializing the mailcap map/hash read, parse and build a hash mapping
385 * content type to handlers. Map is built from a list of mailcap files.
387 * The RFC1524 specifies that a path of mailcap files should be used.
388 * o First we check to see if the user supplied any in mime.mailcap.path
389 * o Then we check the MAILCAP environment variable.
390 * o Finally fall back to reasonable default
393 static struct hash *
394 init_mailcap_map(void)
396 unsigned char *path;
397 unsigned int priority = 0;
399 mailcap_map = init_hash8();
400 if (!mailcap_map) return NULL;
402 /* Try to setup mailcap_path */
403 path = get_mailcap_path();
404 if (!path || !*path) path = getenv("MAILCAP");
405 if (!path) path = DEFAULT_MAILCAP_PATH;
407 while (*path) {
408 unsigned char *filename = get_next_path_filename(&path, ':');
410 if (!filename) continue;
411 parse_mailcap_file(filename, priority++);
412 mem_free(filename);
415 return mailcap_map;
418 static void
419 done_mailcap(struct module *module)
421 struct hash_item *item;
422 int i;
424 if (!mailcap_map) return;
426 foreach_hash_item (item, *mailcap_map, i) {
427 struct mailcap_hash_item *mitem = item->value;
429 if (!mitem) continue;
431 while (!list_empty(mitem->entries)) {
432 struct mailcap_entry *entry = mitem->entries.next;
434 del_from_list(entry);
435 done_mailcap_entry(entry);
438 mem_free(mitem);
441 free_hash(&mailcap_map);
444 #ifndef TEST_MAILCAP
446 static int
447 change_hook_mailcap(struct session *ses, struct option *current, struct option *changed)
449 if (changed == &get_opt_mailcap(MAILCAP_PATH)
450 || (changed == &get_opt_mailcap(MAILCAP_ENABLE)
451 && !get_mailcap_enable())) {
452 done_mailcap(&mailcap_mime_module);
455 return 0;
458 static void
459 init_mailcap(struct module *module)
461 static const struct change_hook_info mimetypes_change_hooks[] = {
462 { "mime.mailcap", change_hook_mailcap },
463 { NULL, NULL },
466 register_change_hooks(mimetypes_change_hooks);
468 if (get_cmd_opt_bool("anonymous"))
469 get_mailcap_enable() = 0;
472 #else
473 #define init_mailcap NULL
474 #endif /* TEST_MAILCAP */
476 /* The command semantics include the following:
478 * %s is the filename that contains the mail body data
479 * %t is the content type, like text/plain
480 * %{parameter} is replaced by the parameter value from the content-type
481 * field
482 * \% is %
484 * Unsupported RFC1524 parameters: these would probably require some doing
485 * by Mutt, and can probably just be done by piping the message to metamail:
487 * %n is the integer number of sub-parts in the multipart
488 * %F is "content-type filename" repeated for each sub-part
489 * Only % is supported by subst_file() which is equivalent to %s. */
491 /* The formatting is postponed until the command is needed. This means
492 * @type can be NULL. If '%t' is used in command we bail out. */
493 static unsigned char *
494 format_command(unsigned char *command, unsigned char *type, int copiousoutput)
496 struct string cmd;
498 if (!init_string(&cmd)) return NULL;
500 while (*command) {
501 unsigned char *start = command;
503 while (*command && *command != '%' && *command != '\\' && *command != '\'')
504 command++;
506 if (start < command)
507 add_bytes_to_string(&cmd, start, command - start);
509 switch (*command) {
510 case '\'': /* Debian's '%s' */
511 command++;
512 if (!strncmp(command, "%s'", 3)) {
513 command += 3;
514 add_char_to_string(&cmd, '%');
515 } else {
516 add_char_to_string(&cmd, '\'');
518 break;
520 case '%':
521 command++;
522 if (!*command) {
523 done_string(&cmd);
524 return NULL;
526 } else if (*command == 's') {
527 add_char_to_string(&cmd, '%');
529 } else if (*command == 't') {
530 if (!type) {
531 done_string(&cmd);
532 return NULL;
535 add_to_string(&cmd, type);
537 command++;
538 break;
540 case '\\':
541 command++;
542 if (*command) {
543 add_char_to_string(&cmd, *command);
544 command++;
546 default:
547 break;
551 if (copiousoutput) {
552 unsigned char *pager = getenv("PAGER");
554 if (!pager && file_exists(DEFAULT_PAGER_PATH)) {
555 pager = DEFAULT_PAGER_PATH;
556 } else if (!pager && file_exists(DEFAULT_LESS_PATH)) {
557 pager = DEFAULT_LESS_PATH;
558 } else if (!pager && file_exists(DEFAULT_MORE_PATH)) {
559 pager = DEFAULT_MORE_PATH;
562 if (pager) {
563 add_char_to_string(&cmd, '|');
564 add_to_string(&cmd, pager);
568 return cmd.source;
571 /* Returns first usable mailcap_entry from a list where @entry is the head.
572 * Use of @filename is not supported (yet). */
573 static struct mailcap_entry *
574 check_entries(struct mailcap_hash_item *item)
576 struct mailcap_entry *entry;
578 foreach (entry, item->entries) {
579 unsigned char *test;
581 /* Accept current if no test is needed */
582 if (!entry->testcommand)
583 return entry;
585 /* We have to run the test command */
586 test = format_command(entry->testcommand, NULL, 0);
587 if (test) {
588 int exitcode = exe(test);
590 mem_free(test);
591 if (!exitcode)
592 return entry;
596 return NULL;
599 /* Attempts to find the given type in the mailcap association map. On success,
600 * this returns the associated command, else NULL. Type is a string with
601 * syntax '<base>/<type>' (ex: 'text/plain')
603 * First the given type is looked up. Then the given <base>-type with added
604 * wildcard '*' (ex: 'text/<star>'). For each lookup all the associated
605 * entries are checked/tested.
607 * The lookup supports testing on files. If no file is given (NULL) any tests
608 * that need a file will be taken as failed. */
610 static struct mailcap_entry *
611 get_mailcap_entry(unsigned char *type)
613 struct mailcap_entry *entry;
614 struct hash_item *item;
616 item = get_hash_item(mailcap_map, type, strlen(type));
618 /* Check list of entries */
619 entry = (item && item->value) ? check_entries(item->value) : NULL;
621 if (!entry || get_mailcap_prioritize()) {
622 /* The type lookup has either failed or we need to check
623 * the priorities so get the wild card handler */
624 struct mailcap_entry *wildcard = NULL;
625 unsigned char *wildpos = strchr(type, '/');
627 if (wildpos) {
628 int wildlen = wildpos - type + 1; /* include '/' */
629 unsigned char *wildtype = memacpy(type, wildlen + 2);
631 if (!wildtype) return NULL;
633 wildtype[wildlen++] = '*';
634 wildtype[wildlen] = '\0';
636 item = get_hash_item(mailcap_map, wildtype, wildlen);
637 mem_free(wildtype);
639 if (item && item->value)
640 wildcard = check_entries(item->value);
643 /* Use @wildcard if its priority is better or @entry is NULL */
644 if (wildcard && (!entry || (wildcard->priority < entry->priority)))
645 entry = wildcard;
648 return entry;
651 static struct mime_handler *
652 get_mime_handler_mailcap(unsigned char *type, int options)
654 struct mailcap_entry *entry;
655 struct mime_handler *handler;
656 unsigned char *program;
657 int block;
659 if (!get_mailcap_enable()
660 || (!mailcap_map && !init_mailcap_map()))
661 return NULL;
663 entry = get_mailcap_entry(type);
664 if (!entry) return NULL;
666 program = format_command(entry->command, type, entry->copiousoutput);
667 if (!program) return NULL;
669 block = (entry->needsterminal || entry->copiousoutput);
670 handler = init_mime_handler(program, entry->description,
671 mailcap_mime_module.name,
672 get_mailcap_ask(), block);
673 mem_free(program);
675 return handler;
679 const struct mime_backend mailcap_mime_backend = {
680 /* get_content_type: */ NULL,
681 /* get_mime_handler: */ get_mime_handler_mailcap,
684 /* Setup the exported module. */
685 struct module mailcap_mime_module = struct_module(
686 /* name: */ N_("Mailcap"),
687 /* options: */ mailcap_options,
688 /* hooks: */ NULL,
689 /* submodules: */ NULL,
690 /* data: */ NULL,
691 /* init: */ init_mailcap,
692 /* done: */ done_mailcap
695 #ifdef TEST_MAILCAP
697 #include "util/test.h"
699 /* Some ugly shortcuts for getting defined symbols to work. */
700 int default_mime_backend,
701 install_signal_handler,
702 mimetypes_mime_backend,
703 program;
704 LIST_OF(struct terminal) terminals;
707 main(int argc, char *argv[])
709 unsigned char *format = "description,ask,block,program";
710 int has_gotten = 0;
711 int i;
713 for (i = 1; i < argc; i++) {
714 char *arg = argv[i];
716 if (strncmp(arg, "--", 2))
717 break;
719 arg += 2;
721 if (get_test_opt(&arg, "path", &i, argc, argv, "a string")) {
722 get_mailcap_path() = arg;
723 done_mailcap(NULL);
725 } else if (get_test_opt(&arg, "format", &i, argc, argv, "a string")) {
726 format = arg;
728 } else if (get_test_opt(&arg, "get", &i, argc, argv, "a string")) {
729 struct mime_handler *handler;
731 if (has_gotten)
732 printf("\n");
733 has_gotten = 1;
734 printf("type: %s\n", arg);
735 handler = get_mime_handler_mailcap(arg, 0);
736 if (!handler) continue;
738 if (strstr(format, "description"))
739 printf("description: %s\n", handler->description);
741 if (strstr(format, "ask"))
742 printf("ask: %d\n", handler->ask);
744 if (strstr(format, "block"))
745 printf("block: %d\n", handler->block);
747 if (strstr(format, "program"))
748 printf("program: %s\n", handler->program);
750 } else {
751 die("Unknown argument '%s'", arg - 2);
755 done_mailcap(NULL);
757 return 0;
760 #endif /* TEST_MAILCAP */