
/*---------------------------------------------------------------------------*/
/* webserver.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/ilib/base64.h"
#include "common/ilib/json.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 "set_certificate_handler.h"
#include "http_method_handler.h"
#include "http_connection.h"
#include "http_datasource.h"
#include "http_vfs_entry.h"
#include "webserver.h"

#define WEBSOCKET_PING_INTERVAL     60 * 1000

inline bool BitIsSet(int i, int b)
{
    return (i & b) == b;
}

Webserver::Webserver(IIoMux * const iomux,
                     ISocketProvider * const socketProvider,
                     ISocketProvider * const socketProviderTLS,
                     ISocketProvider * const socketProviderLocalSocket,
                     app_arguments_t * args) : 
                     iomux(iomux),
                     serviceSocket(iomux, socketProviderLocalSocket, this),
                     timerCleanUpDigestHosts(iomux, this),
                     timerReconnectSockets(iomux, this),
                     timerPingWebsocketConnections(iomux, this)
{
    this->user = nullptr;
    this->args = args;
    this->logFlags = args->logFlags;
    if (args->disableHexdumps) {
        debug->SetHexdump(false);
    }
    if (args->logFileSize > 0) {
        debug->SetLogFileSize(args->logFileSize);
    }
    this->DecodeAppPassword();
    this->tlsContext = socketProviderTLS->CreateSocketContext(this);
    this->tlsContext->SetTlsProfile(args->tlsProfile);
    this->socketProvider = socketProvider;
    this->socketProviderTLS = socketProviderTLS;
    this->socketProviderLocalSocket = socketProviderLocalSocket;
    this->serverSocket = nullptr;
    this->serverSocketTLS = nullptr;
    this->serverSocketv6 = nullptr;
    this->serverSocketTLSv6 = nullptr;
    this->serverSocketLocal = nullptr;

    this->digestHostManager = new HTTPDigestHostManager;
    this->httpConnections = nullptr;
    this->vfsRoot = new HTTPVfsEntry("/", nullptr);

    this->lastConnectionID = 0;
    
    this->localSocketBindDone = false;
    this->tcpSocketBindDone = false;
    this->tlsSocketBindDone = false;
    this->tcpSocketv6BindDone = false;
    this->tlsSocketv6BindDone = false;
    this->portsInUse = false;
    this->bindLocalFailed = false;
    this->bindv4Failed = false;
    this->bindv6Failed = false;
    this->shutDown = false;

    this->ipFilter = false;

    this->curWSToPing = nullptr;
    this->pingCnt = 0;

    this->timerCleanUpDigestHosts.Start(DIGEST_HOST_LIFETIME * 1000);
}


Webserver::~Webserver()
{
    ASSERT(serverSocket == nullptr, "Webserver::~Webserver serverSocket!=nullptr");
    ASSERT(serverSocketTLS == nullptr, "Webserver::~Webserver serverSocketTLS!=nullptr");
    ASSERT(serverSocketv6 == nullptr, "Webserver::~Webserver serverSocketv6!=nullptr");
    ASSERT(serverSocketTLSv6 == nullptr, "Webserver::~Webserver serverSocketTLSv6!=nullptr");
    ASSERT(serverSocketLocal == nullptr, "Webserver::~Webserver serverSocketLocal!=nullptr");
    ASSERT(httpConnections == nullptr, "Webserver::~Webserver httpConnections!=nullptr");
    ASSERT(dataSourceList.empty(), "Webserver::~Webserver dataSourceList not empty");
    delete tlsContext;
    delete digestHostManager;
    RemoveAllowedAddrs();
    
        // Because the vfsRoot - even if it is a btree instance - only will have child
    // objects (handled in internal btrees), it directly can be deleted.
    delete vfsRoot;
}


void Webserver::Init(UWebserver * user)
{
    this->user = user;
    this->BindSockets();
}


void Webserver::DecodeAppPassword()
{
    if (!args->appPassword) return;
    char * webserverPassword = (char *)malloc(strlen(args->appPassword) + 1);
    decode_base64(args->appPassword, (byte *)webserverPassword, strlen(args->appPassword));
    char * tmpOb = webserverPassword;
    char * tmp = (char *)args->appPassword;
    int i = 0;
    while (*tmpOb) {    // now increment or decrement some chars
        if (i == 0 || i == 3 || i == 4 || i == 7 || i == 16) {
            *tmpOb = *tmpOb - 1;
        }
        else if (i == 1 || i == 5 || i == 10 || i == 11 || i == 15) {
            *tmpOb = *tmpOb + 1;
        }
        ++i;
        ++tmpOb;
    }
    i = 0;
    tmpOb = webserverPassword;
    while (*tmpOb) {      // remove duplicated chars
        *tmp = *tmpOb;
        if (i == 1 || i == 3 || i == 4 || i == 6 || i == 8 || i == 9 || i == 12) {
            ++tmpOb;
        }
        ++i;
        ++tmp;
        ++tmpOb;
    }
    *tmp = '\0';
    free(webserverPassword);
}


void Webserver::RegisterConnection(HTTPConnection * connection)
{
    this->httpConnections = this->httpConnections->btree_put(connection);
    if (this->shutDown) iomux->SetExec(this, (void *) 1);
}


void Webserver::ReleaseConnection(HTTPConnection * connection)
{
    this->httpConnections = this->httpConnections->btree_get(connection);
    delete connection;
    if (this->shutDown) this->TryClose();
}


void Webserver::ReleaseDataSource(HTTPDataSource * dataSource)
{
    // Remove the datasouurce from the vfs...
    this->vfsRoot->RemoveDataSourceRecursive(dataSource);
    delete dataSource;
    if (this->shutDown) this->TryClose();
}


HTTPConnection * Webserver::GetConnectionByID(dword connectionID)
{
    return static_cast<HTTPConnection *>(this->httpConnections->btree_find(&connectionID));
}


dword Webserver::GetNextConnectionID()
{
    this->lastConnectionID++;
    if (this->lastConnectionID == CONNECTION_ID_INT_START) this->lastConnectionID = 1;

    btree * tmp = httpConnections->btree_find(&lastConnectionID);
    while (tmp != nullptr) {
        this->lastConnectionID++;
        tmp = httpConnections->btree_find(&lastConnectionID);
    }

    return this->lastConnectionID;
}


class HTTPDigestHostManager * Webserver::GetDigestHostManager()
{
    return this->digestHostManager;
}


void Webserver::SetServerCertificate(const char * password, const byte * cert, size_t certLen, const char * hostName)
{
    if (password && args->appPassword && !strcmp(this->args->appPassword, password)) {
        this->tlsContext->SetServerCertificate(cert, certLen, hostName);
    }
    else {
        this->Log(LOG_WEBSERVER, "Webserver::SetServerCertificate password missmatch %s!=%s", args->appPassword, password);
    }
}

void Webserver::SetIpFilter(bool enabled)
{
    ipFilter = enabled;
}

void Webserver::AddAllowedAddr(const char * addr)
{
    if (addr) ipFilterAllowedAddrs.push_back(new HTTPAllowedAddr(addr));
}

void Webserver::RemoveAllowedAddrs()
{
    class HTTPAllowedAddr * a;
    while ((a = ipFilterAllowedAddrs.front())) {
        a->remove();
        delete a;
    }
}

HTTPVfsEntry * Webserver::GetVfsEntryForPath(const char * path, HTTPRegisteredPath *& rpOut)
{
    rpOut = nullptr;
    if (path == nullptr || *path != '/') {
        this->Log(LOG_WEBSERVER, "#WARNING Webserver::GetVfsEntryForPath(0x%x): invalid path value (%s)", this, path);
        return nullptr;
    }

    char * paramPos = strchr(const_cast<char *>(path), '?');
    if (paramPos != nullptr)
        *paramPos = '\0';

    char name[WS_MAX_PATH_LENGTH + 1];
    char * dst = const_cast<char *>(path);
    size_t pathLen = (paramPos == nullptr ? strlen(path) : paramPos - path);
    if (pathLen > WS_MAX_PATH_LENGTH) {
        this->Log(LOG_WEBSERVER, "Webserver::GetVfsEntryForPath(0x%x): Path: %s", this, path);
        this->Log(LOG_WEBSERVER, "Webserver::GetVfsEntryForPath(0x%x): Length of path (%u) exceeds WS_MAX_PATH_LEN (%u)", this, pathLen, WS_MAX_PATH_LENGTH);
        if (paramPos != nullptr)
            *paramPos = '?';
        return nullptr;
    }

    char * pathEnd = dst + pathLen;
    HTTPVfsEntry * result = nullptr;
    HTTPVfsEntry * node = vfsRoot;

    while (dst < pathEnd) {
        char * pos = strchr(dst, '/');
        if (pos == nullptr) pos = pathEnd;

        size_t namelen = pos - dst;

        if (namelen > 0) {
            memcpy(name, dst, namelen);
            name[namelen] = 0;

            node = node->FindChild(name);
            //node = static_cast<HTTPVfsEntry *>(node->GetChilds()->btree_find(name));
            if (node == nullptr) {
                if (paramPos != nullptr) *paramPos = '?';
                return result;
            }

            dst += namelen;
            result = node;

            HTTPDataSource * tmpDS = node->GetDataSource();
            if (tmpDS) {
                HTTPRegisteredPath * rp = tmpDS->GetPathObject(path, pos);
                if (rp != nullptr) rpOut = rp;
            }
        }

        ++dst; // Remove the '/' at the begin of the current dst position...
    }

    if (paramPos != nullptr) *paramPos = '?';
    return node;
}


HTTPDataSource * Webserver::GetDataSourceForPath(const char * path, HTTPRegisteredPath * &rpOut)
{
    HTTPVfsEntry * node = this->GetVfsEntryForPath(path, rpOut);
    return node ? node->GetDataSource() : nullptr;
}


word Webserver::RegisterToVFS(class HTTPDataSource * dataSource, HTTPRegisteredPath * rp, bool isWebsocketListen, HTTPVfsEntry *& vfsOut)
{
    vfsOut = nullptr;
    const char * path = rp->GetPath();
    if (path == nullptr || *path != '/') {
        this->Log(LOG_WEBSERVER, "Webserver::RegisterToVFS(0x%x, 0x%x): An application tried to register to an invalid path (%s)", dataSource, this, path);
        return WS_REGISTER_PATH_INVALID;
    }

    if (strcmp(path, "/") == 0) {
        this->Log(LOG_WEBSERVER, "Webserver::RegisterToVFS(0x%x, 0x%x): An application tried to register to root directory", dataSource, this);
        return WS_REGISTER_PATH_INVALID;
    }

    char name[WS_MAX_PATH_LENGTH + 1];
    bool cont = true;
    char * dst = const_cast<char *>(path);
    char * pathEnd = dst + strlen(path);
    HTTPVfsEntry * parent = vfsRoot;
    HTTPVfsEntry * child = nullptr;

    if (*dst == '/')
        dst++;

    while (cont) {
        char * pos = strchr(dst, '/');
        if (pos == nullptr) {
            pos = pathEnd;
            cont = false;
        }

        size_t namelen = pos - dst;
        if (namelen > 0) {
            memcpy(name, dst, namelen);
            name[namelen] = 0;

            child = parent->FindChild(name);
            //child = static_cast<HTTPVfsEntry *>(parent->GetChilds()->btree_find(name));
            if (child == nullptr) {
                child = new HTTPVfsEntry(name, parent);
                parent->AddChild(child);
            }
            else {
                // If the child already has a data source, and that data source is not the same as the
                // one who tries to register, the process will be canceled. This will be done to prevent
                // one application, registering a sub path under the path of an other...
                HTTPDataSource * tmp = child->GetDataSource();
                if (tmp && tmp != dataSource) {
                    this->Log(LOG_WEBSERVER, "Webserver::RegisterToVFS(0x%x, 0x%x): An application tried to register to a path (%s) already registered by an other application", dataSource, this, path);
                    return WS_REGISTER_ALREADY_REGISTERD;
                }
            }

            parent = child;
            dst += namelen + 1;
        }
    }

    vfsOut = child;
    bool alreadyRegsitered = false;
    HTTPDataSource * tmp = child->GetDataSource();
    if (!tmp) {
        child->SetDataSource(dataSource);
    }
    else {
        if (tmp != dataSource) alreadyRegsitered = true;
        else if (!rp->IsNoAccessTypeSet()) {
            // If IsNoAccessTypeSet() returns true, the given registeredPath is new. If not, we need
            // to check, if the requested register process is valid. There can be 3 types of registering
            // (listening) to a path: pathtrough, http requests and websocket. That leads to the following
            // priority:
            //
            //   - Passthrough will not allow anything else. So in that case, the path counts as already registered.
            //   - If registered for HTTP requests, registering for websocket is still allowed. In that case, that needs to be checked first.
            //   - And finally check if the path had only be requested for websocket and the current request is websocket, too.
            if (rp->IsPassThrough()) alreadyRegsitered = true;
            else if (rp->IsHTTPListening()) {
                if (isWebsocketListen && !rp->SupportsWebsocket()) alreadyRegsitered = false;
                else alreadyRegsitered = true;
            }
            else alreadyRegsitered = isWebsocketListen && rp->SupportsWebsocket();
            //alreadyRegsitered = (isWebsocketListen && rp->SupportsWebsocket()) || rp->IsHTTPListening() || rp->IsPassThrough();
        }
    }

    if (alreadyRegsitered) {
        this->Log(LOG_WEBSERVER, "#ERROR Webserver::RegisterToVFS(0x%x, 0x%x): An application tried to register to already registered path (%s)", dataSource, this, path);
        return WS_REGISTER_ALREADY_REGISTERD;
    }
    else return WS_REGISTER_DONE;
}


void Webserver::SocketBindResult(ISocket * const socket, const char * localAddr, word localPort)
{
    if (socket == this->serverSocket) {
        this->args->httpPort = localPort;
        this->tcpSocketBindDone = true;
        this->BindSocketTCPv6();
        // this->CheckSocketState();    // do not call CheckSocketState here, just later on BindResult for TCPv6
        // Now when bind successfully, we can listen to that socket...
        socket->Listen();
    }
    else if (socket == this->serverSocketTLS) {
        this->args->httpsPort = localPort;
        this->tlsSocketBindDone = true;
        this->BindSocketTLSv6();
        // this->CheckSocketState();    // do not call CheckSocketState here, just later on BindResult for TLSv6
        // Now when bind successfully, we can listen to that socket...
        socket->Listen();
    }
    else if (socket == this->serverSocketv6) {
        this->args->httpPort = localPort;
        this->tcpSocketv6BindDone = true;
        this->CheckSocketState();
        // Now when bind successfully, we can listen to that socket...
        socket->Listen();
    }
    else if (socket == this->serverSocketTLSv6) {
        this->args->httpsPort = localPort;
        this->tlsSocketv6BindDone = true;
        this->CheckSocketState();
        // Now when bind successfully, we can listen to that socket...
        socket->Listen();
    }
    else if (socket == this->serverSocketLocal) {
        this->localSocketBindDone = true;
        this->CheckSocketState();
    }
    else { // Well - should never happen!!!!
        ASSERT(false, "Webserver::SocketBindResult: Webserver received SocketBindResult() of an unkown socket");
    }
}


void Webserver::SocketListenResult(ISocket * const socket, const char * remoteAddr, word remotePort)
{
    if (this->IsClosing()) {
        socket->Accept(nullptr);
        return;
    }

    if (socket == serverSocketLocal) {
        HTTPDataSource * newDS = new HTTPDataSource(this);
        this->dataSourceList.push_back(newDS);
        socket->Accept(newDS);
        return;
    }

    bool isTLS = (socket == this->serverSocketTLS || socket == this->serverSocketTLSv6);
    bool redirectToTLS = !isTLS && args->redirectToTLS;
    bool localHost = false;
    size_t remoteAddrLen = remoteAddr ? strlen(remoteAddr) : 0;
    if (remoteAddr && (
        (remoteAddrLen >= 9 && !strncmp(remoteAddr, "127.0.0.1", 9)) ||
        (remoteAddrLen >= 9 && !strncmp(remoteAddr, "localhost", 9)) ||
        (remoteAddrLen >= 3 && !strncmp(remoteAddr, "::1", 3)) ||
        (remoteAddrLen >= 5 && !strncmp(remoteAddr, "[::1]", 5)))) {
        localHost = true;
    }

    bool allowed = false;

    if (!ipFilter) allowed = true;
    else if (localHost) allowed = true;
    else if (remoteAddr) for (class HTTPAllowedAddr * a = this->ipFilterAllowedAddrs.front(); a; a = a->goNext()) {
        if (!strcmp(a->addr, remoteAddr)) {
            allowed = true;
            break;
        }
    }
    
    if (allowed) socket->Accept(new HTTPConnection(this, this, this->GetNextConnectionID(), isTLS, redirectToTLS, localHost));
    else socket->Accept(0);
}


void Webserver::SocketShutdown(ISocket * const socket, shutdownreason_t reason)
{
    //printf("*** ServerSocket::SocketShutdown() --> reason = ");
    //switch (reason)
    //{
    //case SOCKET_SHUTDOWN_NORMAL: printf("SOCKET_SHUTDOWN_NORMAL"); break;
    //case SOCKET_SHUTDOWN_BY_PEER: printf("SOCKET_SHUTDOWN_BY_PEER"); break;
    //case SOCKET_LOST: printf("SOCKET_LOST"); break;
    //case SOCKET_ADDRESS_INVALID: printf("SOCKET_ADDRESS_INVALID"); break;
    //case SOCKET_OPEN_FAILED:  printf("SOCKET_OPEN_FAILED"); break;
    //case SOCKET_CONNECT_FAILED: printf("SOCKET_CONNECT_FAILED"); break;
    //case SOCKET_BIND_FAILED: printf("SOCKET_BIND_FAILED"); break;
    //case SOCKET_LISTEN_FAILED: printf("SOCKET_LISTEN_FAILED"); break;
    //case SOCKET_ACCEPT_FAILED: printf("SOCKET_ACCEPT_FAILED"); break;
    //case SOCKET_TLS_HANDSHAKE_FAILED: printf("SOCKET_TLS_HANDSHAKE_FAILED"); break;
    //}
    //printf("\n");
    bool autoReconnect = true;

    if (socket == serverSocket) {
        serverSocket = nullptr;
        if (!this->tcpSocketBindDone) {
            autoReconnect = false;
            this->tcpSocketBindDone = true;
            this->bindv4Failed = true;
            if (reason == SOCKET_ADDR_IN_USE) this->portsInUse = true;
            this->BindSocketTCPv6();    // try to bind TCPv6 socket now
        }
    }
    else if (socket == serverSocketTLS) {
        serverSocketTLS = nullptr;
        if (!this->tlsSocketBindDone) {
            autoReconnect = false;
            this->tlsSocketBindDone = true;
            this->bindv4Failed = true;
            if (reason == SOCKET_ADDR_IN_USE) this->portsInUse = true;
            this->BindSocketTLSv6();    // try to bind TLSv6 socket now
        }
    }
    else if (socket == serverSocketv6) {
        serverSocketv6 = nullptr;
        if (!this->tcpSocketv6BindDone) {
            autoReconnect = false;
            this->tcpSocketv6BindDone = true;
            this->bindv6Failed = true;
            if (reason == SOCKET_ADDR_IN_USE) this->portsInUse = true;
            this->CheckSocketState();
        }
    }
    else if (socket == serverSocketTLSv6) {
        serverSocketTLSv6 = nullptr;
        if (!this->tlsSocketv6BindDone) {
            autoReconnect = false;
            this->tlsSocketv6BindDone = true;
            this->bindv6Failed = true;
            if (reason == SOCKET_ADDR_IN_USE) this->portsInUse = true;
            this->CheckSocketState();
        }
    }
    else if (socket == serverSocketLocal) {
        this->localSocketBindDone = true;
        this->bindLocalFailed = true;
        serverSocketLocal = nullptr;
        this->CheckSocketState();
    }
    delete socket;

    if (this->shutDown) this->TryClose();
    else if (autoReconnect) timerReconnectSockets.Start(1 * 1000);
}


void Webserver::AppServiceShutdown()
{
    if (!this->shutDown) {
        this->shutDown = true;
        Log(LOG_WEBSERVER, "Webserver(%p)::AppServiceShutdown", this);
        this->TryClose();
    }
}


void Webserver::AppServiceShutdownTimeout()
{
    if (this->serverSocket || this->serverSocketTLS || this->serverSocketv6 || this->serverSocketTLSv6 || this->serverSocketLocal) {
        debug->printf("Webserver(%p)::AppServiceShutdownTimeout %d %d %d %d %d", this,
            (this->serverSocket != NULL), (this->serverSocketTLS != NULL), (this->serverSocketv6 != NULL), (this->serverSocketTLSv6 != NULL), (this->serverSocketLocal != NULL));
        return;
    }
    if (this->httpConnections != nullptr) {
        (static_cast<HTTPConnection *>(this->httpConnections))->ShutdownTimeout();
        return;
    }
    if (!this->dataSourceList.empty()) {
        debug->printf("Webserver(%p)::AppServiceShutdownTimeout datasource, '%s'...", this, dataSourceList.front()->GetRoot());
        return;
    }
    if (this->user) {
        debug->printf("Webserver(%p)::AppServiceShutdownTimeout user", this);
        return;
    }
}

void Webserver::UpdateTlsProfile(ISocketContext::tls_profile_t tlsProfile)
{
    if (this->tlsContext != nullptr) {
        this->Log(LOG_WEBSERVER, "Webserver::UpdateTlsProfile %s", ISocketContext::TlsProfileToStr(tlsProfile));
        this->tlsContext->SetTlsProfile(tlsProfile);
    }
}


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


void Webserver::SocketRecvResult(ISocket * const socket, void * buf, size_t len)
{
}


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


void Webserver::BindSockets()
{
    if (!this->serverSocket) {
        this->serverSocket = this->socketProvider->CreateSocket(iomux, this, this, false);
        this->serverSocket->Bind(nullptr, args->httpPort, args->reuseAddr);
    }
    if (!this->serverSocketTLS) {
        this->serverSocketTLS = this->socketProviderTLS->CreateSocket(iomux, this, this, false, this->tlsContext);
        this->serverSocketTLS->Bind(nullptr, args->httpsPort, args->reuseAddr);
    }
    // TCPv6/TLSv6 are bound later, either in SocketBindResult or SocketShutdown
    if (!this->serverSocketLocal) {
        this->serverSocketLocal = this->socketProviderLocalSocket->CreateSocket(iomux, this, this, false);
        this->serverSocketLocal->Bind(this->args->webserverLocalPath);
        this->serverSocketLocal->Listen();
    }
}

void Webserver::BindSocketTCPv6()
{
    if (!this->serverSocketv6) {
        this->serverSocketv6 = this->socketProvider->CreateSocket(iomux, this, this, true);
        this->serverSocketv6->Bind(nullptr, args->httpPort, args->reuseAddr);
    }
}

void Webserver::BindSocketTLSv6()
{
    if (!this->serverSocketTLSv6) {
        this->serverSocketTLSv6 = this->socketProviderTLS->CreateSocket(iomux, this, this, true, this->tlsContext);
        this->serverSocketTLSv6->Bind(nullptr, args->httpsPort, args->reuseAddr);
    }
}


void Webserver::CheckSocketState()
{
    if (this->shutDown) return; // do not do anything anymore if we're in shutdown state

    if (this->localSocketBindDone && this->tcpSocketBindDone && this->tlsSocketBindDone && this->tcpSocketv6BindDone && this->tlsSocketv6BindDone) {
        // if a port is in use, we need new try with new ports anyway
        // or if both v4 and v6 bind failed
        if (this->portsInUse || this->bindLocalFailed || (this->bindv4Failed && this->bindv6Failed)) {
            this->args->httpPort = 0;
            this->args->httpsPort = 0;
            this->serviceSocket.Shutdown();
        }
        else {
            if (this->args->managerLocalPath) {
                serviceSocket.Connect(this->args->managerLocalPath, this->args->serviceID);
            }
            else if (this->user != nullptr) {
                this->user->WebserverInitDone(this->args->httpPort, this->args->httpsPort);
                this->user = nullptr; // Only call InitDone() once...
            }
        }
    }
}


void Webserver::TimerOnTimeout(ITimer * timer)
{
    if (timer == &timerCleanUpDigestHosts) {
        digestHostManager->CleanUpList();
        timerCleanUpDigestHosts.Start(DIGEST_HOST_LIFETIME * 1000);
    }
    else if (timer == &timerReconnectSockets) {
        Log(LOG_WEBSERVER, "Webserver::TimerOnTimeout timerReconnectSockets");
        BindSockets();
    }
    else { // timerPingWebsocketConnections
        if (!this->websocketList.empty()) {
            this->pingCnt = 0;
            this->pingStartTS = ITime::TimeStampMilliseconds();
            this->curWSToPing = this->websocketList.front();
            this->iomux->SetExec(this, nullptr);
        }
    }
}


void Webserver::TryClose()
{
    Log(LOG_WEBSERVER, "Webserver::TryClose()");

    // To make a graceful shuitdown without problems, the following order must be observerd:
    //
    // 1. Close all sockets that are listening for incomming connections.
    // 2. Close all existing HTTP connections (using a graceful shutdown on their side)
    // 3. Close all existing DataSources
    // 4. Repost ApPService, that the close had been completed.
    //
    // If not done in this order, the Webserver will crash under some circumstances.
    
    // Gosh - I forgott to cancel the timers. Well, for the webserver itself, that won't be a problem.
    // But the webserver is used as an object inside myApps, and there it can be a big problem not
    // canceling the timers...
    this->timerCleanUpDigestHosts.Cancel();
    this->timerPingWebsocketConnections.Cancel();
    this->timerReconnectSockets.Cancel();
    this->iomux->CancelSetExec(this); // I think that wonÄt be necessary, but just to be sure...

    // 1. Close all listening sockets.
    if (this->serverSocket) {
        Log(LOG_WEBSERVER, "Webserver::TryClose(%08x): Closing listening TCP socket.", this);
        this->serverSocket->Shutdown();
        return;
    }

    if (this->serverSocketTLS) {
        Log(LOG_WEBSERVER, "Webserver::TryClose(%08x): Closing listening TLS socket.", this);
        this->serverSocketTLS->Shutdown();
        return;
    }

    if (this->serverSocketv6) {
        Log(LOG_WEBSERVER, "Webserver::TryClose(%08x): Closing listening TCPv6 socket.", this);
        this->serverSocketv6->Shutdown();
        return;
    }

    if (this->serverSocketTLSv6) {
        Log(LOG_WEBSERVER, "Webserver::TryClose(%08x): Closing listening TLSv6 socket.", this);
        this->serverSocketTLSv6->Shutdown();
        return;
    }

    if (this->serverSocketLocal) {
        Log(LOG_WEBSERVER, "Webserver::TryClose(%08x): Closing listening local socket.", this);
        this->serverSocketLocal->Shutdown();
        return;
    }


    // 2. Close all existing HTTP connections.
    if (this->httpConnections != nullptr) {
        HTTPConnection * cur = static_cast<HTTPConnection *>(this->httpConnections);
        Log(LOG_WEBSERVER, "Webserver::TryClose(%08x): Closing HTTP connection #%u.", this, cur->GetConnectionID());
        cur->Shutdown();
        return;
    }

    // 3. Close all Datasources...
    if (!this->dataSourceList.empty()) {
        HTTPDataSource * dataSource = dataSourceList.front();
        const char * root = dataSource->GetRoot();
        Log(LOG_WEBSERVER, "Webserver::TryClose(%08x): Closing datasource for primary path '%s'.", this, root == nullptr ? "<unkown>" : root);
        dataSource->Shutdown();
        return;
    }

    Log(LOG_WEBSERVER, "Webserver::TryClose(%08x): Everything had been closed - quitting webserver", this);
    serviceSocket.AppServiceShutdownComplete();

    // 4. Everything had been closed / released, so finalize the shutdown. Note that the user is only
    // set if the webserver will be used as object, whih happens in myApps.
    if (this->user) {
        UWebserver * tmp = this->user;
        this->user = nullptr;
        tmp->WebserverInitDone(0, 0);    // call WebserverInitDone with 0 ports to indicate, that init was not successfull
    }
}


const char * Webserver::GetDefaultPath()
{
    return args->defaultPath;
}


const char * Webserver::GetPassword()
{
    return args->appPassword;
}


void Webserver::RegisterWebsocketForPing(class UWebserverWebsocket * websocket)
{
    this->websocketList.push_back(websocket);
    if (!this->timerPingWebsocketConnections.IsRunning()) this->timerPingWebsocketConnections.Start(WEBSOCKET_PING_INTERVAL);
}


void Webserver::UnregisterWebsocketForPing(class UWebserverWebsocket * websocket)
{
    if (this->curWSToPing != nullptr && this->curWSToPing == websocket) this->curWSToPing = this->curWSToPing->goNext();
    websocket->remove();
    if (this->websocketList.empty()) this->timerPingWebsocketConnections.Cancel();
}


void Webserver::IoExec(void * execContext)
{
    if (execContext == (void *) 1) {
        this->TryClose();
        return;
    }

    // It could be that meanwhile curWSToPing is nullptr because of a websocket connection that had been closed,
    // if this websocket connection was curWSToPing and also the last one in the list.
    if (this->curWSToPing != nullptr) {
        UWebserverWebsocket * next = this->curWSToPing->goNext();
        this->curWSToPing->Ping();
        this->curWSToPing = next;
        ++this->pingCnt;
    }

    // All handled?
    if (this->curWSToPing != nullptr) this->iomux->SetExec(this, nullptr);
    else {
        if (!this->websocketList.empty()) {
            ulong64 now = ITime::TimeStampMilliseconds();
            ulong64 diff = now - this->pingStartTS;
            if (diff >= WEBSOCKET_PING_INTERVAL) {
                this->curWSToPing = this->websocketList.front();
                this->iomux->SetExec(this, nullptr);
            }
            else {
                ulong64 timeOut = WEBSOCKET_PING_INTERVAL - diff;
                this->timerPingWebsocketConnections.Start(dword(timeOut));
            }
        }
    }
}

HTTPAllowedAddr::HTTPAllowedAddr(const char * addr)
{
    ASSERT(addr != nullptr, "HTTPAllowedAddr addr must not be null");
    this->addr = _strdup(addr);
}

HTTPAllowedAddr::~HTTPAllowedAddr()
{
    free(addr);
    addr = 0;
}
