* For mailing lists, Alpine adds a description of the type of link
[alpine.git] / pith / mailcap.c
blob1e5b16c9d50060ac70997293901ba7e0c7b88a8f
1 /*
2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2008 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 #include "../pith/headers.h"
16 #include "../pith/mailcap.h"
17 #include "../pith/init.h"
18 #include "../pith/conf.h"
19 #include "../pith/mimetype.h"
20 #include "../pith/mimedesc.h"
21 #include "../pith/status.h"
22 #include "../pith/util.h"
23 #include "../pith/readfile.h"
26 * We've decided not to implement the RFC1524 standard minimum path, because
27 * some of us think it is harder to debug a problem when you may be misled
28 * into looking at the wrong mailcap entry. Likewise for MIME.Types files.
30 #if defined(DOS) || defined(OS2)
31 #define MC_PATH_SEPARATOR ';'
32 #define MC_USER_FILE "MAILCAP"
33 #define MC_STDPATH NULL
34 #else /* !DOS */
35 #define MC_PATH_SEPARATOR ':'
36 #define MC_USER_FILE NULL
37 #define MC_STDPATH \
38 ".mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap"
39 #endif /* !DOS */
41 #ifdef _WINDOWS
42 #define MC_ADD_TMP " %s"
43 #else
44 #define MC_ADD_TMP " < %s"
45 #endif
47 typedef struct mcap_entry {
48 struct mcap_entry *next;
49 int needsterminal;
50 char *contenttype;
51 char *command;
52 char *testcommand;
53 char *label; /* unused */
54 char *printcommand; /* unused */
55 } MailcapEntry;
57 struct mailcap_data {
58 MailcapEntry *head, **tail;
59 STRINGLIST *raw;
60 } MailcapData;
62 #define MC_TOKEN_MAX 64
66 * Internal prototypes
68 void mc_init(void);
69 void mc_process_file(char *);
70 void mc_parse_file(char *);
71 int mc_parse_line(char **, char **);
72 int mc_comment(char **);
73 int mc_token(char **, char **);
74 void mc_build_entry(char **);
75 int mc_sane_command(char *);
76 MailcapEntry *mc_get_command(int, char *, BODY *, int, int *);
77 int mc_ctype_match(int, char *, char *);
78 int mc_passes_test(MailcapEntry *, int, char *, BODY *);
79 char *mc_bld_test_cmd(char *, int, char *, BODY *);
80 char *mc_cmd_bldr(char *, int, char *, BODY *, char *, char **);
81 MailcapEntry *mc_new_entry(void);
82 void mc_free_entry(MailcapEntry **);
85 char *
86 mc_conf_path(char *def_path, char *env_path, char *user_file, int separator, char *stdpath)
88 char *path;
90 /* We specify MIMETYPES as a path override */
91 if(def_path)
92 /* there may need to be an override specific to pine */
93 path = cpystr(def_path);
94 else if(env_path)
95 path = cpystr(env_path);
96 else{
97 #if defined(DOS) || defined(OS2)
98 char *s;
101 * This gets interesting. Since we don't have any standard location
102 * for config/data files, look in the same directory as the PINERC
103 * and the same dir as PINE.EXE. This is similar to the UNIX
104 * situation with personal config info coming before
105 * potentially shared config data...
107 if(s = last_cmpnt(ps_global->pinerc)){
108 strncpy(tmp_20k_buf+1000, ps_global->pinerc, MIN(s - ps_global->pinerc,SIZEOF_20KBUF-1000));
109 tmp_20k_buf[1000+MIN(s - ps_global->pinerc,SIZEOF_20KBUF-1000-1)] = '\0';
111 else
112 strncpy(tmp_20k_buf+1000, ".\\", SIZEOF_20KBUF-1000);
114 /* pinerc directory version of file */
115 build_path(tmp_20k_buf+2000, tmp_20k_buf+1000, user_file, SIZEOF_20KBUF-2000);
116 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
118 /* pine.exe directory version of file */
119 build_path(tmp_20k_buf+3000, ps_global->pine_dir, user_file, SIZEOF_20KBUF-3000);
120 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
122 /* combine them */
123 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%c%s", tmp_20k_buf+2000, separator, tmp_20k_buf+3000);
125 #else /* !DOS */
126 build_path(tmp_20k_buf, ps_global->home_dir, stdpath, SIZEOF_20KBUF);
127 #endif /* !DOS */
128 path = cpystr(tmp_20k_buf);
131 return(path);
136 * mc_init - Run down the path gathering all the mailcap entries.
137 * Returns with the Mailcap list built.
139 void
140 mc_init(void)
142 char *s,
143 *pathcopy,
144 *path,
145 image_viewer[MAILTMPLEN];
147 if(MailcapData.raw) /* already have the file? */
148 return;
149 else
150 MailcapData.tail = &MailcapData.head;
152 dprint((5, "- mc_init -\n"));
154 pathcopy = mc_conf_path(ps_global->VAR_MAILCAP_PATH, getenv("MAILCAPS"),
155 MC_USER_FILE, MC_PATH_SEPARATOR, MC_STDPATH);
157 path = pathcopy; /* overloaded "path" */
160 * Insert an entry for the image-viewer variable from .pinerc, if present.
162 if(ps_global->VAR_IMAGE_VIEWER && *ps_global->VAR_IMAGE_VIEWER){
163 MailcapEntry *mc = mc_new_entry();
165 snprintf(image_viewer, sizeof(image_viewer), "%s %%s", ps_global->VAR_IMAGE_VIEWER);
167 MailcapData.raw = mail_newstringlist();
168 MailcapData.raw->text.data = (unsigned char *) cpystr(image_viewer);
169 mc->command = (char *) MailcapData.raw->text.data;
170 mc->contenttype = "image/*";
171 mc->label = "Alpine Image Viewer";
172 dprint((5, "mailcap: using image-viewer=%s\n",
173 ps_global->VAR_IMAGE_VIEWER
174 ? ps_global->VAR_IMAGE_VIEWER : "?"));
177 dprint((7, "mailcap: path: %s\n", path ? path : "?"));
178 while(path){
179 s = strindex(path, MC_PATH_SEPARATOR);
180 if(s)
181 *s++ = '\0';
182 mc_process_file(path);
183 path = s;
186 if(pathcopy)
187 fs_give((void **)&pathcopy);
189 #ifdef DEBUG
190 if(debug >= 11){
191 MailcapEntry *mc;
192 int i = 0;
194 dprint((11, "Collected mailcap entries\n"));
195 for(mc = MailcapData.head; mc; mc = mc->next){
197 dprint((11, "%d: ", i++));
198 if(mc->label)
199 dprint((11, "%s\n", mc->label ? mc->label : "?"));
200 if(mc->contenttype)
201 dprint((11, " %s",
202 mc->contenttype ? mc->contenttype : "?"));
203 if(mc->command)
204 dprint((11, " command: %s\n",
205 mc->command ? mc->command : "?"));
206 if(mc->testcommand)
207 dprint((11, " testcommand: %s",
208 mc->testcommand ? mc->testcommand : "?"));
209 if(mc->printcommand)
210 dprint((11, " printcommand: %s",
211 mc->printcommand ? mc->printcommand : "?"));
212 dprint((11, " needsterminal %d\n", mc->needsterminal));
215 #endif /* DEBUG */
220 * Add all the entries from this file onto the Mailcap list.
222 void
223 mc_process_file(char *file)
225 char filebuf[MAXPATH+1], *file_data;
227 dprint((5, "mailcap: process_file: %s\n", file ? file : "?"));
229 (void)strncpy(filebuf, file, MAXPATH);
230 filebuf[MAXPATH] = '\0';
231 file = fnexpand(filebuf, sizeof(filebuf));
232 dprint((7, "mailcap: processing file: %s\n", file ? file : "?"));
233 switch(is_writable_dir(file)){
234 case 0: case 1: /* is a directory */
235 dprint((1, "mailcap: %s is a directory, should be a file\n",
236 file ? file : "?"));
237 return;
239 case 2: /* ok */
240 break;
242 case 3: /* doesn't exist */
243 dprint((5, "mailcap: %s doesn't exist\n", file ? file : "?"));
244 return;
246 default:
247 alpine_panic("Programmer botch in mc_process_file");
248 /*NOTREACHED*/
251 if((file_data = read_file(file, READ_FROM_LOCALE)) != NULL){
252 STRINGLIST *newsl, **sl;
254 /* Create a new container */
255 newsl = mail_newstringlist();
256 newsl->text.data = (unsigned char *) file_data;
258 /* figure out where in the list it should go */
259 for(sl = &MailcapData.raw; *sl; sl = &((*sl)->next))
262 *sl = newsl; /* Add it to the list */
264 mc_parse_file(file_data); /* the process mailcap data */
266 else
267 dprint((5, "mailcap: %s can't be read\n", file ? file : "?"));
271 void
272 mc_parse_file(char *file)
274 char *tokens[MC_TOKEN_MAX];
276 while(file && *file)
277 if(mc_parse_line(&file, tokens)) mc_build_entry(tokens);
282 mc_parse_line(char **line, char **tokens)
284 char **tokenp = tokens;
286 while(mc_comment(line)) /* skip comment lines */
289 while(mc_token(tokenp, line)) /* collect ';' delim'd tokens */
290 if(++tokenp - tokens >= MC_TOKEN_MAX)
291 fatal("Ran out of tokens parsing mailcap file"); /* outch! */
293 *++tokenp = NULL; /* tie off list */
294 return(*tokens != NULL);
299 * Returns 1 if line is a comment, 0 otherwise
302 mc_comment(char **line)
304 if(**line == '\n'){ /* blank line is a comment, too */
305 (*line)++;
306 return(1);
309 if(**line == '#'){
310 while(**line) /* !EOF */
311 if(*++(*line) == '\n'){ /* EOL? */
312 (*line)++;
313 break;
316 return(1);
319 return(0);
324 * Returns 0 if EOL, 1 otherwise
327 mc_token(char **token, char **line)
329 int rv = 0;
330 char *start, *wsp = NULL;
332 *token = NULL; /* init the slot for this token */
334 /* skip leading white space */
335 while(**line && isspace((unsigned char) **line))
336 (*line)++;
338 start = *line;
340 /* Then see what's left */
341 while(1)
342 switch(**line){
343 case ';' : /* End-Of-Token */
344 rv = 1; /* let caller know more follows */
345 case '\n' : /* EOL */
346 if(wsp)
347 *wsp = '\0'; /* truncate white space? */
348 else
349 *start = '\0'; /* if we have a token, tie it off */
351 (*line)++; /* and get ready to parse next one */
353 if(rv == 1){ /* ignore trailing semicolon */
354 while(**line){
355 if(**line == '\n')
356 rv = 0;
358 if(isspace((unsigned char) **line))
359 (*line)++;
360 else
361 break;
365 case '\0' : /* EOF */
366 return(rv);
368 case '\\' : /* Quoted char */
369 (*line)++;
370 #if defined(DOS) || defined(OS2)
372 * RFC 1524 says that backslash is used to quote
373 * the next character, but since backslash is part of pathnames
374 * on DOS we're afraid people will not put double backslashes
375 * in their mailcap files. Therefore, we violate the RFC by
376 * looking ahead to the next character. If it looks like it
377 * is just part of a pathname, then we consider a single
378 * backslash to *not* be a quoting character, but a literal
379 * backslash instead.
381 * SO:
382 * If next char is any of these, treat the backslash
383 * that preceded it like a regular character.
385 if(**line && isascii(**line)
386 && (isalnum((unsigned char) **line) || strchr("_+-=~" , **line))){
387 *start++ = '\\';
388 wsp = NULL;
389 break;
391 else
392 #endif /* !DOS */
394 if(**line == '\n'){ /* quoted line break */
395 *start = ' ';
396 (*line)++; /* just move on */
397 while(isspace((unsigned char) **line))
398 (*line)++;
400 break;
402 else if(**line == '%') /* quoted '%' becomes "%%" */
403 *--(*line) = '%'; /* overwrite '\' !! */
405 /* Fall thru and copy/advance pointers*/
407 default :
408 if(!*token)
409 *token = start;
411 *start = *(*line)++;
412 wsp = (isspace((unsigned char) *start) && !wsp) ? start : NULL;
413 start++;
414 break;
419 void
420 mc_build_entry(char **tokens)
422 MailcapEntry *mc;
424 if(!tokens[0]){
425 dprint((5, "mailcap: missing content type!\n"));
426 return;
428 else if(!tokens[1] || !mc_sane_command(tokens[1])){
429 dprint((5, "mailcap: missing/bogus command!\n"));
430 return;
433 mc = mc_new_entry();
434 mc->contenttype = *tokens++;
435 mc->command = *tokens++;
437 dprint((9, "mailcap: content type: %s\n command: %s\n",
438 mc->contenttype ? mc->contenttype : "?",
439 mc->command ? mc->command : "?"));
441 /* grok options */
442 for( ; *tokens; tokens++){
443 char *arg;
445 /* legit value? */
446 if(!isalnum((unsigned char) **tokens)){
447 dprint((5, "Unknown parameter = \"%s\"", *tokens));
448 continue;
451 if((arg = strindex(*tokens, '=')) != NULL){
452 *arg = ' ';
453 while(arg > *tokens && isspace((unsigned char) arg[-1]))
454 arg--;
456 *arg++ = '\0'; /* tie off parm arg */
457 while(*arg && isspace((unsigned char) *arg))
458 arg++;
460 if(!*arg)
461 arg = NULL;
464 if(!strucmp(*tokens, "needsterminal")){
465 mc->needsterminal = 1;
466 dprint((9, "mailcap: set needsterminal\n"));
468 else if(!strucmp(*tokens, "copiousoutput")){
469 mc->needsterminal = 2;
470 dprint((9, "mailcap: set copiousoutput\n"));
472 else if(arg && !strucmp(*tokens, "test")){
473 mc->testcommand = arg;
474 dprint((9, "mailcap: testcommand=%s\n",
475 mc->testcommand ? mc->testcommand : "?"));
477 else if(arg && !strucmp(*tokens, "description")){
478 mc->label = arg;
479 dprint((9, "mailcap: label=%s\n",
480 mc->label ? mc->label : "?"));
482 else if(arg && !strucmp(*tokens, "print")){
483 mc->printcommand = arg;
484 dprint((9, "mailcap: printcommand=%s\n",
485 mc->printcommand ? mc->printcommand : "?"));
487 else if(arg && !strucmp(*tokens, "compose")){
488 /* not used */
489 dprint((9, "mailcap: not using compose=%s\n",
490 arg ? arg : "?"));
492 else if(arg && !strucmp(arg, "composetyped")){
493 /* not used */
494 dprint((9, "mailcap: not using composetyped=%s\n",
495 arg ? arg : "?"));
497 else if(arg && !strucmp(arg, "textualnewlines")){
498 /* not used */
499 dprint((9,
500 "mailcap: not using texttualnewlines=%s\n",
501 arg ? arg : "?"));
503 else if(arg && !strucmp(arg, "edit")){
504 /* not used */
505 dprint((9, "mailcap: not using edit=%s\n",
506 arg ? arg : "?"));
508 else if(arg && !strucmp(arg, "x11-bitmap")){
509 /* not used */
510 dprint((9, "mailcap: not using x11-bitmap=%s\n",
511 arg ? arg : "?"));
513 else
514 dprint((9, "mailcap: ignoring unknown flag: %s\n",
515 arg ? arg : "?"));
521 * Tests for mailcap defined command's sanity
524 mc_sane_command(char *command)
526 /* First, test that a command string actually exists */
527 if(command && *command){
528 #ifdef LATER
530 * NOTE: Maybe we'll do this later. The problem is when the
531 * mailcap's been misconfigured. We then end up suppressing
532 * valuable output when the user actually tries to launch the
533 * spec'd viewer.
536 /* Second, Make sure we can get at it */
537 if(can_access_in_path(getenv("PATH"), command, EXECUTE_ACCESS) >= 0)
538 #endif
539 return(1);
542 return(0); /* failed! */
547 * Returns the mailcap entry for type/subtype from the successful
548 * mailcap entry, or NULL if none. Command string still contains % stuff.
550 MailcapEntry *
551 mc_get_command(int type, char *subtype, BODY *body,
552 int check_extension, int *sp_handlingp)
554 MailcapEntry *mc;
555 char tmp_subtype[256], tmp_ext[16], *ext = NULL;
557 dprint((5, "- mc_get_command(%s/%s) -\n",
558 body_type_names(type),
559 subtype ? subtype : "?"));
561 if(type == TYPETEXT
562 && (!subtype || !strucmp(subtype, "plain"))
563 && F_ON(F_SHOW_TEXTPLAIN_INT, ps_global))
564 return(NULL);
566 mc_init();
568 if(check_extension){
569 char *fname;
570 MT_MAP_T e2b;
573 * Special handling for when we're looking at what's likely
574 * binary application data. Look for a file name extension
575 * that we might use to hook a helper app to.
577 * NOTE: This used to preclude an "app/o-s" mailcap entry
578 * since this took precedence. Now that there are
579 * typically two scans through the check_extension
580 * mechanism, the mailcap entry now takes precedence.
582 if((fname = get_filename_parameter(NULL, 0, body, &e2b.from.ext)) != NULL
583 && e2b.from.ext && e2b.from.ext[0]){
584 if(strlen(e2b.from.ext) < sizeof(tmp_ext) - 2){
585 strncpy(ext = tmp_ext, e2b.from.ext - 1, sizeof(tmp_ext)); /* remember it */
586 tmp_ext[sizeof(tmp_ext)-1] = '\0';
587 if(mt_srch_mime_type(mt_srch_by_ext, &e2b)){
588 type = e2b.to.mime.type; /* mapped type */
589 strncpy(subtype = tmp_subtype, e2b.to.mime.subtype,
590 sizeof(tmp_subtype)-1);
591 tmp_subtype[sizeof(tmp_subtype)-1] = '\0';
592 fs_give((void **) &e2b.to.mime.subtype);
593 body = NULL; /* the params no longer apply */
597 fs_give((void **) &fname);
599 else{
600 if(fname)
601 fs_give((void **) &fname);
603 return(NULL);
607 for(mc = MailcapData.head; mc; mc = mc->next)
608 if(mc_ctype_match(type, subtype, mc->contenttype)
609 && mc_passes_test(mc, type, subtype, body)){
610 dprint((9,
611 "mc_get_command: type=%s/%s, command=%s\n",
612 body_type_names(type),
613 subtype ? subtype : "?",
614 mc->command ? mc->command : "?"));
615 return(mc);
618 if(mime_os_specific_access()){
619 static MailcapEntry fake_mc;
620 static char fake_cmd[1024];
621 char tmp_mime_type[256];
623 memset(&fake_mc, 0, sizeof(MailcapEntry));
624 fake_cmd[0] = '\0';
625 fake_mc.command = fake_cmd;
627 snprintf(tmp_mime_type, sizeof(tmp_mime_type), "%s/%s", body_types[type], subtype);
628 if(mime_get_os_mimetype_command(tmp_mime_type, ext, fake_cmd,
629 sizeof(fake_cmd), check_extension, sp_handlingp))
630 return(&fake_mc);
633 return(NULL);
638 * Check whether the pattern "pat" matches this type/subtype.
639 * Returns 1 if it does, 0 if not.
642 mc_ctype_match(int type, char *subtype, char *pat)
644 char *type_name = body_type_names(type);
645 int len = strlen(type_name);
647 dprint((5, "mc_ctype_match: %s == %s / %s ?\n",
648 pat ? pat : "?",
649 type_name ? type_name : "?",
650 subtype ? subtype : "?"));
652 return(!struncmp(type_name, pat, len)
653 && ((pat[len] == '/'
654 && (!pat[len+1] || pat[len+1] == '*'
655 || !strucmp(subtype, &pat[len+1])))
656 || !pat[len]));
661 * Run the test command for entry mc to see if this entry currently applies to
662 * applies to this type/subtype.
664 * Returns 1 if it does pass test (exits with status 0), 0 otherwise.
667 mc_passes_test(MailcapEntry *mc, int type, char *subtype, BODY *body)
669 char *cmd = NULL;
670 int rv;
672 dprint((5, "- mc_passes_test -\n"));
674 if(mc->testcommand
675 && *mc->testcommand
676 && !(cmd = mc_bld_test_cmd(mc->testcommand, type, subtype, body)))
677 return(FALSE); /* couldn't be built */
679 if(!mc->testcommand || !cmd || !*cmd){
680 if(cmd)
681 fs_give((void **)&cmd);
683 dprint((7, "no test command, so Pass\n"));
684 return 1;
687 rv = exec_mailcap_test_cmd(cmd);
688 dprint((7, "mc_passes_test: \"%s\" %s (rv=%d)\n",
689 cmd ? cmd : "?", rv ? "Failed" : "Passed", rv)) ;
691 fs_give((void **)&cmd);
693 return(!rv);
698 mailcap_can_display(int type, char *subtype, BODY *body, int check_extension)
700 dprint((5, "- mailcap_can_display -\n"));
702 return(mc_get_command(type, subtype, body,
703 check_extension, NULL) != NULL);
707 MCAP_CMD_S *
708 mailcap_build_command(int type, char *subtype, BODY *body,
709 char *tmp_file, int *needsterm, int chk_extension)
711 MailcapEntry *mc;
712 char *command, *err = NULL;
713 MCAP_CMD_S *mc_cmd = NULL;
714 int sp_handling = 0;
716 dprint((5, "- mailcap_build_command -\n"));
718 mc = mc_get_command(type, subtype, body, chk_extension, &sp_handling);
719 if(!mc){
720 q_status_message(SM_ORDER, 3, 4, "Error constructing viewer command");
721 dprint((1,
722 "mailcap_build_command: no command string for %s/%s\n",
723 body_type_names(type), subtype ? subtype : "?"));
724 return((MCAP_CMD_S *)NULL);
727 if(needsterm)
728 *needsterm = mc->needsterminal;
730 if(sp_handling)
731 command = cpystr(mc->command);
732 else if(!(command = mc_cmd_bldr(mc->command, type, subtype, body, tmp_file, &err)) && err && *err)
733 q_status_message(SM_ORDER, 5, 5, err);
735 dprint((5, "built command: %s\n", command ? command : "?"));
737 if(command){
738 mc_cmd = (MCAP_CMD_S *)fs_get(sizeof(MCAP_CMD_S));
739 mc_cmd->command = command;
740 mc_cmd->special_handling = sp_handling;
742 return(mc_cmd);
747 * mc_bld_test_cmd - build the command to test if the given type flies
749 * mc_cmd_bldr's tmp_file argument is NULL as we're not going to
750 * decode and write each and every MIME segment's data to a temp file
751 * when no test's going to use the data anyway.
753 char *
754 mc_bld_test_cmd(char *controlstring, int type, char *subtype, BODY *body)
756 return(mc_cmd_bldr(controlstring, type, subtype, body, NULL, NULL));
761 * mc_cmd_bldr - construct a command string to execute
763 * If tmp_file is null, then the contents of the given MIME segment
764 * is not provided. This is useful for building the "test=" string
765 * as it doesn't operate on the segment's data.
767 * The return value is an alloc'd copy of the command to be executed.
769 char *
770 mc_cmd_bldr(char *controlstring, int type, char *subtype,
771 BODY *body, char *tmp_file, char **err)
773 char *from, *to, *s, *parm;
774 int prefixed = 0, used_tmp_file = 0;
776 dprint((8, "- mc_cmd_bldr -\n"));
778 for(from = controlstring, to = tmp_20k_buf; *from; ++from){
779 if(prefixed){ /* previous char was % */
780 prefixed = 0;
781 switch(*from){
782 case '%': /* turned \% into this earlier */
783 if(to-tmp_20k_buf < SIZEOF_20KBUF)
784 *to++ = '%';
786 break;
788 case 's': /* insert tmp_file name in cmd */
789 if(tmp_file){
790 used_tmp_file = 1;
791 sstrncpy(&to, tmp_file, SIZEOF_20KBUF-(to-tmp_20k_buf));
793 else
794 dprint((1,
795 "mc_cmd_bldr: %%s in cmd but not supplied!\n"));
797 break;
799 case 't': /* insert MIME type/subtype */
800 /* quote to prevent funny business */
801 if(to-tmp_20k_buf < SIZEOF_20KBUF)
802 *to++ = '\'';
804 sstrncpy(&to, body_type_names(type), SIZEOF_20KBUF-(to-tmp_20k_buf));
806 if(to-tmp_20k_buf < SIZEOF_20KBUF)
807 *to++ = '/';
809 sstrncpy(&to, subtype, SIZEOF_20KBUF-(to-tmp_20k_buf));
811 if(to-tmp_20k_buf < SIZEOF_20KBUF)
812 *to++ = '\'';
814 break;
816 case '{': /* insert requested MIME param */
817 if(F_OFF(F_DO_MAILCAP_PARAM_SUBST, ps_global)){
818 int save;
820 dprint((2, "mc_cmd_bldr: param subs %s\n",
821 from ? from : "?"));
822 if(err){
823 if((s = strindex(from, '}')) != NULL){
824 save = *++s;
825 *s = '\0';
828 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
829 "Mailcap: see hidden feature %.200s (%%%.200s)",
830 feature_list_name(F_DO_MAILCAP_PARAM_SUBST), from);
831 *err = tmp_20k_buf;
832 if(s)
833 *s = save;
836 return(NULL);
839 s = strindex(from, '}');
840 if(!s){
841 q_status_message1(SM_ORDER, 0, 4,
842 "Ignoring ill-formed parameter reference in mailcap file: %.200s", from);
843 break;
846 *s = '\0';
847 ++from; /* from is the part inside the brackets now */
849 parm = parameter_val(body ? body->parameter : NULL, from);
851 dprint((9,
852 "mc_cmd_bldr: parameter %s = %s\n",
853 from ? from : "?", parm ? parm : "(not found)"));
856 * Quote parameter values for /bin/sh.
857 * Put single quotes around the whole thing but every time
858 * there is an actual single quote put it outside of the
859 * single quotes with a backslash in front of it. So the
860 * parameter value fred's car
861 * turns into 'fred'\''s car'
863 if(to-tmp_20k_buf < SIZEOF_20KBUF)
864 *to++ = '\''; /* opening quote */
866 if(parm){
867 char *p;
870 * Copy value, but quote single quotes for /bin/sh
871 * Backslash quote is ignored inside single quotes so
872 * have to put those outside of the single quotes.
873 * (The parm+1000 nonsense is to protect against
874 * malicious mail trying to overflow our buffer.)
876 * TCH - Change 2/8/1999
877 * Also quote the ` to prevent execution of arbitrary code
879 for(p = parm; *p && p < parm+1000; p++){
880 if((*p == '\'') || (*p == '`')){
881 if(to-tmp_20k_buf+4 < SIZEOF_20KBUF){
882 *to++ = '\''; /* closing quote */
883 *to++ = '\\';
884 *to++ = *p; /* quoted character */
885 *to++ = '\''; /* opening quote */
888 else if(to-tmp_20k_buf < SIZEOF_20KBUF)
889 *to++ = *p;
892 fs_give((void **) &parm);
895 if(to-tmp_20k_buf < SIZEOF_20KBUF)
896 *to++ = '\''; /* closing quote for /bin/sh */
898 *s = '}'; /* restore */
899 from = s;
900 break;
903 * %n and %F are used by metamail to support otherwise
904 * unrecognized multipart Content-Types. Pine does
905 * not use these since we're only dealing with the individual
906 * parts at this point.
908 case 'n':
909 case 'F':
910 default:
911 dprint((9,
912 "Ignoring %s format code in mailcap file: %%%c\n",
913 (*from == 'n' || *from == 'F') ? "unimplemented"
914 : "unrecognized",
915 *from));
916 break;
919 else if(*from == '%') /* next char is special */
920 prefixed = 1;
921 else if(to-tmp_20k_buf < SIZEOF_20KBUF) /* regular character, just copy */
922 *to++ = *from;
925 if(to-tmp_20k_buf < SIZEOF_20KBUF)
926 *to = '\0';
928 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
931 * file not specified, redirect to stdin
933 if(!used_tmp_file && tmp_file)
934 snprintf(to, SIZEOF_20KBUF-(to-tmp_20k_buf), MC_ADD_TMP, tmp_file);
936 return(cpystr(tmp_20k_buf));
943 MailcapEntry *
944 mc_new_entry(void)
946 MailcapEntry *mc = (MailcapEntry *) fs_get(sizeof(MailcapEntry));
947 memset(mc, 0, sizeof(MailcapEntry));
948 *MailcapData.tail = mc;
949 MailcapData.tail = &mc->next;
950 return(mc);
955 * Free a list of mailcap entries
957 void
958 mc_free_entry(MailcapEntry **mc)
960 if(mc && *mc){
961 mc_free_entry(&(*mc)->next);
962 fs_give((void **) mc);
967 void
968 mailcap_free(void)
970 mail_free_stringlist(&MailcapData.raw);
971 mc_free_entry(&MailcapData.head);