/*
  ICS Basic Web Server - C++Builder translation of the Delphi demo OverbyteIcsBasicWebServer1.pas
  (demos-delphi-vcl), ICS V9.8.

  A simplified OverbyteIcsSslMultiWebServ: a web application server with a WebSocket echo
  server, no real GUI and every setting in code (no ini file, sessions or security features).
  One IcsHosts entry listens on localhost (127.0.0.1 and ::1) on ports 80 and 443; search for
  IcsHosts below to change addresses or ports. Pages come from demos-data\WebAppServerData.

  How this C++ version differs from the Delphi original, apart from syntax:
  - demos-data is found three levels up from the Win64x\<config> output folder (Delphi: four,
    from bin\<platform>\<config>).
  - The empty Delphi handlers (BeforeAnswer, SslServerName, the WebSocket ping timer and the
    connection destructor) are not translated; the .dfm bindings of the first two are removed.
  - The form destructor frees the components first, because they can still fire events that
    use the form while they are destroyed.
  - Delphi demo bugs are fixed here; each fix has a one-line comment.
*/

#include <vcl.h>
#pragma hdrstop

#include "OverbyteIcsBasicWebServer1.h"
#include "OverbyteIcsUtils.hpp"
#include "OverbyteIcsTicks64.hpp"
#include "OverbyteIcsHtmlUtils.hpp"

#pragma package(smart_init)
#pragma resource "*.dfm"
#pragma resource "ICSMedia.res"   // ICSICON16 for /favicon.ico; the Delphi demo does not link it, so it always answers 404

TWebServerForm *WebServerForm;

namespace {

const wchar_t *const SrvCopyRight = L" OverbyteIcsBasicWebServer (c) 2026 Francois Piette V9.6 ";
const wchar_t *const WebApp = L"WEB-APP";

// Typed null for AnswerPage's UserData: a bare nullptr is ambiguous between its
// TObject* (template file) and PWideChar (resource type) overloads.
TObject *const NoUserData = nullptr;

String SpaceToPlus(const String &S)
{
    return StringReplace(S, " ", "+", TReplaceFlags() << rfReplaceAll);
}

String HostPort(int Port, int DefaultPort)
{
    return Port != DefaultPort ? ":" + IntToStr(Port) : String();
}

} // namespace

/* ---- URL handlers for the index page and the WebSocket client page ---- */

class TUrlHandlerIndexHtml : public TUrlHandler
{
public:
    __fastcall virtual TUrlHandlerIndexHtml(TComponent *AOwner) : TUrlHandler(AOwner) {}
    virtual void __fastcall Execute();
};

class TUrlHandlerWSClientEcho : public TUrlHandler
{
public:
    __fastcall virtual TUrlHandlerWSClientEcho(TComponent *AOwner) : TUrlHandler(AOwner) {}
    virtual void __fastcall Execute();
};

void __fastcall TUrlHandlerIndexHtml::Execute()
{
    AnswerPage("", NO_CACHE, "Index.html", NoUserData,
               ARRAYOFCONST((L"serverinfo", WebServerForm->ServerInfo())));
    Finish();
}

// websocketclient.html offers a WebSocket URL for each WEB-APP host binding.
void __fastcall TUrlHandlerWSClientEcho::Execute()
{
    TSslHttpAppSrv *Server = WebServerForm->SslHttpAppSrv;
    String Options;
    String Sel = " selected";
    for (int I = 0; I < Server->IcsHosts->Count; I++) {
        TIcsHost *Host = Server->IcsHosts->Items[I];
        if (!Host->HostEnabled || Host->HostTag != WebApp)
            continue;
        const String HostName = Host->HostNames->Strings[0];
        if (Host->BindNonPort != 0) {
            Options += "<option" + Sel + ">ws://" + HostName + HostPort(Host->BindNonPort, 80) +
                       "/WebSocket/Echo</option>\r\n";
            Sel = "";
        }
        if (Host->BindSslPort != 0 && !Server->NoSSL) {   // Delphi offers wss:// even with SSL off
            const String Wss = "wss://" + HostName + HostPort(Host->BindSslPort, 443);
            Options += "<option" + Sel + ">" + Wss + "/WebSocket/Echo</option>\r\n";
            Sel = "";
            Options += "<option>" + Wss + "/WebSocket/EchoPing</option>\r\n";
            // Delphi then appends an unterminated "<option>wss://host", an empty broken entry.
        }
    }
    AnswerPage("", NO_CACHE, "websocketclient.html", NoUserData, ARRAYOFCONST((L"WSSURL", Options)));
    Finish();
}

/* ---- Client connection class: per-request log counters and WebSocket events ---- */

__fastcall TMyHttpConnection::TMyHttpConnection(TComponent *AOwner)
    : THttpWSSrvConn(AOwner)
{
    OnBgException = BgExceptionEvent;
    OnWSLogEvent = WebServerForm->ClientWSLogEvent;
    OnWSHandshake = WebServerForm->ClientWSHandshakeEvent;
    OnWSFrameRcvd = WebServerForm->ClientWSFrameRcvdEvent;
    OnWSFrameSent = WebServerForm->ClientWSFrameSentEvent;
    OnWSDisconnected = WebServerForm->ClientWSDisconnectedEvent;
    OnWSReady = WebServerForm->ClientWSReadyEvent;
    // DebugBody decodes all frames, DebugHdr only the main info, DebugConn only connections.
    DebugLevel = WebServerForm->DisplayHeaderCheckBox->Checked ? DebugHdr : DebugConn;
}

void __fastcall TMyHttpConnection::BgExceptionEvent(TObject *Sender, Exception *E, bool &CanClose)
{
    WebServerForm->Display("Client Error - " + IcsGetExceptMess(E));
}

/* ---- Form ---- */

__fastcall TWebServerForm::TWebServerForm(TComponent *Owner)
    : TForm(Owner)
{
}

__fastcall TWebServerForm::~TWebServerForm()
{
    // Free the server and its clients while this form's C++ members still exist.
    DestroyComponents();
}

void TWebServerForm::Display(const String &Msg)
{
    DisplayMemo->Lines->Add(Msg);
}

void __fastcall TWebServerForm::FormClose(TObject *Sender, TCloseAction &Action)
{
    if (FFinalized)
        return;
    FFinalized = true;
    StopButtonClick(this);
}

void __fastcall TWebServerForm::FormCreate(TObject *Sender)
{
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
}

// The URLs that reach the first page of each enabled host.
String TWebServerForm::BuildDemoURIs()
{
    String Result;
    for (int I = 0; I < SslHttpAppSrv->IcsHosts->Count; I++) {
        TIcsHost *Host = SslHttpAppSrv->IcsHosts->Items[I];
        if (!Host->HostEnabled)
            continue;
        const String HostName = Host->HostNames->Strings[0];
        if (Host->BindNonPort != 0)
            Result += "http://" + HostName + HostPort(Host->BindNonPort, 80) + "/" + Host->WebDefDoc + "\r\n";
        if (Host->BindSslPort != 0 && !SslHttpAppSrv->NoSSL)
            Result += "https://" + HostName + HostPort(Host->BindSslPort, 443) + "/" + Host->WebDefDoc + "\r\n";
    }
    return Result;
}

void __fastcall TWebServerForm::StartButtonClick(TObject *Sender)
{
    // demos-data is three levels up from Win64x\<config> (Delphi: four, from bin\<platform>\<config>).
    const String BaseDir = IcsAbsolutisePath(IncludeTrailingPathDelimiter(ExtractFilePath(Application->ExeName)) +
                                             "..\\..\\..\\demos-data\\");
    Display("Demo Pages Base Directory: " + BaseDir);
    FDataDir = BaseDir + "WebAppServerData\\Data";

    try {
        // Server settings are set here in code; most web servers would read an ini file.
        // Other options: hoAllowDirList, hoAllowOutsideRoot, hoAllowOptions, hoAllowConnect,
        // hoIgnoreIfModSince.
        SslHttpAppSrv->Options = THttpOptions() << hoContentEncoding << hoSendServerHdr << hoAllowPut
                                 << hoAllowDelete << hoAllowTrace << hoAllowPatch << hoAddMissPath;
        // Should the browser send a certificate: sslCliCertNone, sslCliCertOptional, sslCliCertRequire.
        SslHttpAppSrv->SslCliCertMethod = sslCliCertNone;
        SslHttpAppSrv->NoSSL = false;
        SslHttpAppSrv->WSDebugOptions = DefWSDebugServer;   // can be reduced for less logging

        FServerInfo = SrvCopyRight;
        Display(SrvCopyRight);
        if (!SslHttpAppSrv->NoSSL) {
            IcsSslRootCAStore->Initialise();   // loads OpenSSL and the internal root CAs if needed
            FServerInfo += "<br>SSL/TLS " + IcsReportOpenSSLVer(false);
            Display("SSL/TLS " + IcsReportOpenSSLVer(true));
        }
        FSrvCompName = IcsGetCompName();
        FServerInfo += "<br>Running on: " + FSrvCompName;

        // One host, set up by hand to show the minimum that is needed.
        SslHttpAppSrv->IcsHosts->Clear();
        TIcsHost *Host = static_cast<TIcsHost *>(SslHttpAppSrv->IcsHosts->Add());
        Host->HostEnabled = true;
        Host->HostNames->Text = "localhost";   // may be several names; they must match the domains in SslCert
        Host->HostTag = WebApp;                // only needed with several hosts
        Host->Descr = "WebApp Server";
        Host->BindIpAddr = "127.0.0.1";        // avoid 0.0.0.0, which fails if any other web server is listening
        Host->BindIpAddr2 = "::1";             // optional, usually an IPv6 address
        Host->BindNonPort = 80;
        Host->BindSslPort = 443;               // 0 for no SSL
        Host->SslCert = GSSL_CERTS_DIR + GSSL_LOCALHOST_NAME;   // C:\ProgramData\ICS-OpenSSL\ICS-Certs\localhost-bundle.pem
        Host->SslPassword = "password";
        Host->SslSrvSecurity = sslSrvSecHigh;  // or sslSrvSecTls12Less, sslSrvSecTls13Only
        Host->WebDefDoc = "index.html";        // returned for a blank path
        Host->WebDocDir = BaseDir + "WebAppServerData\\wwwRoot";
        Host->WebTemplDir = BaseDir + "WebAppServerData\\Templates";
        if (SslHttpAppSrv->IcsHosts->Count <= 0) {
            Display("Can Not Start Server - No Source Server Hosts Configured");
            return;
        }
        Display("Number of Hosts Configured: " + IntToStr(SslHttpAppSrv->IcsHosts->Count));

        // Validate the hosts and keep the site certificate information.
        try {
            const String Errs = SslHttpAppSrv->ValidateHosts(false, true);   // don't stop on the first error, no exceptions
            if (!Errs.IsEmpty())
                Display("Server Validation Errors:\r\n" + Errs);
            Display("Required Listen Bindings:\r\n" + SslHttpAppSrv->ListenStates());
        }
        catch (Exception &E) {
            Display("Host Validation Failed, Server Stopped - " + E.Message);
            return;
        }

        SslHttpAppSrv->TemplateDir = SslHttpAppSrv->IcsHosts->Items[0]->WebTemplDir;
        SslHttpAppSrv->DocDir = SslHttpAppSrv->IcsHosts->Items[0]->WebDocDir;
        SslHttpAppSrv->DefaultDoc = "index.html";
        SslHttpAppSrv->ServerHeader = DefServerHeader;   // the latest version
        SslHttpAppSrv->ClientClass = __classid(TMyHttpConnection);   // our own class, needed for WebSockets
        SslHttpAppSrv->SocketErrs = wsErrFriendly;
        SslHttpAppSrv->ExclusiveAddr = true;
        if (SslHttpAppSrv->SessionTimeout < 30)
            SslHttpAppSrv->SessionTimeout = 300;   // sanity check

        // Allowed paths and handlers must match the HostTag of a host.
        // Only static documents may be fetched directly.
        SslHttpAppSrv->AddGetAllowedPath("/", afBeginBy, WebApp);

        // Dynamic pages, built from a template and dynamic content. A POST needs its own
        // AddPostHandler; demo.html is built in SslHttpAppSrvProcDocument.
        SslHttpAppSrv->AddGetHandler("/", __classid(TUrlHandlerIndexHtml), hgWillSendMySelf, WebApp);
        SslHttpAppSrv->AddGetHandler("/index.html", __classid(TUrlHandlerIndexHtml), hgWillSendMySelf, WebApp);
        SslHttpAppSrv->AddGetHandler("/websocketclient.html", __classid(TUrlHandlerWSClientEcho), hgWillSendMySelf, WebApp);

        // Start as many hosts as possible.
        const String Errs = SslHttpAppSrv->Start(true);
        if (!Errs.IsEmpty())
            Display("Start Web Server Error - " + Errs);
        Display(String(SslHttpAppSrv->ListenAllOK() ? "Started OK, " : "Failed to Start, ") +
                "Listen Bindings:\r\n" + SslHttpAppSrv->ListenStates());
        StartButton->Enabled = false;
        StopButton->Enabled = true;

        Display("Now browse to one of these URLs:\r\n" + BuildDemoURIs());
    }
    catch (Exception &E) {
        Display("Failed to start web server - " + E.Message);
    }
}

void __fastcall TWebServerForm::StopButtonClick(TObject *Sender)
{
    StartButton->Enabled = true;
    StopButton->Enabled = false;
    SslHttpAppSrv->Stop();
    Display("Server stopped");
}

// Called before OnClientConnect, so a connection can be refused before it is accepted.
// Public servers get many hacking attempts a day: see OverbyteIcsSslMultiWebServ for blacklists.
void __fastcall TWebServerForm::SslHttpAppSrvHttpAcceptFilter(TObject *Sender, TObject *Client,
                                                              TIcsSessIpInfo &SessIpInfo, bool &Allowed)
{
    Display(FormatDateTime("HH:NN:SS", Now()) + " New Remote Client: " + SessIpInfo.RemoteAddr +
            ", Server: " + IcsFmtIpv6AddrPort(SessIpInfo.LocalAddr, SessIpInfo.LocalPort));
}

void __fastcall TWebServerForm::SslHttpAppSrvClientConnect(TObject *Sender, TObject *Client, System::Word Error)
{
    TMyHttpConnection *ClientCnx = static_cast<TMyHttpConnection *>(Client);
    ClientCnx->WSessionCookie = "OverbyteIcsWebAppServer" + SslHttpAppSrv->Port;
    ClientCnx->OnBgException = ClientBgException;
}

void __fastcall TWebServerForm::SslHttpAppSrvDisplay(TObject *Sender, const String Msg)
{
    Display(Msg);
}

// Builds a W3C log line, like Microsoft IIS, and displays it; a real server would write a log file.
void __fastcall TWebServerForm::SslHttpAppSrvAfterAnswer(TObject *Sender, TObject *Client)
{
    TMyHttpConnection *RemoteClient = static_cast<TMyHttpConnection *>(Client);
    if (RemoteClient->WebLogIdx < 0)
        return;   // no logging
    String Info = FormatDateTime(ISODateMask L" " ISOTimeMask, Now());
    try {
        // The remote host name instead of the IP address if there is one.
        const String Host = RemoteClient->CPeerHostName.IsEmpty() ? RemoteClient->CPeerAddr
                                                                  : RemoteClient->CPeerHostName;
        const String NewParams = RemoteClient->Params.IsEmpty() ? String("-") : RemoteClient->Params;
        String NewUser = SpaceToPlus(RemoteClient->AuthUserName);
        if (NewUser.IsEmpty())
            NewUser = "-";
        const __int64 CurRead = RemoteClient->ReadCount - RemoteClient->CLastRead;
        const __int64 CurWrite = RemoteClient->WriteCount - RemoteClient->CLastWrite;
        const __int64 Duration = IcsElapsedMsecs64(RemoteClient->CStartTick);
        // #Fields: date time s-sitename s-computername s-ip cs-method cs-uri-stem cs-uri-query
        //   s-port cs-username c-ip cs-version cs(User-Agent) cs(Referer) cs-host sc-status
        //   sc-bytes cs-bytes time-taken
        Info += " " + RemoteClient->HostTag + " " + FSrvCompName + " " + RemoteClient->CServerAddr + " " +
                RemoteClient->Method + " " + SpaceToPlus(RemoteClient->Path) + " " + SpaceToPlus(NewParams) +
                " " + RemoteClient->CServerPort + " " + NewUser + " " + Host + " " + RemoteClient->Version +
                " " + SpaceToPlus(RemoteClient->RequestUserAgent) + " " + SpaceToPlus(RemoteClient->RequestReferer) +
                " " + RemoteClient->RequestHost + " " + IntToStr(RemoteClient->AnswerStatus) +
                " " + IntToStr(CurWrite) + " " + IntToStr(CurRead) + " " + IntToStr(Duration);
    }
    catch (Exception &E) {
        Display("AfterAnswer Error - " + Info + " - " + IcsGetExceptMess(&E));
    }
    Display("Answer Log - " + Info);
    RemoteClient->CLastRead = RemoteClient->ReadCount;   // ready for the next request
}

// Called before a page that needs authentication: find the password for AuthUserName.
void __fastcall TWebServerForm::SslHttpAppSrvAuthGetPassword(TObject *Sender, TObject *Client, String &Password)
{
    TMyHttpConnection *ClientCnx = static_cast<TMyHttpConnection *>(Client);
    Display("AuthGetPassword for \"" + ClientCnx->Path + "\" AuthType is \"" +
            AuthTypesToString(ClientCnx->AuthTypes) + "\" for Username: " + ClientCnx->AuthUserName);
    // A real server would look the user name up in a database to get the password.
    if (ClientCnx->AuthUserName == "test")   // hardcoded for testing
        Password = "password";
}

// Called before every page, so any page can be protected by authentication.
void __fastcall TWebServerForm::SslHttpAppSrvAuthGetType(TObject *Sender, TObject *Client)
{
    TMyHttpConnection *ClientCnx = static_cast<TMyHttpConnection *>(Client);
    ClientCnx->AuthTypes = TAuthenticationTypes();
    if (ClientCnx->Path.Pos("/private/") == 1) {   // every file in /private needs authentication
        ClientCnx->AuthTypes = TAuthenticationTypes() << atBasic << atDigest << atDigestSha2;
        ClientCnx->AuthRealm = "Enter Password";
    }
}

void __fastcall TWebServerForm::SslHttpAppSrvAuthResult(TObject *Sender, TObject *Client, bool Success)
{
    TMyHttpConnection *ClientCnx = static_cast<TMyHttpConnection *>(Client);
    Display("Authentication result " + String(Success ? "OK" : "failed") + " with type " +
            HttpAuthTypeNames[ClientCnx->AuthGetMethod()] + ", Username: " + ClientCnx->AuthUserName +
            " for " + ClientCnx->Path);
}

// Logs each request, and its headers if asked, before authentication and the method events.
void __fastcall TWebServerForm::SslHttpAppSrvBeforeProcessRequest(TObject *Sender, TObject *Client)
{
    TMyHttpConnection *ClientCnx = static_cast<TMyHttpConnection *>(Client);
    const String Protocol = ClientCnx->RequestProtocol + "://";
    ClientCnx->CStartTick = IcsGetTickCount64();
    ClientCnx->CLastWrite = ClientCnx->WriteCount;

    String Request = ClientCnx->Method + " " + ClientCnx->Path;
    if (!ClientCnx->Params.IsEmpty())
        Request += "?" + ClientCnx->Params;
    Display("[" + FormatDateTime("HH:NN:SS", Now()) + " " + ClientCnx->CPeerAddr + "] " +
            ClientCnx->HostTag + " " + Request);

    if (DisplayHeaderCheckBox->Checked) {
        Display("HDR0) " + Request + " " + ClientCnx->Version);
        for (int I = 0; I < ClientCnx->RequestHeader->Count; I++)
            Display("HDR" + IntToStr(I + 1) + ") " + ClientCnx->RequestHeader->Strings[I]);
    }

    // An absolute URL: strip off the protocol and host.
    const String Path = ClientCnx->Path;
    if (Path.Pos(Protocol) == 1) {
        for (int I = Protocol.Length() + 1; I <= Path.Length(); I++) {
            if (Path[I] == L'/') {
                Display("Found and Trimmed Absolute URL: " + Path);
                ClientCnx->Path = Path.SubString(I, 999);
                break;
            }
        }
    }
}

// Something bad happened; it may have crashed the server.
void __fastcall TWebServerForm::SslHttpAppSrvBgException(TObject *Sender, Exception *E, bool &CanClose)
{
    Display("Exception processing page - " + E->ClassName() + ": " + E->Message);
    CanClose = false;   // let the server continue to accept connections
}

void __fastcall TWebServerForm::ClientBgException(TObject *Sender, Exception *E, bool &CanClose)
{
    Display("Exception processing page - " + E->ClassName() + ": " + E->Message);
    CanClose = true;   // shut the client down
}

// Customises the body of the server's standard error responses (404 only here).
void __fastcall TWebServerForm::SslHttpAppSrvHttpCustomError(TObject *Sender, TObject *Client, int ErrNr,
                                                             const String Info, String &Body)
{
    Display("CustomError Event: ErrNr" + IntToStr(ErrNr) + ", Param: " + Info);
    if (ErrNr != 404)
        return;
    Body = "<HTML><HEAD><TITLE>Custom 404 Not Found</TITLE></HEAD>"
           "<BODY><H1>Custom 404 Not Found</H1>The requested URL " + TextToHtmlText(Info) +
           " was not found on this server.<P></BODY></HTML>\r\n";
}

void __fastcall TWebServerForm::SslHttpAppSrvHttpRequestDone(TObject *Sender, TObject *Client)
{
    TMyHttpConnection *ClientCnx = static_cast<TMyHttpConnection *>(Client);
    Display("[" + FormatDateTime("HH:NN:SS", Now()) + " " + ClientCnx->CPeerAddr + "] Request Completed, Status " +
            IntToStr(ClientCnx->AnswerStatus) + "\r\n");
}

void __fastcall TWebServerForm::SslHttpAppSrvHttpRespHdr(TObject *Sender, TObject *Client, const String Header)
{
    if (!DisplayHeaderCheckBox->Checked)
        return;
    TMyHttpConnection *ClientCnx = static_cast<TMyHttpConnection *>(Client);
    Display("[" + FormatDateTime("HH:NN:SS", Now()) + " " + ClientCnx->CPeerAddr + "] Response Headers");
    Display(Header);
}

// Shared by GET, HEAD, POST, PUT and PATCH, after authentication has finished (or failed
// with 401). The default hgSendDoc makes the server send the disk file matching the path;
// a page built here is answered with an Answer method.
void __fastcall TWebServerForm::SslHttpAppSrvProcDocument(TObject *Sender, TObject *Client, THttpGetFlag &Flags)
{
    TMyHttpConnection *ClientCnx = static_cast<TMyHttpConnection *>(Client);

    // Authentication is starting: no pages yet.
    if (Flags == hg401)
        return;

    // Some hosts only redirect.
    if (ClientCnx->WebRedirectStat != 0) {
        Flags = hgWillSendMySelf;
        ClientCnx->AnswerRedirect(ClientCnx->WebRedirectStat, ClientCnx->WebRedirectURL);
        return;
    }

    // Most special pages are URL handlers using templates; these are built in code.
    if (ClientCnx->RequestMethod == httpMethodGet && ClientCnx->HostTag == WebApp) {
        if (CompareText(ClientCnx->Path, "/demo.html") == 0)
            CreateVirtualDocument_Demo(ClientCnx, Flags);
        else if (CompareText(ClientCnx->Path, "/favicon.ico") == 0) {
            const TBytes ImageTB = IcsResourceGetTB("ICSICON16", RT_RCDATA);
            if (ImageTB.Length > 0)
                ClientCnx->AnswerBodyTB(Flags, "200 OK", "image/icon", "", ImageTB);
            else
                ClientCnx->Answer404();
        }
    }
}

// /demo.html; most of its links are pages of OverbyteIcsSslMultiWebServ that this basic sample lacks.
void TWebServerForm::CreateVirtualDocument_Demo(TMyHttpConnection *ClientCnx, THttpGetFlag &Flags)
{
    ClientCnx->AnswerString(Flags, "", "", "",
        "<HTML><HEAD><TITLE>ICS WebServer Demo - Menu</TITLE></HEAD><BODY>"
        "<H2>ICS WebAppServer Demo Menu</H2>"
        "<H3>" + Caption + "</H3>" +
        "<p>" + IcsReportOpenSSLVer() + "</p>" +
        "<p></p><p>"
        "<A HREF=\"/time.html\">Server time</A><BR>"
        "<A HREF=\"/template.html\">Template demo</A><BR>"
        "<A HREF=\"/appindex.html\">Application Web Server session demo</A><BR>"
        "<A HREF=\"/form.html\">Data entry</A><BR>"
        "<A HREF=\"/uploadfile.html\">Upload a file using POST</A><BR>"
        "<A HREF=\"/postinfo.html?GetParam=Hello\">Parameters Display</A><BR>"
        "<A HREF=\"/mailer.html\">Send Email Form</A><BR>"
        "<A HREF=\"/websocketclient.html\">WebSocket Client Echo Test</A><BR>"
        "<A HREF=\"/redir.html\">Redirection</A><BR>"
        "<A HREF=\"/myip.html\">Show client IP and Host Name</A><BR>"
        "<A HREF=\"/DemoBasicAuth.html\">Password protected page</A> (Basic method)<BR>"
        "<A HREF=\"/DemoDigestAuth.html\">Password protected page</A> (Digest MD5 method)<BR>"
        "<A HREF=\"/DemoDigest2Auth.html\">Password protected page</A> (Digest SHA-256 method)<BR>"
        "<A HREF=\"/DemoDigestsAll.html\">Password protected page</A> (Digest MD5 or SHA-256 methods))<BR>"
        "<A HREF=\"/DemoAuthAll.html\">Password protected page</A> (method is selected by the browser)<BR>"
        "<A HREF=\"/DemoNtlmAuth.html\">Password protected page</A> (NTLM method, usercode=(Windows) user, password=(Windows) password)<BR>"
        "<A HREF=\"/\">Default document</A><BR>"
        "<A HREF=\"https://www.overbyte.be\">ICS Home page</A><BR>"
        "<A HREF=\"https://wiki.overbyte.eu/wiki/index.php/Main_Page\">ICS Wiki and Downloads</A><BR>"
        "</p></BODY></HTML>");
}

// Called for SSL connections; reports an error if the connection is refused.
void __fastcall TWebServerForm::SslHttpAppSrvSslHandshakeDone(TObject *Sender, System::Word ErrCode,
                                                              TX509Base *PeerCert, bool &Disconnect)
{
    TMyHttpConnection *ClientCnx = static_cast<TMyHttpConnection *>(Sender);
    Display(ClientCnx->CPeerAddr + " - " + ClientCnx->HostTag + " " + ClientCnx->SslHandshakeRespMsg);

    // A real server would check the client certificate's common name or issuer and maybe
    // set Disconnect.
    if (PeerCert && PeerCert->IsCertLoaded)
        Display(ClientCnx->CPeerAddr + " - " + ClientCnx->HostTag +
                " Client certificate received, should we trust client?\r\n" + PeerCert->CertMainInfo());
}

/* ---- WebSocket servers: /WebSocket/Echo and /WebSocket/EchoPing ---- */

void __fastcall TWebServerForm::ClientWSLogEvent(TObject *Sender, const String Msg)
{
    Display(Msg);
}

void __fastcall TWebServerForm::ClientWSHandshakeEvent(THttpWSSrvConn *Client, bool &OK, String &WelcomeMsg)
{
    OK = false;
    if (CompareText(Client->WSReqPage, "/WebSocket/Echo") == 0) {
        OK = true;
        WelcomeMsg = "Welcome to ICS WebSocket echo server, all messages will be echoed.";
    }
    else if (CompareText(Client->WSReqPage, "/WebSocket/EchoPing") == 0) {
        OK = true;
        WelcomeMsg = "Welcome to ICS WebSocket echo/ping server, all messages will be echoed.";
        Client->WSPingEnabled = true;
    }
    Display("WebSocket " + Client->CPeerAddr + ": Request for " + Client->WSReqPage +
            (OK ? " Accepted" : " Rejected"));
}

// Called once, when the WebSocket is ready for traffic.
void __fastcall TWebServerForm::ClientWSReadyEvent(THttpWSSrvConn *Client)
{
    Display("Websocket " + Client->CPeerAddr + ": Ready for " + Client->WSReqPage);
}

void __fastcall TWebServerForm::ClientWSFrameRcvdEvent(THttpWSSrvConn *Client, const String APacket,
                                                       TWebSocketReceivedFrame *&AFrame)
{
    if (!AFrame || AFrame->Kind == wsfkPong)
        return;
    Display("Websocket " + Client->CPeerAddr + ": " + Client->WSReqPage + " " + GetWSFrameKind(AFrame->Kind) +
            " received, length: " + IntToStr(AFrame->DataBytes));
    const bool Echo = CompareText(Client->WSReqPage, "/WebSocket/Echo") == 0 ||
                      CompareText(Client->WSReqPage, "/WebSocket/EchoPing") == 0;
    if (!Echo)
        return;
    if (AFrame->Kind == wsfkBin) {
        AFrame->Data->Position = 0;
        Client->WSSendBinaryStream(nullptr, AFrame->Data);
    }
    else if (AFrame->Kind == wsfkText)
        Client->WSSendText(nullptr, APacket);
}

void __fastcall TWebServerForm::ClientWSFrameSentEvent(THttpWSSrvConn *Client, TWebSocketOutgoingFrame *&AFrame)
{
    if (!AFrame || AFrame->Kind == wsfkPing)
        return;
    Display("Websocket " + Client->CPeerAddr + ": " + Client->WSReqPage + " " + GetWSFrameKind(AFrame->Kind) + " sent");
}

void __fastcall TWebServerForm::ClientWSDisconnectedEvent(TObject *Sender)
{
    THttpWSSrvConn *Client = static_cast<THttpWSSrvConn *>(Sender);
    Display("Websocket " + Client->CPeerAddr + ": " + Client->WSReqPage + " Closing, Total Frames " +
            IntToStr(Client->WSFrameCounter));
}
