
/*---------------------------------------------------------------------------*/
/* http_datasource.cpp                                                       */
/* copyright (c) innovaphone 2015                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include "platform/platform.h"

#include "common/os/iomux.h"

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

#include "common/lib/httplib.h"
#include "common/lib/appservice.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 "apparguments.h"
#include "webserver.h"
#include "set_certificate_handler.h"
#include "http_vfs_entry.h"
#include "http_method_handler.h"
#include "http_connection.h"
#include "http_datasource.h"

#define HDS_STATE_NONE                  0
#define HDS_STATE_READ_HEADER           1
#define HDS_STATE_READ_DATA             2

#define BUFFER_BLOCK_SIZE   1024


HTTPDataSource::HTTPDataSource(Webserver * const webserver)
    : istd::listElement<HTTPDataSource>(),
      webserver(webserver),
      log(dynamic_cast<IInstanceLog *>(webserver))
{
    this->webRoot = nullptr;
    this->protocolVersion = 0;
    this->remoteSocket = nullptr;
    this->webserverMessageSend = nullptr;
    this->webserverMessageRecv = nullptr;
    this->setCertificateHandler = nullptr;

    this->httpPathRoot = nullptr;
    this->websocketPath = nullptr;
    this->passThroughPath = nullptr;

    this->state = HDS_STATE_NONE;
    this->messageDataSize = 0;

    this->buf = nullptr;
}


HTTPDataSource::~HTTPDataSource()
{
    ASSERT(remoteSocket == nullptr, "HTTPDataSource::DTOR() - remoteSocket != nullptr");
    delete webserverMessageSend;
    delete webserverMessageRecv;
    delete remoteSocket;
    delete this->setCertificateHandler;

    free(this->webRoot);
    free(this->buf);

    if (this->httpPathRoot != this->websocketPath) delete this->websocketPath;
    delete this->passThroughPath;
    delete this->httpPathRoot;

    while (this->registeredPathList.front()) {
        HTTPRegisteredPath * cur = this->registeredPathList.front();
        delete cur;
    }
}


HTTPRegisteredPath * HTTPDataSource::GetPathObject(const char * requestedPath, const char * reqPathEnd)
{
    size_t plen = (requestedPath == reqPathEnd ? strlen(requestedPath) : reqPathEnd - requestedPath);
    for (HTTPRegisteredPath * s = this->registeredPathList.front(); s; s = s->goNext()) {
        if (s->GetPathLen() == plen && memcmp(requestedPath, s->GetPath(), plen) == 0) {
            return s;
        }
    }

    return nullptr;
}


void HTTPDataSource::AddConnectionUsingDataSource(HTTPConnection * connection)
{
    this->registeredConnections.push_back(connection);
}


void HTTPDataSource::RemoveConnectionUsingDataSource(HTTPConnection * connection)
{
    connection->remove();
}


void HTTPDataSource::SocketAcceptComplete(ISocket * const socket)
{
    remoteSocket = socket;
    webserverMessageSend = IWebserverMessage::Create(socket, log);
    webserverMessageRecv = IWebserverMessage::Create(socket, log);
    socket->Recv((void *)webserverMessageRecv->GetHeaderBuffer(), webserverMessageRecv->GetHeaderSize());
    state = HDS_STATE_READ_HEADER;
}


void HTTPDataSource::SocketShutdown(ISocket * const socket, shutdownreason_t reason)
{
    if (remoteSocket == nullptr) { // Socket Accept failed...
        delete socket;
    }
    // RemoveFromVFS will change value inside webRoot (replace each '/' with '\0').

    // Remove all registered connections 
    for (HTTPConnection * cur = this->registeredConnections.front(); cur != nullptr; cur = cur->goNext()) {
        cur->DataSourceClosed();
    }

    if (log) log->Log(LOG_WEBSERVER, "HTTPDataSource::SocketShutdown(%p)", this);
    delete remoteSocket;
    remoteSocket = nullptr;

    // ReleaseDataSource() also will delete the instance.
    this->webserver->ReleaseDataSource(this);
}


void HTTPDataSource::SocketSendResult(ISocket * const socket)
{
}


inline void HTTPDataSource::RegisterForPath(char * registerData, size_t registerDataLen)
{
    bool newRP = false;
    ExtendedHeader extHeader(this->log, this->protocolVersion);
    extHeader.Decode(0, reinterpret_cast<byte *>(registerData), registerDataLen);

    char * regPath = extHeader.PathToRegister();
    size_t regPathlen = extHeader.PathToRegisterLen();

    HTTPRegisteredPath * rp = this->GetPathObject(regPath, regPath + regPathlen);
    if (!rp) {
        rp = new HTTPRegisteredPath(regPath, regPathlen);
        newRP = true;
    }

    HTTPVfsEntry * vfsEntry = nullptr;
    ws_register_result_t res = ws_register_result_t(webserver->RegisterToVFS(this, rp, false, vfsEntry));
    if (res == WS_REGISTER_DONE) {
        if (strcmp(regPath, this->webRoot) == 0) this->httpPathRoot = vfsEntry;
        if (newRP) this->registeredPathList.push_back(rp);
    
        rp->EnableHTTPListen();

        // If there is still data, a password and a user had been sent...
        if (extHeader.AuthPasswordLen() > 0) {
            rp->SetAuthUser(extHeader.AuthUser());
            rp->SetAuthPwd(extHeader.AuthPassword());
        }
        else if (extHeader.DynamicAuthentication()) rp->SetNeedAuth(true);
    }
    else {
        if (newRP) delete rp;
    }

    webserverMessageSend->Send("HTTPDataSource", 0, WSM_REGISTER_RESULT, word(res));
}


inline void HTTPDataSource::RegisterPassthroughForPath(char * path, size_t pathLen)
{
    ExtendedHeader extHeader(this->log, this->protocolVersion);
    extHeader.Decode(0, reinterpret_cast<byte *>(path), pathLen);

    HTTPVfsEntry * vfsEntry = nullptr;
    HTTPRegisteredPath * rp = new HTTPRegisteredPath(extHeader.PathToRegister(), extHeader.PathToRegisterLen());
    ws_register_result_t res = ws_register_result_t(webserver->RegisterToVFS(this, rp, false, vfsEntry));

    if (res == WS_REGISTER_DONE) {
        rp->EnablePassthrough();
        this->registeredPathList.push_back(rp);
        this->passThroughPath = vfsEntry;
    }
    else {
        delete rp;
    }

    webserverMessageSend->Send("HTTPDataSource", 0, WSM_REGISTER_RESULT, word(res));
}


inline void HTTPDataSource::HandleWebsocketListen(char * path, size_t pathLen)
{
    bool newRP = false;
    ExtendedHeader extHeader(this->log, this->protocolVersion);
    extHeader.Decode(0, reinterpret_cast<byte *>(path), pathLen);
    char * wsPath = extHeader.PathToRegister();
    size_t wsPathLen = extHeader.PathToRegisterLen();

    HTTPRegisteredPath * rp = this->GetPathObject(wsPath, wsPath + wsPathLen);
    if (!rp) {
        newRP = true;
        rp = new HTTPRegisteredPath(wsPath, wsPathLen);
    }

    HTTPVfsEntry * vfsEntry = nullptr;
    ws_register_result_t res = ws_register_result_t(webserver->RegisterToVFS(this, rp, true, vfsEntry));
    if (res == WS_REGISTER_DONE) {
        if (newRP) this->registeredPathList.push_back(rp);
        rp->EnableWebsocketSupport();
        this->websocketPath = vfsEntry;
    }
    else {
        if (newRP) delete rp;
    }

    webserverMessageSend->Send("HTTPDataSource", 0, WSM_REGISTER_RESULT, word(res));
}


void HTTPDataSource::HandleSetAuthConfig(byte * data, size_t dataSize)
{
    ExtendedHeader extHeader(this->log, this->protocolVersion);
    extHeader.Decode(CONNECTION_ID_INTERNAL, data, dataSize);
    HTTPRegisteredPath * rp;
    HTTPDataSource * ds = this->webserver->GetDataSourceForPath(extHeader.PathToRegister(), rp);

    // Whats practical - decoding for the extended header won't change the data inside the buffer. The response to the app needs
    // to send the path with the answer, and this must be done using an extended header. But because the receifed data has not
    // been changed, it can be send how it is without the need to encode a new extended header.

    // The path is not the same as the on for the request or no HTTP listen had been called for it?
    if (rp == nullptr || strcmp(rp->GetPath(), extHeader.PathToRegister()) != 0 || rp->IsHTTPListening() == false) {
        webserverMessageSend->Send("HTTPDataSource", CONNECTION_ID_INTERNAL, WSM_SET_AUTH_CONFIG_RESULT, WS_AUTH_ERR_PATH_NOT_LISTENING, data, dataSize);
        return;
    }

    // The assigned datasource for that path is not the same as the current one? Well - than the request is invalid.
    if (ds != this) {
        webserverMessageSend->Send("HTTPDataSource", CONNECTION_ID_INTERNAL, WSM_SET_AUTH_CONFIG_RESULT, WS_AUTH_ERR_PATH_INVALID, data, dataSize);
        return;
    }

    if (extHeader.DynamicAuthentication()) {
        if (rp->DynamicAuthenticationEnabled() == false) {
            // Dynamic authentication is enabled, when authentication is enabled, but no username and password had been set.
            // Because SetAuthUser() changes the internal flag whether authentoication ist used or not, SetNeedAuth() must
            // be called last.
            rp->SetAuthUser(nullptr);
            rp->SetAuthPwd(nullptr);
            rp->SetNeedAuth(true);
        }
        webserverMessageSend->Send("HTTPDataSource", CONNECTION_ID_INTERNAL, WSM_SET_AUTH_CONFIG_RESULT, WS_AUTH_DYNAMIC, data, dataSize);
    }
    else if (extHeader.AuthUser() != nullptr) {
        if (rp->GetAuthUser() == nullptr || strcmp(rp->GetAuthUser(), extHeader.AuthUser()) != 0 || strcmp(rp->GetAuthPwd(), extHeader.AuthPassword()) != 0) {
            // Setting the authUser will activate the need for authentication automatically.
            rp->SetAuthUser(extHeader.AuthUser());
            rp->SetAuthPwd(extHeader.AuthPassword());
        }
        webserverMessageSend->Send("HTTPDataSource", CONNECTION_ID_INTERNAL, WSM_SET_AUTH_CONFIG_RESULT, WS_AUTH_STATIC, data, dataSize);
    }
    else {
        if (rp->NeedAuthentication()) {
            // Because SetAuthUser() changes the internal flag whether authentoication ist used or not, SetNeedAuth() must
            // be called last.
            rp->SetAuthUser(nullptr);
            rp->SetAuthPwd(nullptr);
            rp->SetNeedAuth(false);
        }
        webserverMessageSend->Send("HTTPDataSource", CONNECTION_ID_INTERNAL, WSM_SET_AUTH_CONFIG_RESULT, WS_AUTH_DECACTIVATED, data, dataSize);
    }
}


void HTTPDataSource::SocketRecvResult(ISocket * const socket, void * buf, size_t len)
{
    byte * dataBuffer = nullptr;
    size_t dataBufferSize = 0;

    if (state == HDS_STATE_READ_HEADER && (dataBufferSize = webserverMessageRecv->GetDataSize()) > 0) {
        if (webserverMessageRecv->GetMessageID() == WSM_SET_CERTIFICATE && this->setCertificateHandler != nullptr && this->setCertificateHandler->DataBuffer()) {
            socket->Recv(this->setCertificateHandler->DataBuffer(), this->setCertificateHandler->DataBufferSize());
        }
        else {
            this->buf = malloc(dataBufferSize);
            socket->Recv(this->buf, dataBufferSize);
        }
        state = HDS_STATE_READ_DATA;
        return;
    }
    else if (state == HDS_STATE_READ_DATA) {
        dataBuffer = static_cast<byte *>(buf);
        dataBufferSize = len;
    }

    HTTPConnection * connection;
    ws_message_t message = webserverMessageRecv->GetMessageID();

    if (log->LogFlagSet(LOG_WEBSERVER_PLUGIN)) {
        char buffer[128];
        _sprintf(buffer, "HTTPDataSource(connection id: %u)::SocketRecvResult()", webserverMessageRecv->GetConnectionID());
        webserverMessageRecv->LogRecvMessage(buffer, dataBuffer, dataBufferSize);
    }

    switch (message)
    {
    case WSM_MESSAGE_HANDLER_CLOSED:
        connection = webserver->GetConnectionByID(webserverMessageRecv->GetConnectionID());
        if (connection) {
            HTTPMethodHandler * handler = connection->GetCurrentMethodHandler();
            if (handler) handler->AppResponse(WSM_MESSAGE_HANDLER_CLOSED, 0, nullptr, 0);
        }
        break;

    case WSM_SET_PLUGINS_WEB_ROOT:
        free(this->webRoot);
        this->webRoot = static_cast<char *>(malloc(webserverMessageRecv->GetDataSize()));
        memcpy(this->webRoot, buf, webserverMessageRecv->GetDataSize());
        this->protocolVersion = webserverMessageRecv->GetGeneralPurpose();
        // The maximum protocol version the webserver can talk is the newest one he knows - of course. ;-)
        if (this->protocolVersion > WEBSERVER_PROTOCOL_VERSION_LATEST) this->protocolVersion = WEBSERVER_PROTOCOL_VERSION_LATEST;
        if (this->protocolVersion > WEBSERVER_PROTOCOL_VERSION_0) {
            webserverMessageSend->Send("HTTPDataSource", CONNECTION_ID_INTERNAL, WSM_SET_PLUGINS_WEB_ROOT_RESULT, this->protocolVersion);
        }
        break;

    case WSM_REGISTER:
        this->RegisterForPath(static_cast<char *>(buf), len);
        break;

    case WSM_REGISTER_PASSTHROUGH:
        // See the comment of WSM_WEBSOCKET_LISTEN for details, while to call len - 1
        this->RegisterPassthroughForPath(static_cast<char *>(buf), len);
        break;

    case WSM_WEBSOCKET_LISTEN:
        // Because of the internal structur of the registring / listening message, the closing zero
        // of the path will be send, too. To let HandleWebsocketListen work correctly, we need to
        // remove that zero byte from the length (the function takes the given buffer als the path
        // string).
        this->HandleWebsocketListen(static_cast<char *>(buf), len);
        break;

    case WSM_SET_CERTIFICATE:
        if (this->webserverMessageRecv->GetGeneralPurpose() == GP_SEND_CERT_DONE) {
            delete this->setCertificateHandler;
            this->setCertificateHandler = nullptr;
        }
        else {
            if (this->setCertificateHandler == nullptr) this->setCertificateHandler = new SetCertificateHandler(this->webserver, this->webserverMessageSend, this->log);
            this->setCertificateHandler->HandleMessage(this->webserverMessageRecv->GetGeneralPurpose(), dataBuffer, dataBufferSize);
        }
        break;
         
    case WSM_WEBSOCKET_MESSAGE:
        connection = webserver->GetConnectionByID(webserverMessageRecv->GetConnectionID());
        if (connection && connection->GetCurrentMethodHandler()) {
            ws_msg_websocket_t wsMsg = webserverMessageRecv->GetWebsocketMessageID();
            ws_websocket_flags_t wsFlags = webserverMessageRecv->GetWebsocketFlags();
            connection->GetCurrentMethodHandler()->AppResponse(wsMsg, wsFlags, dataBuffer, dataBufferSize);
        }
        break;

    case WSM_SET_AUTH_CONFIG:
        this->HandleSetAuthConfig(dataBuffer, dataBufferSize);
        break;

    default:
        connection = webserver->GetConnectionByID(webserverMessageRecv->GetConnectionID());
        // It could be (e. g. in case of an error), that the connection already had been closed.
        // For that case, ignore the message...
        if (connection) {
            HTTPMethodHandler * handler = connection->GetCurrentMethodHandler();
            if (!handler) {
                log->Log(LOG_WEBSERVER, "HTTPDataSource::SocketRecvResult(0x%x): ERROR - Connection ID %u receives a message, but there is no active message handler!",
                    socket, webserverMessageRecv->GetConnectionID());
            }
            else handler->AppResponse(message, webserverMessageRecv->GetGeneralPurpose(), dataBuffer, dataBufferSize);
        }
    }

    if (this->buf) {
        free(this->buf);
        this->buf = nullptr;
    }
    socket->Recv(webserverMessageRecv->GetHeaderBuffer(), webserverMessageRecv->GetHeaderSize());
    state = HDS_STATE_READ_HEADER;
}


void HTTPDataSource::SocketRecvCanceled(ISocket * const socket, void * buf)
{
}
