Handle mailcap's copiousoutput without an external pager.
[elinks.git] / src / mime / backend / mailcap.c
blob4c13c1845ebfa499cbff84091b36fcaeb09265b6
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 struct 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;
550 return cmd.source;
553 /* Returns first usable mailcap_entry from a list where @entry is the head.
554 * Use of @filename is not supported (yet). */
555 static struct mailcap_entry *
556 check_entries(struct mailcap_hash_item *item)
558 struct mailcap_entry *entry;
560 foreach (entry, item->entries) {
561 unsigned char *test;
563 /* Accept current if no test is needed */
564 if (!entry->testcommand)
565 return entry;
567 /* We have to run the test command */
568 test = format_command(entry->testcommand, NULL, 0);
569 if (test) {
570 int exitcode = exe(test);
572 mem_free(test);
573 if (!exitcode)
574 return entry;
578 return NULL;
581 /* Attempts to find the given type in the mailcap association map. On success,
582 * this returns the associated command, else NULL. Type is a string with
583 * syntax '<base>/<type>' (ex: 'text/plain')
585 * First the given type is looked up. Then the given <base>-type with added
586 * wildcard '*' (ex: 'text/<star>'). For each lookup all the associated
587 * entries are checked/tested.
589 * The lookup supports testing on files. If no file is given (NULL) any tests
590 * that need a file will be taken as failed. */
592 static struct mailcap_entry *
593 get_mailcap_entry(unsigned char *type)
595 struct mailcap_entry *entry;
596 struct hash_item *item;
598 item = get_hash_item(mailcap_map, type, strlen(type));
600 /* Check list of entries */
601 entry = (item && item->value) ? check_entries(item->value) : NULL;
603 if (!entry || get_mailcap_prioritize()) {
604 /* The type lookup has either failed or we need to check
605 * the priorities so get the wild card handler */
606 struct mailcap_entry *wildcard = NULL;
607 unsigned char *wildpos = strchr(type, '/');
609 if (wildpos) {
610 int wildlen = wildpos - type + 1; /* include '/' */
611 unsigned char *wildtype = memacpy(type, wildlen + 2);
613 if (!wildtype) return NULL;
615 wildtype[wildlen++] = '*';
616 wildtype[wildlen] = '\0';
618 item = get_hash_item(mailcap_map, wildtype, wildlen);
619 mem_free(wildtype);
621 if (item && item->value)
622 wildcard = check_entries(item->value);
625 /* Use @wildcard if its priority is better or @entry is NULL */
626 if (wildcard && (!entry || (wildcard->priority < entry->priority)))
627 entry = wildcard;
630 return entry;
633 #if defined(HAVE_SETENV) || defined(HAVE_PUTENV)
634 /** Set or unset the DISPLAY environment variable before a mailcap
635 * check, or restore it afterwards.
637 * In a mailcap file, each entry can specify a test command that
638 * checks whether the entry is applicable to the user's environment.
639 * For example:
641 * @verbatim
642 * audio/mpegurl; xmms %s; test=test "$DISPLAY" != ""
643 * @endverbatim
645 * This means the entry should be used only if the DISPLAY environment
646 * variable is not empty, i.e. there is an X display. In ELinks,
647 * check_entries() runs these test commands, so they inherit the
648 * environment variables of the master ELinks process. However, if
649 * the user is running ELinks on multiple terminals, then each slave
650 * ELinks process has its own environment variables, which may or may
651 * not include DISPLAY. Because the actual mailcap command may be run
652 * from a slave ELinks process and inherit the environment from it,
653 * any test command should also be run in the same environment.
655 * This function does not fully implement the ideal described above.
656 * Instead, it only sets the DISPLAY environment variable as ":0" if
657 * the terminal has any X display at all, or unsets DISPLAY if not.
658 * This should be enough for most test commands seen in practice.
659 * After the test commands of mailcap entries have been run, this
660 * function must be called again to restore DISPLAY.
662 * @todo Retrieve all environment variables from the slave process and
663 * propagate them to the test commands. Actually, it might be best
664 * to fork the test commands from the slave process, so that they
665 * would also inherit the controlling tty. However, that would
666 * require changing the interlink protocol and might risk deadlocks
667 * or memory leaks if a slave terminates without responding.
669 * @param xwin
670 * Whether the terminal has an associated X display.
671 * @param restore
672 * If this is 0, the function sets or clears DISPLAY, as described above.
673 * If this is 1, the function restores the original value of DISPLAY.
674 * There is only room for one saved value; do not nest calls. */
675 static void
676 set_display(int xwin, int restore)
678 static unsigned char *display = NULL;
680 if (!restore) {
681 assert(display == NULL);
682 if_assert_failed mem_free(display);
684 display = getenv("DISPLAY");
685 if (display) display = stracpy(display);
686 if (xwin) {
687 #ifdef HAVE_SETENV
688 setenv("DISPLAY", ":0", 1);
689 #else
690 putenv("DISPLAY=:0");
691 #endif
692 } else {
693 #ifdef HAVE_UNSETENV
694 unsetenv("DISPLAY");
695 #else
696 putenv("DISPLAY");
697 #endif
699 } else { /* restore DISPLAY */
700 if (display) {
701 #ifdef HAVE_SETENV
702 setenv("DISPLAY", display, 1);
703 #else
705 static unsigned char DISPLAY[1024] = "DISPLAY=";
707 /* DISPLAY[1023] == '\0' and we don't let
708 * strncpy write that far, so putenv always
709 * gets a null-terminated string. */
710 strncpy(DISPLAY + 8, display, 1023 - 8);
711 putenv(DISPLAY);
713 #endif
714 mem_free_set(&display, NULL);
715 } else {
716 #ifdef HAVE_UNSETENV
717 unsetenv("DISPLAY");
718 #else
719 putenv("DISPLAY");
720 #endif
724 #endif
726 static struct mime_handler *
727 get_mime_handler_mailcap(unsigned char *type, int xwin)
729 struct mailcap_entry *entry;
730 struct mime_handler *handler;
731 unsigned char *program;
732 int block;
734 if (!get_mailcap_enable()
735 || (!mailcap_map && !init_mailcap_map()))
736 return NULL;
738 #if defined(HAVE_SETENV) || defined(HAVE_PUTENV)
739 set_display(xwin, 0);
740 #endif
741 entry = get_mailcap_entry(type);
743 #if defined(HAVE_SETENV) || defined(HAVE_PUTENV)
744 set_display(xwin, 1);
745 #endif
746 if (!entry) return NULL;
748 program = format_command(entry->command, type, entry->copiousoutput);
749 if (!program) return NULL;
751 block = (entry->needsterminal || entry->copiousoutput);
752 handler = init_mime_handler(program, entry->description,
753 mailcap_mime_module.name,
754 get_mailcap_ask(), block);
755 mem_free(program);
757 handler->copiousoutput = entry->copiousoutput;
758 return handler;
762 const struct mime_backend mailcap_mime_backend = {
763 /* get_content_type: */ NULL,
764 /* get_mime_handler: */ get_mime_handler_mailcap,
767 /* Setup the exported module. */
768 struct module mailcap_mime_module = struct_module(
769 /* name: */ N_("Mailcap"),
770 /* options: */ mailcap_options,
771 /* hooks: */ NULL,
772 /* submodules: */ NULL,
773 /* data: */ NULL,
774 /* init: */ init_mailcap,
775 /* done: */ done_mailcap
778 #ifdef TEST_MAILCAP
780 #include "util/test.h"
782 /* Some ugly shortcuts for getting defined symbols to work. */
783 int default_mime_backend,
784 install_signal_handler,
785 mimetypes_mime_backend,
786 program;
787 LIST_OF(struct terminal) terminals;
790 main(int argc, char *argv[])
792 unsigned char *format = "description,ask,block,program";
793 int has_gotten = 0;
794 int i;
796 for (i = 1; i < argc; i++) {
797 char *arg = argv[i];
799 if (strncmp(arg, "--", 2))
800 break;
802 arg += 2;
804 if (get_test_opt(&arg, "path", &i, argc, argv, "a string")) {
805 get_mailcap_path() = arg;
806 done_mailcap(NULL);
808 } else if (get_test_opt(&arg, "format", &i, argc, argv, "a string")) {
809 format = arg;
811 } else if (get_test_opt(&arg, "get", &i, argc, argv, "a string")) {
812 struct mime_handler *handler;
814 if (has_gotten)
815 printf("\n");
816 has_gotten = 1;
817 printf("type: %s\n", arg);
818 handler = get_mime_handler_mailcap(arg, 0);
819 if (!handler) continue;
821 if (strstr(format, "description"))
822 printf("description: %s\n", handler->description);
824 if (strstr(format, "ask"))
825 printf("ask: %d\n", handler->ask);
827 if (strstr(format, "block"))
828 printf("block: %d\n", handler->block);
830 if (strstr(format, "program"))
831 printf("program: %s\n", handler->program);
833 } else {
834 die("Unknown argument '%s'", arg - 2);
838 done_mailcap(NULL);
840 return 0;
843 #endif /* TEST_MAILCAP */