/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.com/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */


/*
 * request.c --
 *
 *	Functions that implement the Ns_Request type.
 *	Pool memory is used as an optimization.
 */

static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/request.c,v 1.12 2005/08/10 13:24:41 jgdavidson Exp $, compiled: " __DATE__ " " __TIME__;

#include "nsd.h"

#define HTTP "HTTP/"

/*
 * Local functions defined in this file.
 */

static void SetUrl(Ns_Request * request, char *url, Tcl_Encoding encoding);
static void FreeUrl(Ns_Request * request);
static Ns_Mutex reqlock;


/*
 *----------------------------------------------------------------------
 *
 * Ns_FreeRequest --
 *
 *	Free an Ns_Request structure and all its members. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

void
Ns_FreeRequest(Ns_Request * request)
{
    if (request != NULL) {
        ns_free(request->line);
        ns_free(request->method);
        ns_free(request->protocol);
        ns_free(request->host);
        ns_free(request->query);
        FreeUrl(request);
        ns_free(request);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ParseRequest, Ns_ParseRequestEx --
 *
 *	Parse a request from a browser into an Ns_Request structure. 
 *	Utilize the given encoding, if present.
 *
 * Results:
 *	A new Ns_Request. 
 *
 * Side effects:
 *	The result is newly-allocated. 
 *
 *----------------------------------------------------------------------
 */

Ns_Request *
Ns_ParseRequest(char *line)
{
    return Ns_ParseRequestEx(line, NULL);
}

Ns_Request *
Ns_ParseRequestEx(char *line, Tcl_Encoding encoding)
{
    char       *url;
    char       *p;
    Ns_DString  ds;
    Ns_Request *request;
    unsigned int major, minor;
    int i;

    request = ns_calloc(1, sizeof(Ns_Request));
    Ns_DStringInit(&ds);

    /*
     * Make a copy of the line to chop up. Make sure it isn't blank.
     */
    
    if (line == NULL) {
        goto done;
    }
    Ns_DStringAppend(&ds, line);
    line = Ns_StrTrim(ds.string);
    if (*line == '\0') {
        goto done;
    }

    /*
     * Save the trimmed line for logging purposes.
     */
    
    request->line = ns_strdup(line);

    /*
     * Look for the minimum of method and url.
     */
    
    url = line;
    while (*url != '\0' && !isspace(UCHAR(*url))) {
        ++url;
    }
    if (*url == '\0') {
        goto done;
    }
    *url++ = '\0';
    while (*url != '\0' && isspace(UCHAR(*url))) {
        ++url;
    }
    if (*url == '\0') {
        goto done;
    }
    request->method = ns_strdup(line);


    /*
     * Look for a valid version.
     */
    
    request->version = 0.0;
    p = NsFindVersion(url, &major, &minor);
    if (p != NULL) {
	*p = '\0';
	i = 10;
	while ((minor / i) > 0) {
	    i *= 10;
	}
	request->version = major + (double) minor / i;
    }
    url = Ns_StrTrim(url);
    if (*url == '\0') {
        goto done;
    }

    /*
     * Look for a protocol in the URL.
     */
    
    request->protocol = NULL;
    request->host = NULL;
    request->port = 0;
    if (*url != '/') {
        p = url;
        while (*p != '\0' && *p != '/' && *p != ':') {
            ++p;
        }
        if (*p == ':') {

            /*
             * Found a protocol - copy it and search for host:port.
             */

	    *p++ = '\0';
            request->protocol = ns_strdup(url);
            url = p;
            if ((strlen(url) > 3) && (*p++ == '/')
                && (*p++ == '/') && (*p != '\0') && (*p != '/')) {
                char *h;

                h = p;
                while (*p != '\0' && *p != '/') {
                    ++p;
                }
                if (*p == '/') {
                    *p++ = '\0';
                }
                url = p;
                p = strchr(h, ':');
                if (p != NULL) {
                    *p++ = '\0';
		    if (Tcl_GetInt(NULL, p, &i) != TCL_OK) {
			goto done;
		    }
		    request->port = (unsigned short) i;
                }
                request->host = ns_strdup(h);
            }
        }
    }
    SetUrl(request, url, encoding);

done:
    if (request->url == NULL) {
        Ns_FreeRequest(request);
        request = NULL;
    }
    Ns_DStringFree(&ds);
    return request;
}


/*
 *----------------------------------------------------------------------
 *
 * NsFindVersion --
 *
 *	Find the HTTP/x.y version string in a request line.
 *
 * Results:
 *	Start of version string or NULL if not found.
 *
 * Side effects:
 *	Given majorPtr and minorPtr will be updated.
 *
 *----------------------------------------------------------------------
 */

char *
NsFindVersion(char *request, unsigned int *majorPtr, unsigned int *minorPtr)
{
    char *next, *version;
    int major, minor;

    version = NULL;
    while ((next = strstr(request, " HTTP/")) != NULL) {
	version = next;
	request += 6;
    }
    if (version != NULL && sscanf(version+6, "%u.%u", &major, &minor) != 2) {
	version = NULL;
    }
    if (version != NULL) {
	++version;
	if (majorPtr != NULL) {
	    *majorPtr = major;
	}
	if (minorPtr != NULL) {
	    *minorPtr = minor;
	}
    }
    return version;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_SkipUrl --
 *
 *	Return a pointer n elements into the request's url. 
 *
 * Results:
 *	The url beginning n elements in. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

char *
Ns_SkipUrl(Ns_Request *request, int n)
{
    int skip;

    if (n > request->urlc) {
        return NULL;
    }
    skip = 0;
    while (--n >= 0) {
        skip += strlen(request->urlv[n]) + 1;
    }
    return (request->url + skip);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_SetRequestUrl --
 *
 *	Set the url in a request structure. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	Makes a copy of url. 
 *
 *----------------------------------------------------------------------
 */

void
Ns_SetRequestUrl(Ns_Request * request, char *url)
{
    Ns_DString      ds;

    Ns_MutexLock(&reqlock);
    FreeUrl(request);
    Ns_DStringInit(&ds);
    Ns_DStringAppend(&ds, url);
    SetUrl(request, ds.string, NULL);
    Ns_MutexUnlock(&reqlock);
    Ns_DStringFree(&ds);
}


/*
 *----------------------------------------------------------------------
 *
 * FreeUrl --
 *
 *	Free the url in a request. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

static void
FreeUrl(Ns_Request * request)
{
    if (request->url != NULL) {
        ns_free(request->url);
        request->url = NULL;
    }
    if (request->urlv != NULL) {
        ns_free(request->urlv[0]);
        ns_free(request->urlv);
        request->urlv = NULL;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * SetUrl --
 *
 *	Break up an URL and put it in the request. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	Memory allocated for members.
 *
 *----------------------------------------------------------------------
 */

static void
SetUrl(Ns_Request *request, char *url, Tcl_Encoding encoding)
{
    Ns_Conn *conn;
    Ns_DString  ds1, ds2;
    char       *p;

    Ns_DStringInit(&ds1);
    Ns_DStringInit(&ds2);

    /*
     * Look for a query string at the end of the URL.
     */
    
    p = strchr(url, '?');
    if (p != NULL) {
        *p++ = '\0';
        if (request->query != NULL) {
            ns_free(request->query);
        }
        request->query = ns_strdup(p);
    }

    /*
     * Decode and normalize the URL.  If the encoding isn't specified,
     * use the encoding of the current connection, if any.
     */

    if (encoding == NULL && (conn = Ns_GetConn()) != NULL) {
	encoding = Ns_ConnGetUrlEncoding(conn);
    }
    p = Ns_DecodeUrlWithEncoding(&ds1, url, encoding);
    if (p == NULL) {
        p = url;
    }
    Ns_NormalizePath(&ds2, p);
    Ns_DStringTrunc(&ds1, 0);

    /*
     * Append a trailing slash to the normalized URL if the original URL
     * ended in slash that wasn't also the leading slash.
     */

    while (*url == '/') {
        ++url;
    }
    if (*url != '\0' && url[strlen(url) - 1] == '/') {
        Ns_DStringAppend(&ds2, "/");
    }
    request->url = ns_strdup(ds2.string);
    Ns_DStringFree(&ds2);

    /*
     * Build the urlv and set urlc.
     */

    p = ns_strdup(request->url + 1);
    Ns_DStringNAppend(&ds1, (char *) &p, sizeof(char *));
    while (*p != '\0') {
        if (*p == '/') {
            *p++ = '\0';
            if (*p == '\0') {
		/*
		 * Stop on a trailing slash.
		 */
		
                break;
            }
            Ns_DStringNAppend(&ds1, (char *) &p, sizeof(char *));
        }
        ++p;
    }
    request->urlc = ds1.length / (sizeof(char *));
    p = NULL;
    Ns_DStringNAppend(&ds1, (char *) &p, sizeof(char *));
    request->urlv = (char **) ns_malloc((size_t)ds1.length);
    memcpy(request->urlv, ds1.string, (size_t)ds1.length);
    Ns_DStringFree(&ds1);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ParseHeader --
 *
 *	Consume a header line, handling header continuation, placing
 *	results in given set.
 *
 * Results:
 *	NS_OK/NS_ERROR 
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

int
Ns_ParseHeader(Ns_Set *set, char *line, Ns_HeaderCaseDisposition disp)
{
    char           *key, *sep;
    char           *value;
    int             index;
    Ns_DString	    ds;

    /* 
     * Header lines are first checked if they continue a previous
     * header indicated by any preceeding white space.  Otherwise,
     * they must be in well form key: value form.
     */

    if (isspace(UCHAR(*line))) {
        index = Ns_SetLast(set);
        if (index < 0) {
	    return NS_ERROR;	/* Continue before first header. */
        }
        while (isspace(UCHAR(*line))) {
            ++line;
        }
        if (*line != '\0') {
	    value = Ns_SetValue(set, index);
	    Ns_DStringInit(&ds);
	    Ns_DStringVarAppend(&ds, value, " ", line, NULL);
	    Ns_SetPutValue(set, index, ds.string);
	    Ns_DStringFree(&ds);
	}
    } else {
        sep = strchr(line, ':');
        if (sep == NULL) {
	    return NS_ERROR;	/* Malformed header. */
	}
        *sep = '\0';
        value = sep + 1;
        while (*value != '\0' && isspace(UCHAR(*value))) {
            ++value;
        }
        index = Ns_SetPut(set, line, value);
        key = Ns_SetKey(set, index);
	if (disp == ToLower) {
            while (*key != '\0') {
	        if (isupper(UCHAR(*key))) {
            	    *key = tolower(UCHAR(*key));
		}
            	++key;
	    }
	} else if (disp == ToUpper) {
            while (*key != '\0') {
	        if (islower(UCHAR(*key))) {
		    *key = toupper(UCHAR(*key));
		}
		++key;
	    }
        }
        *sep = ':';
    }
    return NS_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * NsAppendRequest --
 *
 *	Utility function to append string of given request.

 * Results:
 *	None.
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

void
NsAppendRequest(Tcl_DString *dsPtr, Ns_Request *request)
{
    if (request == NULL) {
	Tcl_DStringAppend(dsPtr, " ? ?", -1);
    } else {
    	Ns_MutexLock(&reqlock);
	Tcl_DStringAppendElement(dsPtr, request->method);
	Tcl_DStringAppendElement(dsPtr, request->url);
    	Ns_MutexUnlock(&reqlock);
    }
}
