summaryrefslogtreecommitdiff
path: root/pith/mailcap.c
diff options
context:
space:
mode:
authorEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
committerEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
commit094ca96844842928810f14844413109fc6cdd890 (patch)
treee60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /pith/mailcap.c
downloadalpine-094ca96844842928810f14844413109fc6cdd890.tar.xz
Initial Alpine Version
Diffstat (limited to 'pith/mailcap.c')
-rw-r--r--pith/mailcap.c976
1 files changed, 976 insertions, 0 deletions
diff --git a/pith/mailcap.c b/pith/mailcap.c
new file mode 100644
index 00000000..34dce329
--- /dev/null
+++ b/pith/mailcap.c
@@ -0,0 +1,976 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: mailcap.c 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/mailcap.h"
+#include "../pith/init.h"
+#include "../pith/conf.h"
+#include "../pith/mimetype.h"
+#include "../pith/mimedesc.h"
+#include "../pith/status.h"
+#include "../pith/util.h"
+#include "../pith/readfile.h"
+
+/*
+ * We've decided not to implement the RFC1524 standard minimum path, because
+ * some of us think it is harder to debug a problem when you may be misled
+ * into looking at the wrong mailcap entry. Likewise for MIME.Types files.
+ */
+#if defined(DOS) || defined(OS2)
+#define MC_PATH_SEPARATOR ';'
+#define MC_USER_FILE "MAILCAP"
+#define MC_STDPATH NULL
+#else /* !DOS */
+#define MC_PATH_SEPARATOR ':'
+#define MC_USER_FILE NULL
+#define MC_STDPATH \
+ ".mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap"
+#endif /* !DOS */
+
+#ifdef _WINDOWS
+#define MC_ADD_TMP " %s"
+#else
+#define MC_ADD_TMP " < %s"
+#endif
+
+typedef struct mcap_entry {
+ struct mcap_entry *next;
+ int needsterminal;
+ char *contenttype;
+ char *command;
+ char *testcommand;
+ char *label; /* unused */
+ char *printcommand; /* unused */
+} MailcapEntry;
+
+struct mailcap_data {
+ MailcapEntry *head, **tail;
+ STRINGLIST *raw;
+} MailcapData;
+
+#define MC_TOKEN_MAX 64
+
+
+/*
+ * Internal prototypes
+ */
+void mc_init(void);
+void mc_process_file(char *);
+void mc_parse_file(char *);
+int mc_parse_line(char **, char **);
+int mc_comment(char **);
+int mc_token(char **, char **);
+void mc_build_entry(char **);
+int mc_sane_command(char *);
+MailcapEntry *mc_get_command(int, char *, BODY *, int, int *);
+int mc_ctype_match(int, char *, char *);
+int mc_passes_test(MailcapEntry *, int, char *, BODY *);
+char *mc_bld_test_cmd(char *, int, char *, BODY *);
+char *mc_cmd_bldr(char *, int, char *, BODY *, char *, char **);
+MailcapEntry *mc_new_entry(void);
+void mc_free_entry(MailcapEntry **);
+
+
+char *
+mc_conf_path(char *def_path, char *env_path, char *user_file, int separator, char *stdpath)
+{
+ char *path;
+
+ /* We specify MIMETYPES as a path override */
+ if(def_path)
+ /* there may need to be an override specific to pine */
+ path = cpystr(def_path);
+ else if(env_path)
+ path = cpystr(env_path);
+ else{
+#if defined(DOS) || defined(OS2)
+ char *s;
+
+ /*
+ * This gets interesting. Since we don't have any standard location
+ * for config/data files, look in the same directory as the PINERC
+ * and the same dir as PINE.EXE. This is similar to the UNIX
+ * situation with personal config info coming before
+ * potentially shared config data...
+ */
+ if(s = last_cmpnt(ps_global->pinerc)){
+ strncpy(tmp_20k_buf+1000, ps_global->pinerc, MIN(s - ps_global->pinerc,SIZEOF_20KBUF-1000));
+ tmp_20k_buf[1000+MIN(s - ps_global->pinerc,SIZEOF_20KBUF-1000-1)] = '\0';
+ }
+ else
+ strncpy(tmp_20k_buf+1000, ".\\", SIZEOF_20KBUF-1000);
+
+ /* pinerc directory version of file */
+ build_path(tmp_20k_buf+2000, tmp_20k_buf+1000, user_file, SIZEOF_20KBUF-2000);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ /* pine.exe directory version of file */
+ build_path(tmp_20k_buf+3000, ps_global->pine_dir, user_file, SIZEOF_20KBUF-3000);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ /* combine them */
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%c%s", tmp_20k_buf+2000, separator, tmp_20k_buf+3000);
+
+#else /* !DOS */
+ build_path(tmp_20k_buf, ps_global->home_dir, stdpath, SIZEOF_20KBUF);
+#endif /* !DOS */
+ path = cpystr(tmp_20k_buf);
+ }
+
+ return(path);
+}
+
+
+/*
+ * mc_init - Run down the path gathering all the mailcap entries.
+ * Returns with the Mailcap list built.
+ */
+void
+mc_init(void)
+{
+ char *s,
+ *pathcopy,
+ *path,
+ image_viewer[MAILTMPLEN];
+
+ if(MailcapData.raw) /* already have the file? */
+ return;
+ else
+ MailcapData.tail = &MailcapData.head;
+
+ dprint((5, "- mc_init -\n"));
+
+ pathcopy = mc_conf_path(ps_global->VAR_MAILCAP_PATH, getenv("MAILCAPS"),
+ MC_USER_FILE, MC_PATH_SEPARATOR, MC_STDPATH);
+
+ path = pathcopy; /* overloaded "path" */
+
+ /*
+ * Insert an entry for the image-viewer variable from .pinerc, if present.
+ */
+ if(ps_global->VAR_IMAGE_VIEWER && *ps_global->VAR_IMAGE_VIEWER){
+ MailcapEntry *mc = mc_new_entry();
+
+ snprintf(image_viewer, sizeof(image_viewer), "%s %%s", ps_global->VAR_IMAGE_VIEWER);
+
+ MailcapData.raw = mail_newstringlist();
+ MailcapData.raw->text.data = (unsigned char *) cpystr(image_viewer);
+ mc->command = (char *) MailcapData.raw->text.data;
+ mc->contenttype = "image/*";
+ mc->label = "Alpine Image Viewer";
+ dprint((5, "mailcap: using image-viewer=%s\n",
+ ps_global->VAR_IMAGE_VIEWER
+ ? ps_global->VAR_IMAGE_VIEWER : "?"));
+ }
+
+ dprint((7, "mailcap: path: %s\n", path ? path : "?"));
+ while(path){
+ s = strindex(path, MC_PATH_SEPARATOR);
+ if(s)
+ *s++ = '\0';
+ mc_process_file(path);
+ path = s;
+ }
+
+ if(pathcopy)
+ fs_give((void **)&pathcopy);
+
+#ifdef DEBUG
+ if(debug >= 11){
+ MailcapEntry *mc;
+ int i = 0;
+
+ dprint((11, "Collected mailcap entries\n"));
+ for(mc = MailcapData.head; mc; mc = mc->next){
+
+ dprint((11, "%d: ", i++));
+ if(mc->label)
+ dprint((11, "%s\n", mc->label ? mc->label : "?"));
+ if(mc->contenttype)
+ dprint((11, " %s",
+ mc->contenttype ? mc->contenttype : "?"));
+ if(mc->command)
+ dprint((11, " command: %s\n",
+ mc->command ? mc->command : "?"));
+ if(mc->testcommand)
+ dprint((11, " testcommand: %s",
+ mc->testcommand ? mc->testcommand : "?"));
+ if(mc->printcommand)
+ dprint((11, " printcommand: %s",
+ mc->printcommand ? mc->printcommand : "?"));
+ dprint((11, " needsterminal %d\n", mc->needsterminal));
+ }
+ }
+#endif /* DEBUG */
+}
+
+
+/*
+ * Add all the entries from this file onto the Mailcap list.
+ */
+void
+mc_process_file(char *file)
+{
+ char filebuf[MAXPATH+1], *file_data;
+
+ dprint((5, "mailcap: process_file: %s\n", file ? file : "?"));
+
+ (void)strncpy(filebuf, file, MAXPATH);
+ filebuf[MAXPATH] = '\0';
+ file = fnexpand(filebuf, sizeof(filebuf));
+ dprint((7, "mailcap: processing file: %s\n", file ? file : "?"));
+ switch(is_writable_dir(file)){
+ case 0: case 1: /* is a directory */
+ dprint((1, "mailcap: %s is a directory, should be a file\n",
+ file ? file : "?"));
+ return;
+
+ case 2: /* ok */
+ break;
+
+ case 3: /* doesn't exist */
+ dprint((5, "mailcap: %s doesn't exist\n", file ? file : "?"));
+ return;
+
+ default:
+ panic("Programmer botch in mc_process_file");
+ /*NOTREACHED*/
+ }
+
+ if((file_data = read_file(file, READ_FROM_LOCALE)) != NULL){
+ STRINGLIST *newsl, **sl;
+
+ /* Create a new container */
+ newsl = mail_newstringlist();
+ newsl->text.data = (unsigned char *) file_data;
+
+ /* figure out where in the list it should go */
+ for(sl = &MailcapData.raw; *sl; sl = &((*sl)->next))
+ ;
+
+ *sl = newsl; /* Add it to the list */
+
+ mc_parse_file(file_data); /* the process mailcap data */
+ }
+ else
+ dprint((5, "mailcap: %s can't be read\n", file ? file : "?"));
+}
+
+
+void
+mc_parse_file(char *file)
+{
+ char *tokens[MC_TOKEN_MAX];
+
+ while(mc_parse_line(&file, tokens))
+ mc_build_entry(tokens);
+}
+
+
+int
+mc_parse_line(char **line, char **tokens)
+{
+ char **tokenp = tokens;
+
+ while(mc_comment(line)) /* skip comment lines */
+ ;
+
+ while(mc_token(tokenp, line)) /* collect ';' delim'd tokens */
+ if(++tokenp - tokens >= MC_TOKEN_MAX)
+ fatal("Ran out of tokens parsing mailcap file"); /* outch! */
+
+ *++tokenp = NULL; /* tie off list */
+ return(*tokens != NULL);
+}
+
+
+/*
+ * Retuns 1 if line is a comment, 0 otherwise
+ */
+int
+mc_comment(char **line)
+{
+ if(**line == '\n'){ /* blank line is a comment, too */
+ (*line)++;
+ return(1);
+ }
+
+ if(**line == '#'){
+ while(**line) /* !EOF */
+ if(*++(*line) == '\n'){ /* EOL? */
+ (*line)++;
+ break;
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * Retuns 0 if EOL, 1 otherwise
+ */
+int
+mc_token(char **token, char **line)
+{
+ int rv = 0;
+ char *start, *wsp = NULL;
+
+ *token = NULL; /* init the slot for this token */
+
+ /* skip leading white space */
+ while(**line && isspace((unsigned char) **line))
+ (*line)++;
+
+ start = *line;
+
+ /* Then see what's left */
+ while(1)
+ switch(**line){
+ case ';' : /* End-Of-Token */
+ rv = 1; /* let caller know more follows */
+ case '\n' : /* EOL */
+ if(wsp)
+ *wsp = '\0'; /* truncate white space? */
+ else
+ *start = '\0'; /* if we have a token, tie it off */
+
+ (*line)++; /* and get ready to parse next one */
+
+ if(rv == 1){ /* ignore trailing semicolon */
+ while(**line){
+ if(**line == '\n')
+ rv = 0;
+
+ if(isspace((unsigned char) **line))
+ (*line)++;
+ else
+ break;
+ }
+ }
+
+ case '\0' : /* EOF */
+ return(rv);
+
+ case '\\' : /* Quoted char */
+ (*line)++;
+#if defined(DOS) || defined(OS2)
+ /*
+ * RFC 1524 says that backslash is used to quote
+ * the next character, but since backslash is part of pathnames
+ * on DOS we're afraid people will not put double backslashes
+ * in their mailcap files. Therefore, we violate the RFC by
+ * looking ahead to the next character. If it looks like it
+ * is just part of a pathname, then we consider a single
+ * backslash to *not* be a quoting character, but a literal
+ * backslash instead.
+ *
+ * SO:
+ * If next char is any of these, treat the backslash
+ * that preceded it like a regular character.
+ */
+ if(**line && isascii(**line)
+ && (isalnum((unsigned char) **line) || strchr("_+-=~" , **line))){
+ *start++ = '\\';
+ wsp = NULL;
+ break;
+ }
+ else
+#endif /* !DOS */
+
+ if(**line == '\n'){ /* quoted line break */
+ *start = ' ';
+ (*line)++; /* just move on */
+ while(isspace((unsigned char) **line))
+ (*line)++;
+
+ break;
+ }
+ else if(**line == '%') /* quoted '%' becomes "%%" */
+ *--(*line) = '%'; /* overwrite '\' !! */
+
+ /* Fall thru and copy/advance pointers*/
+
+ default :
+ if(!*token)
+ *token = start;
+
+ *start = *(*line)++;
+ wsp = (isspace((unsigned char) *start) && !wsp) ? start : NULL;
+ start++;
+ break;
+ }
+}
+
+
+void
+mc_build_entry(char **tokens)
+{
+ MailcapEntry *mc;
+
+ if(!tokens[0]){
+ dprint((5, "mailcap: missing content type!\n"));
+ return;
+ }
+ else if(!tokens[1] || !mc_sane_command(tokens[1])){
+ dprint((5, "mailcap: missing/bogus command!\n"));
+ return;
+ }
+
+ mc = mc_new_entry();
+ mc->contenttype = *tokens++;
+ mc->command = *tokens++;
+
+ dprint((9, "mailcap: content type: %s\n command: %s\n",
+ mc->contenttype ? mc->contenttype : "?",
+ mc->command ? mc->command : "?"));
+
+ /* grok options */
+ for( ; *tokens; tokens++){
+ char *arg;
+
+ /* legit value? */
+ if(!isalnum((unsigned char) **tokens)){
+ dprint((5, "Unknown parameter = \"%s\"", *tokens));
+ continue;
+ }
+
+ if((arg = strindex(*tokens, '=')) != NULL){
+ *arg = ' ';
+ while(arg > *tokens && isspace((unsigned char) arg[-1]))
+ arg--;
+
+ *arg++ = '\0'; /* tie off parm arg */
+ while(*arg && isspace((unsigned char) *arg))
+ arg++;
+
+ if(!*arg)
+ arg = NULL;
+ }
+
+ if(!strucmp(*tokens, "needsterminal")){
+ mc->needsterminal = 1;
+ dprint((9, "mailcap: set needsterminal\n"));
+ }
+ else if(!strucmp(*tokens, "copiousoutput")){
+ mc->needsterminal = 2;
+ dprint((9, "mailcap: set copiousoutput\n"));
+ }
+ else if(arg && !strucmp(*tokens, "test")){
+ mc->testcommand = arg;
+ dprint((9, "mailcap: testcommand=%s\n",
+ mc->testcommand ? mc->testcommand : "?"));
+ }
+ else if(arg && !strucmp(*tokens, "description")){
+ mc->label = arg;
+ dprint((9, "mailcap: label=%s\n",
+ mc->label ? mc->label : "?"));
+ }
+ else if(arg && !strucmp(*tokens, "print")){
+ mc->printcommand = arg;
+ dprint((9, "mailcap: printcommand=%s\n",
+ mc->printcommand ? mc->printcommand : "?"));
+ }
+ else if(arg && !strucmp(*tokens, "compose")){
+ /* not used */
+ dprint((9, "mailcap: not using compose=%s\n",
+ arg ? arg : "?"));
+ }
+ else if(arg && !strucmp(arg, "composetyped")){
+ /* not used */
+ dprint((9, "mailcap: not using composetyped=%s\n",
+ arg ? arg : "?"));
+ }
+ else if(arg && !strucmp(arg, "textualnewlines")){
+ /* not used */
+ dprint((9,
+ "mailcap: not using texttualnewlines=%s\n",
+ arg ? arg : "?"));
+ }
+ else if(arg && !strucmp(arg, "edit")){
+ /* not used */
+ dprint((9, "mailcap: not using edit=%s\n",
+ arg ? arg : "?"));
+ }
+ else if(arg && !strucmp(arg, "x11-bitmap")){
+ /* not used */
+ dprint((9, "mailcap: not using x11-bitmap=%s\n",
+ arg ? arg : "?"));
+ }
+ else
+ dprint((9, "mailcap: ignoring unknown flag: %s\n",
+ arg ? arg : "?"));
+ }
+}
+
+
+/*
+ * Tests for mailcap defined command's sanity
+ */
+int
+mc_sane_command(char *command)
+{
+ /* First, test that a command string actually exists */
+ if(command && *command){
+#ifdef LATER
+ /*
+ * NOTE: Maybe we'll do this later. The problem is when the
+ * mailcap's been misconfigured. We then end up supressing
+ * valuable output when the user actually tries to launch the
+ * spec'd viewer.
+ */
+
+ /* Second, Make sure we can get at it */
+ if(can_access_in_path(getenv("PATH"), command, EXECUTE_ACCESS) >= 0)
+#endif
+ return(1);
+ }
+
+ return(0); /* failed! */
+}
+
+
+/*
+ * Returns the mailcap entry for type/subtype from the successfull
+ * mailcap entry, or NULL if none. Command string still contains % stuff.
+ */
+MailcapEntry *
+mc_get_command(int type, char *subtype, BODY *body,
+ int check_extension, int *sp_handlingp)
+{
+ MailcapEntry *mc;
+ char tmp_subtype[256], tmp_ext[16], *ext = NULL;
+
+ dprint((5, "- mc_get_command(%s/%s) -\n",
+ body_type_names(type),
+ subtype ? subtype : "?"));
+
+ if(type == TYPETEXT
+ && (!subtype || !strucmp(subtype, "plain"))
+ && F_ON(F_SHOW_TEXTPLAIN_INT, ps_global))
+ return(NULL);
+
+ mc_init();
+
+ if(check_extension){
+ char *fname;
+ MT_MAP_T e2b;
+
+ /*
+ * Special handling for when we're looking at what's likely
+ * binary application data. Look for a file name extension
+ * that we might use to hook a helper app to.
+ *
+ * NOTE: This used to preclude an "app/o-s" mailcap entry
+ * since this took precedence. Now that there are
+ * typically two scans through the check_extension
+ * mechanism, the mailcap entry now takes precedence.
+ */
+ if((fname = get_filename_parameter(NULL, 0, body, &e2b.from.ext)) != NULL
+ && e2b.from.ext && e2b.from.ext[0]){
+ if(strlen(e2b.from.ext) < sizeof(tmp_ext) - 2){
+ strncpy(ext = tmp_ext, e2b.from.ext - 1, sizeof(tmp_ext)); /* remember it */
+ tmp_ext[sizeof(tmp_ext)-1] = '\0';
+ if(mt_srch_mime_type(mt_srch_by_ext, &e2b)){
+ type = e2b.to.mime.type; /* mapped type */
+ strncpy(subtype = tmp_subtype, e2b.to.mime.subtype,
+ sizeof(tmp_subtype)-1);
+ tmp_subtype[sizeof(tmp_subtype)-1] = '\0';
+ fs_give((void **) &e2b.to.mime.subtype);
+ body = NULL; /* the params no longer apply */
+ }
+ }
+
+ fs_give((void **) &fname);
+ }
+ else{
+ if(fname)
+ fs_give((void **) &fname);
+
+ return(NULL);
+ }
+ }
+
+ for(mc = MailcapData.head; mc; mc = mc->next)
+ if(mc_ctype_match(type, subtype, mc->contenttype)
+ && mc_passes_test(mc, type, subtype, body)){
+ dprint((9,
+ "mc_get_command: type=%s/%s, command=%s\n",
+ body_type_names(type),
+ subtype ? subtype : "?",
+ mc->command ? mc->command : "?"));
+ return(mc);
+ }
+
+ if(mime_os_specific_access()){
+ static MailcapEntry fake_mc;
+ static char fake_cmd[1024];
+ char tmp_mime_type[256];
+
+ memset(&fake_mc, 0, sizeof(MailcapEntry));
+ fake_cmd[0] = '\0';
+ fake_mc.command = fake_cmd;
+
+ snprintf(tmp_mime_type, sizeof(tmp_mime_type), "%s/%s", body_types[type], subtype);
+ if(mime_get_os_mimetype_command(tmp_mime_type, ext, fake_cmd,
+ sizeof(fake_cmd), check_extension, sp_handlingp))
+ return(&fake_mc);
+ }
+
+ return(NULL);
+}
+
+
+/*
+ * Check whether the pattern "pat" matches this type/subtype.
+ * Returns 1 if it does, 0 if not.
+ */
+int
+mc_ctype_match(int type, char *subtype, char *pat)
+{
+ char *type_name = body_type_names(type);
+ int len = strlen(type_name);
+
+ dprint((5, "mc_ctype_match: %s == %s / %s ?\n",
+ pat ? pat : "?",
+ type_name ? type_name : "?",
+ subtype ? subtype : "?"));
+
+ return(!struncmp(type_name, pat, len)
+ && ((pat[len] == '/'
+ && (!pat[len+1] || pat[len+1] == '*'
+ || !strucmp(subtype, &pat[len+1])))
+ || !pat[len]));
+}
+
+
+/*
+ * Run the test command for entry mc to see if this entry currently applies to
+ * applies to this type/subtype.
+ *
+ * Returns 1 if it does pass test (exits with status 0), 0 otherwise.
+ */
+int
+mc_passes_test(MailcapEntry *mc, int type, char *subtype, BODY *body)
+{
+ char *cmd = NULL;
+ int rv;
+
+ dprint((5, "- mc_passes_test -\n"));
+
+ if(mc->testcommand
+ && *mc->testcommand
+ && !(cmd = mc_bld_test_cmd(mc->testcommand, type, subtype, body)))
+ return(FALSE); /* couldn't be built */
+
+ if(!mc->testcommand || !cmd || !*cmd){
+ if(cmd)
+ fs_give((void **)&cmd);
+
+ dprint((7, "no test command, so Pass\n"));
+ return 1;
+ }
+
+ rv = exec_mailcap_test_cmd(cmd);
+ dprint((7, "mc_passes_test: \"%s\" %s (rv=%d)\n",
+ cmd ? cmd : "?", rv ? "Failed" : "Passed", rv)) ;
+
+ fs_give((void **)&cmd);
+
+ return(!rv);
+}
+
+
+int
+mailcap_can_display(int type, char *subtype, BODY *body, int check_extension)
+{
+ dprint((5, "- mailcap_can_display -\n"));
+
+ return(mc_get_command(type, subtype, body,
+ check_extension, NULL) != NULL);
+}
+
+
+MCAP_CMD_S *
+mailcap_build_command(int type, char *subtype, BODY *body,
+ char *tmp_file, int *needsterm, int chk_extension)
+{
+ MailcapEntry *mc;
+ char *command, *err = NULL;
+ MCAP_CMD_S *mc_cmd = NULL;
+ int sp_handling = 0;
+
+ dprint((5, "- mailcap_build_command -\n"));
+
+ mc = mc_get_command(type, subtype, body, chk_extension, &sp_handling);
+ if(!mc){
+ q_status_message(SM_ORDER, 3, 4, "Error constructing viewer command");
+ dprint((1,
+ "mailcap_build_command: no command string for %s/%s\n",
+ body_type_names(type), subtype ? subtype : "?"));
+ return((MCAP_CMD_S *)NULL);
+ }
+
+ if(needsterm)
+ *needsterm = mc->needsterminal;
+
+ if(sp_handling)
+ command = cpystr(mc->command);
+ else if(!(command = mc_cmd_bldr(mc->command, type, subtype, body, tmp_file, &err)) && err && *err)
+ q_status_message(SM_ORDER, 5, 5, err);
+
+ dprint((5, "built command: %s\n", command ? command : "?"));
+
+ if(command){
+ mc_cmd = (MCAP_CMD_S *)fs_get(sizeof(MCAP_CMD_S));
+ mc_cmd->command = command;
+ mc_cmd->special_handling = sp_handling;
+ }
+ return(mc_cmd);
+}
+
+
+/*
+ * mc_bld_test_cmd - build the command to test if the given type flies
+ *
+ * mc_cmd_bldr's tmp_file argument is NULL as we're not going to
+ * decode and write each and every MIME segment's data to a temp file
+ * when no test's going to use the data anyway.
+ */
+char *
+mc_bld_test_cmd(char *controlstring, int type, char *subtype, BODY *body)
+{
+ return(mc_cmd_bldr(controlstring, type, subtype, body, NULL, NULL));
+}
+
+
+/*
+ * mc_cmd_bldr - construct a command string to execute
+ *
+ * If tmp_file is null, then the contents of the given MIME segment
+ * is not provided. This is useful for building the "test=" string
+ * as it doesn't operate on the segment's data.
+ *
+ * The return value is an alloc'd copy of the command to be executed.
+ */
+char *
+mc_cmd_bldr(char *controlstring, int type, char *subtype,
+ BODY *body, char *tmp_file, char **err)
+{
+ char *from, *to, *s, *parm;
+ int prefixed = 0, used_tmp_file = 0;
+
+ dprint((8, "- mc_cmd_bldr -\n"));
+
+ for(from = controlstring, to = tmp_20k_buf; *from; ++from){
+ if(prefixed){ /* previous char was % */
+ prefixed = 0;
+ switch(*from){
+ case '%': /* turned \% into this earlier */
+ if(to-tmp_20k_buf < SIZEOF_20KBUF)
+ *to++ = '%';
+
+ break;
+
+ case 's': /* insert tmp_file name in cmd */
+ if(tmp_file){
+ used_tmp_file = 1;
+ sstrncpy(&to, tmp_file, SIZEOF_20KBUF-(to-tmp_20k_buf));
+ }
+ else
+ dprint((1,
+ "mc_cmd_bldr: %%s in cmd but not supplied!\n"));
+
+ break;
+
+ case 't': /* insert MIME type/subtype */
+ /* quote to prevent funny business */
+ if(to-tmp_20k_buf < SIZEOF_20KBUF)
+ *to++ = '\'';
+
+ sstrncpy(&to, body_type_names(type), SIZEOF_20KBUF-(to-tmp_20k_buf));
+
+ if(to-tmp_20k_buf < SIZEOF_20KBUF)
+ *to++ = '/';
+
+ sstrncpy(&to, subtype, SIZEOF_20KBUF-(to-tmp_20k_buf));
+
+ if(to-tmp_20k_buf < SIZEOF_20KBUF)
+ *to++ = '\'';
+
+ break;
+
+ case '{': /* insert requested MIME param */
+ if(F_OFF(F_DO_MAILCAP_PARAM_SUBST, ps_global)){
+ int save;
+
+ dprint((2, "mc_cmd_bldr: param subs %s\n",
+ from ? from : "?"));
+ if(err){
+ if((s = strindex(from, '}')) != NULL){
+ save = *++s;
+ *s = '\0';
+ }
+
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "Mailcap: see hidden feature %.200s (%%%.200s)",
+ feature_list_name(F_DO_MAILCAP_PARAM_SUBST), from);
+ *err = tmp_20k_buf;
+ if(s)
+ *s = save;
+ }
+
+ return(NULL);
+ }
+
+ s = strindex(from, '}');
+ if(!s){
+ q_status_message1(SM_ORDER, 0, 4,
+ "Ignoring ill-formed parameter reference in mailcap file: %.200s", from);
+ break;
+ }
+
+ *s = '\0';
+ ++from; /* from is the part inside the brackets now */
+
+ parm = parameter_val(body ? body->parameter : NULL, from);
+
+ dprint((9,
+ "mc_cmd_bldr: parameter %s = %s\n",
+ from ? from : "?", parm ? parm : "(not found)"));
+
+ /*
+ * Quote parameter values for /bin/sh.
+ * Put single quotes around the whole thing but every time
+ * there is an actual single quote put it outside of the
+ * single quotes with a backslash in front of it. So the
+ * parameter value fred's car
+ * turns into 'fred'\''s car'
+ */
+ if(to-tmp_20k_buf < SIZEOF_20KBUF)
+ *to++ = '\''; /* opening quote */
+
+ if(parm){
+ char *p;
+
+ /*
+ * Copy value, but quote single quotes for /bin/sh
+ * Backslash quote is ignored inside single quotes so
+ * have to put those outside of the single quotes.
+ * (The parm+1000 nonsense is to protect against
+ * malicious mail trying to overflow our buffer.)
+ *
+ * TCH - Change 2/8/1999
+ * Also quote the ` to prevent execution of arbitrary code
+ */
+ for(p = parm; *p && p < parm+1000; p++){
+ if((*p == '\'') || (*p == '`')){
+ if(to-tmp_20k_buf+4 < SIZEOF_20KBUF){
+ *to++ = '\''; /* closing quote */
+ *to++ = '\\';
+ *to++ = *p; /* quoted character */
+ *to++ = '\''; /* opening quote */
+ }
+ }
+ else if(to-tmp_20k_buf < SIZEOF_20KBUF)
+ *to++ = *p;
+ }
+
+ fs_give((void **) &parm);
+ }
+
+ if(to-tmp_20k_buf < SIZEOF_20KBUF)
+ *to++ = '\''; /* closing quote for /bin/sh */
+
+ *s = '}'; /* restore */
+ from = s;
+ break;
+
+ /*
+ * %n and %F are used by metamail to support otherwise
+ * unrecognized multipart Content-Types. Pine does
+ * not use these since we're only dealing with the individual
+ * parts at this point.
+ */
+ case 'n':
+ case 'F':
+ default:
+ dprint((9,
+ "Ignoring %s format code in mailcap file: %%%c\n",
+ (*from == 'n' || *from == 'F') ? "unimplemented"
+ : "unrecognized",
+ *from));
+ break;
+ }
+ }
+ else if(*from == '%') /* next char is special */
+ prefixed = 1;
+ else if(to-tmp_20k_buf < SIZEOF_20KBUF) /* regular character, just copy */
+ *to++ = *from;
+ }
+
+ if(to-tmp_20k_buf < SIZEOF_20KBUF)
+ *to = '\0';
+
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ /*
+ * file not specified, redirect to stdin
+ */
+ if(!used_tmp_file && tmp_file)
+ snprintf(to, SIZEOF_20KBUF-(to-tmp_20k_buf), MC_ADD_TMP, tmp_file);
+
+ return(cpystr(tmp_20k_buf));
+}
+
+
+/*
+ *
+ */
+MailcapEntry *
+mc_new_entry(void)
+{
+ MailcapEntry *mc = (MailcapEntry *) fs_get(sizeof(MailcapEntry));
+ memset(mc, 0, sizeof(MailcapEntry));
+ *MailcapData.tail = mc;
+ MailcapData.tail = &mc->next;
+ return(mc);
+}
+
+
+/*
+ * Free a list of mailcap entries
+ */
+void
+mc_free_entry(MailcapEntry **mc)
+{
+ if(mc && *mc){
+ mc_free_entry(&(*mc)->next);
+ fs_give((void **) mc);
+ }
+}
+
+
+void
+mailcap_free(void)
+{
+ mail_free_stringlist(&MailcapData.raw);
+ mc_free_entry(&MailcapData.head);
+}