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

#include "platform/platform.h"

#include "common/os/iomux.h"
#include "common/interface/socket.h"
#include "common/interface/websocket_client.h"
#include "common/interface/webserver_plugin.h"

#include "common/service/webserver_shared/webserver_internal.h"

#include "common/lib/httplib.h"
#include "common/lib/appservice.h"

#include "types.h"
#include "apparguments.h"
#include "webserver.h"
#include "http_method_handler.h"
#include "http_connection.h"
#include "http_datasource.h"
#include "http_passthrough_handler.h"


HTTPPassthroughHandler::HTTPPassthroughHandler(UHTTPMethodHandler * const user, class HTTPRegisteredPath * reqPathObj, class HTTPDataSource * dataSource, ISocket * const remoteSocket, bool isTLS, IInstanceLog * const log, connection_flags_t connFlags)
    : HTTPMethodHandler("HTTPPassthroughHandler", user, reqPathObj, dataSource, remoteSocket, log, connFlags)
{
    this->curRestDataBufferPos = this->restDataBuffer = nullptr;
    this->restDataBufferSize = 0;
    this->recvBuffer = nullptr;
    this->recvBufferSize = 0;
    this->recvCalled = false;
    this->isTLS = isTLS;
}


HTTPPassthroughHandler::~HTTPPassthroughHandler()
{
    free(this->restDataBuffer);
    free(this->recvBuffer);
}



void HTTPPassthroughHandler::OnRecv(dword size)
{
    bool recvPartitial;
    if (size == 0) {
        size = WS_MAX_DATA_SIZE;
        recvPartitial = true;
    }
    else recvPartitial = false;

    this->recvBufferSize = size;

    if (this->restDataBufferSize > 0) {
        if (size <= this->restDataBufferSize || recvPartitial) {
            size_t dataToSend = (this->restDataBufferSize < size ? this->restDataBufferSize : size);
            DataSource()->SendMessage("HTTPPassthroughHandler", User()->GetConnectionID(), WSM_RECV_RESULT, 0, this->curRestDataBufferPos, dataToSend);
            this->curRestDataBufferPos += dataToSend;
            this->restDataBufferSize -= dataToSend;
        }
        else {
            this->recvBuffer = static_cast<byte *>(malloc(this->recvBufferSize));
            memcpy(this->recvBuffer, this->curRestDataBufferPos, this->restDataBufferSize);
            Socket()->Recv(this->recvBuffer + this->restDataBufferSize, size - this->recvBufferSize);
            this->recvCalled = true;
            this->restDataBufferSize = 0;
        }

        if (this->restDataBufferSize == 0) {
            free(this->restDataBuffer);
            this->restDataBuffer = this->curRestDataBufferPos = nullptr;
        }

        return;
    }

    this->recvBuffer = static_cast<byte *>(malloc(this->recvBufferSize));
    this->Socket()->Recv(this->recvBuffer, this->recvBufferSize, recvPartitial);
    this->recvCalled = true;
}


void HTTPPassthroughHandler::OnSend(dword info, byte * buffer, size_t size)
{
    this->Socket()->Send(buffer, size);
    this->IncSendCount();
}


void HTTPPassthroughHandler::OnHandlerClose(dword info, class ExtendedHeader & extHeader)
{
    // if a handler has recvCalled on close, the remote socket must be closed too, as the next request might be already
    // send to the handler and thus is broken
    if (this->recvCalled) {
        this->Socket()->Shutdown();
    }
}


void HTTPPassthroughHandler::OnSendMetaData(dword info, ExtendedHeader & extHeader)
{
    bool closeSocket = User()->HandleControlMessage(ResultHeader(), info, &extHeader);
    User()->HTTPHandlerShutdown(DataSource(), closeSocket);
}


void HTTPPassthroughHandler::SocketRecvResult(byte * buffer, size_t size)
{
    // 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.
    this->recvCalled = false;
    size += buffer - recvBuffer;
    this->DataSource()->SendMessage("HTTPPassthroughHandler", this->User()->GetConnectionID(), WSM_RECV_RESULT, 0, recvBuffer, size);
    free(recvBuffer);
    recvBuffer = nullptr;
    this->recvBufferSize = 0;
}


http_method_init_t HTTPPassthroughHandler::OnHandlerInit(const HTTPParser * parser, byte * buffer, size_t size)
{
    // The passthrough handler need to make a copy of the data. It will be deleted after the initialization
    // of the handler to save memory. So make a copy of the data (which - however - makes the handling of the
    // already received data more easier). Also the first line of the request header need to be rebuild,
    // because it is not longer part of the given buffer. When parsing the request line, the webserver
    // realizes, that the request is for a passthrough registered path. In that case, only the first 
    // line had been processed by the parser. The rest of the HTTP header still is inside the buffer, given
    // to the init function.
    const char * reqMethod = HTTPLib::GetHTTPRequestMethodName(parser->GetRequestMethod());
    const char * reqTarget = parser->GetRequestTarget();
    size_t rmLen = strlen(reqMethod);
    size_t rtLen = strlen(reqTarget);

    // REQUEST-TYPE + ' ' + REQUEST-TARGET + ' ' + 'HTTP/x.y' + \r\n (10 bytes)
    size_t flLen = rmLen + 1 + rtLen + 1 + 10;
    this->restDataBufferSize = flLen + size;
    this->restDataBuffer = static_cast<byte *>(malloc(this->restDataBufferSize));
    this->curRestDataBufferPos = restDataBuffer;
    char * curBufPos = reinterpret_cast<char *>(this->restDataBuffer);

    // The first line will be created "manually", that means without "spnrintf", because we already have the
    // length-informations of the data to write.
    memcpy(curBufPos, reqMethod, rmLen);
    curBufPos += rmLen;
    *curBufPos++ = ' ';
    memcpy(curBufPos, reqTarget, rtLen);
    curBufPos += rtLen;
    *curBufPos++ = ' ';
    
    memcpy(curBufPos, "HTTP/", 5);
    curBufPos += 5;
    *curBufPos++ = parser->GetMajorVersion() + 48;
    *curBufPos++ = '.';
    *curBufPos++ = parser->GetMinorVersion() + 48;
    *curBufPos++ = '\r';
    *curBufPos++ = '\n';

    // Now copy the rest of the data...
    memcpy(curBufPos, buffer, size);

    ExtendedHeader extHeader(this->log, this->DataSource()->ProtocolVersion());
    extHeader.SetRequestPath(this->RequestPath(), this->RequestPathLen());
    extHeader.SetRegisteredPathForRequest(this->RegisteredPathForRequest(), this->RegisteredPathForRequestLen());
    extHeader.SetConnectionFlags(this->connFlags);
    extHeader.SetRemoteAddress(this->Socket());
    extHeader.Encode(this->User()->GetConnectionID());

    DataSource()->SendMessage("HTTPPassthroughHandler", User()->GetConnectionID(), WSM_PASSTHROUGH_REQUEST, 0, extHeader.Buffer(), extHeader.BufferSize());

    return HTTP_INIT_DONE;
}


void HTTPPassthroughHandler::SocketShutdown(shutdownreason_t reason)
{
    this->DataSource()->SendMessage("HTTPPassthroughHandler", this->User()->GetConnectionID(), WSM_SHUTDOWN_COMPLETE);
    HTTPMethodHandler::SocketShutdown(reason);
}
