/*
 * COPYRIGHT:   See COPYING in the top level directory
 * PROJECT:     ReactOS HTTP Daemon
 * FILE:        http.cpp
 * PURPOSE:     HTTP 1.1 parser engine
 * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
 * REVISIONS:
 *   CSH  01/09/2000 Created
 * TODO:        - Implement message-body
 *              - Implement more generel-header entries
 *              - Implement more request-header entries
 *              - Implement more entity-header entries
 */
#include <debug.h>
#include <iostream>
#include <string.h>
#include <http.h>

CHAR MethodTable[NUMMETHODS][8] = {"OPTIONS", "GET", "HEAD", "POST", "PUT", 
    "DELETE", "TRACE"};

CHAR GenerelTable[NUMGENERELS][18] = {"Cache-Control", "Connection", "Date", "Pragma", 
    "Transfer-Encoding", "Upgrade", "Via"};

CHAR RequestTable[NUMREQUESTS][20] = {"Accept", "Accept-Charset", "Accept-Encoding",
    "Accept-Language", "Authorization", "From", "Host", "If-Modified-Since", "If-Match",
    "If-None-Match", "If-Range", "If-Unmodified-Since", "Max-Forwards",
    "Proxy-Authorization", "Range", "Referer", "User-Agent"};

CHAR EntityTable[NUMENTITIES][17] = {"Allow", "Content-Base", "Content-Encoding",
    "Content-Language", "Content-Length", "Content-Location", "Content-MD5",
    "Content-Range", "Content-Type", "ETag", "Expires", "Last-Modified"};

// *************************** CHttpParser ***************************

// Default constructor
CHttpParser::CHttpParser()
{
    nHead = 0;
    nTail = 0;
}

// Default destructor
CHttpParser::~CHttpParser()
{
}

// Returns TRUE if a complete HTTP message is in buffer
BOOL CHttpParser::Complete()
{
    UINT nTmp;

    /*DPRINT("--1:-%d---\n", sBuffer[nHead-2]);
    DPRINT("--2:-%d---\n", sBuffer[nHead-1]);

    sBuffer[nHead] = '!';
    sBuffer[nHead+1] = 0;
    DPRINT("Examining buffer: (Head: %d, Tail: %d)\n", nHead, nTail);
    DPRINT("%s\n", (LPSTR)&sBuffer[nTail]);*/

    nTmp = nTail;
    if (!Parse()) {
        if (!bUnknownMethod)
            nTail = nTmp;
        return FALSE;
    } else
        return TRUE;
}


// Read a character from buffer
BOOL CHttpParser::ReadChar(LPSTR lpsStr)
{
    if (nTail <= nHead) {
        if (nTail != nHead) {
            lpsStr[0] = sBuffer[nTail];
            nTail++;
            return TRUE;
        } else {
            lpsStr[0] = 0;
            return FALSE;
        }
    } else {
        if (nTail == sizeof(sBuffer))
            nTail = 0;
        if (nTail != nHead) {
            lpsStr[0] = sBuffer[nTail];
            nTail++;
            return TRUE;
        } else {
            lpsStr[0] = 0;
            return FALSE;
        }
    }
}

// Peek at a character in the buffer
BOOL CHttpParser::PeekChar(LPSTR lpsStr)
{
    UINT nFakeTail;
    
    if (nTail == sizeof(sBuffer))
        nFakeTail = 0;
    else
        nFakeTail = nTail;
    if (nFakeTail != nHead) {
        lpsStr[0] = sBuffer[nFakeTail];
        return TRUE;
    } else {
        lpsStr[0] = 0;
        return FALSE;
    }
}

// Read a string from buffer. Only A-Z, a-z, 0-9 and '-' are valid characters
BOOL CHttpParser::ReadString(LPSTR lpsStr, UINT nLength)
{
    UINT i = 0;
    CHAR sTmp;
    
    while (PeekChar(&sTmp)) {
        if (((sTmp >= 'A') && (sTmp <= 'Z')) || ((sTmp >= 'a') && (sTmp <= 'z')) ||
            ((sTmp >= '0') && (sTmp <= '9')) || (sTmp == '-')) { 
            if (i >= (nLength - 1)) {
                lpsStr[0] = 0;
                return FALSE;
            }
            ReadChar(&sTmp);
            lpsStr[i] = sTmp;
            i++;
        } else {
            lpsStr[i] = 0;
            return TRUE;
        }
    }
    lpsStr[0] = 0;
    return FALSE;
}

// Read a string from buffer. Stop if SP or CR is found or when there are no more 
// characters
BOOL CHttpParser::ReadSpecial(LPSTR lpsStr, UINT nLength)
{
    UINT i = 0;
    CHAR sTmp;
 
    while (PeekChar(&sTmp) && (sTmp != ' ') && (sTmp != 13)) {
        if (i >= (nLength - 1)) {
            lpsStr[nLength - 1] = 0;
            return FALSE;
        }
        ReadChar(&sTmp);
        lpsStr[i] = sTmp;
        i++;
    }
    lpsStr[i] = 0;
    return TRUE;
}

// Skip until "sCh" is found
VOID CHttpParser::Skip(CHAR sCh)
{
    CHAR sTmp;

    while (PeekChar(&sTmp) && (sTmp != sCh))
        ReadChar(&sTmp);
}

// Return TRUE if sCh is the next character
BOOL CHttpParser::Expect(CHAR sCh)
{
    CHAR sTmp;

    if (PeekChar(&sTmp)) {
        if (sTmp == sCh) {
            ReadChar(&sTmp);
            return TRUE;
        }
    }
    return FALSE;
}

// Return TRUE if CRLF are the next characters
BOOL CHttpParser::ExpectCRLF()
{
    return (Expect(13) && Expect(10));
}

// Request = RequestLine | *( GenerelHeader | RequestHeader | EntityHeader ) 
//           CRLF [ MessageBody ]
BOOL CHttpParser::Parse()
{
    BOOL bStatus;

    if (RequestLine()) {
        do {
            if (!ReadString(sHeader, sizeof(sHeader)))
                break;
            bStatus = (GenerelHeader());
            bStatus = (RequestHeader() || bStatus);
            bStatus = (EntityHeader() || bStatus);
        } while (bStatus);
        // CRLF
        if (!ExpectCRLF())
            return FALSE;
        MessageBody();
        return TRUE;
    }
    return FALSE;
}

// RequestLine = Method SP RequestURI SP HTTP-Version CRLF
BOOL CHttpParser::RequestLine()
{
    CHAR sCh;
    UINT i;

    bUnknownMethod = FALSE;

    // RFC 2068 states that servers SHOULD ignore any empty nine(s) received where a 
    // Request-Line is expected
    while (PeekChar(&sCh) && ((sCh == 13) || (sCh == 10)));
    
    if (!ReadString(sMethod, sizeof(sMethod)))
        return FALSE;

    for (i = 0; i < NUMMETHODS; i++) {
        if (strcmp(MethodTable[i], sMethod) == 0) {
            nMethodNo = i;
            if (!Expect(' '))
                return FALSE;
            // URI (ie. host/directory/resource)
            if (!ReadSpecial(sUri, sizeof(sUri)))
                return FALSE;
            if (!Expect(' '))
                return FALSE;
            // HTTP version (eg. HTTP/1.1)
            if (!ReadSpecial(sVersion, sizeof(sVersion)))
                return FALSE;
            // CRLF
            if (!ExpectCRLF())
                return FALSE;

            return TRUE;
        }
    }
    bUnknownMethod = TRUE;
    return FALSE;
}

// GenerelHeader = Cache-Control | Connection | Date | Pragma | Transfer-Encoding | 
//                 Upgrade | Via
BOOL CHttpParser::GenerelHeader()
{
    INT i;

    for (i = 0; i < NUMGENERELS; i++) {
        if (strcmp(GenerelTable[i], sHeader) == 0) {
            switch (i) {
                case 1: {
                    //Connection
                    Expect(':');
                    Expect(' ');
                    Skip(13);
                    ExpectCRLF();
                    break;
                }
                default: {
                    Expect(':');
                    Expect(' ');
                    Skip(13);
                    ExpectCRLF();
                }
            }
            return TRUE;
        }
    }
    return FALSE;
}

// RequestHeader = Accept | Accept-Charset | Accept-Encoding | Accept-Language |
//                 Authorization | From | Host | If-Modified-Since | If-Match |
//                 If-None-Match | If-Range | If-Unmodified-Since | Max-Forwards |
//                 Proxy-Authorization | Range | Referer | User-Agent
BOOL CHttpParser::RequestHeader()
{
    INT i;

    for (i = 0; i < NUMREQUESTS; i++) {
        if (strcmp(RequestTable[i], sHeader) == 0) {
            switch (i) {
                case 0: {
                    //Accept
                    Expect(':');
                    Expect(' ');
                    Skip(13);
                    ExpectCRLF();
                    break;
                }
                case 2: {
                    //Accept-Encoding
                    Expect(':');
                    Expect(' ');
                    Skip(13);
                    ExpectCRLF();
                    break;
                }
                case 3: {
                    //Accept-Language
                    Expect(':');
                    Expect(' ');
                    Skip(13);
                    ExpectCRLF();
                    break;
                }
                case 6: {
                    //Host
                    Expect(':');
                    Expect(' ');
                    Skip(13);
                    ExpectCRLF();
                    break;
                }
                case 16: {
                    //User-Agent
                    Expect(':');
                    Expect(' ');
                    Skip(13);
                    ExpectCRLF();
                    break;
                }
                default: {
                    Expect(':');
                    Expect(' ');
                    Skip(13);
                    ExpectCRLF();
                    return TRUE;
                }
            }
            return TRUE;
        }
    }
    return FALSE;
}

// EntityHeader = Allow | Content-Base | Content-Encoding | Content-Language |
//                Content-Length | Content-Location | Content-MD5 |
//                Content-Range | Content-Type | ETag | Expires |
//                Last-Modified | extension-header
BOOL CHttpParser::EntityHeader()
{
    INT i;

    for (i = 0; i < NUMENTITIES; i++) {
        if (strcmp(EntityTable[i], sHeader) == 0) {
            switch (i) {
                case 0: 
                default: {
                    //cout << "<Entity-Header>: #" << i << endl; 
                    Expect(':');
                    Expect(' ');
                    Skip(13);
                    ExpectCRLF();
                    return TRUE;
                }
            }
            return FALSE;
        }
    }
    return FALSE;
}

// MessageBody = *OCTET
BOOL CHttpParser::MessageBody()
{
    return FALSE;
}
