/*
 * Copyright 2007 Stephen Liu
 * For license terms, see the file COPYING along with this library.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "sphttpmsg.hpp"
#include "sputils.hpp"

static char * sp_strsep(char **s, const char *del)
{
	char *d, *tok;

	if (!s || !*s) return NULL;
	tok = *s;
	d = strstr(tok, del);

	if (d) {
		*s = d + strlen(del);
		*d = '\0';
	} else {
		*s = NULL;
	}

	return tok;
}

SP_HttpMsgParser :: SP_HttpMsgParser()
{
	mMessage = NULL;
	mStatus = eStartLine;
	mIgnoreContent = 0;
}

SP_HttpMsgParser :: ~SP_HttpMsgParser()
{
	if( NULL != mMessage ) delete mMessage;
}

void SP_HttpMsgParser :: setIgnoreContent( int ignoreContent )
{
	mIgnoreContent = ignoreContent;
}

int SP_HttpMsgParser :: isIgnoreContent() const
{
	return 0 != mIgnoreContent;
}

int SP_HttpMsgParser :: parseStartLine( SP_HttpMessage ** message,
		const void * buffer, int len )
{
	int lineLen = 0;

	char * pos = (char*)memchr( buffer, '\n', len );
	if( NULL != pos ) {
		lineLen = pos - (char*)buffer + 1;

		char * line = (char*)malloc( lineLen + 1 );
		memcpy( line, buffer, lineLen );
		line[ lineLen ] = '\0';

		pos = line;

		char * first, * second;
		first = sp_strsep( &pos, " " );
		second = sp_strsep( &pos, " " );

		if( 0 == strncasecmp( line, "HTTP", 4 ) ) {
			SP_HttpResponse * response = new SP_HttpResponse();
			if( NULL != first ) response->setVersion( first );
			if( NULL != second ) response->setStatusCode( atoi( second ) );
			if( NULL != pos ) response->setReasonPhrase( strtok( pos, "\r\n" ) );

			*message = response;
		} else {
			SP_HttpRequest * request = new SP_HttpRequest();
			if( NULL != first ) request->setMethod( first );
			if( NULL != second ) request->setURI( sp_strsep( &second, "?" ) );
			if( NULL != pos ) request->setVersion( strtok( pos, "\r\n" ) );

			char * params = second;
			for( ; NULL != params && '\0' != *params; ) {
				char * value = sp_strsep( &params, "&" );
				char * name = sp_strsep( &value, "=" );
				request->addParam( name, NULL == value ? "" : value );
			}

			*message = request;
		}

		free( line );
	}

	return lineLen;
}

int SP_HttpMsgParser :: parseHeader( SP_HttpMessage * message,
		const void * buffer, int len )
{
	int lineLen = 0;

	char * pos = (char*)memchr( buffer, '\n', len );
	if( NULL != pos ) {
		lineLen = pos - (char*)buffer + 1;

		char * line = (char*)malloc( lineLen + 1 );
		memcpy( line, buffer, lineLen );
		line[ lineLen ] = '\0';

		pos = line;

		char * name = sp_strsep( &pos, ":" );

		if( NULL != pos ) {
			pos = strtok( pos, "\r\n" );
			pos += strspn( pos, " " );
			message->addHeader( name, pos );
		}

		free( line );
	}

	return lineLen;
}

int SP_HttpMsgParser :: getLine( const void * buffer, int len,
		char * line, int size )
{
	int lineLen = 0;

	char * pos = (char*)memchr( buffer, '\n', len );
	if( NULL != pos ) {
		lineLen = pos - (char*)buffer + 1;

		int realLen = size - 1;
		realLen = realLen > lineLen ? lineLen : realLen;

		memcpy( line, buffer, realLen );
		line[ realLen ] = '\0';
		strtok( line, "\r\n" );
	}

	return lineLen;
}

int SP_HttpMsgParser :: parseChunked( SP_HttpMessage * message,
		const void * buffer, int len, int * status )
{
	int parsedLen = 0, hasChunk = 1;

	for( ; 0 != hasChunk && eCompleted != *status; ) {

		hasChunk = 0;

		char chunkSize[ 32 ] = { 0 };

		int lineLen = getLine( ((char*)buffer) + parsedLen, len - parsedLen,
				chunkSize, sizeof( chunkSize ) );

		int contentLen = strtol( chunkSize, NULL, 16 );

		if( contentLen > 0 && ( len - parsedLen ) > ( contentLen + lineLen ) ) {
			int emptyLen = getLine( ((char*)buffer) + parsedLen + lineLen + contentLen,
				len - parsedLen - lineLen - contentLen,
				chunkSize, sizeof( chunkSize ) );

			if( emptyLen > 0 ) {
				parsedLen += lineLen;
				message->appendContent( ((char*)buffer) + parsedLen, contentLen );
				parsedLen += contentLen + emptyLen;
				hasChunk = 1;
			}
		}

		if( 0 == contentLen && lineLen > 0 ) {
			parsedLen += lineLen;
			*status = eCompleted;
		}
	}

	return parsedLen;
}

int SP_HttpMsgParser :: parseContent( SP_HttpMessage * message,
		const void * buffer, int len, int * status )
{
	int parsedLen = 0;

	const char * value = message->getHeaderValue( SP_HttpMessage::HEADER_CONTENT_LENGTH );
	int contentLen = atoi( NULL == value ? "0" : value );

	if( contentLen > 0 && len >= contentLen ) {
		message->appendContent( ((char*)buffer), contentLen );
		parsedLen = contentLen;
	}

	if( contentLen == message->getContentLength() ) *status = eCompleted;

	return parsedLen;
}

int SP_HttpMsgParser :: append( const void * buffer, int len )
{
	int parsedLen = 0;

	if( eCompleted == mStatus ) return parsedLen;

	// parse start-line
	if( NULL == mMessage ) {
		parsedLen = parseStartLine( &mMessage, buffer, len );
		if( parsedLen > 0 ) mStatus = eHeader;
	}

	if( NULL != mMessage ) {
		// parse header
		for( int headerLen = 1; eHeader == mStatus
				&& headerLen > 0 && parsedLen < len; parsedLen += headerLen ) {
			headerLen = parseHeader( mMessage, ((char*)buffer) + parsedLen, len - parsedLen );

			char ch = * ( ((char*)buffer) + parsedLen );
			if( '\r' == ch || '\n' == ch ) mStatus = eContent;
		}

		if( SP_HttpMessage::eResponse == mMessage->getType()
			&& eContent == mStatus && mIgnoreContent ) mStatus = eCompleted;

		// parse content
		if( eContent == mStatus ) {
			const char * encoding = mMessage->getHeaderValue( SP_HttpMessage::HEADER_TRANSFER_ENCODING );
			if( NULL != encoding && 0 == strcasecmp( encoding, "chunked" ) ) {
				parsedLen += parseChunked( mMessage, ((char*)buffer) + parsedLen,
					len - parsedLen, &mStatus );
			} else {
				parsedLen += parseContent( mMessage, ((char*)buffer) + parsedLen,
					len - parsedLen, &mStatus );
			}
		}

		if( eCompleted == mStatus ) postProcess( mMessage );
	}

	return parsedLen;
}

void SP_HttpMsgParser :: postProcess( SP_HttpMessage * message )
{
	if( SP_HttpMessage::eRequest == message->getType() ) {
		SP_HttpRequest * request = (SP_HttpRequest*)message;
		const char * contentType = request->getHeaderValue(
			SP_HttpMessage::HEADER_CONTENT_TYPE );
		if( request->getContentLength() > 0 && NULL != contentType
			&& 0 == strcasecmp( contentType, "application/x-www-form-urlencoded" ) ) {

			char * content = (char*)malloc( request->getContentLength() + 1 );
			memcpy( content, request->getContent(), request->getContentLength() );
			content[ request->getContentLength() ] = '\0';

			char * params = content;
			for( ; NULL != params && '\0' != *params; ) {
				char * value = sp_strsep( &params, "&" );
				char * name = sp_strsep( &value, "=" );
				request->addParam( name, NULL == value ? "" : value );
			}

			free( content );
		}
	}
}

int SP_HttpMsgParser :: isCompleted() const
{
	return eCompleted == mStatus;
}

SP_HttpRequest * SP_HttpMsgParser :: getRequest() const
{
	if( NULL != mMessage && SP_HttpMessage::eRequest == mMessage->getType() ) {
		return (SP_HttpRequest*)mMessage;
	}

	return NULL;
}

SP_HttpResponse * SP_HttpMsgParser :: getResponse() const
{
	if( NULL != mMessage && SP_HttpMessage::eResponse== mMessage->getType() ) {
		return (SP_HttpResponse*)mMessage;
	}

	return NULL;
}

//---------------------------------------------------------

const char * SP_HttpMessage :: HEADER_CONTENT_LENGTH = "Content-Length";
const char * SP_HttpMessage :: HEADER_CONTENT_TYPE = "Content-Type";
const char * SP_HttpMessage :: HEADER_CONNECTION = "Connection";
const char * SP_HttpMessage :: HEADER_PROXY_CONNECTION = "Proxy-Connection";
const char * SP_HttpMessage :: HEADER_TRANSFER_ENCODING = "Transfer-Encoding";
const char * SP_HttpMessage :: HEADER_DATE = "Date";
const char * SP_HttpMessage :: HEADER_SERVER = "Server";

SP_HttpMessage :: SP_HttpMessage( int type )
	: mType( type )
{
	mContent = NULL;
	mContentLength = 0;
	mMaxLength = 0;

	mHeaderNameList = new SP_ArrayList();
	mHeaderValueList = new SP_ArrayList();

	snprintf( mVersion, sizeof( mVersion ), "%s", "HTTP/1.0" );
}

SP_HttpMessage :: ~SP_HttpMessage()
{
	for( int i = mHeaderNameList->getCount() - 1; i >= 0; i-- ) {
		free( mHeaderNameList->takeItem( i ) );
		free( mHeaderValueList->takeItem( i ) );
	}

	delete mHeaderNameList;
	delete mHeaderValueList;

	if( NULL != mContent ) free( mContent );
}

int SP_HttpMessage :: getType() const
{
	return mType;
}

void SP_HttpMessage :: setVersion( const char * version )
{
	snprintf( mVersion, sizeof( mVersion ), "%s", version );
}

const char * SP_HttpMessage :: getVersion() const
{
	return mVersion;
}

void SP_HttpMessage :: appendContent( const void * content, int length, int maxLength )
{
	if( length <= 0 ) length = strlen( (char*)content );

	int realLength = mContentLength + length;
	realLength = realLength > maxLength ? realLength : maxLength;

	if( realLength > mMaxLength ) {
		if( NULL == mContent ) {
			mContent = malloc( realLength + 1 );
		} else {
			mContent = realloc( mContent, realLength + 1 );
		}
		mMaxLength = realLength;
	}

	memcpy( ((char*)mContent) + mContentLength, content, length );
	mContentLength = mContentLength + length;

	((char*)mContent)[ mContentLength ] = '\0';
}

void SP_HttpMessage :: setContent( const void * content, int length )
{
	mContentLength = 0;
	appendContent( content, length );
}

const void * SP_HttpMessage :: getContent() const
{
	return mContent;
}

int SP_HttpMessage :: getContentLength() const
{
	return mContentLength;
}

void SP_HttpMessage :: addHeader( const char * name, const char * value )
{
	mHeaderNameList->append( strdup( name ) );
	mHeaderValueList->append( strdup( value ) );
}

int SP_HttpMessage :: removeHeader( const char * name )
{
	int ret = 0;

	for( int i = 0; i < mHeaderNameList->getCount() && 0 == ret; i++ ) {
		if( 0 == strcasecmp( name, (char*)mHeaderNameList->getItem( i ) ) ) {
			free( mHeaderNameList->takeItem( i ) );	
			free( mHeaderValueList->takeItem( i ) );
			ret = 1;
		}
	}

	return ret;
}

int SP_HttpMessage :: getHeaderCount() const
{
	return mHeaderNameList->getCount();
}

const char * SP_HttpMessage :: getHeaderName( int index ) const
{
	return (char*)mHeaderNameList->getItem( index );
}

const char * SP_HttpMessage :: getHeaderValue( int index ) const
{
	return (char*)mHeaderValueList->getItem( index );
}

const char * SP_HttpMessage :: getHeaderValue( const char * name ) const
{
	const char * value = NULL;

	for( int i = 0; i < mHeaderNameList->getCount() && NULL == value; i++ ) {
		if( 0 == strcasecmp( name, (char*)mHeaderNameList->getItem( i ) ) ) {
			value = (char*)mHeaderValueList->getItem( i );
		}
	}

	return value;
}

int SP_HttpMessage :: isKeepAlive() const
{
	const char * proxy = getHeaderValue( HEADER_PROXY_CONNECTION );
	const char * local = getHeaderValue( HEADER_CONNECTION );

	if( ( NULL != proxy && 0 == strcasecmp( proxy, "Keep-Alive" ) )
			|| ( NULL != local && 0 == strcasecmp( local, "Keep-Alive" ) ) ) {
		return 1;
	}

	return 0;
}

//---------------------------------------------------------

SP_HttpRequest :: SP_HttpRequest()
	: SP_HttpMessage( eRequest )
{
	memset( mMethod, 0, sizeof( mMethod ) );
	memset( mClientIP, 0, sizeof( mClientIP ) );
	mURI = NULL;

	mParamNameList = new SP_ArrayList();
	mParamValueList = new SP_ArrayList();
}

SP_HttpRequest :: ~SP_HttpRequest()
{
	if( NULL != mURI ) free( mURI );

	for( int i = mParamNameList->getCount() - 1; i >= 0; i-- ) {
		free( mParamNameList->takeItem( i ) );
		free( mParamValueList->takeItem( i ) );
	}
	delete mParamNameList;
	delete mParamValueList;
}

void SP_HttpRequest :: setMethod( const char * method )
{
	snprintf( mMethod, sizeof( mMethod ), "%s", method );
}

const char * SP_HttpRequest :: getMethod() const
{
	return mMethod;
}

void SP_HttpRequest :: setURI( const char * uri )
{
	char * temp = mURI;

	mURI = strdup( uri );

	if( NULL != temp ) free( mURI );
}

const char * SP_HttpRequest :: getURI() const
{
	return mURI;
}

void SP_HttpRequest :: setClinetIP( const char * clientIP )
{
	snprintf( mClientIP, sizeof( mClientIP ), "%s", clientIP );
}

const char * SP_HttpRequest :: getClientIP() const
{
	return mClientIP;
}

void SP_HttpRequest :: addParam( const char * name, const char * value )
{
	mParamNameList->append( strdup( name ) );
	mParamValueList->append( strdup( value ) );
}

int SP_HttpRequest :: removeParam( const char * name )
{
	int ret = 0;

	for( int i = 0; i < mParamNameList->getCount() && 0 == ret; i++ ) {
		if( 0 == strcasecmp( name, (char*)mParamNameList->getItem( i ) ) ) {
			free( mParamNameList->takeItem( i ) );	
			free( mParamValueList->takeItem( i ) );
			ret = 1;
		}
	}

	return ret;
}

int SP_HttpRequest :: getParamCount() const
{
	return mParamNameList->getCount();
}

const char * SP_HttpRequest :: getParamName( int index ) const
{
	return (char*)mParamNameList->getItem( index );
}

const char * SP_HttpRequest :: getParamValue( int index ) const
{
	return (char*)mParamValueList->getItem( index );
}

const char * SP_HttpRequest :: getParamValue( const char * name ) const
{
	const char * value = NULL;

	for( int i = 0; i < mParamNameList->getCount() && NULL == value; i++ ) {
		if( 0 == strcasecmp( name, (char*)mParamNameList->getItem( i ) ) ) {
			value = (char*)mParamValueList->getItem( i );
		}
	}

	return value;
}

//---------------------------------------------------------

SP_HttpResponse :: SP_HttpResponse()
	: SP_HttpMessage( eResponse )
{
	mStatusCode = 200;
	snprintf( mReasonPhrase, sizeof( mReasonPhrase ), "%s", "OK" );
}

SP_HttpResponse :: ~SP_HttpResponse()
{
}

void SP_HttpResponse :: setStatusCode( int statusCode )
{
	mStatusCode = statusCode;
}

int SP_HttpResponse :: getStatusCode() const
{
	return mStatusCode;
}

void SP_HttpResponse :: setReasonPhrase( const char * reasonPhrase )
{
	snprintf( mReasonPhrase, sizeof( mReasonPhrase ), "%s", reasonPhrase );
}

const char * SP_HttpResponse :: getReasonPhrase() const
{
	return mReasonPhrase;
}

//---------------------------------------------------------



syntax highlighted by Code2HTML, v. 0.9.1