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

#include <platform.h>

#include <string>
#include <cctype>

#include "common/ilib/str.h"
#include "sdk/common/os/iomux.h"
#include "sdk/common/interface/socket.h"
#include "sdk/common/interface/webserver_plugin.h"
#include "sdk/common/interface/httpfile.h"
#include "sdk/common/interface/time.h"

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

#include "webserver_registered_path.h"
#include "webserver_message_handler.h"
#include "webserver_webdav_message_handler.h"
#include "webserver_get_handler.h"
#include "webserver_post_handler.h"
#include "webserver_put_handler.h"
#include "webserver_passthrough_handler.h"
#include "webserver_propfind_handler.h"
#include "webserver_move_handler.h"
#include "webserver_mkcol_handler.h"
#include "webserver_copy_handler.h"
#include "webserver_delete_handler.h"
#include "webserver_lock_handler.h"
#include "webserver_unlock_handler.h"
#include "webserver_proppatch_handler.h"
#include "webserver_static_file.h"
#include "webserver_websocket_handler.h"
#include "webserver_options_handler.h"
#include "webserver_set_certificate_handler.h"
#include "webserver_digest_authenticator.h"
#include "webserver_plugin.h"

#define BUFFER_BLOCK_SIZE           1024

#define RECONNECT_INTERVAL_START    1000
#if defined(DEBUG)
#define RECONNECT_INTERVAL_MAX      1000
#else
#define RECONNECT_INTERVAL_MAX      60000
#endif

// Conenction-States
#define WSP_STATE_DISCONNECTED      0
#define WSP_STATE_CONNECTING        1
#define WSP_STATE_IDLE              2
#define WSP_STATE_READ_HEADER       3
#define WSP_STATE_READ_DATA         4

#define RPT_HTTP_LISTEN             1
#define RPT_PASSTHROUGH_LISTEN      2
#define RPT_WEBSOCKET_LISTEN        3

constexpr char __DYNAMIC_AUTH[] = "<dynamic authentication activated>";
constexpr const char DEFAULT_REALM[] = "Innovaphone";

/*---------------------------------------------------------------------------*/
/* helper function                                                           */
/*---------------------------------------------------------------------------*/

inline bool ItemInList(UWebserverPlugin * item, std::list<UWebserverPlugin *> &thelist)
{
    for (std::list<UWebserverPlugin *>::iterator i = thelist.begin(); i != thelist.end(); ++i) {
        if (*i == item)
            return true;
    }

    return false;
}


inline const char * PathTypeToStr(wsp_path_type_t pt)
{
    const char * result = "<unkown type>";
    switch (pt)
    {
    case WSP_PATH_TYPE_HTTP: result = "WSP_PATH_TYPE_HTTP"; break;
    case WSP_PATH_TYPE_WEBSOCKET: result = "WSP_PATH_TYPE_WEBSOCKET"; break;
    case WSP_PATH_TYPE_PASSTHROUGH: result = "WSP_PATH_TYPE_PASSTHROUGH"; break;
    }

    return result;
}


/*---------------------------------------------------------------------------*/
/* class WebserverPluginProvider                                             */
/*---------------------------------------------------------------------------*/

IWebserverPluginProvider * CreateWebserverPluginProvider()
{
    return new WebserverPluginProvider();
}


IWebserverPlugin * WebserverPluginProvider::CreateWebserverPlugin(IIoMux * const iomux,
    ISocketProvider * localSocketProvider,
    UWebserverPlugin * const user,
    const char * webserverAddress,
    const char * appWebRoot,
    class IInstanceLog * const log)
{
    ASSERT(strlen(appWebRoot) <= WS_MAX_PATH_LENGTH, "WebserverPluginProvider::CreateWebserverPlugin(): Length of appWebRoot to register is greater than WS_MAX_PATH_LEN");
    return new WebserverPlugin(iomux, localSocketProvider, user, webserverAddress, appWebRoot, log);
}


/*---------------------------------------------------------------------------*/
/* class WebserverPlugin                                                     */
/*---------------------------------------------------------------------------*/

WebserverPlugin::WebserverPlugin(IIoMux * const iomux,
                                 ISocketProvider * const localSocketProvider,
                                 UWebserverPlugin * const user,
                                 const char * webserverAddress,
                                 const char * appWebRoot,
                                 IInstanceLog * const log)
    : iomux(iomux),
      localSocketProvider(localSocketProvider),
      user(user),
      log(log)
{
    this->log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(0x%08x)::WebserverPlugin", this);
    this->localSocket = nullptr;
    this->webserverMessageRecv = nullptr;
    this->webserverMessageSend = nullptr;
    this->pendingMessageHandler = nullptr;
    this->messageHandlers = nullptr;
    this->webserverAddress = _strdup(webserverAddress);

    this->appWebRootLen = strlen(appWebRoot);
    ASSERT(this->appWebRootLen <= WS_MAX_PATH_LENGTH, "WebserverPlugin::WebserverPlugin(): path length is grater than WS_MAX_PATH_LENGTH!");

    if ((this->appWebRootLen > 1) && (appWebRoot[this->appWebRootLen - 1] == '/')) {
        this->appWebRootLen--;
    }

    // The apps web root MUST start with '/'.
    if (*appWebRoot != '/') {
        ++this->appWebRootLen;
        this->appWebRoot = static_cast<char *>(malloc(this->appWebRootLen + 1));
        if (this->appWebRoot != nullptr) memcpy(this->appWebRoot + 1, appWebRoot, this->appWebRootLen);
        else {
            ASSERT(false, "WebServerPlugin::CTOR out of memory error: malloc() returned nullptr");
        }
        this->appWebRoot[0] = '/';
    }
    else {
        //this->appWebRoot = static_cast<char *>(malloc(this->appWebRootLen + 1));
        this->appWebRoot = static_cast<char *>(malloc(this->appWebRootLen + 1));
        if (this->appWebRoot != nullptr) memcpy(this->appWebRoot, appWebRoot, this->appWebRootLen);
        else {
            ASSERT(false, "WebServerPlugin::CTOR out of memory error: malloc() returned nullptr");
        }
    }
    this->appWebRoot[this->appWebRootLen] = '\0';

    // Initialize the protocol with V0 - it will be updated to V1 when receiving
    // the specified message from the webserver.
    this->protocolVersion = WEBSERVER_PROTOCOL_VERSION_0;

    this->reconnectTimer = new ITimer(iomux, this);
	this->cleanUpDigestDataTimer = nullptr;
    this->reconnectInterval = RECONNECT_INTERVAL_START;
    this->closing = false;
    this->state = WSP_STATE_DISCONNECTED;
    this->needFreeBuffer = false;
    this->closeReason = WSP_NORMAL_CLOSE;

    this->registerResultCnt = 0;
    this->pluginConnected = false;
    this->curRecvBuffer = nullptr;
    this->userRecvBuffer = nullptr;
	this->digestHostManager = nullptr;
	this->digestAuthRealm = DEFAULT_REALM;

    this->Connect();
}


WebserverPlugin::~WebserverPlugin()
{
    this->log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(0x%08x)::~WebserverPlugin()", this);
    delete this->webserverMessageRecv;
    delete this->webserverMessageSend;
    delete this->pendingMessageHandler;
    delete this->localSocket;

    while (!this->registerPathStack.empty()) delete this->registerPathStack.front();
    while (!this->websocketListenList.empty()) delete this->websocketListenList.front();
    while (!this->passthroughListenList.empty()) delete this->passthroughListenList.front();
    while (!this->httpListenList.empty()) delete this->httpListenList.front();
	delete this->digestHostManager;
	
    free(this->webserverAddress);
    free(this->appWebRoot);
    free(this->curRecvBuffer);

    delete this->reconnectTimer;
	delete this->cleanUpDigestDataTimer;
}


void WebserverPlugin::Connect()
{
    this->localSocket = localSocketProvider->CreateSocket(this->iomux, this, this->log, false);
    this->localSocket->Connect(this->webserverAddress);
    this->state = WSP_STATE_CONNECTING;
}


void WebserverPlugin::RegisterHandler(WebserverMessageHandler * handler)
{
    messageHandlers = messageHandlers->btree_put(handler);
}


void WebserverPlugin::UnregisterHandler(class WebserverMessageHandler * handler)
{
	auto i = this->digestAuthenticators.find(handler->GetConnectionID());
	if (i != this->digestAuthenticators.end()) {
		i->second.AssignedToHandler(false);
	}
    messageHandlers = messageHandlers->btree_get(handler);
}


void WebserverPlugin::HandleListen(ws_message_t msg, const char * path, const char * staticFilePfx, UWebserverPlugin * user, const char * authUsr, const char * authPwd)
{
    char * thePath;
    if (user == this->user) user = nullptr;

    if (path) {
        while (*path == '/' || *path == ' ') {
            ++path;
        }
    }

    if (!path || *path == '\0') thePath = this->appWebRoot;
    else {
        thePath = static_cast<char *>(malloc(this->appWebRootLen + strlen(path) + 2));
        _sprintf(thePath, "%s%s%s", this->appWebRoot, (this->appWebRootLen > 1) ? "/" : "", path);
    }

    bool mapStaticFiles = (msg == WSM_REGISTER && this->PathHasStaticFiles(thePath));
    WSRegisteredPath * rp = nullptr;

    switch (msg) {
    case WSM_REGISTER:
        rp = new WSRegisteredPath((user ? user : this->user), thePath, staticFilePfx, authUsr, authPwd, authUsr == __DYNAMIC_AUTH, mapStaticFiles, WSP_PATH_TYPE_HTTP, this->log);
        for (WSRegisteredPath * i = this->httpListenList.front(); i; i = i->goNext()) {
            ASSERT(strcmp(i->GetPath(), thePath) != 0, "WebserverPlugin::HttpListen(): called with a path already listening to.");
        }
        this->httpListenList.push_back(rp);
        break;

    case WSM_REGISTER_PASSTHROUGH:
        rp = new WSRegisteredPath((user ? user : this->user), thePath, nullptr, authUsr, authPwd, false, mapStaticFiles, WSP_PATH_TYPE_PASSTHROUGH, this->log);
        for (WSRegisteredPath * i = this->passthroughListenList.front(); i; i = i->goNext())
            ASSERT(strcmp(i->GetPath(), thePath) != 0, "WebserverPlugin::PassthroughListen(): called with a path already listening to.");
        this->passthroughListenList.push_back(rp);
        break;

    case WSM_WEBSOCKET_LISTEN:
        rp = new WSRegisteredPath((user ? user : this->user), thePath, nullptr, authUsr, authPwd, false, mapStaticFiles, WSP_PATH_TYPE_WEBSOCKET, this->log);
        for (WSRegisteredPath * i = this->websocketListenList.front(); i; i = i->goNext())
            ASSERT(strcmp(i->GetPath(), thePath) != 0, "WebserverPlugin::WebsocketListen(): called with a path already listening to.");
        this->websocketListenList.push_back(rp);
        break;

    default:
        break;
    }

    this->registerPathStack.push_back(new WSRegisterPathItem(rp));

    if (thePath != this->appWebRoot) free(thePath);

    if (this->state >= WSP_STATE_IDLE){
        webserverMessageSend->Send("WebserverPlugin", CONNECTION_ID_INTERNAL, msg, 0, rp->GetRawBuffer(), rp->GetRawBufferSize());
    }
}


WSRegisteredPath * WebserverPlugin::GetRegisterdPathForRequest(int pathType, const char * reqPath)
{
    switch (pathType)
    {
    case RPT_HTTP_LISTEN:
        for (WSRegisteredPath * i = this->httpListenList.front(); i; i = i->goNext()) {
            if (strcmp(reqPath, i->GetPath()) == 0) return i;
        }
        break;

    case RPT_PASSTHROUGH_LISTEN:
        for (WSRegisteredPath * i = this->passthroughListenList.front(); i; i = i->goNext()) {
            if (strcmp(reqPath, i->GetPath()) == 0) return i;
        }
        break;

    case RPT_WEBSOCKET_LISTEN:
        for (WSRegisteredPath * i = this->websocketListenList.front(); i; i = i->goNext()) {
            if (strcmp(reqPath, i->GetPath()) == 0) return i;
        }
        break;

    default:
        break;
    }

    return nullptr;
}


void WebserverPlugin::HttpListen(const char * path, UWebserverPlugin * user, const char * authUser, const char * authUserPwd, const char * staticFilePrefix)
{
    if (user && user != this->user) {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(%08x,%08x)::HttpListen(): path to listen = '%s', staticFilePrefix = '%s'", this, user, path, staticFilePrefix);
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(%08x,%08x (default user))::HttpListen(): path to listen = '%s', staticFilePrefix = '%s'", this, this->user, path, staticFilePrefix);
    }

    if (path) {
        ASSERT(strlen(path) + this->appWebRootLen <= WS_MAX_PATH_LENGTH, "IWebserverPlugin::RegisterForPath(): path length is grater than WS_MAX_PATH_LENGTH!");
    }
    
    this->HandleListen(WSM_REGISTER, path, staticFilePrefix, user, authUser, authUserPwd);
}


void WebserverPlugin::HttpListen(const char * path, UWebserverPlugin * user, bool dynamicAuth, const char * staticFilePrefix)
{
    this->HttpListen(path, user, dynamicAuth ? __DYNAMIC_AUTH : nullptr, 0, staticFilePrefix);
}


void WebserverPlugin::PassthroughListen(const char * path, UWebserverPlugin * user)
{
    if (user && user != this->user) {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(%08x,%08x)::PassthroughListen(): path to listen = '%s'", this, user, path);
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(%08x,%08x (default user))::PassthroughListen(): path to listen = '%s'", this, this->user, path);
    }

    if (path) {
        ASSERT(strlen(path) + this->appWebRootLen <= WS_MAX_PATH_LENGTH, "IWebserverPlugin::PassthroughListen(): path length is grater than WS_MAX_PATH_LENGTH!");
    }

    this->HandleListen(WSM_REGISTER_PASSTHROUGH, path, nullptr,  user, nullptr, nullptr);
}


void WebserverPlugin::WebsocketListen(const char * path, UWebserverPlugin * user)
{
    if (user && user != this->user) {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(%08x,%08x)::WebsocketListen(): path to listen = '%s'", this, user, path);
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(%08x,%08x (default user))::WebsocketListen(): path to listen = '%s'", this, this->user, path);
    }

    if (path) {
        ASSERT(strlen(path) + this->appWebRootLen <= WS_MAX_PATH_LENGTH, "IWebserverPlugin::WebsocketListen(): path length is grater than WS_MAX_PATH_LENGTH!");
    }

    this->HandleListen(WSM_WEBSOCKET_LISTEN, path, nullptr, user, nullptr, nullptr);
}


void WebserverPlugin::DoCancel(wsr_cancel_type_t reason, dword connectionID)
{
    log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(%08x,%08x)::Cancel(): reason = '%s'", this, this->user,  WSCancelTypeToStr(reason));

    bool websocketCancel = false;
    if (pendingMessageHandler) {
        if (pendingMessageHandler->GetHandlerType() == MSG_HANDLER_WEBSOCKET) websocketCancel = true;
        delete pendingMessageHandler;
        pendingMessageHandler = nullptr;
    }

    if (websocketCancel) webserverMessageSend->WebsocketSend(connectionID, WS_WEBSOCKET_CONNECT_REJECT, WS_WEBSOCKET_FLAG_NONE, nullptr, 0);
    else webserverMessageSend->Send("WebserverPlugin", connectionID, WSM_SEND_META_DATA, reason);
}


void WebserverPlugin::Cancel(wsr_cancel_type_t reason)
{
    this->DoCancel(reason, this->pendingMessageHandler != nullptr ? this->pendingMessageHandler->GetConnectionID() : 0);
}


void WebserverPlugin::Redirect(const char * newDestination)
{
    constexpr char ALLOW_ORIGIN_FIELD[] = "Access-Control-Allow-Origin\0*";
    
    ASSERT(this->pendingMessageHandler != nullptr && this->pendingMessageHandler->GetHandlerType() != MSG_HANDLER_WEBSOCKET, "IWebserverPlugin::Redirect() : not supported for websocket connections!");
    log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(%08x,%08x)::Redirect(): new destination = '%s'", this, this->user, newDestination);
    ASSERT(newDestination, "IWebserverPlugin::Redirect() called with newDestination = nullptr");

    // Make sure to eat up whitespaces....
    while (*newDestination != '\0' && (*newDestination == ' ' || *newDestination == '\t')) {
        ++newDestination;
    }
    ASSERT(*newDestination != '\0', "IWebserverPlugin::Redirect() called with an empty newDestination string");

    ExtendedHeader extHeader(this->log, this->protocolVersion);
    extHeader.SetRedirectPath(newDestination);
    extHeader.SetHeaderFields(ALLOW_ORIGIN_FIELD, sizeof(ALLOW_ORIGIN_FIELD));
    size_t ehSize = extHeader.BufferSize();
    extHeader.Encode(pendingMessageHandler->GetConnectionID(), ALLOC_STACK_BUFFER(ehSize, 512));
    webserverMessageSend->Send(pendingMessageHandler->InterfaceName(), pendingMessageHandler->GetConnectionID(), WSM_SEND_META_DATA, WSR_INT_REDIRECT, extHeader.Buffer(), extHeader.BufferSize());
    delete pendingMessageHandler;
    pendingMessageHandler = nullptr;
}


bool WebserverPlugin::BuildRedirect(const char * resourceName, const char * build, unsigned buildLen)
{
    if (resourceName[0]=='/' && strncmp(&resourceName[1], build, buildLen)) {
        char b[WS_MAX_PATH_LENGTH];
        int i;
        word l = 0;
        for (i = 1; resourceName[i]; i++) {
            if ((resourceName[i] == '/') && (l + 8 <= WS_MAX_PATH_LENGTH)) {
                b[l++] = '.'; b[l++] = '.'; b[l++] = '/';
            }
        }
        if (!l) {
            b[l++] = '.'; b[l++] = '/';
        }
        if (resourceName[1] >= '0' && resourceName[1] <= '9' && l) {
            if ((i >= 5) && ((str::casecmp(&resourceName[i-4], ".htm") == 0) || (str::casecmp(&resourceName[i-5], ".html") == 0))) {
                const char * p = resourceName;
                for (p = &resourceName[2]; *p && *p != '/'; p++);
                _snprintf(&b[l], sizeof(b)-l, "%s%s", build, p);
                Redirect(b);
                return true;
            }
        }
        else {
            if (l) _snprintf(&b[l], sizeof(b)-l, "%s%s", build, resourceName);
            else _snprintf(b, sizeof(b), "./%s%s", build, resourceName);
            Redirect(b);
            return true;
        }
    }
    return false;
}


bool WebserverPlugin::BuildRedirectIP(const char * resourceName, const char * build, unsigned buildLen)
{
    unsigned buildBegin = 0;
    unsigned buildEnd = 0;
    for (unsigned i = 0; resourceName[i] && resourceName[i] != '?'; i++) {
        if (resourceName[i] == '/') {
            buildBegin = buildEnd;
            buildEnd = i;
        }
    }
    if (resourceName[buildBegin + 1] >= '0' && resourceName[buildBegin + 1] <= '9') {
        if (buildBegin && buildEnd && strncmp(&resourceName[buildBegin + 1], build, buildEnd - buildBegin - 1)) {
            char b[1000];
            isnprintf(b, sizeof(b), "../%s%s", build, &resourceName[buildEnd]);
            Redirect(b);
            return true;
        }
    }
    else {
        char b[1000];
        isnprintf(b, sizeof(b), "%s%s", build, &resourceName[buildEnd]);
        Redirect(b);
        return true;
    }
    return false;
}


void WebserverPlugin::Accept(class UWebserverGet * const user)
{
    if (!user) {
        // The cancel function will clean up the pending message handler, too...
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverGet * = nullptr) rejecting request", this->webserverMessageRecv->GetConnectionID());
        this->Cancel(WSP_CANCEL_NOT_FOUND);
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverGet *) request accepted", this->webserverMessageRecv->GetConnectionID(), this->user);
        ASSERT(pendingMessageHandler->GetHandlerType() == MSG_HANDLER_GET, "Calling IWebserverPlugin::Accept(UWebserverGet *) wihtout pending GET request");
        WebserverGetHandler * handler = static_cast<WebserverGetHandler *>(pendingMessageHandler);
        ASSERT(handler != nullptr, "Calling IWebserverPlugin::Accept(UWebserverGet *) wihtout pending GET request");
        pendingMessageHandler = nullptr;

        handler->RegisterHandler();
        handler->SetUser(user);
        handler->AcceptComplete();
    }
}


void WebserverPlugin::Accept(class UWebserverPost * const user)
{
    if (!user) {
        // The cancel function will clean up the pending message handler, too...
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverPost * = nullptr) rejecting request", this->webserverMessageRecv->GetConnectionID());
        this->Cancel(WSP_CANCEL_NOT_FOUND);
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverPost *) request accepted", this->webserverMessageRecv->GetConnectionID(), this->user);
        ASSERT(pendingMessageHandler->GetHandlerType() == MSG_HANDLER_POST, "Calling IWebserverPlugin::Accept(UWebserverPost *) wihtout pending POST request");
        WebserverPostHandler * handler = static_cast<WebserverPostHandler *>(pendingMessageHandler);
        pendingMessageHandler = nullptr;

        handler->RegisterHandler();
        handler->SetUser(user);
        handler->AcceptComplete();
    }
}


void WebserverPlugin::Accept(class UWebserverPut * const user)
{
    if (!user) {
        // The cancel function will clean up the pending message handler, too...
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverPut * = nullptr) rejecting request", this->webserverMessageRecv->GetConnectionID());
        this->Cancel(WSP_CANCEL_NOT_FOUND);
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverPut *) request accepted", this->webserverMessageRecv->GetConnectionID(), this->user);
        ASSERT(pendingMessageHandler->GetHandlerType() == MSG_HANDLER_PUT, "Calling IWebserverPlugin::Accept(UWebserverPut *) wihtout pending PUT request");
        WebserverPutHandler * handler = static_cast<WebserverPutHandler *>(pendingMessageHandler);
        pendingMessageHandler = nullptr;

        handler->RegisterHandler();
        handler->SetUser(user);
        handler->AcceptComplete();
    }
}


void WebserverPlugin::Accept(class UWebserverPassthrough * const user)
{
    if (!user) {
        // The cancel function will clean up the pending message handler, too...
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverPassthrough * = nullptr) rejecting request", this->webserverMessageRecv->GetConnectionID());
        this->Cancel(WSP_CANCEL_NOT_FOUND);
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverPassthrough *) request accepted", this->webserverMessageRecv->GetConnectionID(), this->user);
        ASSERT(pendingMessageHandler->GetHandlerType() == MSG_HANDLER_PASSTHROUGH, "Calling IWebserverPlugin::Accept(UWebserverPassthrough *) wihtout pending passthrough request");
        WebserverPassthroughHandler * handler = static_cast<WebserverPassthroughHandler *>(pendingMessageHandler);
        pendingMessageHandler = nullptr;

        handler->RegisterHandler();
        handler->SetUser(user);
        handler->AcceptComplete();
    }
}


void WebserverPlugin::Accept(class UWebserverPropfind * const user)
{
    if (!user) {
        // The cancel function will clean up the pending message handler, too...
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverPropfind * = nullptr) rejecting request", this->webserverMessageRecv->GetConnectionID());
        this->Cancel(WSP_CANCEL_NOT_FOUND);
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverPropfind *) request accepted", this->webserverMessageRecv->GetConnectionID(), this->user);
        ASSERT(pendingMessageHandler->GetHandlerType() == MSG_HANDLER_PROPFIND, "Calling IWebserverPlugin::Accept(UWebserverPropfind *) wihtout pending propfind request");
        WebserverPropfindHandler * handler = static_cast<WebserverPropfindHandler *>(pendingMessageHandler);
        pendingMessageHandler = nullptr;

        handler->RegisterHandler();
        handler->SetUser(user);
        handler->AcceptComplete();
    }
}


void WebserverPlugin::Accept(class UWebserverMove * const user)
{
    if (!user) {
        // The cancel function will clean up the pending message handler, too...
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverMove * = nullptr) rejecting request", this->webserverMessageRecv->GetConnectionID());
        this->Cancel(WSP_CANCEL_NOT_FOUND);
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverMove *) request accepted", this->webserverMessageRecv->GetConnectionID(), this->user);
        ASSERT(pendingMessageHandler->GetHandlerType() == MSG_HANDLER_MOVE, "Calling IWebserverPlugin::Accept(UWebserverMove *) wihtout pending move request");
        WebserverMoveHandler * handler = static_cast<WebserverMoveHandler *>(pendingMessageHandler);
        pendingMessageHandler = nullptr;

        handler->RegisterHandler();
        handler->SetUser(user);
        handler->AcceptComplete();
    }
}


void WebserverPlugin::Accept(class UWebserverMkCol * const user)
{
    if (!user) {
        // The cancel function will clean up the pending message handler, too...
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverMkCol * = nullptr) rejecting request", this->webserverMessageRecv->GetConnectionID());
        this->Cancel(WSP_CANCEL_NOT_FOUND);
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverMkCol *) request accepted", this->webserverMessageRecv->GetConnectionID(), this->user);
        ASSERT(pendingMessageHandler->GetHandlerType() == MSG_HANDLER_MKCOL, "Calling IWebserverPlugin::Accept(UWebserverMkCol *) wihtout pending mkcol request");
        WebserverMkColHandler * handler = static_cast<WebserverMkColHandler *>(pendingMessageHandler);
        pendingMessageHandler = nullptr;

        handler->RegisterHandler();
        handler->SetUser(user);
        handler->AcceptComplete();
    }
}


void WebserverPlugin::Accept(class UWebserverCopy * const user)
{
    if (!user) {
        // The cancel function will clean up the pending message handler, too...
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverCopy * = nullptr) rejecting request", this->webserverMessageRecv->GetConnectionID());
        this->Cancel(WSP_CANCEL_NOT_FOUND);
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverCopy *) request accepted", this->webserverMessageRecv->GetConnectionID(), this->user);
        ASSERT(pendingMessageHandler->GetHandlerType() == MSG_HANDLER_COPY, "Calling IWebserverPlugin::Accept(UWebserverCopy *) wihtout pending copy request");
        WebserverCopyHandler * handler = static_cast<WebserverCopyHandler *>(pendingMessageHandler);
        pendingMessageHandler = nullptr;

        handler->RegisterHandler();
        handler->SetUser(user);
        handler->AcceptComplete();
    }
}


void WebserverPlugin::Accept(class UWebserverDelete * const user)
{
    if (!user) {
        // The cancel function will clean up the pending message handler, too...
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverDelete * = nullptr) rejecting request", this->webserverMessageRecv->GetConnectionID());
        this->Cancel(WSP_CANCEL_NOT_FOUND);
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverDelete *) request accepted", this->webserverMessageRecv->GetConnectionID(), this->user);
        ASSERT(pendingMessageHandler->GetHandlerType() == MSG_HANDLER_DELETE, "Calling IWebserverPlugin::Accept(UWebserverDelete *) wihtout pending delete request");
        WebserverDeleteHandler * handler = static_cast<WebserverDeleteHandler *>(pendingMessageHandler);
        pendingMessageHandler = nullptr;

        handler->RegisterHandler();
        handler->SetUser(user);
        handler->AcceptComplete();
    }
}


void WebserverPlugin::Accept(class UWebserverLock * const user)
{
    if (!user) {
        // The cancel function will clean up the pending message handler, too...
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverLock * = nullptr) rejecting request", this->webserverMessageRecv->GetConnectionID());
        this->Cancel(WSP_CANCEL_NOT_FOUND);
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverLock *) request accepted", this->webserverMessageRecv->GetConnectionID(), this->user);
        ASSERT(pendingMessageHandler->GetHandlerType() == MSG_HANDLER_LOCK, "Calling IWebserverPlugin::Accept(UWebserverLock *) wihtout pending lock request");
        WebserverLockHandler * handler = static_cast<WebserverLockHandler *>(pendingMessageHandler);
        pendingMessageHandler = nullptr;

        handler->RegisterHandler();
        handler->SetUser(user);
        handler->AcceptComplete();
    }
}


void WebserverPlugin::Accept(class UWebserverUnlock * const user)
{
    if (!user) {
        // The cancel function will clean up the pending message handler, too...
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverUnlock * = nullptr) rejecting request", this->webserverMessageRecv->GetConnectionID());
        this->Cancel(WSP_CANCEL_NOT_FOUND);
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverUnlock *) request accepted", this->webserverMessageRecv->GetConnectionID(), this->user);
        ASSERT(pendingMessageHandler->GetHandlerType() == MSG_HANDLER_UNLOCK, "Calling IWebserverPlugin::Accept(UWebserverUnlock *) wihtout pending unlock request");
        WebserverUnlockHandler * handler = static_cast<WebserverUnlockHandler *>(pendingMessageHandler);
        pendingMessageHandler = nullptr;

        handler->RegisterHandler();
        handler->SetUser(user);
        handler->AcceptComplete();
    }
}


void WebserverPlugin::Accept(class UWebserverProppatch * const user)
{
    if (!user) {
        // The cancel function will clean up the pending message handler, too...
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverProppatch * = nullptr) rejecting request", this->webserverMessageRecv->GetConnectionID());
        this->Cancel(WSP_CANCEL_NOT_FOUND);
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverProppatch *) request accepted", this->webserverMessageRecv->GetConnectionID(), this->user);
        ASSERT(pendingMessageHandler->GetHandlerType() == MSG_HANDLER_PROPPATCH, "Calling IWebserverPlugin::Accept(UWebserverProppatch *) wihtout pending proppatch request");
        WebserverProppatchHandler * handler = static_cast<WebserverProppatchHandler *>(pendingMessageHandler);
        pendingMessageHandler = nullptr;

        handler->RegisterHandler();
        handler->SetUser(user);
        handler->AcceptComplete();
    }
}


void WebserverPlugin::Accept(class UWebserverOptions * const user)
{
    if (!user) {
        // The cancel function will clean up the pending message handler, too...
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverOptions * = nullptr) rejecting request", this->webserverMessageRecv->GetConnectionID());
        this->Cancel(WSP_CANCEL_NOT_FOUND);
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::Accept(UWebserverOptions *) request accepted", this->webserverMessageRecv->GetConnectionID(), this->user);
        ASSERT(pendingMessageHandler->GetHandlerType() == MSG_HANDLER_OPTIONS, "Calling IWebserverPlugin::Accept(UWebserverOptions *) wihtout pending options request");
        WebserverOptionsHandler * handler = static_cast<WebserverOptionsHandler *>(pendingMessageHandler);
        pendingMessageHandler = nullptr;

        handler->RegisterHandler();
        handler->SetUser(user);
        handler->AcceptComplete();
    }
}


void WebserverPlugin::WebsocketAccept(class UWebsocket * user)
{
    log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(%08x,%08x)::WebsocketAccept(): user = 0x%08x", this, this->user, user);

    ASSERT(pendingMessageHandler->GetHandlerType() == MSG_HANDLER_WEBSOCKET, "Calling IWebserverPlugin::WebsocketAccept() wihtout pending websocket connection request");
    WebserverWebsocketHandler * handler = static_cast<WebserverWebsocketHandler *>(pendingMessageHandler);
    pendingMessageHandler = nullptr;
    
    dword connectionID = handler->GetConnectionID();
    if (user == nullptr) {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::WebsocketAccept(nullptr) rejecting request", this->webserverMessageRecv->GetConnectionID());
        webserverMessageSend->WebsocketSend(connectionID, WS_WEBSOCKET_CONNECT_REJECT, WS_WEBSOCKET_FLAG_NONE, nullptr, 0);
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(connection id: %u)::WebsocketAccept() request accepted", this->webserverMessageRecv->GetConnectionID(), this->user);
        handler->RegisterHandler();
        handler->SetUser(user);
        webserverMessageSend->WebsocketSend(connectionID, WS_WEBSOCKET_CONNECT_ACCEPT, WS_WEBSOCKET_FLAG_NONE, nullptr, 0);
    }
}


void WebserverPlugin::Close()
{
    log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(%08x,%08x)::Close()", this, this->user);

    reconnectTimer->Cancel();
    closing = true;
    closeReason = WSP_NORMAL_CLOSE;
    if(localSocket) localSocket->Shutdown();
    else this->iomux->SetExec(this, nullptr); // Let simulate an asynchronously callback... 
}


void WebserverPlugin::SocketConnectComplete(ISocket * const socket)
{
    reconnectInterval = RECONNECT_INTERVAL_START;
    webserverMessageRecv = IWebserverMessage::Create(socket, log);
    webserverMessageSend = IWebserverMessage::Create(socket, log);
    
    webserverMessageSend->Send("WebserverPlugin", CONNECTION_ID_INTERNAL, WSM_SET_PLUGINS_WEB_ROOT, WEBSERVER_PROTOCOL_VERSION_LATEST, this->appWebRoot, this->appWebRootLen + 1);

    for (WSRegisteredPath * i = this->httpListenList.front(); i; i = i->goNext()) {
        webserverMessageSend->Send("WebserverPlugin", CONNECTION_ID_INTERNAL, WSM_REGISTER, 0, i->GetRawBuffer(), i->GetRawBufferSize());
        this->registerPathStack.push_back(new WSRegisterPathItem(i));
    }

    for (WSRegisteredPath * i = this->passthroughListenList.front(); i; i = i->goNext()) {
        webserverMessageSend->Send("WebserverPlugin", CONNECTION_ID_INTERNAL, WSM_REGISTER_PASSTHROUGH, 0, i->GetRawBuffer(), i->GetRawBufferSize());
        this->registerPathStack.push_back(new WSRegisterPathItem(i));
    }

    for (WSRegisteredPath * i = this->websocketListenList.front(); i; i = i->goNext()) {
        webserverMessageSend->Send("WebserverPlugin", CONNECTION_ID_INTERNAL, WSM_WEBSOCKET_LISTEN, 0, i->GetRawBuffer(), i->GetRawBufferSize());
        this->registerPathStack.push_back(new WSRegisterPathItem(i));
    }

    socket->Recv(webserverMessageRecv->GetHeaderBuffer(), webserverMessageRecv->GetHeaderSize());
    state = WSP_STATE_READ_HEADER;
}


void WebserverPlugin::SocketShutdown(ISocket * const socket, shutdownreason_t reason)
{
    bool doReconnect = false;
    this->pluginConnected = false;
    this->protocolVersion = WEBSERVER_PROTOCOL_VERSION_0;

    switch (reason)
    {
    case SOCKET_ADDRESS_INVALID:
        closeReason = WSP_ADDRESS_INVALID;
        break;

    case SOCKET_OPEN_FAILED:
    case SOCKET_CONNECT_FAILED:
        if (!closing)
            doReconnect = true;
        closeReason = WSP_CONNECTION_ERROR;
        break;

    case SOCKET_LOST:
        if (!closing)
            doReconnect = true;
        closeReason = WSP_CONNECTION_ERROR;
        break;


    default:
        if (!closing)
            doReconnect = true;
    }

    // If socket failes to connect, a shutdown will be called bevore the webserverMessage* objects
    // will be created. However, they will be created together, so there is only need to test one
    // of them.
    if (webserverMessageSend) {
        if (this->userRecvBuffer != nullptr) {
            dword cid = webserverMessageRecv->GetConnectionID();
            WebserverMessageHandler * handler = static_cast<WebserverMessageHandler *>(messageHandlers->btree_find((void *)(uintp)cid));
            if (handler) {
                handler->WebserverPluginRecvCanceled(this->userRecvBuffer);
                this->userRecvBuffer = nullptr;
            }
        }
        webserverMessageRecv->Shutdown();
        webserverMessageSend->Shutdown();
    }

    while (messageHandlers != nullptr) {
        WebserverMessageHandler * cur = static_cast<WebserverMessageHandler *>(messageHandlers);
        cur->WebserverPluginWillClose(closeReason);
        // If the object still is the current btree object, it has not been removed. So we give a 
        // warning to help de developer make is app better (he should delete the object in the
        // *ShutdownComplete() user callback method).
        if (cur == static_cast<WebserverMessageHandler *>(messageHandlers)) {
            debug->printf("WARNING: IWebserverPlugin::SocketShutdown(): Existing message handler will not be deleted!\n");
            messageHandlers = messageHandlers->btree_get(cur);
        }
    }

    delete webserverMessageRecv;
    delete webserverMessageSend;
    webserverMessageRecv = webserverMessageSend = nullptr;

    delete localSocket;
    localSocket = nullptr;
    state = WSP_STATE_DISCONNECTED;

    while (this->registerPathStack.front())
        delete this->registerPathStack.front();

    if (doReconnect) {
        reconnectTimer->Start(reconnectInterval);
        if (reconnectInterval < RECONNECT_INTERVAL_MAX)
            reconnectInterval *= 2;
    }
    else {
        CloseWebserverPluginUsers();
    }
}

void WebserverPlugin::CloseWebserverPluginUsers()
{
    wsp_close_reason_t cur = closeReason;
    closeReason = WSP_NORMAL_CLOSE;
    closing = false;

    std::list<UWebserverPlugin *> informedUsers;

    for (WSRegisteredPath * i = this->httpListenList.front(); i; i = i->goNext()) {
        if (i->GetUser() != this->user && !ItemInList(i->GetUser(), informedUsers)) {
            log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(%08x,%08x)::WebserverPluginClose(): close reason = %s", this, i, WSPCloseReasonToStr(cur));
            informedUsers.push_back(i->GetUser());
            i->GetUser()->WebserverPluginClose(this, cur, false);
        }
    }

    for (WSRegisteredPath * i = this->passthroughListenList.front(); i; i = i->goNext()) {
        if (i->GetUser() != this->user && !ItemInList(i->GetUser(), informedUsers)) {
            log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(%08x,%08x)::WebserverPluginClose(): close reason = %s", this, i, WSPCloseReasonToStr(cur));
            informedUsers.push_back(i->GetUser());
            i->GetUser()->WebserverPluginClose(this, cur, false);
        }
    }

    for (WSRegisteredPath * i = this->websocketListenList.front(); i; i = i->goNext()) {
        if (i->GetUser() != this->user && !ItemInList(i->GetUser(), informedUsers)) {
            log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(%08x,%08x)::WebserverPluginClose(): close reason = %s", this, i, WSPCloseReasonToStr(cur));
            informedUsers.push_back(i->GetUser());
            i->GetUser()->WebserverPluginClose(this, cur, false);
        }
    }

    log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(%08x,%08x)::WebserverPluginClose(): close reason = %s", this, this->user, WSPCloseReasonToStr(cur));
    user->WebserverPluginClose(this, cur, true);
}

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


void WebserverPlugin::TimerOnTimeout(ITimer * timer)
{
	if (timer == this->cleanUpDigestDataTimer) {
		dword now = static_cast<dword>(ITime::TimeStampMilliseconds() / 1000);
		auto i = this->digestAuthenticators.begin();
		while (i != this->digestAuthenticators.end()) {
			if (!i->second.AssignedToHandler() && now - i->second.LastAccess() > DIGEST_HOST_LIFETIME) i = this->digestAuthenticators.erase(i);
			else ++i;
		}
		
		this->digestHostManager->CleanUpList(now);
		
		if (this->digestAuthenticators.empty() && this->digestHostManager->Empty()) {
			delete this->digestHostManager;
			this->digestHostManager = nullptr;
			delete this->cleanUpDigestDataTimer;
			this->cleanUpDigestDataTimer = nullptr;
		}
		else {
			this->cleanUpDigestDataTimer->Start(DIGEST_HOST_LIFETIME * 1000);
		}
	}
	else {
		this->Connect();
	}
}


inline void WebserverPlugin::HandleRegisterResult(ExtendedHeader & extHeader)
{
    ws_register_result_t regRes = ws_register_result_t(webserverMessageRecv->GetGeneralPurpose());
    WSRegisterPathItem * ri = this->registerPathStack.front();

    if (regRes != WS_REGISTER_DONE) {
        if (regRes == WS_REGISTER_PATH_INVALID) {
            this->log->Log(LOG_WEBSERVER_PLUGIN, "WebserverPlugin::HandleRegisterResult(): Failed to register path %s (%s) - path is invalid.", ri->RegisteredPath()->GetPath(), PathTypeToStr(ri->RegisteredPath()->PathType()));
            closeReason = WSP_REGISTER_PATH_INVALID;
        }
        else {
            this->log->Log(LOG_WEBSERVER_PLUGIN, "WebserverPlugin::HandleRegisterResult(): Failed to register path %s (%s) - path is already registered.", ri->RegisteredPath()->GetPath(), PathTypeToStr(ri->RegisteredPath()->PathType()));
            closeReason = WSP_PATH_ALREADY_REGISTERD;
        }
        closing = true;
        localSocket->Shutdown();
    }
    else {
        this->user->WebserverPluginRegisterPathResult(this, ri->RegisteredPath()->GetPath(), ri->RegisteredPath()->PathType());
    }

    delete ri;
}


template<class T> void WebserverPlugin::HandleRequest(ExtendedHeader & extHeader, byte * extHeaderBuffer, ws_request_type_t requestType)
{
    char * resName = extHeader.RequestPath();
    char * pathForRequest = extHeader.RegisteredPathForRequest();

    if (!resName|| *resName == '\0') {
        free(extHeaderBuffer);
        this->DoCancel(WSP_CANCEL_BAD_REQUEST, this->webserverMessageRecv->GetConnectionID());
        return;
    }

    WSRegisteredPath * rp = this->GetRegisterdPathForRequest(requestType == WS_REQUEST_PASSTHROUGH ? RPT_PASSTHROUGH_LISTEN : RPT_HTTP_LISTEN, pathForRequest);
    if (!rp) {
        this->log->Log(LOG_WEBSERVER_PLUGIN,
                       "WebserverPlugin::HandleRequest(): got request %s for not registered path %s",
                       WSRequestTypeToStr(requestType),
                       pathForRequest);
        free(extHeaderBuffer);
        this->DoCancel(WSP_CANCEL_BAD_REQUEST, this->webserverMessageRecv->GetConnectionID());
        return;
    }

    dword connID = webserverMessageRecv->GetConnectionID();
    ulong64 dSize;
    if (this->protocolVersion == WEBSERVER_PROTOCOL_VERSION_0) dSize = webserverMessageRecv->GetGeneralPurpose();
    else {
        dSize = extHeader.DataSize();
        if (extHeader.ChunkEncodedTransfer()) dSize = WS_RESPONSE_CHUNKED;
    }

    // For the message handler and the users callback, remove the applications root from the requested path
    if (this->appWebRootLen > 1) pathForRequest += this->appWebRootLen;
    pendingMessageHandler = new T(this, extHeaderBuffer, connID, dSize, resName, pathForRequest, extHeader, webserverMessageSend, this->log, this->protocolVersion);

    // It could be that there is additional data - a list of header fields and values. In that
    // case, give the data to the handler and let the handler do the rest...
    if (requestType == WS_REQUEST_PASSTHROUGH) {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(%08x,%08x)::WebserverPluginPassthroughListenResult(): resourceName = '%s', registeredPathForRequest = '%s', dataSize = %llu",
            this, rp->GetUser(), resName, pathForRequest, dSize);
        rp->GetUser()->WebserverPluginPassthroughListenResult(this, resName, pathForRequest, dSize);
        ASSERT(!pendingMessageHandler, "WebserverPlugin::HandleRequest no Accept or Cancel in UWebserverPlugin::WebserverPluginPassthroughListenResult called!");
    }
    else {
        if (extHeader.FieldListBufferSize() > 0) pendingMessageHandler->SetFieldList(extHeader.FieldListBuffer(), extHeader.FieldListBufferSize());
        log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(%08x,%08x)::WebserverPluginHttpListenResult(): requestType = %s, resourceName = '%s', registeredPathForRequest = '%s', dataSize = %llu",
                 this, rp->GetUser(), WSRequestTypeToStr(requestType), resName, pathForRequest, dSize);
        rp->GetUser()->WebserverPluginHttpListenResult(this, requestType, resName, pathForRequest, dSize);
        ASSERT(!pendingMessageHandler, "WebserverPlugin::HandleRequest no Accept or Cancel in UWebserverPlugin::WebserverPluginHttpListenResult called!");
    }
}


void WebserverPlugin::HandleGetRequest(ExtendedHeader & extHeader, byte * extHeaderBuffer)
{
    // NOTE: This function is responsibel to free the given buffer. Either here or
    //       later inside an WebserverGetHandler instance.
    char * resName = extHeader.RequestPath();
    char * pathForRequest = extHeader.RegisteredPathForRequest();

    if (resName == nullptr || *resName == '\0') {
        free(extHeaderBuffer);
        this->DoCancel(WSP_CANCEL_BAD_REQUEST, this->webserverMessageRecv->GetConnectionID());
        return;
    }
    
    WSRegisteredPath * rp = this->GetRegisterdPathForRequest(RPT_HTTP_LISTEN, pathForRequest);
    if (!rp) {
        this->log->Log(LOG_WEBSERVER_PLUGIN, "The path for the request (%s) seems to be not registered.", pathForRequest);
        free(extHeaderBuffer);
        this->DoCancel(WSP_CANCEL_BAD_REQUEST, this->webserverMessageRecv->GetConnectionID());
        return;
    }

    if (this->appWebRootLen > 1) pathForRequest += this->appWebRootLen;

    dword connID = webserverMessageRecv->GetConnectionID();
    WebserverGetHandler * handler = new WebserverGetHandler(this,
                                                            connID,
                                                            extHeaderBuffer,
                                                            resName,
                                                            pathForRequest,
                                                            extHeader,
                                                            webserverMessageSend,
                                                            this->log,
                                                            this->protocolVersion);

    // Try to resolve with the list of static files. if there are parameters inside the resource
    // name, the httpfiles btree functions will handle that correctly. So we don#t worry about
    // that here.
    if (rp->HadStaticFilesMapped() && (rp->StaticFilePfxLen() == 0 || strncmp(rp->StaticFilePrefix(), *resName == '/' ? resName + 1 : resName, rp->StaticFilePfxLen()) == 0)) {
        char tmpBuf[WS_MAX_PATH_LENGTH];
        _snprintf(tmpBuf, sizeof(tmpBuf), "%s/%s", pathForRequest, (*resName == '/' ? resName + 1 : resName) + rp->StaticFilePfxLen());

        httpfile * staticFile = static_cast<httpfile *>(staticFilesRoot->btree_find(tmpBuf + 1)); // tmpBuf always has a leading /
        
        if (staticFile && (staticFile->GetFlags() & HTTP_HIDE) == 0) {
            handler->RegisterHandler();
            handler->SetUser(new WebserverStaticFile(staticFile));
            handler->AcceptComplete();
            //handler->HandleMessage(webserverMessageRecv, nullptr, 0);
            return;
        }
    }

    // Handle the field list here, because for the static files, the field lists aren't needed.
    if (extHeader.FieldListBufferSize() > 0) handler->SetFieldList(extHeader.FieldListBuffer(), extHeader.FieldListBufferSize());

    pendingMessageHandler = handler;
    rp->GetUser()->WebserverPluginHttpListenResult(this, WS_REQUEST_GET, resName, pathForRequest, 0);
    ASSERT(!pendingMessageHandler, "WebserverPlugin::HandleGetRequest no Accept or Cancel in UWebserverPlugin::WebserverPluginHttpListenResult called!");
}


void WebserverPlugin::SocketRecvResult(ISocket * const socket, void * buf, size_t len)
{
    bool doFreeBuf = false;
    size_t dataSize;
    dword cid = webserverMessageRecv->GetConnectionID();
    this->curRecvBuffer = nullptr; // The buffer will be released elsewhere now...

    WebserverMessageHandler * handler = static_cast<WebserverMessageHandler *>(messageHandlers->btree_find((void *)(uintp)cid));

    if (handler) {
        if (state == WSP_STATE_READ_HEADER) {
            // Prevent to use the header buffer as data when calling the halders HandleMessage later...
            buf = nullptr;
            len = 0;
            dataSize = webserverMessageRecv->GetDataSize();

            if (dataSize > 0) {
                // If we receive a Wensocket Message and that message is close, don't aks the user for a buffer because
                // it is something that will be handled internally. The WebserverWebsocketHandler will release the
                // buffer when handling the message.
                if (handler->GetHandlerType() == MSG_HANDLER_WEBSOCKET && webserverMessageRecv->GetWebsocketMessageID() == WS_WEBSOCKET_CLOSE_COMPLETE) {
                    this->curRecvBuffer = malloc(dataSize);
                    socket->Recv(this->curRecvBuffer, dataSize);
                }
                else {
                    this->userRecvBuffer = handler->GetUserBuffer(dataSize);
                    socket->Recv(this->userRecvBuffer, dataSize);
                }
                state = WSP_STATE_READ_DATA;
                return;
            }
        }

        if (log->LogFlagSet(LOG_WEBSERVER_PLUGIN)) {
            char buffer[128];
            _snprintf(buffer, sizeof(buffer), "%s(connection id: %u)::SocketRecvResult()", handler->InterfaceName(), webserverMessageRecv->GetConnectionID());
            webserverMessageRecv->LogRecvMessage(buffer, static_cast<byte *>(buf), len);
        }

        // There is no need to ask for the state WSP_STATE_READ_DATA. Because if we are here,
        // additional data already had been read or there was none.
        this->userRecvBuffer = nullptr;
        handler->HandleMessage(webserverMessageRecv, buf, len);
    }
    else {
        // Read additional data, if necessary...
        bool headerOnly = false;
        if (state == WSP_STATE_READ_HEADER) {
            dataSize = webserverMessageRecv->GetDataSize();
            headerOnly = true;
            if (dataSize > 0) {
                state = WSP_STATE_READ_DATA;
                this->curRecvBuffer = malloc(dataSize);
                socket->Recv(this->curRecvBuffer, dataSize);
                return;
            }
        }

        if (log->LogFlagSet(LOG_WEBSERVER_PLUGIN)) {
            char buffer[64];
            _snprintf(buffer, sizeof(buffer), "WebserverPlugin(%p,%p)::SocketRecvResult", socket, this);
            if (headerOnly) webserverMessageRecv->LogRecvMessage(buffer, static_cast<byte *>(buf) + 12, len - 12);
            else webserverMessageRecv->LogRecvMessage(buffer, static_cast<byte *>(buf), len);
        }

        // By reading the extended header here (even if there is no extended header), the WebserverPlugin always will be backward compatible
        // (from now on...)
        WSRegisteredPath * rp = nullptr;
        
        // There can be a race condition that a WS_WEBSOCKET_CLOSE_COMPLETE message will arrive but the handler for that is closed already.
        // In that case, the data coming with that close message is just the close reason. Trying to use that to decode the extended header
        // will lead to a crash. So only use that, if the message is not WSM_WEBSOCLET_CLOSE_COMPLETE. That will prevent the crash and lead
        // to the "unexpected message received" log output below. After that, the plugin continues with business as usual.
        ExtendedHeader extHeader(this->log, this->protocolVersion);

        bool isWSCloseComplete = webserverMessageRecv->GetMessageID() == WSM_WEBSOCKET_MESSAGE ? webserverMessageRecv->GetWebsocketMessageID() == WS_WEBSOCKET_CLOSE_COMPLETE : false;
        if (!headerOnly && len > 0 && !isWSCloseComplete) {
            extHeader.Decode(cid, static_cast<byte *>(buf), len);
        }

        // There was no handler responsible for that message - so check the message itself
        // and create a new one depending on the message type.
        switch (webserverMessageRecv->GetMessageID())
        {
        case WSM_SET_PLUGINS_WEB_ROOT_RESULT:
            // This message will only be send from Webserver Protocol v1 and newer.
            this->pluginConnected = true;
            this->protocolVersion = webserverMessageRecv->GetGeneralPurpose();
            this->log->Log(LOG_WEBSERVER_PLUGIN, "WebserverPlugin SET_PLUGINS_WEB_ROOT_RESULT: supported protocol version: %u (Plugin), %u (Webserver)", WEBSERVER_PROTOCOL_VERSION_LATEST, this->protocolVersion);
            if (this->protocolVersion != WEBSERVER_PROTOCOL_VERSION_LATEST) this->log->Log(LOG_WEBSERVER_PLUGIN, "NOTE: WebserverPlugin will run in protocol compatibility mode, some functionality might not be available.");
            this->user->WebserverPluginConnected(this); // inform user that the plugin is now connected (again)
            break;

        case WSM_REGISTER_RESULT:
            doFreeBuf = state != WSP_STATE_READ_HEADER;
            this->HandleRegisterResult(extHeader);
            break;

        case WSM_GET_REQUEST:
            // NOTE: to prevent unnecessary buffer copies, the HandleDataRequest function itself will
            //       take ownership of the managment of the buffer;
            this->HandleGetRequest(extHeader, static_cast<byte *>(buf));
            break;

        case WSM_POST_REQUEST:
            this->HandleRequest<WebserverPostHandler>(extHeader, static_cast<byte *>(buf), WS_REQUEST_POST);
            break;

        case WSM_PUT_REQUEST:
            this->HandleRequest<WebserverPutHandler>(extHeader, static_cast<byte *>(buf), WS_REQUEST_PUT);
            break;

        case WSM_PASSTHROUGH_REQUEST:
            this->HandleRequest<WebserverPassthroughHandler>(extHeader, static_cast<byte *>(buf), WS_REQUEST_PASSTHROUGH);
            break;
            
        case WSM_PROPFIND_REQUEST:
            this->HandleRequest<WebserverPropfindHandler>(extHeader, static_cast<byte *>(buf), WS_REQUEST_PROPFIND);
            break;

        case WSM_MOVE_REQUEST:
            this->HandleRequest<WebserverMoveHandler>(extHeader, static_cast<byte *>(buf), WS_REQUEST_MOVE);
            break;

        case WSM_MKCOL_REQUEST:
            this->HandleRequest<WebserverMkColHandler>(extHeader, static_cast<byte *>(buf), WS_REQUEST_MKCOL);
            break;

        case WSM_COPY_REQUEST:
            this->HandleRequest<WebserverCopyHandler>(extHeader, static_cast<byte *>(buf), WS_REQUEST_COPY);
            break;

        case WSM_DELETE_REQUEST:
            this->HandleRequest<WebserverDeleteHandler>(extHeader, static_cast<byte *>(buf), WS_REQUEST_DELETE);
            break;

        case WSM_LOCK_REQUEST:
            this->HandleRequest<WebserverLockHandler>(extHeader, static_cast<byte *>(buf), WS_REQUEST_LOCK);
            break;

        case WSM_UNLOCK_REQUEST:
            this->HandleRequest<WebserverUnlockHandler>(extHeader, static_cast<byte *>(buf), WS_REQUEST_UNLOCK);
            break;

        case WSM_PROPPATCH_REQUEST:
            this->HandleRequest<WebserverProppatchHandler>(extHeader, static_cast<byte *>(buf), WS_REQUEST_PROPPATCH);
            break;

        case WSM_OPTIONS_REQUEST:
            this->HandleRequest<WebserverOptionsHandler>(extHeader, static_cast<byte *>(buf), WS_REQUEST_OPTIONS);
            break;

        case WSM_WEBSOCKET_LISTEN_RESULT:
        {
            char * path = extHeader.RequestPath();
            char * pathForRequest = extHeader.RegisteredPathForRequest();
            char * host = extHeader.Host();

            WSRegisteredPath * rp = this->GetRegisterdPathForRequest(RPT_WEBSOCKET_LISTEN, pathForRequest);
            if (!rp) {
                this->log->Log(LOG_WEBSERVER_PLUGIN, "WebserverPlugin::SocketRecvResult(): websocket listen result for not registered path %s", pathForRequest);
                doFreeBuf = true;
                this->DoCancel(WSP_CANCEL_BAD_REQUEST, this->webserverMessageRecv->GetConnectionID());
            }
            else {
                // For the message handler and the user callback, remove the applications root.
                if (this->appWebRootLen > 1) {
                    pathForRequest += this->appWebRootLen;
                }
                
                pendingMessageHandler = new WebserverWebsocketHandler(this,
                                                                      static_cast<byte *>(buf),
                                                                      webserverMessageRecv->GetConnectionID(),
                                                                      host,
                                                                      path,
                                                                      pathForRequest,
                                                                      extHeader,
                                                                      webserverMessageSend,
                                                                      this->log,
                                                                      this->protocolVersion);

                if (extHeader.FieldListBufferSize() > 0) pendingMessageHandler->SetFieldList(extHeader.FieldListBuffer(), extHeader.FieldListBufferSize());

                log->Log(LOG_WEBSOCKET_CLIENT, "WebserverPlugin(%08x,%08x)::WebserverPluginWebsocketListenResult(): path = %s, registeredPathForRequest = %s, host = %s",
                                               this, rp->GetUser(), path, pathForRequest, host);
                rp->GetUser()->WebserverPluginWebsocketListenResult(this, path, pathForRequest, host);
                ASSERT(!pendingMessageHandler, "WebserverPlugin::SocketRecvResult no Accept or Cancel in UWebserverPlugin::WebserverPluginWebsocketListenResult called!");
            }
        } break;

        case WSM_SEND_RESULT:
            // If there is a pending request, rejected by the application, the rejection will send a reason
            // to the Webserver, who will send back a send result. Because of the rejected connection,
            // there is no handler to need or can be informed about the send result. So it simply will
            // be ignored.
            doFreeBuf = state != WSP_STATE_READ_HEADER;
            break;

        case WSM_REQUEST_USER_PASSWORD:
            rp = this->GetRegisterdPathForRequest(RPT_HTTP_LISTEN, extHeader.RegisteredPathForRequest());
            (rp != nullptr && rp->GetUser() != nullptr ? rp->GetUser() : this->user)->WebserverPluginRequestUserPassword(this, this->webserverMessageRecv->GetConnectionID(), extHeader.RequestPath(), extHeader.RegisteredPathForRequest(), extHeader.Host(), extHeader.AuthUser());
            doFreeBuf = true;
            break;

        case WSM_SET_AUTH_CONFIG_RESULT:
            this->user->SetAuthConfigResult(strlen(extHeader.PathToRegister()) == this->appWebRootLen ? "/" : extHeader.PathToRegister() + this->appWebRootLen, ws_update_auth_result_t(this->webserverMessageRecv->GetGeneralPurpose()));
            doFreeBuf = true;
            break;

        default:
            this->log->Log(LOG_WEBSERVER_PLUGIN, "WARNING: IWebserverPlugin received unexpected message (%s) - ignoring", WsMessageToStr(webserverMessageRecv->GetMessageID()));
            doFreeBuf = state != WSP_STATE_READ_HEADER;
        }
    }

    if (doFreeBuf) free(buf);

    if (!closing) {
        socket->Recv(webserverMessageRecv->GetHeaderBuffer(), webserverMessageRecv->GetHeaderSize());
        state = WSP_STATE_READ_HEADER;
    }
}


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


void WebserverPlugin::SendCertificate(const char * password, const byte * certBuf, size_t certLen, const char * hostName, UWebserverPlugin * user)
{
    static const int SEND_CERT_CONNECTION_ID = CONNECTION_ID_SET_CERT;

    this->log->Log(LOG_WEBSERVER_PLUGIN, "WebserverPlugin(%08x,%08x)::SendCertificate password:%s certLen:%u hostName:%s connected:%i", this, user, password, certLen, hostName, this->webserverMessageSend ? 1:0);
    
    if (this->webserverMessageSend) {   // just send if we are connected
        WebserverSetCertificateHandler * setCertHandler = static_cast<WebserverSetCertificateHandler *>(messageHandlers->btree_find((void *)(intp)SEND_CERT_CONNECTION_ID));
        if (setCertHandler == nullptr) {
            ExtendedHeader extHeader(this->log, this->protocolVersion);
            setCertHandler = new WebserverSetCertificateHandler(this, this->webserverMessageSend, this->log, extHeader, this->protocolVersion);
            setCertHandler->RegisterHandler();
        }

        setCertHandler->SendCert(password, hostName, certBuf, certLen, user != nullptr ? user : this->user);
    }
}


bool WebserverPlugin::PathHasStaticFiles(const char * thePath)
{
    if (thePath == this->appWebRoot)
        return true;

    // Remove the apps web root + 1 to remove the / after the apps web root. If the path is malformed,
    // the developer won't get the expected result.
    thePath += (this->appWebRootLen > 1) ? this->appWebRootLen + 1 : 1;
    if (*thePath == '\0')
        return false;

    size_t plen = strlen(thePath);

    // Check each file of the btee. If on has the same path at the beginning like the name of the file name has,
    // the given path has static files.
    httpfile * cur = static_cast<httpfile *>(staticFilesRoot->btree_find_left());
    while (cur != nullptr) {
        if (strncmp(thePath, cur->GetFileName(), plen) == 0 && (cur->GetFlags() & HTTP_HIDE) == 0)
            return true;

        cur = static_cast<httpfile *>(staticFilesRoot->btree_find_next_left(cur->GetFileName()));
    }

    return false;
}


void WebserverPlugin::RequestUserPasswordResult(dword connectionID, const char * user, const char * password)
{
    ExtendedHeader extHeader(this->log, this->protocolVersion);
    extHeader.SetAuthInfos(user, password);
    size_t bsize = extHeader.BufferSize();
    extHeader.Encode(connectionID, ALLOC_STACK_BUFFER(bsize, 512));
    webserverMessageSend->Send("WebserverPlugin", connectionID, WSM_REQUEST_USER_PASSWORD_RESULT, 0, extHeader.Buffer(), bsize);
}


void WebserverPlugin::SetAuthConfig(const char * path, const char * authUser, const char * authPwd)
{
    if (path != nullptr) while ((*path == '/' || *path == ' ') && *path != '\0') ++path;

    ExtendedHeader extHeader(this->log, this->protocolVersion);
    char * tmp;
    if (path == nullptr || *path == '\0') tmp = _strdup(this->appWebRoot);
    else {
        tmp = static_cast<char *>(malloc(this->appWebRootLen * strlen(path) + 2)); // +2 = closing zero and / between the two path...
        sprintf(tmp, "%s/%s", this->appWebRoot, path);
    }
    extHeader.SetPathToRegister(tmp);
    extHeader.SetAuthInfos(authUser, authPwd);

    size_t bsize = extHeader.BufferSize();
    extHeader.Encode(CONNECTION_ID_INTERNAL, ALLOC_STACK_BUFFER(bsize, 512));
    webserverMessageSend->Send("WebserverPlugin", CONNECTION_ID_INTERNAL, WSM_SET_AUTH_CONFIG, 0, extHeader.Buffer(), extHeader.BufferSize());
    free(tmp);
}


void WebserverPlugin::SetAuthConfig(const char * path, bool enableDynAuth)
{
    if (path != nullptr) while ((*path == '/' || *path == ' ') && *path != '\0') ++path;

    ExtendedHeader extHeader(this->log, this->protocolVersion);
    char * tmp;
    if (path == nullptr || *path == '\0') tmp = _strdup(this->appWebRoot);
    else {
        tmp = static_cast<char *>(malloc(this->appWebRootLen * strlen(path) + 2)); // +2 = closing zero and / between the two path...
        sprintf(tmp, "%s/%s", this->appWebRoot, path);
    }
    extHeader.SetPathToRegister(tmp);
    extHeader.SetDynamicAuthentication(enableDynAuth);

    size_t bsize = extHeader.BufferSize();
    extHeader.Encode(CONNECTION_ID_INTERNAL, ALLOC_STACK_BUFFER(bsize, 512));
    webserverMessageSend->Send("WebserverPlugin", CONNECTION_ID_INTERNAL, WSM_SET_AUTH_CONFIG, 0, extHeader.Buffer(), extHeader.BufferSize());
    free(tmp);
}


void WebserverPlugin::IoExec(void * execContext)
{
    // For now, that callback only is used to simulate a asynchronous close if the socket already had been closed
    CloseWebserverPluginUsers();
}


class WebserverDigestAuthenticator * WebserverPlugin::GetDigestAuthenticator(dword connectionID, const char * host)
{
	std::string s = Trim(host);
	//for (char & c : s) c = std::tolower(c);
	
	if (this->digestHostManager == nullptr) this->digestHostManager = new HTTPDigestHostManager();
	HTTPDigestHost * digestHost = this->digestHostManager->GetDigestHost(s.c_str());
	if (digestHost == nullptr) {
		digestHost = new HTTPDigestHost(s.c_str());
		this->digestHostManager->AddDigestHost(digestHost);
        if (this->cleanUpDigestDataTimer == nullptr) {
            this->cleanUpDigestDataTimer = new ITimer(this->iomux, this);
            this->cleanUpDigestDataTimer->Start(DIGEST_HOST_LIFETIME * 1000);
        }
	}
	
	HTTPAuthDigest * authDigest;
	auto i = this->digestAuthenticators.find(connectionID);
	if (i == this->digestAuthenticators.end()) {
		authDigest = new HTTPAuthDigest();
		this->digestAuthenticators.insert(std::pair<dword, DigestAuthenticatorLifeTime>(connectionID, authDigest));
	}
	else {
		i->second.UpdateLastAccess();
		i->second.AssignedToHandler(true); // Will be done below.
		authDigest = i->second.Authenticator();
	}
	
	authDigest->SetDigestHost(digestHost);
	WebserverDigestAuthenticator * result = new WebserverDigestAuthenticator(authDigest);
	return result;
}
