#define WANT_BASENAME_MATCH
/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000 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.
 */

#include "httpd.h"
#include "http_core.h"
#include "http_config.h"
#include "http_log.h"

/* mod_nocase.c - Robert Adams (MisterBlue@MisterBlue.com) August 2001
 *   based on mod_speling.c by Alexei Kosut <akosut@organic.com> June, 1996
 *
 * This module is a simple, transparent module that makes
 * URLs to the site case insensitive.  The algorithm first checks for
 * an exact match (most hits) and if that doesn't work, it tries for
 * a caseless matching.
 *
 * This module was built on mod_speling by removing the spelling and partial
 * matching code.  I didn't like the potential for a user getting other
 * pages that the one requested but, at the same time, URLs are defined
 * to be case insensitive despite the feature of a UNIX style filesystem.
 * (Actually, the RFC says URLs "should" be interpreted as case insensitive
 * for robustness.  So, technically, it's not a requirement.)
 *
 * I also couldn't allow the "choice" selection dialog... that would confuse
 * the users and let people see the other things in the directories.
 *
 * Activate it with "NoCase On" in the <Directory> entry.
 *
 * Note: 
 *    1) As written, this only checks GETs so author your POSTs cleanly.
 *    2) If there is more then one directory entry that matches, this
 *       filter returns the exact match or, if no exact match, the "first"
 *       match.  "first" in directory order.  My presumption is that, if
 *       you are running a case insensitive site, you don't have entries
 *       that only differ by case.
 */

MODULE_VAR_EXPORT module nocase_module;

typedef struct {
    int enabled;
} ncconfig;

/*
 * Create a configuration specific to this module for a server or directory
 * location, and fill it with the default settings.
 *
 * The API says that in the absence of a merge function, the record for the
 * closest ancestor is used exclusively.  That's what we want, so we don't
 * bother to have such a function.
 */

static void *mkconfig(pool *p)
{
    ncconfig *cfg = ap_pcalloc(p, sizeof(ncconfig));

    cfg->enabled = 0;
    return cfg;
}

/*
 * Respond to a callback to create configuration record for a server or
 * vhost environment.
 */
static void *create_mconfig_for_server(pool *p, server_rec *s)
{
    return mkconfig(p);
}

/*
 * Respond to a callback to create a config record for a specific directory.
 */
static void *create_mconfig_for_directory(pool *p, char *dir)
{
    return mkconfig(p);
}

/*
 * Handler for the NoCase directive, which is FLAG.
 */
static const char *set_nocase(cmd_parms *cmd, void *mconfig, int arg)
{
    ncconfig *cfg = (ncconfig *) mconfig;

    cfg->enabled = arg;
    return NULL;
}

/*
 * Define the directives specific to this module.  This structure is referenced
 * later by the 'module' structure.
 */
static const command_rec nocase_cmds[] =
{
    { "NoCase", set_nocase, NULL, OR_OPTIONS, FLAG,
      "whether or not to fix case mismatched requests" },
    { NULL }
};

typedef enum {
    NC_IDENTICAL = 0,
    NC_MISCAPITALIZED = 1,
} nc_reason;

static const char *nc_reason_str[] =
{
    "identical",
    "miscapitalized",
};

typedef struct {
    const char *name;
    nc_reason quality;
} misspelled_file;

static int check_nocase(request_rec *r)
{
    ncconfig *cfg;
    char *good, *bad, *postgood, *url;
    int filoc, dotloc, urlen, pglen;
    DIR *dirp;
    struct DIR_TYPE *dir_entry;
    array_header *candidates = NULL;

    cfg = ap_get_module_config(r->per_dir_config, &nocase_module);
    if (!cfg->enabled) {
        return DECLINED;
    }

    /* We only want to worry about GETs */
    if (r->method_number != M_GET) {
        return DECLINED;
    }

    /* We've already got a file of some kind or another */
    if (r->proxyreq != NOT_PROXY || (r->finfo.st_mode != 0)) {
        return DECLINED;
    }

    /* This is a sub request - don't mess with it */
    if (r->main) {
        return DECLINED;
    }

    /*
     * The request should end up looking like this:
     * r->uri: /correct-url/mispelling/more
     * r->filename: /correct-file/mispelling r->path_info: /more
     *
     * So we do this in steps. First break r->filename into two pieces
     */

    filoc = ap_rind(r->filename, '/');
    /*
     * Don't do anything if the request doesn't contain a slash, or
     * requests "/" 
     */
    if (filoc == -1 || strcmp(r->uri, "/") == 0) {
        return DECLINED;
    }

    /* good = /correct-file */
    good = ap_pstrndup(r->pool, r->filename, filoc);
    /* bad = mispelling */
    bad = ap_pstrdup(r->pool, r->filename + filoc + 1);
    /* postgood = mispelling/more */
    postgood = ap_pstrcat(r->pool, bad, r->path_info, NULL);

    urlen = strlen(r->uri);
    pglen = strlen(postgood);

    /* Check to see if the URL pieces add up */
    if (strcmp(postgood, r->uri + (urlen - pglen))) {
        return DECLINED;
    }

    /* url = /correct-url */
    url = ap_pstrndup(r->pool, r->uri, (urlen - pglen));

    /* Now open the directory and do ourselves a check... */
    dirp = ap_popendir(r->pool, good);
    if (dirp == NULL) {          /* Oops, not a directory... */
        return DECLINED;
    }

    candidates = ap_make_array(r->pool, 2, sizeof(misspelled_file));

    dotloc = ap_ind(bad, '.');
    if (dotloc == -1) {
        dotloc = strlen(bad);
    }

    while ((dir_entry = readdir(dirp)) != NULL) {
        nc_reason q;

        /*
         * If we end up with a "fixed" URL which is identical to the
         * requested one, we must have found a broken symlink or some such.
         * Do _not_ try to redirect this, it causes a loop!
         */
        if (strcmp(bad, dir_entry->d_name) == 0) {
            ap_pclosedir(r->pool, dirp);
            return OK;
        }
        /*
         * miscapitalization errors 
         */
        else if (strcasecmp(bad, dir_entry->d_name) == 0) {
            misspelled_file *nc_new;

			nc_new = (misspelled_file *) ap_push_array(candidates);
				nc_new->name = ap_pstrdup(r->pool, dir_entry->d_name);
				nc_new->quality = NC_MISCAPITALIZED;
        }
    }

    ap_pclosedir(r->pool, dirp);	/* close the directory */

    if (candidates->nelts != 0) {
        /* Wow... we found us a case match. Construct a fixed url */
        char *nuri;
		const char *ref;
        misspelled_file *variant = (misspelled_file *) candidates->elts;
        int i;

        ref = ap_table_get(r->headers_in, "Referer");

		/*
		 * If there is only one match, it's an easy redirect.
		 * If there is more than one, we just go with the first assuming
		 *    that case insensitive sites would only have one and if there
		 *    is more than one, they had created links to overcome the
		 *    default case sensitive behaviour of Apache.
		 */
		nuri = ap_escape_uri(r->pool, ap_pstrcat(r->pool, url,
							 variant[0].name,
							 r->path_info, NULL));
		if (r->parsed_uri.query)
			nuri = ap_pstrcat(r->pool, nuri, "?", r->parsed_uri.query, NULL);
		ap_table_setn(r->headers_out, "Location",
		ap_construct_url(r->pool, nuri, r));
		ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, r,
				 ref ? "Fixed case: %s to %s from %s"
					 : "Fixed case: %s to %s",
				 r->uri, nuri, ref);

		return HTTP_MOVED_PERMANENTLY;
    }
	else {
		/* no case matches found */
	}

    return OK;
}

module MODULE_VAR_EXPORT nocase_module =
{
    STANDARD_MODULE_STUFF,
    NULL,                       /* initializer */
    create_mconfig_for_directory,  /* create per-dir config */
    NULL,                       /* merge per-dir config */
    create_mconfig_for_server,  /* server config */
    NULL,                       /* merge server config */
    nocase_cmds,               /* command table */
    NULL,                       /* handlers */
    NULL,                       /* filename translation */
    NULL,                       /* check_user_id */
    NULL,                       /* check auth */
    NULL,                       /* check access */
    NULL,                       /* type_checker */
    check_nocase,              /* fixups */
    NULL,                       /* logger */
    NULL,                       /* header parser */
    NULL,                       /* child_init */
    NULL,                       /* child_exit */
    NULL                        /* post read-request */
};

