1 /* RFC1524 (mailcap file) implementation */
3 /* This file contains various functions for implementing a fair subset of
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
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
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];
88 static struct option_info mailcap_options
[] = {
89 INIT_OPT_TREE("mime", N_("Mailcap"),
91 N_("Options for mailcap support.")),
93 INIT_OPT_BOOL("mime.mailcap", N_("Enable"),
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"),
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"),
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 "
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
;
138 done_mailcap_entry(struct mailcap_entry
*entry
)
141 mem_free_if(entry
->testcommand
);
142 mem_free_if(entry
->description
);
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
;
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
);
175 mitem
= mem_alloc(sizeof(*mitem
) + typelen
);
177 done_mailcap_entry(entry
);
181 safe_strncpy(mitem
->type
, type
, typelen
+ 1);
183 init_list(mitem
->entries
);
185 item
= add_hash_item(mailcap_map
, mitem
->type
, typelen
, mitem
);
188 done_mailcap_entry(entry
);
191 } else if (item
->value
) {
194 done_mailcap_entry(entry
);
198 add_to_list_end(mitem
->entries
, entry
);
201 /* Parsing of a RFC1524 mailcap file */
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
;
229 /* End field at the next occurence of ';' but not escaped '\;' */
231 /* Handle both if ';' is the first char or if it's escaped */
232 if (*fieldend
== ';')
235 fieldend
= strchr(fieldend
, ';');
236 } while (fieldend
&& *(fieldend
-1) == '\\');
246 fieldend
= field
+ strlen(field
) - 1;
249 /* Remove trailing whitespace */
250 while (field
<= fieldend
&& isspace(*fieldend
))
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
261 static unsigned char *
262 get_mailcap_field_text(unsigned char *field
)
270 return stracpy(field
);
276 /* Parse optional extra definitions. Zero return value means syntax error */
278 parse_optional_fields(struct mailcap_entry
*entry
, unsigned char *line
)
281 unsigned char *field
= get_mailcap_field(&line
);
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
)
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
);
303 } else if (!c_strncasecmp(field
, "description", 11)) {
304 entry
->description
= get_mailcap_field_text(field
+ 11);
305 if (!entry
->description
)
313 /* Parses whole mailcap files line-by-line adding entries to the map
314 * assigning them the given @priority */
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
;
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
;
333 /* Ignore comments */
334 if (*line
== '#') continue;
339 type
= get_mailcap_field(&linepos
);
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
);
357 basetypeend
= strchr(type
, '/');
358 typelen
= strlen(type
);
361 unsigned char implicitwild
[64];
363 if (typelen
+ 3 > sizeof(implicitwild
)) {
364 done_mailcap_entry(entry
);
368 memcpy(implicitwild
, type
, typelen
);
369 implicitwild
[typelen
++] = '/';
370 implicitwild
[typelen
++] = '*';
371 implicitwild
[typelen
++] = '\0';
372 add_mailcap_entry(entry
, implicitwild
, typelen
);
376 add_mailcap_entry(entry
, type
, typelen
);
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
394 init_mailcap_map(void)
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
;
408 unsigned char *filename
= get_next_path_filename(&path
, ':');
410 if (!filename
) continue;
411 parse_mailcap_file(filename
, priority
++);
419 done_mailcap(struct module
*module
)
421 struct hash_item
*item
;
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
);
441 free_hash(&mailcap_map
);
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
);
459 init_mailcap(struct module
*module
)
461 static const struct change_hook_info mimetypes_change_hooks
[] = {
462 { "mime.mailcap", change_hook_mailcap
},
466 register_change_hooks(mimetypes_change_hooks
);
468 if (get_cmd_opt_bool("anonymous"))
469 get_mailcap_enable() = 0;
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
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
)
498 if (!init_string(&cmd
)) return NULL
;
501 unsigned char *start
= command
;
503 while (*command
&& *command
!= '%' && *command
!= '\\' && *command
!= '\'')
507 add_bytes_to_string(&cmd
, start
, command
- start
);
510 case '\'': /* Debian's '%s' */
512 if (!strncmp(command
, "%s'", 3)) {
514 add_char_to_string(&cmd
, '%');
516 add_char_to_string(&cmd
, '\'');
526 } else if (*command
== 's') {
527 add_char_to_string(&cmd
, '%');
529 } else if (*command
== 't') {
535 add_to_string(&cmd
, type
);
543 add_char_to_string(&cmd
, *command
);
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
) {
563 /* Accept current if no test is needed */
564 if (!entry
->testcommand
)
567 /* We have to run the test command */
568 test
= format_command(entry
->testcommand
, NULL
, 0);
570 int exitcode
= exe(test
);
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
, '/');
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
);
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
)))
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.
642 * audio/mpegurl; xmms %s; test=test "$DISPLAY" != ""
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.
670 * Whether the terminal has an associated X display.
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. */
676 set_display(int xwin
, int restore
)
678 static unsigned char *display
= NULL
;
681 assert(display
== NULL
);
682 if_assert_failed
mem_free(display
);
684 display
= getenv("DISPLAY");
685 if (display
) display
= stracpy(display
);
688 setenv("DISPLAY", ":0", 1);
690 putenv("DISPLAY=:0");
699 } else { /* restore DISPLAY */
702 setenv("DISPLAY", display
, 1);
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);
714 mem_free_set(&display
, NULL
);
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
;
734 if (!get_mailcap_enable()
735 || (!mailcap_map
&& !init_mailcap_map()))
738 #if defined(HAVE_SETENV) || defined(HAVE_PUTENV)
739 set_display(xwin
, 0);
741 entry
= get_mailcap_entry(type
);
743 #if defined(HAVE_SETENV) || defined(HAVE_PUTENV)
744 set_display(xwin
, 1);
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
);
757 handler
->copiousoutput
= entry
->copiousoutput
;
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
,
772 /* submodules: */ NULL
,
774 /* init: */ init_mailcap
,
775 /* done: */ done_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
,
787 LIST_OF(struct terminal
) terminals
;
790 main(int argc
, char *argv
[])
792 unsigned char *format
= "description,ask,block,program";
796 for (i
= 1; i
< argc
; i
++) {
799 if (strncmp(arg
, "--", 2))
804 if (get_test_opt(&arg
, "path", &i
, argc
, argv
, "a string")) {
805 get_mailcap_path() = arg
;
808 } else if (get_test_opt(&arg
, "format", &i
, argc
, argv
, "a string")) {
811 } else if (get_test_opt(&arg
, "get", &i
, argc
, argv
, "a string")) {
812 struct mime_handler
*handler
;
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
);
834 die("Unknown argument '%s'", arg
- 2);
843 #endif /* TEST_MAILCAP */