
/*---------------------------------------------------------------------------*/
/* webserver_message_handler.h                                               */
/* copyright (c) innovaphone 2016                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include <platform.h>

#include <string>
#include <cctype>

#include "sdk/common/interface/webserver_plugin.h"

#include "sdk/common/service/webserver_shared/webserver_internal.h"
#include "sdk/common/lib/httplib.h"

#include "webserver_digest_authenticator.h"
#include "webserver_message_handler.h"


WebserverMessageHandler::WebserverMessageHandler(const char * interfaceName,
                                                 msg_handler_type_t handlerType,
                                                 UWebserverMessageHandler * baseUser,
                                                 byte * extHeaderBuffer,
                                                 dword connectionID,
                                                 ulong64 dataSize,
                                                 IWebserverMessage * const webserverMessageSend,
                                                 char * resourceName,
                                                 char * reqPathForRequest,
                                                 IInstanceLog * const log,
                                                 class ExtendedHeader & extHeader,
                                                 word protocolVersion)
    : log(log),
      baseUser(baseUser),
      webserverMessageSend(webserverMessageSend),
      extHeader(log, protocolVersion),
      interfaceName(interfaceName),
      handlerType(handlerType)
{
    this->log->Log(LOG_WEBSERVER_PLUGIN, "%s(connection id: %u)::%s(): resourceName: '%s', reqPathForRequest: '%s'",
                   interfaceName, connectionID, interfaceName, resourceName != nullptr ? resourceName : "none", reqPathForRequest != nullptr ? reqPathForRequest : "none");
	this->digestAuthenticator = nullptr;
    this->protocolVersion = protocolVersion;
    this->connectionID = connectionID;
    this->resourceName = resourceName == nullptr ? "" : resourceName;
    this->reqRegPath = reqPathForRequest == nullptr ? "" : reqPathForRequest;
    this->dataSize = dataSize;
    this->sendCount = 0;
    this->headerFieldsToSend = nullptr;
    this->headerFieldsToSendSize = 0;
    this->userBuffer = nullptr;
    this->userBufferSize = 0;
    this->handlerIsRegistered = false;
    this->closeHandler = false;
    this->recvCalled = false;
    this->userSet = false;
    this->connFlags = extHeader.ConnectionFlags();
    if (extHeader.GetRemoteAddress() != nullptr) {
        this->remoteAddress = extHeader.GetRemoteAddress();
        this->remotePort = extHeader.GetRemotePort();
    }
    this->extHeaderBuffer = extHeaderBuffer;

    this->fieldNames = nullptr;
    this->fieldValues = nullptr;
    this->fieldCount = 0;
}


WebserverMessageHandler::~WebserverMessageHandler()
{
    this->log->Log(LOG_WEBSERVER_PLUGIN, "%s(connection id: %u)::~%s()", this->interfaceName, this->connectionID, this->interfaceName);
    free(this->extHeaderBuffer);
    free(this->headerFieldsToSend);
    free(fieldNames);
    free(fieldValues);
	delete this->digestAuthenticator;
    if (handlerIsRegistered) baseUser->UnregisterHandler(this);
}


void WebserverMessageHandler::RegisterHandler()
{
    baseUser->RegisterHandler(this);
    handlerIsRegistered = true;
}


void WebserverMessageHandler::Close()
{
    this->DoClose(true);
}

void WebserverMessageHandler::DoClose(bool sendCloseMessage)
{
    this->log->Log(LOG_WEBSERVER_PLUGIN, "%s(connection id: %u)::Close()", this->interfaceName, this->connectionID);
    if (!closeHandler) {
        closeHandler = true;

        // It could be that we need to send some "meta-data" as information...
        if (this->headerFieldsToSendSize > 0) this->extHeader.SetHeaderFields(this->headerFieldsToSend, this->headerFieldsToSendSize);
        if (this->extHeader.BufferSize() > 0) {
            size_t ehSize = this->extHeader.BufferSize();
            this->extHeader.Encode(this->connectionID, ALLOC_STACK_BUFFER(ehSize, 200));
            webserverMessageSend->Send(this->interfaceName, this->connectionID, WSM_SEND_META_DATA, 0, this->extHeader.Buffer(), ehSize);
        }

        if (sendCount == 0 && sendCloseMessage) {
            webserverMessageSend->Send(this->interfaceName, connectionID, WSM_MESSAGE_HANDLER_CLOSED);
        }
    }
}


void WebserverMessageHandler::Cancel(wsr_cancel_type_t reason)
{
    if (!closeHandler) {
        webserverMessageSend->Send(this->interfaceName, connectionID, WSM_SEND_META_DATA, reason);
        this->DoClose(false);
    }
    else {
        this->log->Log(LOG_WEBSERVER_PLUGIN, "%s(connection id: %u)::Cancel(reason = %s): will be ignored, because handler already is closing", this->interfaceName, this->connectionID, WSCancelTypeToStr(reason));
    }
}


void * WebserverMessageHandler::GetUserBuffer(size_t size)
{
    char errMsg[256];
    if (userBuffer) {
        if (userBufferSize < size) {
            sprintf(errMsg, "ERROR (Buffer overflow): Buffer size previously set with %s::Recv() to small (%u, size needed = %u)", this->interfaceName, (unsigned)userBufferSize, (unsigned)size);
            ASSERT(userBufferSize >= size, errMsg);
        }
    }
    else {
        this->log->Log(LOG_WEBSERVER_PLUGIN, "%s(connection id: %u)::RecvBuffer(): size: %u", this->interfaceName, this->connectionID, (unsigned)size);
        userBuffer = (byte *)this->RecvBuffer(size);
        if (userBuffer == nullptr) {
            sprintf(errMsg, "%s: call to users *RecvBuffer() function returned NULL", this->interfaceName);
            ASSERT(userBuffer != NULL, errMsg);
        }
        userBufferSize = size;
    }
    return userBuffer;
}


void WebserverMessageHandler::SetFieldList(char * fieldListBuffer, size_t fieldListBufferSize)
{
    size_t curBufSize = 0;
    size_t bytesHandled = 0;

    while (fieldListBufferSize != bytesHandled) {
        if (fieldCount == curBufSize) {
            curBufSize += 5;
            fieldNames = static_cast<char **>(realloc(fieldNames, curBufSize * sizeof(char *)));
            fieldValues = static_cast<char **>(realloc(fieldValues, curBufSize * sizeof(char *)));
        }

        fieldNames[fieldCount] = fieldListBuffer;
        size_t slen = strlen(fieldListBuffer);
        bytesHandled += slen + 1;
        fieldListBuffer += slen + 1;

        fieldValues[fieldCount] = fieldListBuffer;
        slen = strlen(fieldListBuffer);
        bytesHandled += slen + 1;
        fieldListBuffer += slen + 1;

        fieldCount++;
    };
}


const char * WebserverMessageHandler::GetHeaderFieldValue(const char * fieldName)
{
    for (size_t i = 0; i < fieldCount; i++) {
        if (strcasecmp(fieldName, fieldNames[i]) == 0) return fieldValues[i];
    }

    return nullptr;
}


void WebserverMessageHandler::SetHeaderField(const char * fname, const char * fvalue)
{
    size_t fnlen = strlen(fname) + 1;
    size_t fvlen = strlen(fvalue) + 1;
    size_t oldSize = this->headerFieldsToSendSize;
    this->headerFieldsToSendSize += fnlen + fvlen;
    this->headerFieldsToSend = static_cast<char *>(realloc(this->headerFieldsToSend, this->headerFieldsToSendSize));
    char * curPos = this->headerFieldsToSend + oldSize;
    memcpy(curPos, fname, fnlen);
    memcpy(curPos + fnlen, fvalue, fvlen);
}

void WebserverMessageHandler::SetTransferInfo(wsr_type_t resourceType, ulong64 dataSize, wsr_flags_t flags, const char * etag)
{
    if (this->log->LogFlagSet(LOG_WEBSERVER_PLUGIN)) {
        std::string ds;
        if (dataSize == WS_RESPONSE_CHUNKED) ds = "WSP_FLAG_SEND_RANGE";
        else ds = std::to_string(dataSize);

        this->log->Log(LOG_WEBSERVER_PLUGIN,
            "WebserverMessageHandler(type: %s, connection id: %u)::SetTransferInfo() resourceType: %s, dataSize: %s, flags: %s, etag: '%s'",
            WSPHandlerTypeToStr(this->handlerType),
            this->connectionID,
            WSResourceTypeToStr(resourceType),
            ds.c_str(),
            WSRFlagToStr(flags),
            etag == nullptr ? "nullptr" : etag);
    }
    
	this->extHeader.SetTransferInfo(resourceType, flags, dataSize);
	this->extHeader.SetETag(etag);
}

void WebserverMessageHandler::Send(const void * buffer, size_t len)
{
    if (!closeHandler) {
        if (len > WS_MAX_DATA_SIZE) {
            char errMsg[150];
            sprintf(errMsg, "%s::Send(connection id: %u): length of data is greater than WS_MAX_DATA_SIZE (%u)", this->interfaceName, this->connectionID, WS_MAX_DATA_SIZE);
            ASSERT(len <= WS_MAX_DATA_SIZE, errMsg);
        }

        if (this->headerFieldsToSend != nullptr) this->extHeader.SetHeaderFields(this->headerFieldsToSend, this->headerFieldsToSendSize);

        size_t ehSize = this->extHeader.BufferSize();
        if (ehSize > 0) {
            this->extHeader.Encode(this->connectionID, ALLOC_STACK_BUFFER(ehSize, 100));
            webserverMessageSend->Send(this->interfaceName, this->connectionID, WSM_SEND_META_DATA, 0, this->extHeader.Buffer(), this->extHeader.BufferSize());
            this->extHeader.Reset();
            free(this->headerFieldsToSend);
            this->headerFieldsToSend = nullptr;
            this->headerFieldsToSendSize = 0;
        }

        webserverMessageSend->Send(this->interfaceName, this->connectionID, WSM_SEND, 0, buffer, len);
        sendCount++;
    }
    else {
        this->log->Log(LOG_WEBSERVER_PLUGIN, "%s(connection id: %u)::Send(len: %u): NOTE: handler is closing, data will not be sent!", this->interfaceName, this->connectionID, len);
    }
}


void WebserverMessageHandler::Recv(byte * buffer, size_t len)
{
    if (userBuffer || recvCalled) {
        char errMsg[256];
        sprintf(errMsg, "%s::Recv() called before receiving a RecvResult()", this->interfaceName);
        ASSERT(userBuffer == NULL, errMsg);
    }

    recvCalled = true;
    userBuffer = buffer;
    userBufferSize = len;
    webserverMessageSend->Send(this->interfaceName, this->connectionID, WSM_RECV, (dword)len);
}


void WebserverMessageHandler::HandleMessage(IWebserverMessage * const message, void * buffer, size_t len)
{
    switch (message->GetMessageID())
    {
    case WSM_RECV_RESULT:
        userBuffer = nullptr;
        userBufferSize = 0;
        recvCalled = false;
        this->RecvResult(static_cast<byte *>(buffer), len);
        break;

    case WSM_SEND_RESULT:
        // To prevent that a call to Close() will lead to a crash when called inside WebserverSendResult(),
        // increment the sendCount after SendResult had been handled (Close() will check the value of
        // sendCount). A call to Send() inside that function will only lead to an incremented send result,
        // so it won't be a problem at all...
        this->SendResult();
        sendCount--;

        // Only close the handler, if we are not waiting for an other send result because of a new Send()
        // called inside the users SendResult() fuction.
        if (closeHandler && sendCount == 0) {
            webserverMessageSend->Send(this->interfaceName, this->connectionID, WSM_MESSAGE_HANDLER_CLOSED);
            //this->CloseComplete();
        }
        break;

    case WSM_SHUTDOWN_COMPLETE:
        if (userBuffer != nullptr) {
            this->RecvCanceled(userBuffer);
            userBuffer = nullptr;
            userBufferSize = 0;
        }
        if (this->handlerIsRegistered) {
            baseUser->UnregisterHandler(this);
            this->handlerIsRegistered = false;
        }
        this->CloseComplete();
        break;

    default:
        this->log->Log(LOG_WEBSERVER_PLUGIN, "%s(connection id: %u)::HandleMessage(): ERROR: received unexpected message id (%s) - closing", this->interfaceName, this->connectionID, WsMessageToStr(message->GetMessageID()));
        this->Close();
    }
}


void WebserverMessageHandler::WebserverPluginRecvCanceled(void * buffer)
{
}


void WebserverMessageHandler::WebserverPluginWillClose(wsp_close_reason_t reason)
{
    if (this->InterfaceUserSet()) {
        if (userBuffer != nullptr) {
            this->log->Log(LOG_WEBSERVER_PLUGIN, "%s(connection id: %u)::WebserverPluginRecvCanceled(): WebserverPlugin is closing, calling RecvCanceled()", this->interfaceName, this->connectionID);
            this->RecvCanceled(userBuffer);
            userBuffer = nullptr;
            userBufferSize = 0;
        }
        this->log->Log(LOG_WEBSERVER_PLUGIN, "%s(connection id: %u)::WebserverPluginWillClose(): WebserverPlugin is closing, calling CloseComplete(%s)", this->interfaceName, this->connectionID, WSPCloseReasonToStr(reason));
        this->CloseComplete();
    }
    else delete this;
}


void WebserverMessageHandler::SetStatusCode(dword code, const char * description)
{
	ASSERT(code >= 100 && code <= 999, "WebsewrverMessageHandler::SetStatusCode(): a response code must be 3 digets (between 100 and 999) as defined by RFC!");
    if (this->protocolVersion < WEBSERVER_PROTOCOL_VERSION_2) {
        this->log->Log(LOG_WEBSERVER_PLUGIN, "%s(connection id: %u)::SetStatusCode(): WARNING: SetStatusCode() not supported by reported webserver protocol version (%u). Please upgrade your webserver and check the protocol version in your app.", this->interfaceName, this->connectionID, this->protocolVersion);
        this->log->Log(LOG_WEBSERVER_PLUGIN, "%s(connection id: %u)::SetStatusCode(): WARNING: SetStatusCode() call ignored, no status code will be send. The result might be unexpected.", this->interfaceName, this->connectionID);
    }
    else {
	    const HTTPLib::http_result_t * resultCode = HTTPLib::HTTPResultFromStatusNumber(code);
	    if (description == nullptr) {
		    description = resultCode != nullptr ? resultCode->description + 4 : "User defined";
	    }

        this->log->Log(LOG_WEBSERVER_PLUGIN, "%s(connection id: %u)::SetStatusCode(): Status code: %u, description: '%s'", this->interfaceName, this->connectionID, code, description);
	    this->extHeader.SetStatusCode(code);
	    this->extHeader.SetStatusDescription(description);
    }
}

IDigestAuthenticator * WebserverMessageHandler::GetAuthenticator()
{
	if (this->digestAuthenticator == nullptr) {
		this->digestAuthenticator = this->baseUser->GetDigestAuthenticator(this->connectionID, this->GetHeaderFieldValue("Host"));
		this->digestAuthenticator->Init(this);
	}
	
	return this->digestAuthenticator;
}

const std::string WebserverMessageHandler::GetRequestTarget()
{
	std::string result = this->baseUser->GetAppWebRoot();
	if (result.back() != '/') result += "/";
	
	if (this->reqRegPath.front() == '/') result += this->reqRegPath.substr(1);
	else result += this->reqRegPath;
	if (result.back() != '/') result += "/";
	
	if (this->resourceName.front() == '/') result += this->resourceName.substr(1);
	else result += this->resourceName;
	
	return result;
}
