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 union 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 /* Don't leak memory if a corrupted mailcap
293 * file has multiple test commands in the same
295 mem_free_set(&entry
->testcommand
,
296 get_mailcap_field_text(field
+ 4));
297 if (!entry
->testcommand
)
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
);
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
)
318 /* Parses whole mailcap files line-by-line adding entries to the map
319 * assigning them the given @priority */
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
;
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
;
338 /* Ignore comments */
339 if (*line
== '#') continue;
344 type
= get_mailcap_field(&linepos
);
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
);
362 basetypeend
= strchr(type
, '/');
363 typelen
= strlen(type
);
366 unsigned char implicitwild
[64];
368 if (typelen
+ 3 > sizeof(implicitwild
)) {
369 done_mailcap_entry(entry
);
373 memcpy(implicitwild
, type
, typelen
);
374 implicitwild
[typelen
++] = '/';
375 implicitwild
[typelen
++] = '*';
376 implicitwild
[typelen
++] = '\0';
377 add_mailcap_entry(entry
, implicitwild
, typelen
);
381 add_mailcap_entry(entry
, type
, typelen
);
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
399 init_mailcap_map(void)
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
;
413 unsigned char *filename
= get_next_path_filename(&path
, ':');
415 if (!filename
) continue;
416 parse_mailcap_file(filename
, priority
++);
424 done_mailcap(struct module
*module
)
426 struct hash_item
*item
;
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
);
446 free_hash(&mailcap_map
);
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
);
464 init_mailcap(struct module
*module
)
466 static const struct change_hook_info mimetypes_change_hooks
[] = {
467 { "mime.mailcap", change_hook_mailcap
},
471 register_change_hooks(mimetypes_change_hooks
);
473 if (get_cmd_opt_bool("anonymous"))
474 get_mailcap_enable() = 0;
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
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
)
503 if (!init_string(&cmd
)) return NULL
;
506 unsigned char *start
= command
;
508 while (*command
&& *command
!= '%' && *command
!= '\\' && *command
!= '\'')
512 add_bytes_to_string(&cmd
, start
, command
- start
);
515 case '\'': /* Debian's '%s' */
517 if (!strncmp(command
, "%s'", 3)) {
519 add_char_to_string(&cmd
, '%');
521 add_char_to_string(&cmd
, '\'');
531 } else if (*command
== 's') {
532 add_char_to_string(&cmd
, '%');
534 } else if (*command
== 't') {
540 add_to_string(&cmd
, type
);
548 add_char_to_string(&cmd
, *command
);
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
;
568 add_char_to_string(&cmd
, '|');
569 add_to_string(&cmd
, pager
);
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
) {
586 /* Accept current if no test is needed */
587 if (!entry
->testcommand
)
590 /* We have to run the test command */
591 test
= format_command(entry
->testcommand
, NULL
, 0);
593 int exitcode
= exe(test
);
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
, '/');
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
);
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
)))
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
;
664 if (!get_mailcap_enable()
665 || (!mailcap_map
&& !init_mailcap_map()))
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
);
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
,
694 /* submodules: */ NULL
,
696 /* init: */ init_mailcap
,
697 /* done: */ done_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
,
709 LIST_OF(struct terminal
) terminals
;
712 main(int argc
, char *argv
[])
714 unsigned char *format
= "description,ask,block,program";
718 for (i
= 1; i
< argc
; i
++) {
721 if (strncmp(arg
, "--", 2))
726 if (get_test_opt(&arg
, "path", &i
, argc
, argv
, "a string")) {
727 get_mailcap_path() = arg
;
730 } else if (get_test_opt(&arg
, "format", &i
, argc
, argv
, "a string")) {
733 } else if (get_test_opt(&arg
, "get", &i
, argc
, argv
, "a string")) {
734 struct mime_handler
*handler
;
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
);
756 die("Unknown argument '%s'", arg
- 2);
765 #endif /* TEST_MAILCAP */