/*
 * Copyright (C) 2007-2023 Xagasoft, All rights reserved.
 *
 * This file is part of the libbu++ library and is released under the
 * terms of the license contained in the file LICENSE.
 */

#include <dirent.h>

#ifndef WIN32
    #include <sys/wait.h>
#endif 

#include <errno.h>
#include <stdlib.h>
#include "bu/protocolhttp.h"
#include "bu/logger.h"

#define CRLF "\x0D\x0A"
#define CR '\x0D'
#define LF '\x0A'

using namespace Bu;

Bu::ProtocolHttp::ProtocolHttp()
{
}

Bu::ProtocolHttp::~ProtocolHttp()
{
}

void Bu::ProtocolHttp::onNewConnection( Bu::Client *pClient )
{
    this->pClient = pClient;

    iState = 0;
}

#define SDB( i ) (void)0
//#define SDB( i ) printf("state %d: %d, \"%s\"\n", i, tt, sToken.getStr() )

void Bu::ProtocolHttp::onNewData( Bu::Client *pClient )
{
/*  logHexDump(
        1,
        pClient->getInput().getStr(),
        pClient->getInput().getSize(),
        "input"
        );*/

    for(;;)
    {
        Bu::String sToken;
        TokenType tt = getToken( sToken );

        if( tt == ttOutOfData )
            return;

        switch( iState )
        {
            case 0:     // Start token, should be "method" (get, put, etc)
                SDB( 0 );
                sMethod = sToken;
                iState = 1;
                break;

            case 1:     // The path requested
                SDB( 1 );
                sPath = sToken;
                iState = 2;
                break;

            case 2:     // The protocol name and version
                SDB( 2 );
                if( strncmp( sToken.getStr(), "HTTP/", 5 ) )
                {
                    pClient->disconnect();
                    return;
                }
                else
                {
                    char *s, *s2;
                    s = sToken.getStr()+5;
                    iMajor = strtol( s, &s2, 10 );
                    iMinor = strtol( s2+1, NULL, 10 );
                    iState = 3;
                }
                break;

            case 3:     // End of initial header, now comes mime-style blocks.
                SDB( 3 );
                if( tt == ttNewline )
                {
                    iState = 10;
                }
                else if( tt == ttDoubleNewline )
                {
                    earlyResponse();
                }
                else
                {
                    pClient->disconnect();
                    return;
                }
                break;

            case 10:        // HTTP-Message (skipped for now...)
                SDB( 10 );
                if( tt == ttString )
                {
                    iState = 11;
                }
                else
                {
                    pClient->disconnect();
                }
                break;

            case 11:        // Should be a colon...
                SDB( 11 );
                if( tt == ttSeperator && sToken == ":" )
                {
                    iState = 12;
                }
                else
                {
                    pClient->disconnect();
                }
                break;

            case 12:
                SDB( 12 );
                if( tt == ttNewline )
                {
                    iState = 10;
                }
                if( tt == ttDoubleNewline )
                {
                    earlyResponse();
                }
                break;

            case 20:
                SDB( 20 );
                break;
        }
    }
}

Bu::ProtocolHttp::TokenType Bu::ProtocolHttp::getToken( Bu::String &line )
{
    char s;
    int jmax = pClient->getInputSize();
    bool bNonWS = false;
    
    for( int j = 0; j < jmax; j++ )
    {
        pClient->peek( &s, 1, j );
        if( iState > 2 && isSeperator( s ) )
        {
            if( j == 0 )
            {
                line += s;
                pClient->seek( 1 );
                return ttSeperator;
            }
            else
            {
                pClient->seek( j );
                return ttString;
            }
        }
        else if( isWS( s ) )
        {
            if( bNonWS )
            {
                pClient->seek( j );
                return ttString;
            }
        }
        else if( s == CR )
        {
            if( pClient->getInputSize() < 4 )
                return ttOutOfData;
            
            char ss[3];
            pClient->peek( ss, 3, j+1 );
            if( ss[0] == LF && ss[1] != ' ' && ss[1] != '\t' )
            {
                if( bNonWS )
                {
                    pClient->seek( j );
                    return ttString;
                }
                else if( ss[1] == CR && ss[2] == LF )
                {
                    pClient->seek( 4 );
                    return ttDoubleNewline;
                }
                else
                {
                    pClient->seek( 2 );
                    return ttNewline;
                }
            }
            
            j += 2;
            if( bNonWS )
            {
                pClient->seek( j );
                return ttString;
            }
        }
        else
        {
            line += s;
            bNonWS = true;
        }
    }

    return ttOutOfData;
}

bool Bu::ProtocolHttp::isWS( char buf )
{
    return (buf == ' ' || buf == '\t');
}

bool Bu::ProtocolHttp::isSeperator( char buf )
{
    return (buf == '(' || buf == ')' || buf == '<' || buf == '>' ||
            buf == '@' || buf == ',' || buf == ';' || buf == ':' ||
            buf == '\\' || buf == '\"' || buf == '/' || buf == '[' ||
			buf == ']' || buf == '?' || buf == '=' || buf == '{' ||
			buf == '}' );
}

void Bu::ProtocolHttp::earlyResponse()
{
	if( sMethod == "GET" )
	{
		onRequest( sMethod, sPath );
		iState = 0;
	}
	else
	{
		iState = 20;
	}
}

void Bu::ProtocolHttp::lateResponse()
{
	onRequest( sMethod, sPath );
}

void Bu::ProtocolHttp::sendResponse( const Response &rRes )
{
	char buf[1024];
	int iSize = sprintf( buf, "HTTP/1.1 %d ", rRes.iCode );

	pClient->write( buf, iSize );
	pClient->write( rRes.sReason );
	pClient->write( CRLF, 2 );

	for( Response::StringHash::const_iterator i = rRes.hHeaders.begin();
		 i != rRes.hHeaders.end(); i++ )
	{
		pClient->write( i.getKey() );
		pClient->write(": ", 2 );
		pClient->write( i.getValue() );
		pClient->write( CRLF, 2 );
	}

	iSize = sprintf( buf, "Content-Length: %ld" CRLF, rRes.sContent.getSize() );
	pClient->write( buf, iSize );

	pClient->write( CRLF, 2 );
	pClient->write( rRes.sContent );
}

//
// Bu::ProtocolHttp::Response
//
Bu::ProtocolHttp::Response::Response( int iCode ) :
	iCode( iCode )
{
	switch( iCode )
	{
		case 100:	sReason = "Continue"; break;
		case 101:	sReason = "Switching Protocols"; break;
		case 200:	sReason = "OK"; break;
		case 201:	sReason = "Created"; break;
		case 202:	sReason = "Accepted"; break;
		case 203:	sReason = "Non-Authoritative Information"; break;
		case 204:	sReason = "No Content"; break;
		case 205:	sReason = "Reset Content"; break;
		case 206:	sReason = "Partial Content"; break;
		case 300:	sReason = "Multiple Choices"; break;
		case 301:	sReason = "Moved Permanently"; break;
		case 302:	sReason = "Found"; break;
		case 303:	sReason = "See Other"; break;
		case 304:	sReason = "Not Modified"; break;
		case 305:	sReason = "Use Proxy"; break;
		case 307:	sReason = "Temporary Redirect"; break;
		case 400:	sReason = "Bad Request"; break;
		case 401:	sReason = "Unauthorized"; break;
		case 402:	sReason = "Payment Required"; break;
		case 403:	sReason = "Forbidden"; break;
		case 404:	sReason = "Not Found"; break;
		case 405:	sReason = "Method Not Allowed"; break;
		case 406:	sReason = "Not Acceptable"; break;
		case 407:	sReason = "Proxy Authentication Required"; break;
		case 408:	sReason = "Request Time-out"; break;
		case 409:	sReason = "Conflict"; break;
		case 410:	sReason = "Gone"; break;
		case 411:	sReason = "Length Required"; break;
		case 412:	sReason = "Precondition Failed"; break;
		case 413:	sReason = "Request Entity Too Large"; break;
		case 414:	sReason = "Request-URI Too Large"; break;
		case 415:	sReason = "Unsupported Media Type"; break;
		case 416:	sReason = "Requested range not satisfiable"; break;
		case 417:	sReason = "Expectation Failed"; break;
		case 500:	sReason = "Internal Server Error"; break;
		case 501:	sReason = "Not Implemented"; break;
		case 502:	sReason = "Bad Gateway"; break;
		case 503:	sReason = "Service Unavailable"; break;
		case 504:	sReason = "Gateway Time-out"; break;
		case 505:	sReason = "HTTP Version not supported"; break;
	}
}

Bu::ProtocolHttp::Response::Response( int iCode, const Bu::String &sReason ) :
	iCode( iCode ),
	sReason( sReason )
{
}

Bu::ProtocolHttp::Response::~Response()
{
}

void Bu::ProtocolHttp::Response::setHeader(
	const Bu::String &sKey, const Bu::String &sVal )
{
	hHeaders.insert( sKey, sVal );
}

void Bu::ProtocolHttp::Response::setContent( const Bu::String &sCont )
{
	sContent = sCont;
}

