/* ====================================================================
 * 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.
 */

/*
 * This module is intended to test the util_uri routines by parsing a
 * bunch of urls and comparing the results with what we expect to
 * see.
 *
 * Usage:
 *
 * <Location /test-util-uri>
 * SetHandler test-util-uri
 * </Location>
 *
 * Then make a request to /test-util-uri.  An html apr_table_t of errors will
 * be output... and a total count of errors.
 */

#include "httpd.h"
#include "http_protocol.h"
#include "http_config.h"
#include "http_main.h"

typedef struct {
    const char *scheme;
    const char *user;
    const char *password;
    const char *hostname;
    const char *port_str;
    const char *path;
    const char *query;
    const char *fragment;
} test_uri_t;

#define T_scheme	0x01
#define T_user		0x02
#define T_password	0x04
#define T_hostname	0x08
#define T_port_str	0x10
#define T_path		0x20
#define T_query		0x40
#define T_fragment	0x80
#define T_MAX		0x100

/* The idea is that we list here a bunch of url pieces that we want
 * stitched together in every way that's valid.
 */
static const test_uri_t uri_tests[] = {
    { "http", "userid", "passwd", "hostname.goes.here", "80", "/path/goes/here", "query-here", "frag-here" },
    { "http", "", "passwd", "hostname.goes.here", "80", "/path/goes/here", "query-here", "frag-here" },
    { "http", "userid", "", "hostname.goes.here", "80", "/path/goes/here", "query-here", "frag-here" },
    { "http", "userid", "passwd", "", "80", "/path/goes/here", "query-here", "frag-here" },
    { "http", "userid", "passwd", "hostname.goes.here", "", "/path/goes/here", "query-here", "frag-here" },
#if 0
    /* An empty path means two different things depending on whether this is a
     * relative or an absolute uri... consider <a href="#frag"> versus "GET
     * http://hostname HTTP/1.1".  So this is why parse_uri_components returns
     * a NULL for path when it doesn't find one, instead of returning an empty
     * string.
     *
     * We don't really need to test it explicitly since path has no explicit
     * character that indicates its precense, and so we test empty paths all
     * the time by varying T_path in the loop.  It would just cost us extra
     * code to special case the empty path string...
     */
    { "http", "userid", "passwd", "hostname.goes.here", "80", "", "query-here", "frag-here" },
#endif
    { "http", "userid", "passwd", "hostname.goes.here", "80", "/path/goes/here", "", "frag-here" },
    { "http", "userid", "passwd", "hostname.goes.here", "80", "/path/goes/here", "query-here", "" },
    { "https", "user@d", "pa:swd", "hostname.goes.here.", "", "/~path/goes/here", "query&query?crud", "frag-here?baby" }

};

static char *my_stpcpy(char *d, const char *s)
{
    while((*d = *s)) {
	++d;
	++s;
    }
    return d;
}

/* return the number of failures */
static unsigned iterate_pieces(request_rec *r, const test_uri_t *pieces, int row)
{
    unsigned u;
    apr_pool_t *sub;
    char *input_uri;
    char *strp;
    uri_components result;
    unsigned expect;
    int status;
    unsigned failures;

    failures = 0;

    input_uri = apr_palloc(r->pool,
	strlen(pieces->scheme) + 3
	+ strlen(pieces->user) + 1
	+ strlen(pieces->password) + 1
	+ strlen(pieces->hostname) + 1
	+ strlen(pieces->port_str) + 1
	+ strlen(pieces->path) +
	+ strlen(pieces->query) + 1
	+ strlen(pieces->fragment) + 1
	+ 1);

    for (u = 0; u < T_MAX; ++u) {
	strp = input_uri;
	expect = 0;

	/* a scheme requires a hostinfo and vice versa */
	/* a hostinfo requires a hostname */
	if (u & (T_scheme|T_user|T_password|T_hostname|T_port_str)) {
	    expect |= T_scheme;
	    strp = my_stpcpy(strp, pieces->scheme);
	    *strp++ = ':';
	    *strp++ = '/';
	    *strp++ = '/';
	    /* can't have password without user */
	    if (u & (T_user|T_password)) {
		expect |= T_user;
		strp = my_stpcpy(strp, pieces->user);
		if (u & T_password) {
		    expect |= T_password;
		    *strp++ = ':';
		    strp = my_stpcpy(strp, pieces->password);
		}
		*strp++ = '@';
	    }
	    expect |= T_hostname;
	    strp = my_stpcpy(strp, pieces->hostname);
	    if (u & T_port_str) {
		expect |= T_port_str;
		*strp++ = ':';
		strp = my_stpcpy(strp, pieces->port_str);
	    }
	}
	if (u & T_path) {
	    expect |= T_path;
	    strp = my_stpcpy(strp, pieces->path);
	}
	if (u & T_query) {
	    expect |= T_query;
	    *strp++ = '?';
	    strp = my_stpcpy(strp, pieces->query);
	}
	if (u & T_fragment) {
	    expect |= T_fragment;
	    *strp++ = '#';
	    strp = my_stpcpy(strp, pieces->fragment);
	}
	*strp = 0;

	sub = apr_pool_sub_make(r->pool);
	status = ap_parse_uri_components(sub, input_uri, &result);
	if (status == HTTP_OK) {
#define CHECK(f)							\
	    if ((expect & T_##f)					\
		&& (result.f == NULL || strcmp(result.f, pieces->f))) { \
		status = HTTP_INTERNAL_SERVER_ERROR;			\
	    }								\
	    else if (!(expect & T_##f) && result.f != NULL) {		\
		status = HTTP_INTERNAL_SERVER_ERROR;			\
	    }
	    CHECK(scheme)
	    CHECK(user)
	    CHECK(password)
	    CHECK(hostname)
	    CHECK(port_str)
	    CHECK(path)
	    CHECK(query)
	    CHECK(fragment)
#undef CHECK
	}
	if (status != HTTP_OK) {
	    ap_rprintf(r, "<tr><td>%d</td><td>0x%02x</td><td>0x%02x</td><td>%d</td><td>\"%s\"</td>", row, u, expect, status, input_uri);
#define DUMP(f) 							\
	    if (result.f) {						\
		ap_rvputs(r, "<td>\"", result.f, "\"<br>", NULL);		\
	    }								\
	    else {							\
		ap_rputs("<td>NULL<br>", r);				\
	    }								\
	    if (expect & T_##f) {					\
		ap_rvputs(r, "\"", pieces->f, "\"</td>", NULL);		\
	    }								\
	    else {							\
		ap_rputs("NULL</td>", r);					\
	    }
	    DUMP(scheme);
	    DUMP(user);
	    DUMP(password);
	    DUMP(hostname);
	    DUMP(port_str);
	    DUMP(path);
	    DUMP(query);
	    DUMP(fragment);
#undef DUMP
	    ap_rputs("</tr>\n", r);
	    ++failures;
	}
	apr_pool_destroy(sub);
    }
    return failures;
}

static int test_util_uri(request_rec *r)
{
    unsigned total_failures;
    int i;

    r->allowed |= (1 << M_GET);
    if (r->method_number != M_GET)
	return DECLINED;

    r->content_type = "text/html";		
    if(r->header_only) {
	return 0;
    }
    ap_hard_timeout("test_util_uri", r);

    ap_rputs(
DOCTYPE_HTML_2_0 "
<html><body>
<p>Key:
<dl>
<dt>row
<dd>entry number in the uri_tests array
<dt>u
<dd>fields under test
<dt>expected
<dd>fields expected in the result
<dt>status
<dd>response from parse_uri_components, or 500 if unexpected results
<dt>input uri
<dd>the uri given to parse_uri_components
</dl>
<p>The remaining fields are the pieces returned from parse_uri_components, and
the values we expected for each piece (resp.).
<p>Only failures are displayed.
<p>
<table><tr><th>row</th><th>u</th><th>expect</th><th>status</th><th>input uri</th>", r);
#define HEADER(f) ap_rprintf(r, "<th>" #f "<br>0x%02x</th>", T_##f)
    HEADER(scheme);
    HEADER(user);
    HEADER(password);
    HEADER(hostname);
    HEADER(port_str);
    HEADER(path);
    HEADER(query);
    HEADER(fragment);
#undef HEADER

    if (r->args) {
	i = atoi(r->args);
	total_failures = iterate_pieces(r, &uri_tests[i], i);
    }
    else {
	total_failures = 0;
	for (i = 0; i < sizeof(uri_tests) / sizeof(uri_tests[0]); ++i) {
	    total_failures += iterate_pieces(r, &uri_tests[i], i);
	    if (total_failures > 256) {
		ap_rprintf(r, "</table>\n<b>Stopped early to save your browser "
			   "from certain death!</b>\nTOTAL FAILURES = %u\n",
			   total_failures);
		return OK;
	    }
	}
    }
    ap_rprintf(r, "</table>\nTOTAL FAILURES = %u\n", total_failures);

    return OK;
}

static const handler_rec test_util_uri_handlers[] =
{
    {"test-util-uri", test_util_uri},
    {NULL}
};

module test_util_uri_module = {
    STANDARD_MODULE_STUFF,
    NULL,                       /* initializer */
    NULL,			/* dir config creater */
    NULL,                       /* dir merger --- default is to override */
    NULL,                       /* server config */
    NULL,                       /* merge server config */
    NULL,			/* command apr_table_t */
    test_util_uri_handlers,	/* handlers */
    NULL,                       /* filename translation */
    NULL,                       /* check_user_id */
    NULL,                       /* check auth */
    NULL,                       /* check access */
    NULL,                       /* type_checker */
    NULL,                       /* fixups */
    NULL,                       /* logger */
    NULL                        /* header parser */
};
