/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * Portions of this software are based upon public domain software
 * originally written at the National Center for Supercomputing Applications,
 * University of Illinois, Urbana-Champaign.
 */

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

#include "apr.h"
#include "apr_strings.h"
#include "apr_lib.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"


/* 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;
} attrib_info;

typedef struct {
    apr_table_t *forced_types;        /* Additional AddTyped stuff */
    apr_table_t *encoding_types;      /* Added with AddEncoding... */
    apr_table_t *language_types;      /* Added with AddLanguage... */
    apr_table_t *handlers;            /* Added with AddHandler...  */
    apr_table_t *charset_types;       /* Added with AddCharset... */       
    apr_array_header_t *handlers_remove;  /* List of handlers to remove */
    apr_array_header_t *types_remove;     /* List of MIME types to remove */
    apr_array_header_t *encodings_remove; /* List of encodings to remove */

    char *type;                 /* Type forced with ForceType  */
    char *handler;              /* Handler forced with SetHandler */
    char *default_language;     /* Language if no AddLanguage ext found */
	                        /* Due to the FUD about JS and charsets 
                                 * default_charset is actually in src/main */
} mime_dir_config;

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

typedef struct {
    char *type;
    char *subtype;
    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 =
    (mime_dir_config *) apr_palloc(p, sizeof(mime_dir_config));

    new->forced_types = apr_table_make(p, 4);
    new->encoding_types = apr_table_make(p, 4);
    new->charset_types = apr_table_make(p, 4);
    new->language_types = apr_table_make(p, 4);
    new->handlers = apr_table_make(p, 4);
    new->handlers_remove = apr_array_make(p, 4, sizeof(attrib_info));
    new->types_remove = apr_array_make(p, 4, sizeof(attrib_info));
    new->encodings_remove = apr_array_make(p, 4, sizeof(attrib_info));

    new->type = NULL;
    new->handler = NULL;
    new->default_language = NULL;

    return new;
}

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 =
        (mime_dir_config *) apr_palloc(p, sizeof(mime_dir_config));
    int i;
    attrib_info *suffix;

    new->forced_types = apr_table_overlay(p, add->forced_types,
					 base->forced_types);
    new->encoding_types = apr_table_overlay(p, add->encoding_types,
                                         base->encoding_types);
    new->charset_types = apr_table_overlay(p, add->charset_types,
					   base->charset_types);
    new->language_types = apr_table_overlay(p, add->language_types,
                                         base->language_types);
    new->handlers = apr_table_overlay(p, add->handlers,
                                   base->handlers);

    suffix = (attrib_info *) add->handlers_remove->elts;
    for (i = 0; i < add->handlers_remove->nelts; i++) {
        apr_table_unset(new->handlers, suffix[i].name);
    }

    suffix = (attrib_info *) add->types_remove->elts;
    for (i = 0; i < add->types_remove->nelts; i++) {
        apr_table_unset(new->forced_types, suffix[i].name);
    }

    suffix = (attrib_info *) add->encodings_remove->elts;
    for (i = 0; i < add->encodings_remove->nelts; i++) {
        apr_table_unset(new->encoding_types, suffix[i].name);
    }


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

    return new;
}

static const char *add_type(cmd_parms *cmd, void *m_, const char *ct_,
                            const char *ext)
{
    mime_dir_config *m=m_;
    char *ct=apr_pstrdup(cmd->pool,ct_);

    if (*ext == '.')
	++ext;
	
    ap_str_tolower(ct);
    apr_table_setn(m->forced_types, ext, ct);
    return NULL;
}

static const char *add_encoding(cmd_parms *cmd, void *m_, const char *enc_,
				const char *ext)
{
    mime_dir_config *m=m_;
    char *enc=apr_pstrdup(cmd->pool,enc_);

    if (*ext == '.')
        ++ext;
    ap_str_tolower(enc);
    apr_table_setn(m->encoding_types, ext, enc);
    return NULL;
}

static const char *add_charset(cmd_parms *cmd, void *m_, const char *charset_,
			       const char *ext)
{
    mime_dir_config *m=m_;
    char *charset=apr_pstrdup(cmd->pool,charset_);

    if (*ext == '.') {
	++ext;
    }
    ap_str_tolower(charset);
    apr_table_setn(m->charset_types, ext, charset);
    return NULL;
}

static const char *add_language(cmd_parms *cmd, void *m_, const char *lang_,
                                const char *ext)
{
    mime_dir_config *m=m_;
    char *lang=apr_pstrdup(cmd->pool,lang_);

    if (*ext == '.') {
	++ext;
    }
    ap_str_tolower(lang);
    apr_table_setn(m->language_types, ext, lang);
    return NULL;
}

static const char *add_handler(cmd_parms *cmd, void *m_, const char *hdlr_,
                               const char *ext)
{
    mime_dir_config *m=m_;
    char *hdlr=apr_pstrdup(cmd->pool,hdlr_);

    if (*ext == '.')
        ++ext;
    ap_str_tolower(hdlr);
    apr_table_setn(m->handlers, ext, hdlr);
    return NULL;
}

/*
 * Note handler names that should be un-added for this location.  This
 * will keep the association from being inherited, as well, but not
 * from being re-added at a subordinate level.
 */
static const char *remove_handler(cmd_parms *cmd, void *m, const char *ext)
{
    mime_dir_config *mcfg = (mime_dir_config *) m;
    attrib_info *suffix;

    if (*ext == '.') {
        ++ext;
    }
    suffix = (attrib_info *) apr_array_push(mcfg->handlers_remove);
    suffix->name = apr_pstrdup(cmd->pool, ext);
    return NULL;
}

/*
 * Just like the previous function, except that it records encoding
 * associations to be undone.
 */
static const char *remove_encoding(cmd_parms *cmd, void *m, const char *ext)
{
    mime_dir_config *mcfg = (mime_dir_config *) m;
    attrib_info *suffix;

    if (*ext == '.') {
        ++ext;
    }
    suffix = (attrib_info *) apr_array_push(mcfg->encodings_remove);
    suffix->name = apr_pstrdup(cmd->pool, ext);
    return NULL;
}

/*
 * Similar to the previous functions, except that it deals with filename
 * suffix/MIME-type associations.
 */
static const char *remove_type(cmd_parms *cmd, void *m, const char *ext)
{
    mime_dir_config *mcfg = (mime_dir_config *) m;
    attrib_info *suffix;

    if (*ext == '.') {
        ++ext;
    }
    suffix = (attrib_info *) apr_array_push(mcfg->types_remove);
    suffix->name = apr_pstrdup(cmd->pool, ext);
    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 command_rec mime_cmds[] =
{
AP_INIT_ITERATE2("AddType", add_type, NULL, OR_FILEINFO, 
     "a mime type followed by one or more file extensions"),
AP_INIT_ITERATE2("AddEncoding", add_encoding, NULL, OR_FILEINFO,
     "an encoding (e.g., gzip), followed by one or more file extensions"),
AP_INIT_ITERATE2("AddCharset", add_charset, NULL, OR_FILEINFO,
     "a charset (e.g., iso-2022-jp), followed by one or more file extensions"),
AP_INIT_ITERATE2("AddLanguage", add_language, NULL, OR_FILEINFO,
     "a language (e.g., fr), followed by one or more file extensions"),
AP_INIT_ITERATE2("AddHandler", add_handler, NULL, OR_FILEINFO,
     "a handler name followed by one or more file extensions"),
AP_INIT_TAKE1("ForceType", ap_set_string_slot_lower, 
     (void *)XtOffsetOf(mime_dir_config, type), OR_FILEINFO,
     "a media type"),
AP_INIT_ITERATE("RemoveHandler", remove_handler, NULL, OR_FILEINFO,
     "one or more file extensions"),
AP_INIT_ITERATE("RemoveEncoding", remove_encoding, NULL, OR_FILEINFO,
     "one or more file extensions"),
AP_INIT_ITERATE("RemoveType", remove_type, NULL, OR_FILEINFO,
     "one or more file extensions"),
AP_INIT_TAKE1("SetHandler", ap_set_string_slot_lower, 
     (void *)XtOffsetOf(mime_dir_config, handler), OR_FILEINFO,
     "a handler name"),
AP_INIT_TAKE1("TypesConfig", set_types_config, NULL, RSRC_CONF,
     "the MIME types config file"),
AP_INIT_TAKE1("DefaultLanguage", ap_set_string_slot,
     (void*)XtOffsetOf(mime_dir_config, default_language), OR_FILEINFO,
     "language to use for documents with no other language file extension"),
    {NULL}
};

/* Hash apr_table_t  --- only one of these per daemon; virtual hosts can
 * get private versions through AddType...
 */

#define MIME_HASHSIZE (32)
#define hash(i) (apr_tolower(i) % MIME_HASHSIZE)

static apr_table_t *hash_buckets[MIME_HASHSIZE];

static void 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];
    int x;
    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 ((status = ap_pcfg_openfile(&f, p, types_confname)) != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, status, s,
		     "could not open mime types config file %s.", types_confname);
        exit(1);
    }

    for (x = 0; x < MIME_HASHSIZE; x++)
        hash_buckets[x] = apr_table_make(p, 10);

    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_table_setn(hash_buckets[hash(ext[0])], ext, ct);
        }
    }
    ap_cfg_closefile(f);
}

static char *zap_sp(char *s)
{
    char *tp;

    if (s == NULL) {
	return (NULL);
    }
    if (*s == '\0') {
	return (s);
    }

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

    /* delete postfixed white space */
    for (tp = s; *tp != '\0'; tp++);
    for (tp--; tp != s && (*tp == ' ' || *tp == '\t' || *tp == '\n'); tp--) {
	*tp = '\0';
    }
    return (s);
}

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(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, char *s)
{
    char *tp, *mp, *cp;
    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;

    tp = apr_pstrdup(p, s);

    mp = tp;
    cp = mp;

    /* getting a type */
    if (!(cp = strchr(mp, '/'))) {
	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 = apr_pstrndup(p, mp, cp - mp);
    ctp->type = zap_sp(ctp->type);
    if (ctp->type == NULL || *(ctp->type) == '\0' ||
	strchr(ctp->type, ';') || strchr(ctp->type, ' ') ||
	strchr(ctp->type, '\t')) {
	ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss,
		     "Cannot get media subtype.");
	return (NULL);
    }

    /* getting a subtype */
    cp++;
    mp = cp;

    for (; *cp != ';' && *cp != '\0'; cp++)
        continue;
    ctp->subtype = apr_pstrndup(p, mp, cp - mp);
    ctp->subtype = zap_sp(ctp->subtype);
    if ((ctp->subtype == NULL) || (*(ctp->subtype) == '\0') ||
	strchr(ctp->subtype, ' ') || strchr(ctp->subtype, '\t')) {
	ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss,
		     "Cannot get media subtype.");
	return (NULL);
    }
    cp = zap_sp(cp);
    if (cp == NULL || *cp == '\0') {
	return (ctp);
    }

    /* getting parameters */
    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;
    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 = apr_pstrndup(p, mp, cp - mp);
		attribute = zap_sp(attribute);
		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 = apr_pstrndup(p, mp, cp - mp);
	    value = zap_sp(value);
	    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);
}

static int find_ct(request_rec *r)
{
    const char *fn = strrchr(r->filename, '/');
    mime_dir_config *conf =
    (mime_dir_config *) ap_get_module_config(r->per_dir_config, &mime_module);
    char *ext;
    const char *orighandler = r->handler;
    const char *type;
    const char *charset = NULL;

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

    /* TM -- FIXME
     * if r->filename does not contain a '/', the following passes a null
     * pointer to getword, causing a SEGV ..
     */

    if (fn == NULL) {
	fn = r->filename;
    }

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

        /* Check for Content-Type */
        if ((type = apr_table_get(conf->forced_types, ext))
            || (type = apr_table_get(hash_buckets[hash(*ext)], ext))) {
            r->content_type = type;
            found = 1;
        }

	/* Add charset to Content-Type */
	if ((type = apr_table_get(conf->charset_types, ext))) {
	    charset = type;
	    found = 1;
	}

        /* Check for Content-Language */
        if ((type = apr_table_get(conf->language_types, ext))) {
            const char **new;

            r->content_language = type;         /* back compat. only */
            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 = type;
            found = 1;
        }

        /* Check for Content-Encoding */
        if ((type = apr_table_get(conf->encoding_types, ext))) {
            if (!r->content_encoding)
                r->content_encoding = type;
            else
                r->content_encoding = apr_pstrcat(r->pool, r->content_encoding,
                                              ", ", type, NULL);
            found = 1;
        }

        /* Check for a special handler, but not for proxy request */
        if ((type = apr_table_get(conf->handlers, ext))
	    && (PROXYREQ_NONE == r->proxyreq) 
        ) {
            r->handler = type;
            found = 1;
        }

        /* This is to deal with cases such as foo.gif.bak, which we want
         * to not have a type. So if we find an unknown extension, we
         * zap the type/language/encoding and reset the handler
         */

        if (!found) {
            r->content_type = NULL;
            r->content_language = NULL;
            r->content_languages = NULL;
            r->content_encoding = NULL;
            r->handler = orighandler;
	    charset = NULL;
	}
    }

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

	ct = (char *) apr_palloc(r->pool,
				sizeof(char) * (strlen(r->content_type) + 1));
	strcpy(ct, r->content_type);

	if ((ctp = analyze_ct(r, ct))) {
	    param *pp = ctp->param;
	    r->content_type = apr_pstrcat(r->pool, ctp->type, "/",
					 ctp->subtype, NULL);
	    while (pp != NULL) {
		if (charset && !strcmp(pp->attr, "charset")) {
		    if (!override) {
			r->content_type = apr_pstrcat(r->pool, r->content_type,
						     "; charset=", charset,
						     NULL);
			override = 1;
		    }
		}
		else {
		    r->content_type = apr_pstrcat(r->pool, r->content_type,
						 "; ", pp->attr,
						 "=", pp->val,
						 NULL);
		}
		pp = pp->next;
	    }
	    if (charset && !override) {
		r->content_type = 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;

        r->content_language = conf->default_language; /* back compat. only */
        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;
    }

    /* Check for overrides with ForceType/SetHandler */

    if (conf->type && strcmp(conf->type, "none"))
        r->content_type = conf->type;
    if (conf->handler && strcmp(conf->handler, "none"))
        r->handler = conf->handler;

    if (!r->content_type)
        return DECLINED;

    return OK;
}

static void register_hooks(apr_pool_t *p)
{
    ap_hook_type_checker(find_ct,NULL,NULL,APR_HOOK_MIDDLE);
    ap_hook_post_config(mime_post_config,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 */
};
