
/*---------------------------------------------------------------------------*/
/* http_method_handler.cpp                                                   */
/* copyright (c) innovaphone 2016                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include "platform/platform.h"

#include "common/os/iomux.h"

#include <string>

#include "common/lib/httplib.h"

#include "common/interface/time.h"
#include "common/interface/random.h"
#include "common/interface/socket.h"
#include "common/interface/websocket_client.h"
#include "common/interface/webserver_plugin.h"

#include "common/service/webserver_shared/inno_error_pages.h"
#include "common/service/webserver_shared/webserver_internal.h"
#include "common/service/webserver_shared/http_auth_digest.h"
#include "common/service/webserver_shared/http_digest_host.h"

#include "types.h"
#include "http_method_handler.h"

// This two headers depending on http_method_handler and so need to be included after.
#include "http_connection.h"
#include "http_datasource.h"

constexpr size_t MAX_CONSUME_DATA_SIZE = 5242880;


#define RECV_CHUNK_STATE_NONE       0
#define RECV_CHUNK_STATE_READ_SIZE  1
#define RECV_CHUNK_STATE_READ_DATA  2
#define RECV_CHUNK_STATE_EOF        3
#define RECV_CHUNK_STATE_DONE       4


/*---------------------------------------------------------------------------*/
/* class HTTPMethodHandler                                                   */
/*---------------------------------------------------------------------------*/

HTTPMethodHandler::HTTPMethodHandler(const char * handlerName, UHTTPMethodHandler * const user, class HTTPRegisteredPath * reqPathObj, class HTTPDataSource * dataSource, ISocket * const remoteSocket, IInstanceLog * const log, connection_flags_t connFlags)
    : handlerName(handlerName),
      user(user),
      remoteSocket(remoteSocket),
      log(log)
{
    this->resultHeader = new HTTPHeader;
    this->dataSource = dataSource;
    this->connFlags = connFlags;
    this->authenticationStarted = false;
    this->shutdownHandler = false;
    this->shutdownSocket = false;
    this->needSendHeader = true;
    this->sendDataChunkEncoded = false;
    this->firstChunkSent = false;

    this->needConsumeData = consume_data_t::NONE;
    this->cancelReason = wsr_cancel_type_t::WSP_CANCEL_STREAM;

    this->handlerInitState = HTTP_INIT_NONE;
    this->dataSendingActive = false;
	this->statusCode = 0;

    this->requestIsChunkEncoded = false;
    this->chunkReadState = RECV_CHUNK_STATE_READ_SIZE;
    this->chunkRemoveNextCLRF = false;
    this->chunkBytesToRead = 0;
    this->dataToSend = 0;
    this->requestContentLength = 0;
    this->totalDataReceived = 0;

    this->restDataBuffer = nullptr;
    this->restDataSize = 0;
    this->restDataBufPtr = nullptr;
    this->recvBuffer = nullptr;
    this->recvBufferSize = 0;
    this->recvPartitial = false;

    this->chunkRecvBufferPtr = nullptr;

    this->appRequestPath = nullptr;
    this->appRequestPathLen = 0;
    this->pathObjForRequest = reqPathObj;

    this->fieldItemBuffer = nullptr;
    this->fieldItemsStart = nullptr;
    this->fieldItemsEnd = nullptr;
}


HTTPMethodHandler::~HTTPMethodHandler()
{
    // Note: never ever release restBuffer, because it will be managed by the HTTPConnection object.
    free(this->recvBuffer);
    free(this->restDataBuffer);
    free(this->fieldItemBuffer);

    while (this->fieldItemsStart) {
        http_field_item_t * tmp = this->fieldItemsStart->next;
        delete this->fieldItemsStart;
        this->fieldItemsStart = tmp;
    }
    
    delete this->resultHeader;
}


void HTTPMethodHandler::IncSendCount(int incBy, bool createNew)
{
    if (createNew) sendCountStack.push_back(incBy);
    else sendCountStack.front() += incBy;
}


void HTTPMethodHandler::CheckDataConsumingState()
{
    bool maxDataExceeded = this->totalDataReceived >= MAX_CONSUME_DATA_SIZE;
    if (!maxDataExceeded && (this->dataToSend > 0 || (this->requestIsChunkEncoded && this->chunkReadState != RECV_CHUNK_STATE_EOF))) this->OnRecv(0); // giving 0 leads to max buffer size of 64k
    else {
        consume_data_t reason = this->needConsumeData;
        this->EnableDataConsuming(consume_data_t::NONE);
        if (reason == consume_data_t::CANCEL_REQUEST) {
            // If the socket had not been closed, it will be reused. In that case, the method handler must be shutdown manually....
            bool socketClosed = this->User()->HandleControlMessage(ResultHeader(), dword(maxDataExceeded) ? wsr_cancel_type_t::WSP_CANCEL_STREAM : this->cancelReason, nullptr);
            if (!socketClosed) this->User()->HTTPHandlerShutdown(DataSource(), false);
        }
        else this->OnRecv(CONNECTION_DEFAULT_RECV_SIZE);
    }
}


void HTTPMethodHandler::HandleCancel(dword info, ExtendedHeader & extHeader)
{
    bool handleDirectly = true;
    this->cancelReason = wsr_cancel_type_t(info);

    if (this->RequestDeliveredData() && this->cancelReason != wsr_cancel_type_t::WSP_CANCEL_STREAM && !this->dataSendingActive) {
        handleDirectly = false;
        this->needConsumeData = consume_data_t::CANCEL_REQUEST;
        this->CheckDataConsumingState();
    }

    if (handleDirectly) {
        bool socketClosed = this->User()->HandleControlMessage(ResultHeader(), info, &extHeader);
        if (!socketClosed) this->User()->HTTPHandlerShutdown(DataSource(), false);
    }
}


auth_state_t HTTPMethodHandler::IsAuthenticating(const HTTPParser * parser, HTTPRegisteredPath * rp, bool asyncInitCall)
{
    if (!rp->NeedAuthentication()) return AUTH_DONE;
    if (parser->GetHost() == nullptr || *parser->GetHost() == '\0') return AUTH_ERROR;

    HTTPAuthDigest * authDigest = this->user->GetAuthenticationObject();
    if (!authDigest) {
        authDigest = new HTTPAuthDigest();
        this->user->SetAuthenticationObject(authDigest);
    }

    HTTPDigestHostManager * digestHostManager = this->user->GetDigestHostManager();
    HTTPDigestHost * dgHost = digestHostManager->GetDigestHost(parser->GetHost());
    if (!dgHost) {
        dgHost = new HTTPDigestHost(parser->GetHost());
        digestHostManager->AddDigestHost(dgHost);
    }

    authDigest->SetDigestHost(dgHost);

    if (!asyncInitCall) { // || authDigest->GetNonceObj() == nullptr) {
        if (!authDigest->ParseAuthField(parser)) {
            this->StartAuthentication(parser, authDigest);
            return AUTH_IN_PROCESS;
        }
    }

    // There are two ways of authentication. A static authentication (one user / pwd for all access), one dynamic (the webserver will
    // ask the IWebserverPlugin for the password). Because the HTTPAuthDigest object is connected to the HTTPConection object, it will
    // be deleted when the connection will be closed. So it can be used to store an cache a password for the dynamic authentication.
    // This will work als follows:
    //
    // Static-Authentication:  authDigest->Password always returns nullptr. But in that case, rp->GetAuthUser() will never be nullptr
    //                         and the static password will be used.
    //
    // Dynamic-Authentication: The Webserver will ask the IWebserverPlugin for the password of the user. When receiving the response,
    //                         the password will be stored in the authObject of the connection and used later to make the authentication,
    //                         also when an other request over the same connection comes in. The IWebserverPlugin will only be asked once
    //                         per connection (which however accidentally imitated the behaviour of NTLM connections in some wired way...)
    
    const char * user;
    const char * pwd;

    if (rp->DynamicAuthenticationEnabled()) {
        user = authDigest->DynUser();
        pwd = authDigest->DynPassword();

        // If the function not had been called because of a RequestUserPasswordResult and the there is no user, ask for it.
        if (!asyncInitCall) { // && user == nullptr) {
            ExtendedHeader extHeader(this->log, this->DataSource()->ProtocolVersion());
            extHeader.SetHost(parser->GetHost(), strlen(parser->GetHost()));
            extHeader.SetAuthInfos(authDigest->GetUser(), nullptr);
            extHeader.SetRequestPath(this->appRequestPath, this->appRequestPathLen);
            extHeader.SetRegisteredPathForRequest(rp->GetPath(), rp->GetPathLen());
            size_t bsize = extHeader.BufferSize();
            extHeader.Encode(this->User()->GetConnectionID(), bsize < 512 ? static_cast<byte *>(alloca(bsize)) : nullptr);
            dataSource->SendMessage(this->handlerName, this->user->GetConnectionID(), WSM_REQUEST_USER_PASSWORD, 0, extHeader.Buffer(), bsize);
            
            return AUTH_VERIFING_USER;
        }
    }
    else {
        user = rp->GetAuthUser();
        pwd = rp->GetAuthPwd();
    }

    if (user == nullptr) user = "";
    if (pwd == nullptr) pwd = "";

    if (!authDigest->VerifyAuthentication(user, pwd, parser->GetRequestTarget())) {
        this->StartAuthentication(parser, authDigest);
        return AUTH_IN_PROCESS;
    }

    return AUTH_DONE;
}


void HTTPMethodHandler::StartAuthentication(const HTTPParser * parser, HTTPAuthDigest * authDigest)
{
    char buf[2048];

    digest_nonce_t dn;
    authDigest->ReadDigestNonce(dn);

    if (dn.needStale) {
        _snprintf(buf, sizeof(buf), "Digest realm=\"%s\", qop=\"auth\", nonce=\"%s\", opaque=\"%s\", stale=TRUE",
            "Innovaphone",
            dn.nonce->GetNonce(),
            authDigest->GetServerOpaque());
    }
    else {
        _snprintf(buf, sizeof(buf), "Digest realm=\"%s\", qop=\"auth\", nonce=\"%s\", opaque=\"%s\"",
            "Innovaphone",
            dn.nonce->GetNonce(),
            authDigest->GetServerOpaque());
    }

    resultHeader->SetHTTPResult(&HTTPLib::HTTP_401);
    resultHeader->SetCustomField("WWW-Authenticate", buf);
    resultHeader->SetContentType("text/html; charset=utf-8");
    resultHeader->SetContentSize(INNO_401_LEN);

    // The answer will be send after all incoming data had been consumed...

    //remoteSocket->Send(resultHeader->GetHeaderData(), resultHeader->GetHeaderSize());
    //remoteSocket->Send((void *)INNO_401, INNO_401_LEN);

    //this->IncSendCount(2);

    authenticationStarted = true;
}


void HTTPMethodHandler::Init(const class HTTPParser * parser, byte * buffer, size_t size, bool asyncInitCall)
{
    if (!asyncInitCall) { // Not called from RequestPasswordResponse?
        if (!this->pathObjForRequest) {
            this->ResultHeader()->SetHTTPVersion(parser->GetMajorVersion(), parser->GetMinorVersion());
            this->user->HandleControlMessage(this->ResultHeader(), WSP_CANCEL_INTERNAL_ERROR);
            return;
        }

        resultHeader->SetHTTPVersion(parser->GetMajorVersion(), parser->GetMinorVersion());

        this->appRequestPath = parser->GetRequestTarget() + this->pathObjForRequest->GetPathLen();
        this->appRequestPathLen = strlen(this->appRequestPath);
    }

    this->requestIsChunkEncoded = const_cast<HTTPParser *>(parser)->IsChunkedTransfer();
    if (!this->requestIsChunkEncoded) this->requestContentLength = this->dataToSend = parser->GetContentLength();

    auth_state_t as = this->IsAuthenticating(parser, this->pathObjForRequest, asyncInitCall);
    if (as == AUTH_ERROR) {
        this->user->HTTPHandlerShutdown(this->DataSource(), true, &HTTPLib::HTTP_400);
        return;
    }
    
    if (as == AUTH_VERIFING_USER) {
        user->GetAuthenticationObject()->SaveParserAndBuffer(parser, buffer, size);
        return;
    }

    if (as != AUTH_DONE) {
        this->handlerInitState = HTTP_INIT_AUTHENTICATE;
        this->needSendHeader = false;

        // Check if there is data that need to be consumed first, before sending the authentication header.
        // Well - sending the header will work, but on the other hand the result won't - the data will lead
        // to an error because the data will be intepreted as HTTP header (which won't work).
        if (this->requestIsChunkEncoded || this->dataToSend) this->EnableDataConsuming(consume_data_t::AUTHENTICATE);
        if (this->IsConsumingData() && size > 0) {
            restDataSize = size;
            restDataBuffer = static_cast<byte *>(malloc(size));
            restDataBufPtr = restDataBuffer;
            memcpy(restDataBuffer, buffer, size);
            this->OnRecv(dword(this->dataToSend == 0 || this->dataToSend > CONNECTION_DEFAULT_RECV_SIZE ? CONNECTION_DEFAULT_RECV_SIZE : this->dataToSend));
        }
        else {
            this->OnRecv(CONNECTION_DEFAULT_RECV_SIZE);
        }
        return;
    }

    if (size > 0) {
        restDataSize = size;
        restDataBuffer = static_cast<byte *>(malloc(size));
        restDataBufPtr = restDataBuffer;
        memcpy(restDataBuffer, buffer, size);
    }

    // If the initialization reportes an error, close the method handler because it won't be used anymore (initialization failed, it's useless now).
    // The socket will be closed or not depending if it is a critical error or a uncritical one. An uncritical error means, that the method handler
    // was able to send a correct HTTP response and with that, the socket is clean to receive a next request. A critical error means, that because of
    // some circumstances, the state of the HTTP stream is unclear. So a new response may or may not come through. Because of that, the socket
    // connection will be closed.
    this->handlerInitState = this->OnHandlerInit(parser, buffer, size);
    if (this->handlerInitState == http_method_init_t::HTTP_INIT_ERROR) this->User()->HTTPHandlerShutdown(this->DataSource(), false); 
    else if (this->handlerInitState == http_method_init_t::HTTP_INIT_CRITICAL_ERROR) this->User()->HTTPHandlerShutdown(this->DataSource(), true);
}


void HTTPMethodHandler::Init(const class HTTPParser * parser, byte * buffer, size_t size)
{
    this->Init(parser, buffer, size, false);
}


const char * HTTPMethodHandler::RegisteredPathForRequest()
{
    return this->pathObjForRequest == nullptr ? nullptr : this->pathObjForRequest->GetPath();
}


size_t HTTPMethodHandler::RegisteredPathForRequestLen()
{
    return this->pathObjForRequest == nullptr ? 0 : this->pathObjForRequest->GetPathLen();
}


void HTTPMethodHandler::AddFieldsToHeader()
{
    // Finally add the rest of available header fields...
    http_field_item_t * itm = fieldItemsStart;
    while (itm) {
        resultHeader->SetCustomField(itm->fieldName, itm->fieldValue);
        http_field_item_t * tmp = itm->next;
        delete itm;
        itm = tmp;
    }

    fieldItemsStart = fieldItemsEnd = nullptr;
    free(this->fieldItemBuffer);
    this->fieldItemBuffer = nullptr;
}


void HTTPMethodHandler::OnSend(dword info, byte * buffer, size_t size)
{
    int sendCount = 0;
    this->dataSendingActive = true;

    if (needSendHeader) {
        this->OnPrepareHeader();
        this->AddFieldsToHeader();
        remoteSocket->Send(resultHeader->GetHeaderData(), resultHeader->GetHeaderSize());
        sendCount++;
        needSendHeader = false;
    }

    if (buffer) {
        if (sendDataChunkEncoded) {
            char tmpbuf[10];
            size_t bl;
            if (firstChunkSent) bl = sprintf(tmpbuf, CLRF "%X" CLRF, (unsigned)size);
            else {
                firstChunkSent = true;
                bl = sprintf(tmpbuf, "%X" CLRF, (unsigned)size);
            }
            Socket()->Send(tmpbuf, bl);
            sendCount++;
        }

        remoteSocket->Send(buffer, size);
        sendCount++;
    }

    if (sendCount > 0) this->IncSendCount(sendCount);
    else dataSource->SendMessage(this->handlerName, user->GetConnectionID(), WSM_SEND_RESULT);
}


inline void HTTPMethodHandler::AdjustRestBufferSize(size_t bytesHandled)
{
    chunkRecvBufferPtr += bytesHandled;
    restDataBufPtr += bytesHandled;
    restDataSize -= bytesHandled;
    dataToSend -= bytesHandled;

    if (restDataSize == 0) {
        free(restDataBuffer);
        restDataBuffer = restDataBufPtr = nullptr;
    }
}


inline void HTTPMethodHandler::ChunkRecvNextPart()
{
    static const size_t size = 16;
    byte * tmpBuf = static_cast<byte *>(malloc(size));
    size_t restSize = 0;
    if (restDataSize > 0) {
        ASSERT(restDataSize < size, "HTTPMethodHandler::ChunkRecvNextPart(): existing data is more the malloced memory");
        memcpy(tmpBuf, restDataBufPtr, restDataSize);
        restSize = restDataSize;
    }
    free(restDataBuffer);
    restDataBufPtr = restDataBuffer = tmpBuf;
    remoteSocket->Recv(restDataBuffer + restSize, size - restSize, true);
}


bool HTTPMethodHandler::ChunkGetNextChunkSize()
{
    byte * pos = (byte *)(memchr(restDataBufPtr, '\n', restDataSize));
    
    if (!pos) {
        // For now we only support a chunk size with a maximum of 0xFFFF. So
        // the maxium bytes length of the length part will be 6 (FFFF\n\r).
        // If the rest data size is greater, something goes wrong. In that
        // case close the connection.
        if (restDataSize > 6) user->HTTPHandlerShutdown(dataSource, true);
        else ChunkRecvNextPart();

        return false;
    }

    if (pos == restDataBufPtr || (restDataSize > 1 && *(pos - 1) != '\r')) { // There must be a \r\n before the number!!!
        this->log->Log(LOG_WEBSERVER, "#ERROR HTTPMethodHandler::GetNextChunkSize(0x%x): Webserver receives chunk encoded data for connection %u, but coud not found \\r\\n after the chunk size!",
            this,
            User()->GetConnectionID());
        user->HTTPHandlerShutdown(dataSource, true);
        return false;
    }

    dataToSend = strtoul(reinterpret_cast<char *>(restDataBufPtr), nullptr, 16);

    restDataSize -= pos - restDataBufPtr + 1;
    restDataBufPtr = pos + 1;
    chunkReadState = RECV_CHUNK_STATE_READ_DATA;
    return true;
}


inline void HTTPMethodHandler::ChunkSendRecvBuffer(size_t len)
{
    if (!this->IsConsumingData() && this->authenticationStarted) {
        free(recvBuffer);
        recvBuffer = chunkRecvBufferPtr = nullptr;
        recvBufferSize = 0;
        this->OnRecv(CONNECTION_DEFAULT_RECV_SIZE);
    }
    else this->SendRecvBufferToDataSource(len);
}


inline bool HTTPMethodHandler::ChunkRemoveCurCLRF()
{
    if (restDataSize < 2) {
        ChunkRecvNextPart();
        return false;
    }

    if (*restDataBufPtr++ != '\r' || *restDataBufPtr++ != '\n') {
        this->log->Log(LOG_WEBSERVER, "#ERROR HTTPMethodHandler::RemoveCurCLRF(0x%x): Expected \\r\\n while handling received chunked data not found!", this);
        user->HTTPHandlerShutdown(dataSource, true);
        return false;
    }
    else {
        chunkRemoveNextCLRF = false;
        restDataSize -= 2;
        return true;
    }
}

void HTTPMethodHandler::SendRecvBufferToDataSource(size_t len)
{
    // It could be, that there is already a part inside the buffer. This can be checked by comparing the
    // buffer with the recvBuffer pointer. The difference of that needs to be added to size.
    if (this->IsConsumingData()) {
        free(recvBuffer);
        recvBuffer = chunkRecvBufferPtr = nullptr;
        recvBufferSize = 0;
        this->CheckDataConsumingState();
    }
    else {
        dataSource->SendMessage(this->handlerName, user->GetConnectionID(), WSM_RECV_RESULT, 0, recvBuffer, len);
        free(recvBuffer);
        recvBuffer = chunkRecvBufferPtr = nullptr;
        recvBufferSize = 0;
    }
}


// NOTE: Return true --> Need to be called again. Doing this helps to prevent recursive calls...
bool HTTPMethodHandler::RecvChunked()
{
    if (this->chunkRemoveNextCLRF && !ChunkRemoveCurCLRF()) return false;

    size_t recvBufDataSize = this->chunkRecvBufferPtr - this->recvBuffer;

    if (this->chunkReadState == RECV_CHUNK_STATE_EOF) {
        this->ChunkSendRecvBuffer(recvBufDataSize);
        return false;
    }

    if (this->chunkReadState == RECV_CHUNK_STATE_READ_SIZE) {
        if (!ChunkGetNextChunkSize()) return false;

        if (dataToSend == 0) { // End of data - here the function calls itself once because there need to be some tests to get the EOF state
            this->chunkReadState = RECV_CHUNK_STATE_EOF;
            this->chunkRemoveNextCLRF = true;
            return true; // Lets call the function again...
        }
    }

    // There are three different cases to handle the data: case 1 - data is inside the already loaded buffer
    ulong64 recvBufRestSize = this->recvBufferSize - recvBufDataSize;
    if (restDataSize > 0) {
        ulong64 cnt = restDataSize > recvBufRestSize ? recvBufRestSize : restDataSize;
        if (cnt > dataToSend) cnt = dataToSend;
        memcpy(this->chunkRecvBufferPtr, this->restDataBufPtr, size_t(cnt)); // cnt will never be bigger than 64k!
        this->AdjustRestBufferSize(size_t(cnt));
        recvBufRestSize -= cnt;
    }

    if (recvBufRestSize == 0) {
        this->ChunkSendRecvBuffer(size_t(this->recvBufferSize));
        return false;
    }

    // Case 2 - There is still data to read on the socket...
    if (this->dataToSend > 0) {
        ulong64 cnt = this->dataToSend > recvBufRestSize ? recvBufRestSize : this->dataToSend;
        remoteSocket->Recv(this->chunkRecvBufferPtr, size_t(cnt));
        return false;
    }
    else {
        this->chunkRemoveNextCLRF = true;
    }
    
    // Case 3 - We need to load the next chunk. This chunk can be inside the buffer of already loaded data or it could be
    // thet we need to read it from the socket...
    this->chunkReadState = RECV_CHUNK_STATE_READ_SIZE;

    if (restDataSize > 0) return true;

    this->ChunkRecvNextPart();
    return false;
}


inline void HTTPMethodHandler::RecvStreamed()
{
    // Make sure that the app didn't reqested more then the available data. If so,
    // the recv() will hang and that's it.
    if (!recvPartitial && recvBufferSize > this->dataToSend) recvBufferSize = this->dataToSend;

    if (restDataSize == 0) {
        if (restDataBuffer) {
            free(restDataBuffer);
            restDataBuffer = nullptr;
            restDataBufPtr = nullptr;
        }
        remoteSocket->Recv(recvBuffer, size_t(recvBufferSize), recvPartitial);
    }
    else {
        size_t dataToCopy = size_t(restDataSize > recvBufferSize ? recvBufferSize : restDataSize);

        memcpy(recvBuffer, restDataBufPtr, dataToCopy);
        restDataBufPtr += dataToCopy;
        restDataSize -= dataToCopy;

        if (recvPartitial || (recvBufferSize == dataToCopy)) {
            // So for the case that there is more data sent than the header defines in content length!
            dataToSend = dataToCopy > dataToSend ? 0 : dataToSend - dataToCopy;
            this->SendRecvBufferToDataSource(dataToCopy);
        }
        else remoteSocket->Recv(recvBuffer + dataToCopy, size_t(recvBufferSize - dataToCopy));
    }
}


void HTTPMethodHandler::OnRecv(dword size)
{
    // End of transfer reached? --> Send nullptr response so that the app can know...
    if (!this->IsConsumingData()) {
        if ((requestIsChunkEncoded && chunkReadState == RECV_CHUNK_STATE_EOF) || (!requestIsChunkEncoded && dataToSend == 0)) {
            if (this->authenticationStarted) {
                // Send the authentication request header...
                remoteSocket->Send(resultHeader->GetHeaderData(), resultHeader->GetHeaderSize());
                remoteSocket->Send((void *)INNO_401, INNO_401_LEN);
                this->IncSendCount(2);
            }
            else dataSource->SendMessage(this->handlerName, user->GetConnectionID(), WSM_RECV_RESULT);
            return;
        }
    }

    // Prepare the buffer to read the data to...
    if (size == 0) {
        recvBufferSize = WS_MAX_DATA_SIZE;
        recvPartitial = true;
    }
    else {
        recvBufferSize = size;
        recvPartitial = false;
    }
    recvBuffer = static_cast<byte *>(malloc(size_t(recvBufferSize)));

    if (requestIsChunkEncoded) {
        // RecvChunked() will be called until it will return false. This seems to be a little
        // bit odd, but helps to prevent make recursive calls...
        chunkRecvBufferPtr = recvBuffer;
        while (this->RecvChunked());
    }
    else this->RecvStreamed();
}


void HTTPMethodHandler::OnHandlerClose(dword info, ExtendedHeader & extHeader)
{
    if (needSendHeader) {
        this->OnPrepareHeader();
        this->AddFieldsToHeader();
        remoteSocket->Send(resultHeader->GetHeaderData(), resultHeader->GetHeaderSize());
        this->IncSendCount();
    }
    
    if (sendDataChunkEncoded) {
        if (firstChunkSent) remoteSocket->Send(CLRF "0" CLRF CLRF, 7);
        else remoteSocket->Send("0" CLRF CLRF, 5);
        this->IncSendCount();
    }

    shutdownHandler = true;
}


void HTTPMethodHandler::SetHeaderFields(ExtendedHeader & extHeader)
{
    if (extHeader.FieldListBufferSize() > 0) {
        size_t dataLen = extHeader.FieldListBufferSize();
        this->fieldItemBuffer = static_cast<char *>(malloc(dataLen));
        memcpy(this->fieldItemBuffer, extHeader.FieldListBuffer(), dataLen);
        char * curPos = this->fieldItemBuffer;
        char * endPos = curPos + dataLen;

        while (curPos < endPos) {
            http_field_item_t * newItem = new http_field_item_t;
            newItem->next = nullptr;
            newItem->fieldName = curPos;
            curPos += strlen(curPos) + 1;
            newItem->fieldValue = curPos;
            curPos += strlen(curPos) + 1;

            if (this->fieldItemsStart == nullptr) fieldItemsStart = fieldItemsEnd = newItem;
            else {
                fieldItemsEnd->next = newItem;
                fieldItemsEnd = newItem;
            }
        }
    }
}


void HTTPMethodHandler::SocketSendResult()
{
    if (!sendCountStack.empty()) {
        --sendCountStack.front();

        if (sendCountStack.front() == 0) {
            sendCountStack.pop_front();

            // If the authentication had been started, don't redirect the send result to the dataSource.
            // Instead shutdown the handler, so that a new one will manage the rest of the authentication
            // (because of a new GET request with authentication informations)
            if (authenticationStarted) {
                user->HTTPHandlerShutdown(dataSource, false);
                return;
            }

            dataSource->SendMessage(this->handlerName, user->GetConnectionID(), WSM_SEND_RESULT);

            if (shutdownHandler) user->HTTPHandlerShutdown(dataSource, shutdownSocket);
        }
    }
}


void HTTPMethodHandler::SocketRecvResult(byte * buffer, size_t size)
{
    // If there is no datasource anymore, ignore the received data...
    this->totalDataReceived += size;
    if (this->dataSource == nullptr && !this->IsConsumingData()) return;

    if (requestIsChunkEncoded) {
        if (this->chunkReadState == RECV_CHUNK_STATE_READ_DATA) {
            chunkRecvBufferPtr += size;
            dataToSend -= size;
        }
        else restDataSize += size;

        // Call the function until false will be returned. Doing this helps to prevent make
        // recursive calls...
        while(this->RecvChunked());
    }
    else {
        size += buffer - recvBuffer;
        dataToSend -= size;
        this->SendRecvBufferToDataSource(size);
    }
}


void HTTPMethodHandler::SocketShutdown(shutdownreason_t reason)
{
    // If a shutdown will be told to this object, there is an active data transfer.
    // So we need to tell the application, that the socket had been lost.
    user->HTTPHandlerShutdown(dataSource, true);
}


void HTTPMethodHandler::AppResponse(dword id, dword info, byte * buffer, size_t size)
{
    ExtendedHeader extHeader(this->log, this->DataSource()->ProtocolVersion());

    switch (id)
    {
    case WSM_SEND_META_DATA:
        if (buffer) extHeader.Decode(this->User()->GetConnectionID(), buffer, size);
	    this->statusCode = extHeader.GetStatusCode();
	    if (extHeader.GetStatusDescription() != nullptr) this->statusDescription = extHeader.GetStatusDescription();
        this->SetHeaderFields(extHeader);
        this->OnSendMetaData(info, extHeader);
        break;

    case WSM_SEND:
        this->OnSend(info, buffer, size);
        break;

    case WSM_RECV:
        this->OnRecv(info);
        break;

    case WSM_MESSAGE_HANDLER_CLOSED:
        // The message only need to be handled in case that data consuming is not running because the request had been canceled by the app.
        // In that case, the message handler will be closed anyways.
        if (this->needConsumeData != consume_data_t::CANCEL_REQUEST) {
            if (buffer) extHeader.Decode(this->User()->GetConnectionID(), buffer, size);
            this->OnHandlerClose(info, extHeader);
            user->HTTPHandlerShutdown(dataSource, shutdownSocket);
        }
        break;

    case WSM_REQUEST_USER_PASSWORD_RESULT:
        extHeader.Decode(this->User()->GetConnectionID(), buffer, size);
        this->OnRequestUserPasswordResult(extHeader);
        break;

    default:
        this->log->Log(LOG_WEBSERVER, "#WARNING %s::AppResponse(0x%x): Unexpected id %u for connection %u", handlerName, this, id, User()->GetConnectionID());
        user->HTTPHandlerShutdown(dataSource, true);
        break;
    }
}


bool HTTPMethodHandler::IsPassTrough()
{
    return pathObjForRequest == nullptr ? false : this->pathObjForRequest->IsPassThrough();
}


void HTTPMethodHandler::OnRequestUserPasswordResult(class ExtendedHeader & extHeader)
{
    HTTPAuthDigest * authObj = user->GetAuthenticationObject();
    const HTTPParser * parser = authObj != nullptr ? authObj->SaveParser() : nullptr;

    // If parser is nullptr, no request for a user password is pending...
    if (authObj == nullptr || parser == nullptr) {
        this->log->Log(LOG_WEBSERVER, "#WARNING: %s::OnRequestUserPasswordResult(0x%x): Received user password, but there is not authentication process active (connection ID %u)", this, user->GetConnectionID());
        this->user->HTTPHandlerShutdown(dataSource, true);
        return;
    }

    byte * buf = authObj->SaveRequestBuffer();
    size_t bufSize = authObj->SaveRequestBufferSize();
    authObj->SaveParserAndBuffer(nullptr, nullptr, 0);
    authObj->SetDynAuthData(extHeader.AuthUser(), extHeader.AuthPassword());

    this->Init(parser, buf, bufSize, true);
}


void HTTPMethodHandler::ShutdownHandler()
{
    const HTTPLib::http_result_t* httpResult = this->dataSendingActive || this->dataSource != nullptr ? nullptr : &HTTPLib::HTTP_503;
    user->HTTPHandlerShutdown(dataSource, true, httpResult);
}


void HTTPMethodHandler::InvalidateDatasource()
{
    this->dataSource = nullptr;
}


std::string HTTPMethodHandler::GetStatusCodeStr(const HTTPLib::http_result_t & defaultStatus)
{
	std::string result;
	if (this->statusCode > 0) {
		result = std::to_string(this->statusCode);
		result += " ";
		result += this->statusDescription;
	}
	else {
		result = defaultStatus.description;
	}
	return result;
}

void HTTPMethodHandler::SetResultMIMEType(wsr_type_t type)
{
    switch (type)
    {
    case WSP_RESPONSE_HTML:
        ResultHeader()->SetContentType("text/html; charset=utf-8");
        break;

    case WSP_RESPONSE_CSS:
        ResultHeader()->SetContentType("text/css");
        break;

    case WSP_RESPONSE_JAVASCRIPT:
    case WSP_RESPONSE_MJS:
        ResultHeader()->SetContentType("text/javascript");
        break;

    case WSP_RESPONSE_JSON:
        ResultHeader()->SetContentType("application/json");
        break;

    case WSP_RESPONSE_XML:
        ResultHeader()->SetContentType("text/xml");
        break;

    case WSP_RESPONSE_JAVA:
        ResultHeader()->SetContentType("java/*");
        break;

    case WSP_RESPONSE_TEXT:
        ResultHeader()->SetContentType("text/plain; charset=utf-8");
        break;

    case WSP_RESPONSE_ICON:
        ResultHeader()->SetContentType("image/x-icon");
        break;

    case WSP_RESPONSE_PNG:
        ResultHeader()->SetContentType("image/png");
        break;

    case WSP_RESPONSE_GIF:
        ResultHeader()->SetContentType("image/gif");
        break;

    case WSP_RESPONSE_BMP:
        ResultHeader()->SetContentType("image/bmp");
        break;

    case WSP_RESPONSE_WAV:
        ResultHeader()->SetContentType("audio/wav");
        break;

    case WSP_RESPONSE_OGG:
        ResultHeader()->SetContentType("audio/ogg");
        break;

    case WSP_RESPONSE_MP3:
        ResultHeader()->SetContentType("audio/mpeg");
        break;

    case WSP_RESPONSE_BINARY:
        ResultHeader()->SetContentType("application/octet-stream");
        break;

    case WSP_RESPONSE_SVG:
        ResultHeader()->SetContentType("image/svg+xml");
        break;

    case WSP_RESPONSE_PDF:
        ResultHeader()->SetContentType("application/pdf");
        break;

    case WSP_RESPONSE_TTF:
    case WSP_RESPONSE_TFLITE:
        ResultHeader()->SetContentType("application/octet-stream");
        break;

    case WSP_RESPONSE_EOT:
        ResultHeader()->SetContentType("application/vnd.ms-fontobject");
        break;

    case WSP_RESPONSE_WOFF:
        ResultHeader()->SetContentType("application/font-woff");
        break;

    case WSP_RESPONSE_G711:
        ResultHeader()->SetContentType("audio/G711-0");
        break;

    case WSP_RESPONSE_G722:
        ResultHeader()->SetContentType("audio/G722");
        break;

    case WSP_RESPONSE_G729:
        ResultHeader()->SetContentType("audio/G729");
        break;

    case WSP_RESPONSE_JPEG:
        ResultHeader()->SetContentType("image/jpeg");
        break;

    case WSP_RESPONSE_MP4:
        ResultHeader()->SetContentType("video/mp4");
        break;

    case WSP_RESPONSE_WEBM:
        ResultHeader()->SetContentType("video/webm");
        break;

    case WSP_RESPONSE_PEM:
        ResultHeader()->SetContentType("application/x-pem-file");
        break;

    case WSP_RESPONSE_MOBILECONFIG:
        ResultHeader()->SetContentType("application/x-apple-aspen-config");
        break;

    case WSP_RESPONSE_CACHE_MANIFEST:
        ResultHeader()->SetContentType("text/cache-manifest");
        break;

    case WSP_RESPONSE_CSV:
        ResultHeader()->SetContentType("text/csv");
        break;

    case WSP_RESPONSE_APPLICATION_XML:
        ResultHeader()->SetContentType("application/xml; charset=utf-8");
        break;

    case WSP_RESPONSE_WASM:
        ResultHeader()->SetContentType("application/wasm");
        break;

    default:
        //this->log->Log(LOG_WEBSERVER, "#WARNING HTTPGetHandler::SetResultMIMEType(0x%x): Connection %u received unkown resource type %u - using plain text",
        //    this,
        //    User()->GetConnectionID(),
        //    type);
        ResultHeader()->SetContentType("text/plain; charset=utf-8");
        break;
    }
}
