/* Copyright information is at the end of the file */

#define _XOPEN_SOURCE 600   /* For strdup() */
#define _BSD_SOURCE   /* For xmlrpc_strcaseeq() */

#include <ctype.h>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <time.h>

#include "xmlrpc_config.h"
#include "bool.h"
#include "mallocvar.h"
#include "xmlrpc-c/util.h"
#include "xmlrpc-c/string_int.h"
#include "xmlrpc-c/base64_int.h"
#include "xmlrpc-c/abyss.h"

#include "server.h"
#include "session.h"
#include "conn.h"
#include "token.h"
#include "date.h"
#include "data.h"

#include "http.h"

/*********************************************************************
** Request Parser
*********************************************************************/

/*********************************************************************
** Request
*********************************************************************/

static void
initRequestInfo(TRequestInfo * const requestInfoP,
                httpVersion    const httpVersion,
                const char *   const requestLine,
                TMethod        const httpMethod,
                const char *   const host,
                unsigned int   const port,
                const char *   const path,
                const char *   const query) {
/*----------------------------------------------------------------------------
  Set up the request info structure.  For information that is
  controlled by headers, use the defaults -- I.e. the value that
  applies if the request contains no applicable header.
-----------------------------------------------------------------------------*/
    XMLRPC_ASSERT_PTR_OK(requestLine);
    XMLRPC_ASSERT_PTR_OK(path);

    requestInfoP->requestline = strdup(requestLine);
    requestInfoP->method      = httpMethod;
    requestInfoP->host        = xmlrpc_strdupnull(host);
    requestInfoP->port        = port;
    requestInfoP->uri         = strdup(path);
    requestInfoP->query       = xmlrpc_strdupnull(query);
    requestInfoP->from        = NULL;
    requestInfoP->useragent   = NULL;
    requestInfoP->referer     = NULL;
    requestInfoP->user        = NULL;

    if (httpVersion.major > 1 ||
        (httpVersion.major == 1 && httpVersion.minor >= 1))
        requestInfoP->keepalive = TRUE;
    else
        requestInfoP->keepalive = FALSE;
}



static void
freeRequestInfo(TRequestInfo * const requestInfoP) {

    xmlrpc_strfreenull(requestInfoP->host);

    xmlrpc_strfreenull(requestInfoP->user);

    xmlrpc_strfree(requestInfoP->uri);

    xmlrpc_strfree(requestInfoP->requestline);
}



void
RequestInit(TSession * const sessionP,
            TConn *    const connectionP) {

    sessionP->validRequest = false;  /* Don't have valid request yet */

    time(&sessionP->date);

    sessionP->connP = connectionP;

    sessionP->responseStarted = FALSE;

    sessionP->chunkedwrite = FALSE;
    sessionP->chunkedwritemode = FALSE;

    sessionP->continueRequired = FALSE;

    ListInit(&sessionP->cookies);
    ListInit(&sessionP->ranges);
    TableInit(&sessionP->requestHeaderFields);
    TableInit(&sessionP->responseHeaderFields);

    sessionP->status = 0;  /* No status from handler yet */

    StringAlloc(&(sessionP->header));
}



void
RequestFree(TSession * const sessionP) {

    if (sessionP->validRequest)
        freeRequestInfo(&sessionP->requestInfo);

    ListFree(&sessionP->cookies);
    ListFree(&sessionP->ranges);
    TableFree(&sessionP->requestHeaderFields);
    TableFree(&sessionP->responseHeaderFields);
    StringFree(&(sessionP->header));
}



static char *
firstLfPos(TConn * const connectionP,
           char *  const lineStart) {
/*----------------------------------------------------------------------------
   Return a pointer in the connection's receive buffer to the first
   LF (linefeed aka newline) character in the buffer at or after 'lineStart'.

   If there is no LF in the buffer at or after 'lineStart', return NULL.
-----------------------------------------------------------------------------*/
    const char * const bufferEnd =
        connectionP->buffer.t + connectionP->buffersize;

    char * p;

    for (p = lineStart; p < bufferEnd && *p != LF; ++p);

    if (p < bufferEnd)
        return p;
    else
        return NULL;
}



static void
getLineInBuffer(TConn * const connectionP,
                char *  const lineStart,
                time_t  const deadline,
                char ** const lineEndP,
                bool *  const errorP) {
/*----------------------------------------------------------------------------
   Get a line into the connection's read buffer, starting at position
   'lineStart', if there isn't one already there.   'lineStart' is either
   within the buffer or just after it.

   Read the channel until we get a full line, except fail if we don't get
   one by 'deadline'.
-----------------------------------------------------------------------------*/
    bool error;
    char * lfPos;

    assert(lineStart <= connectionP->buffer.t + connectionP->buffersize);

    error = FALSE;  /* initial value */
    lfPos = NULL;  /* initial value */

    while (!error && !lfPos) {
        int const timeLeft = (int)(deadline - time(NULL));
        if (timeLeft <= 0)
            error = TRUE;
        else {
            lfPos = firstLfPos(connectionP, lineStart);
            if (!lfPos) {
                const char * readError;
                ConnRead(connectionP, timeLeft, NULL, NULL, &readError);
                if (readError) {
                    error = TRUE;
                    xmlrpc_strfree(readError);
                }
            }
        }
    }    
    *errorP = error;
    *lineEndP = lfPos + 1;
}



static bool
isContinuationLine(const char * const line) {

    return (line[0] == ' ' || line[0] == '\t');
}



static bool
isEmptyLine(const char * const line) {

    return (line[0] == '\n' || (line[0] == '\r' && line[1] == '\n'));
}



static void
convertLineEnd(char * const lineStart,
               char * const prevLineStart,
               char   const newVal) {
/*----------------------------------------------------------------------------
   Assuming a line begins at 'lineStart' and the line before it (the
   "previous line") begins at 'prevLineStart', replace the line
   delimiter at the end of the previous line with the character 'newVal'.

   The line delimiter is either CRLF or LF.  In the CRLF case, we replace
   both CR and LF with 'newVal'.
-----------------------------------------------------------------------------*/
    assert(lineStart >= prevLineStart + 1);
    *(lineStart-1) = newVal;
    if (prevLineStart + 1 < lineStart &&
        *(lineStart-2) == CR)
        *(lineStart-2) = newVal;
}



static void
getRestOfHeader(TConn *       const connectionP,
                char *        const lineEnd,
                time_t        const deadline,
                const char ** const headerEndP,
                bool *        const errorP) {
/*----------------------------------------------------------------------------
   Given that the read buffer for connection *connectionP contains (at
   its current read position) the first line of an HTTP header, which
   ends at position 'lineEnd', find the rest of it.

   Some or all of the rest of the header may be in the buffer already;
   we read more from the connection as necessary, but not if it takes past
   'deadline'.  In the latter case, we fail.

   We return the location of the end of the whole header as *headerEndP.
   We do not remove the header from the buffer, but we do modify the
   buffer so as to join the multiple lines of the header into a single
   line, and to NUL-terminate the header.
-----------------------------------------------------------------------------*/
    char * const headerStart = connectionP->buffer.t + connectionP->bufferpos;

    char * headerEnd;
        /* End of the header lines we've seen at so far */
    bool gotWholeHeader;
    bool error;

    headerEnd = lineEnd;  /* initial value - end of 1st line */
        
    for (gotWholeHeader = FALSE, error = FALSE;
         !gotWholeHeader && !error;) {

        char * nextLineEnd;

        /* Note that we are guaranteed, assuming the HTTP stream is
           valid, that there is at least one more line in it.  Worst
           case, it's the empty line that marks the end of the headers.
        */
        getLineInBuffer(connectionP, headerEnd, deadline,
                        &nextLineEnd, &error);
        if (!error) {
            if (isContinuationLine(headerEnd)) {
                /* Join previous line to this one */
                convertLineEnd(headerEnd, headerStart, ' ');
                /* Add this line to the header */
                headerEnd = nextLineEnd;
            } else {
                gotWholeHeader = TRUE;

                /* NUL-terminate the whole header */
                convertLineEnd(headerEnd, headerStart, '\0');
            }
        }
    }
    *headerEndP = headerEnd;
    *errorP     = error;
}



static void
readHeader(TConn * const connectionP,
           time_t  const deadline,
           bool *  const endOfHeadersP,
           char ** const headerP,
           bool *  const errorP) {
/*----------------------------------------------------------------------------
   Read an HTTP header, or the end of headers empty line, on connection
   *connectionP.

   An HTTP header is basically a line, except that if a line starts
   with white space, it's a continuation of the previous line.  A line
   is delimited by either LF or CRLF.

   The first line of an HTTP header is never empty; an empty line signals
   the end of the HTTP headers and beginning of the HTTP body.  We call
   that empty line the EOH mark.

   We assume the connection is positioned to a header or EOH mark.
   
   In the course of reading, we read at least one character past the
   line delimiter at the end of the header or EOH mark; we may read
   much more.  But we leave everything after the header or EOH (and
   its line delimiter) in the internal buffer, with the buffer pointer
   pointing to it.

   We use stuff already in the internal buffer (perhaps left by a
   previous call to this subroutine) before reading any more from from
   the channel.

   We return as *headerP the next header as an ASCIIZ string, with no
   line delimiter.  That string is stored in the "unused" portion of
   the connection's internal buffer.  Iff there is no next header, we
   return *endOfHeadersP == true and nothing meaningful as *headerP.
-----------------------------------------------------------------------------*/
    char * const bufferStart = connectionP->buffer.t + connectionP->bufferpos;

    bool error;
    char * lineEnd;

    getLineInBuffer(connectionP, bufferStart, deadline, &lineEnd, &error);

    if (!error) {
        if (isContinuationLine(bufferStart))
            error = TRUE;
        else if (isEmptyLine(bufferStart)) {
            /* Consume the EOH mark from the buffer */
            connectionP->bufferpos = lineEnd - connectionP->buffer.t;
            *endOfHeadersP = TRUE;
        } else {
            /* We have the first line of a header; there may be more. */

            const char * headerEnd;

            *endOfHeadersP = FALSE;

            getRestOfHeader(connectionP, lineEnd, deadline,
                            &headerEnd, &error);

            if (!error) {
                *headerP = bufferStart;

                /* Consume the header from the buffer (but be careful --
                   you can't reuse that part of the buffer because the
                   string we will return is in it!
                */
                connectionP->bufferpos = headerEnd - connectionP->buffer.t;
            }
        }
    }
    *errorP = error;
}



static void
skipToNonemptyLine(TConn * const connectionP,
                   time_t  const deadline,
                   bool *  const errorP) {

    char * const bufferStart = connectionP->buffer.t + connectionP->bufferpos;

    bool gotNonEmptyLine;
    bool error;
    char * lineStart;
    
    lineStart       = bufferStart;  /* initial value */
    gotNonEmptyLine = FALSE;        /* initial value */
    error           = FALSE;        /* initial value */          

    while (!gotNonEmptyLine && !error) {
        char * lineEnd;

        getLineInBuffer(connectionP, lineStart, deadline, &lineEnd, &error);

        if (!error) {
            if (!isEmptyLine(lineStart))
                gotNonEmptyLine = TRUE;
            else
                lineStart = lineEnd;
        }
    }
    if (!error) {
        /* Consume all the empty lines; advance buffer pointer to first
           non-empty line.
        */
        connectionP->bufferpos = lineStart - connectionP->buffer.t;
    }
    *errorP = error;
}



static void
readRequestHeader(TSession * const sessionP,
                  time_t     const deadline,
                  char **    const requestLineP,
                  uint16_t * const httpErrorCodeP) {
/*----------------------------------------------------------------------------
   Read the HTTP request header (aka request header field) from
   session 'sessionP'.  We read through the session's internal buffer;
   i.e.  we may get data that was previously read from the network, or
   we may read more from the network.

   We assume the connection is presently positioned to the beginning of
   the HTTP document.  We leave it positioned after the request header.
   
   We ignore any empty lines at the beginning of the stream, per
   RFC2616 Section 4.1.

   Fail if we can't get the header before 'deadline'.

   Return as *requestLineP the request header read.  This ASCIIZ string is
   in the session's internal buffer.

   Return as *httpErrorCodeP the HTTP error code that describes how we
   are not able to read the request header, or 0 if we can.
   If we can't, *requestLineP is meaningless.
-----------------------------------------------------------------------------*/
    char * line;
    bool error;
    bool endOfHeaders;

    skipToNonemptyLine(sessionP->connP, deadline, &error);

    if (!error) {
        readHeader(sessionP->connP, deadline, &endOfHeaders, &line, &error);

        /* End of headers is delimited by an empty line, and we skipped all
           the empty lines above, so readHeader() could not have encountered
           EOH:
        */
        assert(!endOfHeaders);
    }
    if (error)
        *httpErrorCodeP = 408;  /* Request Timeout */
    else {
        *httpErrorCodeP = 0;
        *requestLineP = line;
    }
}



static void
unescapeUri(char * const uri,
            bool * const errorP) {

    char * x;
    char * y;

    x = y = uri;
    
    *errorP = FALSE;

    while (*x && !*errorP) {
        switch (*x) {
        case '%': {
            char c;
            ++x;
            c = tolower(*x++);
            if ((c >= '0') && (c <= '9'))
                c -= '0';
            else if ((c >= 'a') && (c <= 'f'))
                c -= 'a' - 10;
            else
                *errorP = TRUE;

            if (!*errorP) {
                char d;
                d = tolower(*x++);
                if ((d >= '0') && (d <= '9'))
                    d -= '0';
                else if ((d >= 'a') && (d <= 'f'))
                    d -= 'a' - 10;
                else
                    *errorP = TRUE;

                if (!*errorP)
                    *y++ = ((c << 4) | d);
            }
        } break;

        default:
            *y++ = *x++;
            break;
        }
    }
    *y = '\0';
}



static void
parseHostPort(const char *     const hostport,
              const char **    const hostP,
              unsigned short * const portP,
              const char **    const errorP,
              uint16_t *       const httpErrorCodeP) {
/*----------------------------------------------------------------------------
   Parse a 'hostport', a string in the form www.acme.com:8080 .

   Return the host name part (www.acme.com) as *hostP (in newly
   malloced storage), and the port part (8080) as *portP.

   Default the port to 80 if 'hostport' doesn't have the port part.
-----------------------------------------------------------------------------*/
    char * buffer;

    buffer = strdup(hostport);

    if (!buffer)
        xmlrpc_asprintf(errorP, "Couldn't get memory for host/port buffer");
    else {
        /* Note that the host portion may contain colons.  The old RFC says
           it can't, but a newer one says the host may be an IPv6 address
           in the form [x:x:x...].  But the port portion may contain only
           digits, so we use the _last_ colon as the delimiter.
        */
        char * const colonPos = strrchr(buffer, ':');

        if (colonPos) {
            const char * p;
            uint32_t port;

            *colonPos = '\0';  /* Split hostport at the colon */

            for (p = colonPos + 1, port = 0;
                 isdigit(*p) && port < 65535;
                 (port = port * 10 + (*p - '0')), ++p);
            
            if (*p || port == 0) {
                xmlrpc_asprintf(errorP, "There is nothing, or something "
                                "non-numeric for the port number after the "
                                "colon in '%s'", hostport);
                *httpErrorCodeP = 400;  /* Bad Request */
            } else {
                *hostP = strdup(buffer);
                *portP = port;
                *errorP = NULL;
            }
        } else {
            *hostP          = strdup(buffer);
            *portP          = 80;
            *errorP = NULL;
        }
        free(buffer);
    }
}



static void
parseRequestUri(char *           const requestUri,
                const char **    const hostP,
                unsigned short * const portP,
                const char **    const pathP,
                const char **    const queryP,
                uint16_t *       const httpErrorCodeP) {
/*----------------------------------------------------------------------------
  Parse the request URI (in the request line
  "GET http://www.myserver.com:8080/myfile.cgi?parm HTTP/1.1",
  "http://www.myserver.com:8080/myfile.cgi?parm" is the request URI).

  Return as *hostP the "www.myserver.com" in the above example.  If
  that part of the URI doesn't exist, return *hostP == NULL.

  Return as *portP the 8080 in the above example.  If it doesn't exist,
  return 80.

  Return as *pathP the "/myfile.cgi" in the above example.  If it
  doesn't exist, return "*".

  Return as *queryP the "parm" in the above example.  If it doesn't
  exist, return *queryP == NULL.

  Return strings in newly malloc'ed storage.

  Return as *httpErrorCodeP the HTTP error code that describes how the
  URI is invalid, or 0 if it is valid.  If it's invalid, other return
  values are meaningless.

  This destroys 'requestUri'.  We should fix that.
-----------------------------------------------------------------------------*/
    bool error;

    unescapeUri(requestUri, &error);
    
    if (error)
        *httpErrorCodeP = 400;  /* Bad Request */
    else {
        char * requestUriNoQuery;
           /* The request URI with any query (the stuff marked by a question
              mark at the end of a request URI) chopped off.
           */
        {
            /* Split requestUri at the question mark */
            char * const qmark = strchr(requestUri, '?');
            
            if (qmark) {
                *qmark = '\0';
                *queryP = strdup(qmark + 1);
            } else
                *queryP = NULL;

            requestUriNoQuery = requestUri;
        }

        if (requestUriNoQuery[0] == '/') {
            *hostP = NULL;
            *pathP = strdup(requestUriNoQuery);
            *portP = 80;
            *httpErrorCodeP = 0;
        } else {
            if (!xmlrpc_strneq(requestUriNoQuery, "http://", 7))
                *httpErrorCodeP = 400;  /* Bad Request */
            else {
                char * const hostportpath = &requestUriNoQuery[7];
                char * const slashPos = strchr(hostportpath, '/');

                const char * host;
                const char * path;
                unsigned short port;

                char * hostport;
                
                if (slashPos) {
                    char * p;
                    path = strdup(slashPos);
                    
                    /* Nul-terminate the host name.  To make space for
                       it, slide the whole name back one character.
                       This moves it into the space now occupied by
                       the end of "http://", which we don't need.
                    */
                    for (p = hostportpath; *p != '/'; ++p)
                        *(p-1) = *p;
                    *(p-1) = '\0';
                    
                    hostport = hostportpath - 1;
                    *httpErrorCodeP = 0;
                } else {
                    path = strdup("*");
                    hostport = hostportpath;
                    *httpErrorCodeP = 0;
                }
                if (!*httpErrorCodeP) {
                    const char * error;
                    parseHostPort(hostport, &host, &port,
                                  &error, httpErrorCodeP);
                    if (error)
                        xmlrpc_strfree(error);
                    else
                        *httpErrorCodeP = 0;
                }
                if (*httpErrorCodeP)
                    xmlrpc_strfree(path);

                *hostP  = host;
                *portP  = port;
                *pathP  = path;
            }
        }
    }
}



static void
parseRequestLine(char *           const requestLine,
                 TMethod *        const httpMethodP,
                 httpVersion *    const httpVersionP,
                 const char **    const hostP,
                 unsigned short * const portP,
                 const char **    const pathP,
                 const char **    const queryP,
                 bool *           const moreLinesP,
                 uint16_t *       const httpErrorCodeP) {
/*----------------------------------------------------------------------------
   Modifies *requestLine!
-----------------------------------------------------------------------------*/
    const char * httpMethodName;
    char * p;

    p = requestLine;

    /* Jump over spaces */
    NextToken((const char **)&p);

    httpMethodName = GetToken(&p);
    if (!httpMethodName)
        *httpErrorCodeP = 400;  /* Bad Request */
    else {
        char * requestUri;

        if (xmlrpc_streq(httpMethodName, "GET"))
            *httpMethodP = m_get;
        else if (xmlrpc_streq(httpMethodName, "PUT"))
            *httpMethodP = m_put;
        else if (xmlrpc_streq(httpMethodName, "OPTIONS"))
            *httpMethodP = m_options;
        else if (xmlrpc_streq(httpMethodName, "DELETE"))
            *httpMethodP = m_delete;
        else if (xmlrpc_streq(httpMethodName, "POST"))
            *httpMethodP = m_post;
        else if (xmlrpc_streq(httpMethodName, "TRACE"))
            *httpMethodP = m_trace;
        else if (xmlrpc_streq(httpMethodName, "HEAD"))
            *httpMethodP = m_head;
        else
            *httpMethodP = m_unknown;
        
        /* URI and Query Decoding */
        NextToken((const char **)&p);

        requestUri = GetToken(&p);
        if (!requestUri)
            *httpErrorCodeP = 400;  /* Bad Request */
        else {
            const char * host;
            unsigned short port;
            const char * path;
            const char * query;

            parseRequestUri(requestUri, &host, &port, &path, &query,
                            httpErrorCodeP);

            if (!*httpErrorCodeP) {
                const char * httpVersion;

                NextToken((const char **)&p);
        
                /* HTTP Version Decoding */
                
                httpVersion = GetToken(&p);
                if (httpVersion) {
                    uint32_t vmin, vmaj;
                    if (sscanf(httpVersion, "HTTP/%d.%d", &vmaj, &vmin) != 2)
                        *httpErrorCodeP = 400;  /* Bad Request */
                    else {
                        httpVersionP->major = vmaj;
                        httpVersionP->minor = vmin;
                        *httpErrorCodeP = 0;  /* no error */
                    }
                    *moreLinesP = TRUE;
                } else {
                    /* There is no HTTP version, so this is a single
                       line request.
                    */
                    *httpErrorCodeP = 0;  /* no error */
                    *moreLinesP = FALSE;
                }
                if (*httpErrorCodeP) {
                    xmlrpc_strfree(host);
                    xmlrpc_strfree(path);
                    xmlrpc_strfree(query);
                }
                *hostP = host;
                *portP = port;
                *pathP = path;
                *queryP = query;
            }
        }
    }
}



static void
strtolower(char * const s) {

    char * t;

    t = &s[0];
    while (*t) {
        *t = tolower(*t);
        ++t;
    }
}



static void
getFieldNameToken(char **       const pP,
                  char **       const fieldNameP,
                  const char ** const errorP,
                  uint16_t *    const httpErrorCodeP) {
/*----------------------------------------------------------------------------
   Assuming that *pP points to the place in an HTTP header where the field
   name belongs, return the field name and advance *pP past that token.

   The field name is the lower case representation of the value of the
   field name token.
-----------------------------------------------------------------------------*/
    char * fieldName;

    NextToken((const char **)pP);
    
    fieldName = GetToken(pP);
    if (!fieldName) {
        xmlrpc_asprintf(errorP, "The header has no field name token");
        *httpErrorCodeP = 400;  /* Bad Request */
    } else {
        if (fieldName[strlen(fieldName)-1] != ':') {
            /* Not a valid field name */
            xmlrpc_asprintf(errorP, "The field name token '%s' "
                            "does not end with a colon (:)", fieldName);
            *httpErrorCodeP = 400;  /* Bad Request */
        } else {
            fieldName[strlen(fieldName)-1] = '\0';  /* remove trailing colon */

            strtolower(fieldName);
            
            *errorP = NULL;
        }
    }
    *fieldNameP = fieldName;
}



static void
processHeader(const char *  const fieldName,
              char *        const fieldValue,
              TSession *    const sessionP,
              const char ** const errorP,
              uint16_t *    const httpErrorCodeP) {
/*----------------------------------------------------------------------------
   We may modify *fieldValue, and we put pointers to *fieldValue and
   *fieldName into *sessionP.

   We must fix this some day.  *sessionP should point to individual
   malloc'ed strings.
-----------------------------------------------------------------------------*/
    *errorP = NULL;  /* initial assumption */

    if (xmlrpc_streq(fieldName, "connection")) {
        if (xmlrpc_strcaseeq(fieldValue, "keep-alive"))
            sessionP->requestInfo.keepalive = TRUE;
        else
            sessionP->requestInfo.keepalive = FALSE;
    } else if (xmlrpc_streq(fieldName, "host")) {
        if (sessionP->requestInfo.host) {
            xmlrpc_strfree(sessionP->requestInfo.host);
            sessionP->requestInfo.host = NULL;
        }
        parseHostPort(fieldValue, &sessionP->requestInfo.host,
                      &sessionP->requestInfo.port, errorP, httpErrorCodeP);
    } else if (xmlrpc_streq(fieldName, "from"))
        sessionP->requestInfo.from = fieldValue;
    else if (xmlrpc_streq(fieldName, "user-agent"))
        sessionP->requestInfo.useragent = fieldValue;
    else if (xmlrpc_streq(fieldName, "referer"))
        sessionP->requestInfo.referer = fieldValue;
    else if (xmlrpc_streq(fieldName, "range")) {
        if (xmlrpc_strneq(fieldValue, "bytes=", 6)) {
            bool succeeded;
            succeeded = ListAddFromString(&sessionP->ranges, &fieldValue[6]);
            if (!succeeded) {
                xmlrpc_asprintf(errorP, "ListAddFromString() failed for "
                                "\"range: bytes=...\" header value '%s'",
                                &fieldValue[6]);
                *httpErrorCodeP = 400;
            }
        }
    } else if (xmlrpc_streq(fieldName, "cookies")) {
        bool succeeded;
        succeeded = ListAddFromString(&sessionP->cookies, fieldValue);
        if (!succeeded) {
            xmlrpc_asprintf(errorP, "ListAddFromString() failed for "
                            "cookies: header value '%s'", fieldValue);
            *httpErrorCodeP = 400;
        }
    } else if (xmlrpc_streq(fieldName, "expect")) {
        if (xmlrpc_strcaseeq(fieldValue, "100-continue"))
            sessionP->continueRequired = TRUE;
    }
}



static void
readAndProcessHeaders(TSession *    const sessionP,
                      time_t        const deadline,
                      const char ** const errorP,
                      uint16_t *    const httpErrorCodeP) {
/*----------------------------------------------------------------------------
   Read all the HTTP headers from the session *sessionP, which has at
   least one header coming.  Update *sessionP to reflect the
   information in the headers.

   If we find an error in the headers or while trying to read them, we return
   a text explanation of the problem as *errorP and an appropriate HTTP error
   code as *httpErrorCodeP.  Otherwise, we return *errorP = NULL and nothing
   as *httpErrorCodeP.
-----------------------------------------------------------------------------*/
    bool endOfHeaders;

    assert(!sessionP->validRequest);
        /* Calling us doesn't make sense if there is already a valid request */

    *errorP = NULL;  /* initial assumption */
    endOfHeaders = false;  /* Caller assures us there is at least one header */

    while (!endOfHeaders && !*errorP) {
        char * header;
        bool error;
        readHeader(sessionP->connP, deadline, &endOfHeaders, &header, &error);
        if (error) {
            xmlrpc_asprintf(errorP, "Failed to read headers from "
                            "client connection.");
            *httpErrorCodeP = 408;  /* Request Timeout */
        } else {
            if (!endOfHeaders) {
                char * p;
                char * fieldName;

                p = &header[0];
                getFieldNameToken(&p, &fieldName, errorP, httpErrorCodeP);
                if (!*errorP) {
                    char * fieldValue;
                    
                    NextToken((const char **)&p);
                    
                    fieldValue = p;

                    TableAdd(&sessionP->requestHeaderFields,
                             fieldName, fieldValue);
                    
                    processHeader(fieldName, fieldValue, sessionP, errorP,
                                  httpErrorCodeP);
                }
            }
        }
    }
}



void
RequestRead(TSession *    const sessionP,
            uint32_t      const timeout,
            const char ** const errorP,
            uint16_t *    const httpErrorCodeP) {
/*----------------------------------------------------------------------------
   Read the headers of a new HTTP request (assuming nothing has yet been
   read on the session).

   Update *sessionP with the information from the headers.

   Leave the connection positioned to the body of the request, ready
   to be read by an HTTP request handler (via SessionRefillBuffer() and
   SessionGetReadData()).
-----------------------------------------------------------------------------*/
    time_t const deadline = time(NULL) + timeout;

    uint16_t httpErrorCode;  /* zero for no error */
    char * requestLine;  /* In connection;s internal buffer */

    readRequestHeader(sessionP, deadline, &requestLine, &httpErrorCode);
    if (httpErrorCode) {
        xmlrpc_asprintf(errorP, "Problem getting the request header");
        *httpErrorCodeP = httpErrorCode;
    } else {
        TMethod httpMethod;
        const char * host;
        const char * path;
        const char * query;
        unsigned short port;
        bool moreHeaders;

        parseRequestLine(requestLine, &httpMethod, &sessionP->version,
                         &host, &port, &path, &query,
                         &moreHeaders, &httpErrorCode);

        if (httpErrorCode) {
            xmlrpc_asprintf(errorP, "Unable to parse the request header "
                            "'%s'", requestLine);
            *httpErrorCodeP = httpErrorCode;
        } else {
            initRequestInfo(&sessionP->requestInfo, sessionP->version,
                            requestLine,
                            httpMethod, host, port, path, query);

            if (moreHeaders) {
                readAndProcessHeaders(sessionP, deadline,
                                      errorP, httpErrorCodeP);
            } else
                *errorP = NULL;

            if (!*errorP)
                sessionP->validRequest = true;

            xmlrpc_strfreenull(host);
            xmlrpc_strfree(path);
            xmlrpc_strfreenull(query);
        }
    }
}



char *
RequestHeaderValue(TSession *   const sessionP,
                   const char * const name) {

    return (TableFind(&sessionP->requestHeaderFields, name));
}



bool
RequestValidURI(TSession * const sessionP) {

    if (!sessionP->requestInfo.uri)
        return FALSE;
    
    if (xmlrpc_streq(sessionP->requestInfo.uri, "*"))
        return (sessionP->requestInfo.method != m_options);

    if (strchr(sessionP->requestInfo.uri, '*'))
        return FALSE;

    return TRUE;
}



bool
RequestValidURIPath(TSession * const sessionP) {

    uint32_t i;
    const char * p;

    p = sessionP->requestInfo.uri;

    i = 0;

    if (*p == '/') {
        i = 1;
        while (*p)
            if (*(p++) == '/') {
                if (*p == '/')
                    break;
                else if ((xmlrpc_strneq(p,"./", 2)) || (xmlrpc_streq(p, ".")))
                    ++p;
                else if ((xmlrpc_strneq(p, "../", 2)) ||
                         (xmlrpc_streq(p, ".."))) {
                    p += 2;
                    --i;
                    if (i == 0)
                        break;
                }
                /* Prevent accessing hidden files (starting with .) */
                else if (*p == '.')
                    return FALSE;
                else
                    if (*p)
                        ++i;
            }
    }
    return (*p == 0 && i > 0);
}



bool
RequestAuth(TSession *   const sessionP,
            const char * const credential,
            const char * const user,
            const char * const pass) {
/*----------------------------------------------------------------------------
   Authenticate requester, in a very simplistic fashion.

   If the request executing on session *sessionP specifies basic
   authentication (via Authorization header) with username 'user', password
   'pass', then return TRUE.  Else, return FALSE and set up an authorization
   failure response (HTTP response status 401) that says user must supply an
   identity in the 'credential' domain.

   When we return TRUE, we also set the username in the request info for the
   session to 'user' so that a future SessionGetRequestInfo can get it.
-----------------------------------------------------------------------------*/
    bool authorized;
    char * authHdrPtr;

    authHdrPtr = RequestHeaderValue(sessionP, "authorization");
    if (authHdrPtr) {
        const char * authType;
        NextToken((const char **)&authHdrPtr);
        GetTokenConst(&authHdrPtr, &authType);
        authType = GetToken(&authHdrPtr);
        if (authType) {
            if (xmlrpc_strcaseeq(authType, "basic")) {
                const char * userPass;
                char userPassEncoded[80];

                NextToken((const char **)&authHdrPtr);

                xmlrpc_asprintf(&userPass, "%s:%s", user, pass);
                xmlrpc_base64Encode(userPass, userPassEncoded);
                xmlrpc_strfree(userPass);

                if (xmlrpc_streq(authHdrPtr, userPassEncoded)) {
                    sessionP->requestInfo.user = strdup(user);
                    authorized = TRUE;
                } else
                    authorized = FALSE;
            } else
                authorized = FALSE;
        } else
            authorized = FALSE;
    } else
        authorized = FALSE;

    if (!authorized) {
        const char * hdrValue;
        xmlrpc_asprintf(&hdrValue, "Basic realm=\"%s\"", credential);
        ResponseAddField(sessionP, "WWW-Authenticate", hdrValue);

        xmlrpc_strfree(hdrValue);

        ResponseStatus(sessionP, 401);
    }
    return authorized;
}



/*********************************************************************
** Range
*********************************************************************/

abyss_bool
RangeDecode(char *            const strArg,
            xmlrpc_uint64_t   const filesize,
            xmlrpc_uint64_t * const start,
            xmlrpc_uint64_t * const end) {

    char *str;
    char *ss;

    str = strArg;  /* initial value */

    *start=0;
    *end=filesize-1;

    if (*str=='-')
    {
        *start=filesize-strtol(str+1,&ss,10);
        return ((ss!=str) && (!*ss));
    };

    *start=strtol(str,&ss,10);

    if ((ss==str) || (*ss!='-'))
        return FALSE;

    str=ss+1;

    if (!*str)
        return TRUE;

    *end=strtol(str,&ss,10);

    if ((ss==str) || (*ss) || (*end<*start))
        return FALSE;

    return TRUE;
}

/*********************************************************************
** HTTP
*********************************************************************/

const char *
HTTPReasonByStatus(uint16_t const code) {

    struct _HTTPReasons {
        uint16_t status;
        const char * reason;
    };

    static struct _HTTPReasons const reasons[] =  {
        { 100,"Continue" }, 
        { 101,"Switching Protocols" }, 
        { 200,"OK" }, 
        { 201,"Created" }, 
        { 202,"Accepted" }, 
        { 203,"Non-Authoritative Information" }, 
        { 204,"No Content" }, 
        { 205,"Reset Content" }, 
        { 206,"Partial Content" }, 
        { 300,"Multiple Choices" }, 
        { 301,"Moved Permanently" }, 
        { 302,"Moved Temporarily" }, 
        { 303,"See Other" }, 
        { 304,"Not Modified" }, 
        { 305,"Use Proxy" }, 
        { 400,"Bad Request" }, 
        { 401,"Unauthorized" }, 
        { 402,"Payment Required" }, 
        { 403,"Forbidden" }, 
        { 404,"Not Found" }, 
        { 405,"Method Not Allowed" }, 
        { 406,"Not Acceptable" }, 
        { 407,"Proxy Authentication Required" }, 
        { 408,"Request Timeout" }, 
        { 409,"Conflict" }, 
        { 410,"Gone" }, 
        { 411,"Length Required" }, 
        { 412,"Precondition Failed" }, 
        { 413,"Request Entity Too Large" }, 
        { 414,"Request-URI Too Long" }, 
        { 415,"Unsupported Media Type" }, 
        { 500,"Internal Server Error" }, 
        { 501,"Not Implemented" }, 
        { 502,"Bad Gateway" }, 
        { 503,"Service Unavailable" }, 
        { 504,"Gateway Timeout" }, 
        { 505,"HTTP Version Not Supported" },
        { 000, NULL }
    };
    const struct _HTTPReasons * reasonP;

    reasonP = &reasons[0];

    while (reasonP->status <= code)
        if (reasonP->status == code)
            return reasonP->reason;
        else
            ++reasonP;

    return "No Reason";
}



int32_t
HTTPRead(TSession *   const s ATTR_UNUSED,
         const char * const buffer ATTR_UNUSED,
         uint32_t     const len ATTR_UNUSED) {

    return 0;
}



bool
HTTPWriteBodyChunk(TSession *   const sessionP,
                   const char * const buffer,
                   uint32_t     const len) {

    bool succeeded;

    if (sessionP->chunkedwrite && sessionP->chunkedwritemode) {
        char chunkHeader[16];

        sprintf(chunkHeader, "%x\r\n", len);

        succeeded =
            ConnWrite(sessionP->connP, chunkHeader, strlen(chunkHeader));
        if (succeeded) {
            succeeded = ConnWrite(sessionP->connP, buffer, len);
            if (succeeded)
                succeeded = ConnWrite(sessionP->connP, "\r\n", 2);
        }
    } else
        succeeded = ConnWrite(sessionP->connP, buffer, len);

    return succeeded;
}



bool
HTTPWriteEndChunk(TSession * const sessionP) {

    bool retval;

    if (sessionP->chunkedwritemode && sessionP->chunkedwrite) {
        /* May be one day trailer dumping will be added */
        sessionP->chunkedwritemode = FALSE;
        retval = ConnWrite(sessionP->connP, "0\r\n\r\n", 5);
    } else
        retval = TRUE;

    return retval;
}



bool
HTTPKeepalive(TSession * const sessionP) {
/*----------------------------------------------------------------------------
   Return value: the connection should be kept alive after the session
   *sessionP is over.
-----------------------------------------------------------------------------*/
    return (sessionP->requestInfo.keepalive &&
            !sessionP->serverDeniesKeepalive &&
            sessionP->status < 400);
}



bool
HTTPWriteContinue(TSession * const sessionP) {

    char const continueStatus[] = "HTTP/1.1 100 continue\r\n\r\n";
        /* This is a status line plus an end-of-headers empty line */

    return ConnWrite(sessionP->connP, continueStatus, strlen(continueStatus));
}



/******************************************************************************
**
** http.c
**
** Copyright (C) 2000 by Moez Mahfoudh <mmoez@bigfoot.com>.
** 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 name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
** 
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS 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 AUTHOR OR 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.
**
******************************************************************************/
