/* Copyright 1999-2004 The Apache Software Foundation
 *
 * 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
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * http_mime.c: Sends/gets MIME headers for requests
 * 
 * Rob McCool
 * 
 */

#include "apr.h"
#include "apr_strings.h"
#include "apr_lib.h"
#include "apr_hash.h"

#define APR_WANT_STRFUNC
#include "apr_want.h"

#include "ap_config.h"
#include "httpd.h"
#include "http_config.h"
#include "http_log.h"
#include "http_request.h"
#include "http_protocol.h"

/* XXXX - fix me / EBCDIC
 *        there was a cludge here which would use its
 *        own version apr_isascii(). Indicating that
 *        on some platforms that might be needed. 
 *
 *        #define OS_ASC(c) (c)             -- for mere mortals 
 *     or
 *        #define OS_ASC(c) (ebcdic2ascii[c]) -- for dino's
 *
 *        #define apr_isascii(c) ((OS_ASC(c) & 0x80) == 0)
 */

/* XXXXX - fix me - See note with NOT_PROXY 
 */

typedef struct attrib_info {
    char *name;
    int   offset;
} attrib_info;

/* Information to which an extension can be mapped
 */
typedef struct extension_info {
    char *forced_type;                /* Additional AddTyped stuff */
    char *encoding_type;              /* Added with AddEncoding... */
    char *language_type;              /* Added with AddLanguage... */
    char *handler;                    /* Added with AddHandler... */
    char *charset_type;               /* Added with AddCharset... */
    char *input_filters;              /* Added with AddInputFilter... */
    char *output_filters;             /* Added with AddOutputFilter... */
} extension_info;

#define MULTIMATCH_UNSET      0
#define MULTIMATCH_ANY        1
#define MULTIMATCH_NEGOTIATED 2
#define MULTIMATCH_HANDLERS   4
#define MULTIMATCH_FILTERS    8

typedef struct {
    apr_hash_t *extension_mappings;  /* Map from extension name to
                                      * extension_info structure */

    apr_array_header_t *remove_mappings; /* A simple list, walked once */

    char *default_language;     /* Language if no AddLanguage ext found */

    int multimatch;       /* Extensions to include in multiview matching
                           * for filenames, e.g. Filters and Handlers 
                           */
    int use_path_info;    /* If set to 0, only use filename.
                           * If set to 1, append PATH_INFO to filename for
                           *   lookups.
                           * If set to 2, this value is unset and is
                           *   effectively 0.  
                           */
} mime_dir_config;

typedef struct param_s {
    char *attr;
    char *val;
    struct param_s *next;
} param;

typedef struct {
    const char *type;
    apr_size_t type_len;
    const char *subtype;
    apr_size_t subtype_len;
    param *param;
} content_type;

static char tspecial[] = {
    '(', ')', '<', '>', '@', ',', ';', ':',
    '\\', '"', '/', '[', ']', '?', '=',
    '\0'
};

module AP_MODULE_DECLARE_DATA mime_module;

static void *create_mime_dir_config(apr_pool_t *p, char *dummy)
{
    mime_dir_config *new = apr_palloc(p, sizeof(mime_dir_config));

    new->extension_mappings = NULL;
    new->remove_mappings = NULL;

    new->default_language = NULL;

    new->multimatch = MULTIMATCH_UNSET;

    new->use_path_info = 2;

    return new;
}
/*
 * Overlay one hash table of extension_mappings onto another
 */
static void *overlay_extension_mappings(apr_pool_t *p,
                                        const void *key,
                                        apr_ssize_t klen,
                                        const void *overlay_val,
                                        const void *base_val,
                                        const void *data)
{
    extension_info *new_info = apr_palloc(p, sizeof(extension_info));
    const extension_info *overlay_info = (const extension_info *)overlay_val;
    const extension_info *base_info = (const extension_info *)base_val;

    memcpy(new_info, base_info, sizeof(extension_info));
    if (overlay_info->forced_type) {
        new_info->forced_type = overlay_info->forced_type;
    }
    if (overlay_info->encoding_type) {
        new_info->encoding_type = overlay_info->encoding_type;
    }
    if (overlay_info->language_type) {
        new_info->language_type = overlay_info->language_type;
    }
    if (overlay_info->handler) {
        new_info->handler = overlay_info->handler;
    }
    if (overlay_info->charset_type) {
        new_info->charset_type = overlay_info->charset_type;
    }
    if (overlay_info->input_filters) {
        new_info->input_filters = overlay_info->input_filters;
    }
    if (overlay_info->output_filters) {
        new_info->output_filters = overlay_info->output_filters;
    }

    return new_info;
}

/* Member is the offset within an extension_info of the pointer to reset 
 */
static void remove_items(apr_pool_t *p, apr_array_header_t *remove, 
                         apr_hash_t *mappings)
{
    attrib_info *suffix = (attrib_info *) remove->elts;
    int i;
    for (i = 0; i < remove->nelts; i++) {
        extension_info *exinfo = apr_hash_get(mappings,
                                              suffix[i].name,
                                              APR_HASH_KEY_STRING);
        if (exinfo && *(const char**)((char *)exinfo + suffix[i].offset)) {
            extension_info *copyinfo = exinfo;
            exinfo = (extension_info*)apr_palloc(p, sizeof(*exinfo));
            apr_hash_set(mappings, suffix[i].name, 
                         APR_HASH_KEY_STRING, exinfo);
            memcpy(exinfo, copyinfo, sizeof(*exinfo));
            *(const char**)((char *)exinfo + suffix[i].offset) = NULL;
        }
    }
}

static void *merge_mime_dir_configs(apr_pool_t *p, void *basev, void *addv)
{
    mime_dir_config *base = (mime_dir_config *)basev;
    mime_dir_config *add = (mime_dir_config *)addv;
    mime_dir_config *new = apr_palloc(p, sizeof(mime_dir_config));

    if (base->extension_mappings && add->extension_mappings) {
        new->extension_mappings = apr_hash_merge(p, add->extension_mappings,
                                                 base->extension_mappings,
                                                 overlay_extension_mappings,
                                                 NULL);
    }
    else {
        if (base->extension_mappings == NULL) {
            new->extension_mappings = add->extension_mappings;
        }
        else {
            new->extension_mappings = base->extension_mappings;
        }
        /* We may not be merging the tables, but if we potentially will change
         * an exinfo member, then we are about to trounce it anyways.
         * We must have a copy for safety.
         */
        if (new->extension_mappings && add->remove_mappings) {
            new->extension_mappings =
                apr_hash_copy(p, new->extension_mappings);
        }
    }

    if (new->extension_mappings) {
        if (add->remove_mappings)
            remove_items(p, add->remove_mappings, new->extension_mappings);
    }
    new->remove_mappings = NULL;

    new->default_language = add->default_language ?
        add->default_language : base->default_language;

    new->multimatch = (add->multimatch != MULTIMATCH_UNSET) ?
        add->multimatch : base->multimatch;

    if ((add->use_path_info & 2) == 0) {
        new->use_path_info = add->use_path_info;
    }
    else {
        new->use_path_info = base->use_path_info;
    }

    return new;
}

static const char *add_extension_info(cmd_parms *cmd, void *m_, 
                                      const char *value_, const char* ext)
{
    mime_dir_config *m=m_;
    extension_info *exinfo;
    int offset = (int) (long) cmd->info;
    char *key = apr_pstrdup(cmd->temp_pool, ext);
    char *value = apr_pstrdup(cmd->pool, value_);
    ap_str_tolower(value);
    ap_str_tolower(key);

    if (*key == '.') {
        ++key;
    }
    if (!m->extension_mappings) {
        m->extension_mappings = apr_hash_make(cmd->pool);
        exinfo = NULL;
    }
    else {
        exinfo = (extension_info*)apr_hash_get(m->extension_mappings, key,
                                               APR_HASH_KEY_STRING);
    }
    if (!exinfo) {
        exinfo = apr_pcalloc(cmd->pool, sizeof(extension_info));
        key = apr_pstrdup(cmd->pool, key);
        apr_hash_set(m->extension_mappings, key, APR_HASH_KEY_STRING, exinfo);
    }
    *(const char**)((char *)exinfo + offset) = value;
    return NULL;
}

/*
 * Note handler names are un-added with each per_dir_config merge.
 * This keeps the association from being inherited, but not
 * from being re-added at a subordinate level.
 */
static const char *remove_extension_info(cmd_parms *cmd, void *m_, 
                                         const char *ext)
{
    mime_dir_config *m = (mime_dir_config *) m_;
    attrib_info *suffix;
    if (*ext == '.') {
        ++ext;
    }
    if (!m->remove_mappings) {
        m->remove_mappings = apr_array_make(cmd->pool, 4, sizeof(*suffix));
    }
    suffix = (attrib_info *)apr_array_push(m->remove_mappings);
    suffix->name = apr_pstrdup(cmd->pool, ext);
    ap_str_tolower(suffix->name);
    suffix->offset = (int) (long) cmd->info;
    return NULL;
}

/* The sole bit of server configuration that the MIME module has is
 * the name of its config file, so...
 */

static const char *set_types_config(cmd_parms *cmd, void *dummy,
                                    const char *arg)
{
    ap_set_module_config(cmd->server->module_config, &mime_module,
                         (void *)arg);
    return NULL;
}

static const char *multiviews_match(cmd_parms *cmd, void *m_, 
                                    const char *include)
{
    mime_dir_config *m = (mime_dir_config *) m_;

    if (strcasecmp(include, "Any") == 0) {
        if (m->multimatch && (m->multimatch & ~MULTIMATCH_ANY)) {
            return "Any is incompatible with NegotiatedOnly, "
                   "Filters and Handlers";
        }
        m->multimatch |= MULTIMATCH_ANY;
    }
    else if (strcasecmp(include, "NegotiatedOnly") == 0) {
        if (m->multimatch && (m->multimatch & ~MULTIMATCH_NEGOTIATED)) {
            return "Any is incompatible with NegotiatedOnly, "
                   "Filters and Handlers";
        }
        m->multimatch |= MULTIMATCH_NEGOTIATED;
    }
    else if (strcasecmp(include, "Filters") == 0) {
        if (m->multimatch && (m->multimatch & (MULTIMATCH_NEGOTIATED 
                                             | MULTIMATCH_ANY))) {
            return "Filters is incompatible with Any and NegotiatedOnly";
        }
        m->multimatch |= MULTIMATCH_FILTERS;
    }
    else if (strcasecmp(include, "Handlers") == 0) {
        if (m->multimatch && (m->multimatch & (MULTIMATCH_NEGOTIATED 
                                             | MULTIMATCH_ANY))) {
            return "Handlers is incompatible with Any and NegotiatedOnly";
        }
        m->multimatch |= MULTIMATCH_HANDLERS;
    }
    else {
        return "Unrecognized option";
    }

    return NULL;
}

static const command_rec mime_cmds[] =
{
    AP_INIT_ITERATE2("AddCharset", add_extension_info, 
        (void *)APR_OFFSETOF(extension_info, charset_type), OR_FILEINFO,
        "a charset (e.g., iso-2022-jp), followed by one or more "
        "file extensions"),
    AP_INIT_ITERATE2("AddEncoding", add_extension_info, 
        (void *)APR_OFFSETOF(extension_info, encoding_type), OR_FILEINFO,
        "an encoding (e.g., gzip), followed by one or more file extensions"),
    AP_INIT_ITERATE2("AddHandler", add_extension_info, 
        (void *)APR_OFFSETOF(extension_info, handler), OR_FILEINFO,
        "a handler name followed by one or more file extensions"),
    AP_INIT_ITERATE2("AddInputFilter", add_extension_info, 
        (void *)APR_OFFSETOF(extension_info, input_filters), OR_FILEINFO,
        "input filter name (or ; delimited names) followed by one or "
        "more file extensions"),
    AP_INIT_ITERATE2("AddLanguage", add_extension_info, 
        (void *)APR_OFFSETOF(extension_info, language_type), OR_FILEINFO,
        "a language (e.g., fr), followed by one or more file extensions"),
    AP_INIT_ITERATE2("AddOutputFilter", add_extension_info, 
        (void *)APR_OFFSETOF(extension_info, output_filters), OR_FILEINFO, 
        "output filter name (or ; delimited names) followed by one or "
        "more file extensions"),
    AP_INIT_ITERATE2("AddType", add_extension_info, 
        (void *)APR_OFFSETOF(extension_info, forced_type), OR_FILEINFO, 
        "a mime type followed by one or more file extensions"),
    AP_INIT_TAKE1("DefaultLanguage", ap_set_string_slot,
        (void*)APR_OFFSETOF(mime_dir_config, default_language), OR_FILEINFO,
        "language to use for documents with no other language file extension"),
    AP_INIT_ITERATE("MultiviewsMatch", multiviews_match, NULL, OR_FILEINFO,
        "NegotiatedOnly (default), Handlers and/or Filters, or Any"),
    AP_INIT_ITERATE("RemoveCharset", remove_extension_info, 
        (void *)APR_OFFSETOF(extension_info, charset_type), OR_FILEINFO,
        "one or more file extensions"),
    AP_INIT_ITERATE("RemoveEncoding", remove_extension_info, 
        (void *)APR_OFFSETOF(extension_info, encoding_type), OR_FILEINFO,
        "one or more file extensions"),
    AP_INIT_ITERATE("RemoveHandler", remove_extension_info, 
        (void *)APR_OFFSETOF(extension_info, handler), OR_FILEINFO,
        "one or more file extensions"),
    AP_INIT_ITERATE("RemoveInputFilter", remove_extension_info, 
        (void *)APR_OFFSETOF(extension_info, input_filters), OR_FILEINFO,
        "one or more file extensions"),
    AP_INIT_ITERATE("RemoveLanguage", remove_extension_info, 
        (void *)APR_OFFSETOF(extension_info, language_type), OR_FILEINFO,
        "one or more file extensions"),
    AP_INIT_ITERATE("RemoveOutputFilter", remove_extension_info, 
        (void *)APR_OFFSETOF(extension_info, output_filters), OR_FILEINFO,
        "one or more file extensions"),
    AP_INIT_ITERATE("RemoveType", remove_extension_info, 
        (void *)APR_OFFSETOF(extension_info, forced_type), OR_FILEINFO,
        "one or more file extensions"),
    AP_INIT_TAKE1("TypesConfig", set_types_config, NULL, RSRC_CONF,
        "the MIME types config file"),
    AP_INIT_FLAG("ModMimeUsePathInfo", ap_set_flag_slot,
        (void *)APR_OFFSETOF(mime_dir_config, use_path_info), ACCESS_CONF,
        "Set to 'yes' to allow mod_mime to use path info for type checking"),
    {NULL}
};

static apr_hash_t *mime_type_extensions;

static int mime_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
{
    ap_configfile_t *f;
    char l[MAX_STRING_LEN];
    const char *types_confname = ap_get_module_config(s->module_config,
                                                      &mime_module);
    apr_status_t status;

    if (!types_confname) {
        types_confname = AP_TYPES_CONFIG_FILE;
    }

    types_confname = ap_server_root_relative(p, types_confname);
    if (!types_confname) {
        ap_log_error(APLOG_MARK, APLOG_ERR, APR_EBADPATH, s,
                     "Invalid mime types config path %s", 
                     (const char *)ap_get_module_config(s->module_config,
                                                        &mime_module));
        return HTTP_INTERNAL_SERVER_ERROR;
    }
    if ((status = ap_pcfg_openfile(&f, ptemp, types_confname)) 
                != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, status, s,
                     "could not open mime types config file %s.", 
                     types_confname);
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    mime_type_extensions = apr_hash_make(p);

    while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) {
        const char *ll = l, *ct;

        if (l[0] == '#') {
            continue;
        }
        ct = ap_getword_conf(p, &ll);

        while (ll[0]) {
            char *ext = ap_getword_conf(p, &ll);
            ap_str_tolower(ext);
            apr_hash_set(mime_type_extensions, ext, APR_HASH_KEY_STRING, ct);
        }
    }
    ap_cfg_closefile(f);
    return OK;
}

static const char *zap_sp(const char *s)
{
    if (s == NULL) {
        return (NULL);
    }
    if (*s == '\0') {
        return (s);
    }

    /* skip prefixed white space */
    for (; *s == ' ' || *s == '\t' || *s == '\n'; s++)
        ;

    return (s);
}

static char *zap_sp_and_dup(apr_pool_t *p, const char *start,
                            const char *end, apr_size_t *len)
{
    while ((start < end) && apr_isspace(*start)) {
        start++;
    }
    while ((end > start) && apr_isspace(*(end - 1))) {
        end--;
    }
    if (len) {
        *len = end - start;
    }
    return apr_pstrmemdup(p, start, end - start);
}

static int is_token(char c)
{
    int res;

    res = (apr_isascii(c) && apr_isgraph(c)
           && (strchr(tspecial, c) == NULL)) ? 1 : -1;
    return res;
}

static int is_qtext(char c)
{
    int res;

    res = (apr_isascii(c) && (c != '"') && (c != '\\') && (c != '\n'))
        ? 1 : -1;
    return res;
}

static int is_quoted_pair(const char *s)
{
    int res = -1;
    int c;

    if (((s + 1) != NULL) && (*s == '\\')) {
        c = (int) *(s + 1);
        if (apr_isascii(c)) {
            res = 1;
        }
    }
    return (res);
}

static content_type *analyze_ct(request_rec *r, const char *s)
{
    const char *cp, *mp;
    char *attribute, *value;
    int quoted = 0;
    server_rec * ss = r->server;
    apr_pool_t * p = r->pool;

    content_type *ctp;
    param *pp, *npp;

    /* initialize ctp */
    ctp = (content_type *)apr_palloc(p, sizeof(content_type));
    ctp->type = NULL;
    ctp->subtype = NULL;
    ctp->param = NULL;

    mp = s;

    /* getting a type */
    cp = mp;
    while (apr_isspace(*cp)) {
        cp++;
    }
    if (!*cp) {
        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss,
                     "mod_mime: analyze_ct: cannot get media type from '%s'",
                     (const char *) mp);
        return (NULL);
    }
    ctp->type = cp;
    do {
        cp++;
    } while (*cp && (*cp != '/') && !apr_isspace(*cp) && (*cp != ';'));
    if (!*cp || (*cp == ';')) {
        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss,
                     "Cannot get media type from '%s'",
                     (const char *) mp);
        return (NULL);
    }
    while (apr_isspace(*cp)) {
        cp++;
    }
    if (*cp != '/') {
        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss,
                     "mod_mime: analyze_ct: cannot get media type from '%s'",
                     (const char *) mp);
        return (NULL);
    }
    ctp->type_len = cp - ctp->type;

    cp++; /* skip the '/' */

    /* getting a subtype */
    while (apr_isspace(*cp)) {
        cp++;
    }
    if (!*cp) {
        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss,
                     "Cannot get media subtype.");
        return (NULL);
    }
    ctp->subtype = cp;
    do {
        cp++;
    } while (*cp && !apr_isspace(*cp) && (*cp != ';'));
    ctp->subtype_len = cp - ctp->subtype;
    while (apr_isspace(*cp)) {
        cp++;
    }

    if (*cp == '\0') {
        return (ctp);
    }

    /* getting parameters */
    cp++; /* skip the ';' */
    cp = zap_sp(cp);
    if (cp == NULL || *cp == '\0') {
        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss,
                     "Cannot get media parameter.");
        return (NULL);
    }
    mp = cp;
    attribute = NULL;
    value = NULL;

    while (cp != NULL && *cp != '\0') {
        if (attribute == NULL) {
            if (is_token(*cp) > 0) {
                cp++;
                continue;
            }
            else if (*cp == ' ' || *cp == '\t' || *cp == '\n') {
                cp++;
                continue;
            }
            else if (*cp == '=') {
                attribute = zap_sp_and_dup(p, mp, cp, NULL);
                if (attribute == NULL || *attribute == '\0') {
                    ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss,
                                 "Cannot get media parameter.");
                    return (NULL);
                }
                cp++;
                cp = zap_sp(cp);
                if (cp == NULL || *cp == '\0') {
                    ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss,
                                 "Cannot get media parameter.");
                    return (NULL);
                }
                mp = cp;
                continue;
            }
            else {
                ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss,
                             "Cannot get media parameter.");
                return (NULL);
            }
        }
        else {
            if (mp == cp) {
                if (*cp == '"') {
                    quoted = 1;
                    cp++;
                }
                else {
                    quoted = 0;
                }
            }
            if (quoted > 0) {
                while (quoted && *cp != '\0') {
                    if (is_qtext(*cp) > 0) {
                        cp++;
                    }
                    else if (is_quoted_pair(cp) > 0) {
                        cp += 2;
                    }
                    else if (*cp == '"') {
                        cp++;
                        while (*cp == ' ' || *cp == '\t' || *cp == '\n') {
                            cp++;
                        }
                        if (*cp != ';' && *cp != '\0') {
                            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss,
                                         "Cannot get media parameter.");
                            return(NULL);
                        }
                        quoted = 0;
                    }
                    else {
                        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss,
                                     "Cannot get media parameter.");
                        return (NULL);
                    }
                }
            }
            else {
                while (1) {
                    if (is_token(*cp) > 0) {
                        cp++;
                    }
                    else if (*cp == '\0' || *cp == ';') {
                        break;
                    }
                    else {
                        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss,
                                     "Cannot get media parameter.");
                        return (NULL);
                    }
                }
            }
            value = zap_sp_and_dup(p, mp, cp, NULL);
            if (value == NULL || *value == '\0') {
                ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss,
                             "Cannot get media parameter.");
                return (NULL);
            }

            pp = apr_palloc(p, sizeof(param));
            pp->attr = attribute;
            pp->val = value;
            pp->next = NULL;

            if (ctp->param == NULL) {
                ctp->param = pp;
            }
            else {
                npp = ctp->param;
                while (npp->next) {
                    npp = npp->next;
                }
                npp->next = pp;
            }
            quoted = 0;
            attribute = NULL;
            value = NULL;
            if (*cp == '\0') {
                break;
            }
            cp++;
            mp = cp;
        }
    }
    return (ctp);
}

/*
 * find_ct is the hook routine for determining content-type and other
 * MIME-related metadata.  It assumes that r->filename has already been
 * set and stat has been called for r->finfo.  It also assumes that the
 * non-path base file name is not the empty string unless it is a dir.
 */
static int find_ct(request_rec *r)
{
    mime_dir_config *conf;
    apr_array_header_t *exception_list;
    char *ext;
    const char *fn, *type, *charset = NULL, *resource_name;
    int found_metadata = 0;

    if (r->finfo.filetype == APR_DIR) {
        ap_set_content_type(r, DIR_MAGIC_TYPE);
        return OK;
    }

    if (!r->filename) {
        return DECLINED;
    }

    conf = (mime_dir_config *)ap_get_module_config(r->per_dir_config,
                                                   &mime_module);
    exception_list = apr_array_make(r->pool, 2, sizeof(char *));

    /* If use_path_info is explicitly set to on (value & 1 == 1), append. */
    if (conf->use_path_info & 1) {
        resource_name = apr_pstrcat(r->pool, r->filename, r->path_info, NULL);
    }
    else {
        resource_name = r->filename;
    }

    /* Always drop the path leading up to the file name.
     */
    if ((fn = ap_strrchr_c(resource_name, '/')) == NULL) {
        fn = resource_name;
    }
    else {
        ++fn;
    }

    /* The exception list keeps track of those filename components that
     * are not associated with extensions indicating metadata.
     * The base name is always the first exception (i.e., "txt.html" has
     * a basename of "txt" even though it might look like an extension).
     */
    ext = ap_getword(r->pool, &fn, '.');
    *((const char **)apr_array_push(exception_list)) = ext;

    /* Parse filename extensions which can be in any order 
     */
    while (*fn && (ext = ap_getword(r->pool, &fn, '.'))) {
        const extension_info *exinfo = NULL;
        int found;

        if (*ext == '\0') {  /* ignore empty extensions "bad..html" */
            continue;
        }

        found = 0;

        ap_str_tolower(ext);

        if (conf->extension_mappings != NULL) {
            exinfo = (extension_info*)apr_hash_get(conf->extension_mappings,
                                                   ext, APR_HASH_KEY_STRING);
        }

        if (exinfo == NULL || !exinfo->forced_type) {
            if ((type = apr_hash_get(mime_type_extensions, ext,
                                     APR_HASH_KEY_STRING)) != NULL) {
                ap_set_content_type(r, (char*) type);
                found = 1;
            }
        }

        if (exinfo != NULL) {

            if (exinfo->forced_type) {
                ap_set_content_type(r, exinfo->forced_type);
                found = 1;
            }

            if (exinfo->charset_type) {
                charset = exinfo->charset_type;
                found = 1;
            }
            if (exinfo->language_type) {
                if (!r->content_languages) {
                    r->content_languages = apr_array_make(r->pool, 2,
                                                          sizeof(char *));
                }
                *((const char **)apr_array_push(r->content_languages))
                    = exinfo->language_type;
                found = 1;
            }
            if (exinfo->encoding_type) {
                if (!r->content_encoding) {
                    r->content_encoding = exinfo->encoding_type;
                }
                else {
                    /* XXX should eliminate duplicate entities */
                    r->content_encoding = apr_pstrcat(r->pool,
                                                      r->content_encoding,
                                                      ", ",
                                                      exinfo->encoding_type,
                                                      NULL);
                }
                found = 1;
            }
            /* The following extensions are not 'Found'.  That is, they don't
             * make any contribution to metadata negotation, so they must have
             * been explicitly requested by name. 
             */
            if (exinfo->handler && r->proxyreq == PROXYREQ_NONE) {
                r->handler = exinfo->handler;
                if (conf->multimatch & MULTIMATCH_HANDLERS) {
                    found = 1;
                }
            }
            /* XXX Two significant problems; 1, we don't check to see if we are
             * setting redundant filters.    2, we insert these in the types config
             * hook, which may be too early (dunno.)
             */
            if (exinfo->input_filters && r->proxyreq == PROXYREQ_NONE) {
                const char *filter, *filters = exinfo->input_filters;
                while (*filters 
                    && (filter = ap_getword(r->pool, &filters, ';'))) {
                    ap_add_input_filter(filter, NULL, r, r->connection);
                }
                if (conf->multimatch & MULTIMATCH_FILTERS) {
                    found = 1;
                }
            }
            if (exinfo->output_filters && r->proxyreq == PROXYREQ_NONE) {
                const char *filter, *filters = exinfo->output_filters;
                while (*filters 
                    && (filter = ap_getword(r->pool, &filters, ';'))) {
                    ap_add_output_filter(filter, NULL, r, r->connection);
                }
                if (conf->multimatch & MULTIMATCH_FILTERS) {
                    found = 1;
                }
            }
        }

        if (found || (conf->multimatch & MULTIMATCH_ANY)) {
            found_metadata = 1;
        }
        else {
            *((const char **) apr_array_push(exception_list)) = ext;
        }
    }

    /*
     * Need to set a notes entry on r for unrecognized elements.
     * Somebody better claim them!  If we did absolutely nothing,
     * skip the notes to alert mod_negotiation we are clueless.
     */
    if (found_metadata) {
        apr_table_setn(r->notes, "ap-mime-exceptions-list", 
                       (void *)exception_list);
    }

    if (r->content_type) {
        content_type *ctp;
        int override = 0;

        if ((ctp = analyze_ct(r, r->content_type))) {
            param *pp = ctp->param;
            char *base_content_type = apr_palloc(r->pool, ctp->type_len +
                                                 ctp->subtype_len +
                                                 sizeof("/"));
            char *tmp = base_content_type;
            memcpy(tmp, ctp->type, ctp->type_len);
            tmp += ctp->type_len;
            *tmp++ = '/';
            memcpy(tmp, ctp->subtype, ctp->subtype_len);
            tmp += ctp->subtype_len;
            *tmp = 0;
            ap_set_content_type(r, base_content_type);
            while (pp != NULL) {
                if (charset && !strcmp(pp->attr, "charset")) {
                    if (!override) {
                        ap_set_content_type(r,
                                            apr_pstrcat(r->pool,
                                                        r->content_type,
                                                        "; charset=",
                                                        charset,
                                                        NULL));
                        override = 1;
                    }
                }
                else {
                    ap_set_content_type(r,
                                        apr_pstrcat(r->pool,
                                                    r->content_type,
                                                    "; ", pp->attr,
                                                    "=", pp->val,
                                                    NULL));
                }
                pp = pp->next;
            }
            if (charset && !override) {
                ap_set_content_type(r, apr_pstrcat(r->pool, r->content_type,
                                                   "; charset=", charset,
                                                   NULL));
            }
        }
    }

    /* Set default language, if none was specified by the extensions
     * and we have a DefaultLanguage setting in force
     */

    if (!r->content_languages && conf->default_language) {
        const char **new;

        if (!r->content_languages) {
            r->content_languages = apr_array_make(r->pool, 2, sizeof(char *));
        }
        new = (const char **)apr_array_push(r->content_languages);
        *new = conf->default_language;
    }

    if (!r->content_type) {
        return DECLINED;
    }

    return OK;
}

static void register_hooks(apr_pool_t *p)
{
    ap_hook_post_config(mime_post_config,NULL,NULL,APR_HOOK_MIDDLE);
    ap_hook_type_checker(find_ct,NULL,NULL,APR_HOOK_MIDDLE);
    /* 
     * this hook seems redundant ... is there any reason a type checker isn't
     * allowed to do this already?  I'd think that fixups in general would be
     * the last opportunity to get the filters right.
     * ap_hook_insert_filter(mime_insert_filters,NULL,NULL,APR_HOOK_MIDDLE);
     */
}

module AP_MODULE_DECLARE_DATA mime_module = {
    STANDARD20_MODULE_STUFF,
    create_mime_dir_config,     /* create per-directory config structure */
    merge_mime_dir_configs,     /* merge per-directory config structures */
    NULL,                       /* create per-server config structure */
    NULL,                       /* merge per-server config structures */
    mime_cmds,                  /* command apr_table_t */
    register_hooks              /* register hooks */
};
