#if !defined(lint) && !defined(DOS) static char rcsid[] = "$Id: mailcap.c 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $"; #endif /* * ======================================================================== * Copyright 2013-2021 Eduardo Chappa * Copyright 2006-2008 University of Washington * * 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: alpine_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(file && *file) if(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); } /* * Returns 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); } /* * Returns 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 suppressing * 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 successful * 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); }