/*
  ICS Snippets - C++Builder translation of the Delphi demo OverbyteIcsSnippets1.pas
  (demos-delphi-vcl), ICS V9.8.

  Small self-contained samples of the ICS high level components: file copying, FTP
  upload and download, HTTP download, REST requests, form uploads, IP stream logging
  (local and remote sockets), WebSocket client and mail queue.

  Each snippet is deliberately self-contained so it can be copied into another
  application, which is why the component setup is repeated rather than shared.
  Use DirTemp (default c:\temp-ics) as a scratch directory; several snippets use
  files downloaded by an earlier one.

  How this C++ version differs from the Delphi original, apart from syntax:
  - Components are created with no Owner and held in std::unique_ptr, so they are
    freed on every exit path (Delphi: Create(Self) then FreeAndNil in finally).
  - TButtonBusy sets the completion caption and re-enables a snippet's button on every
    exit path (Delphi: finally).
  - The three async wait loops are one helper, WaitWhileRunning, which also sleeps
    until a message arrives instead of spinning on ProcessMessages.
  - Delphi's bare try/except becomes catch (Exception &E), which catches everything ICS
    and the RTL raise, but not standard C++ exceptions such as std::bad_alloc.
  - Commented-out Delphi property lines are listed as optional properties in comments.
*/

#include <vcl.h>
#pragma hdrstop

#include <memory>

#include "OverbyteIcsSnippets1.h"
#include "OverbyteIcsUrl.hpp"
#include "OverbyteIcsUtils.hpp"
#include "OverbyteIcsTicks64.hpp"
#include "OverbyteIcsFtpMulti.hpp"
#include "OverbyteIcsHttpMulti.hpp"
#include "OverbyteIcsSslHttpRest.hpp"
#include "OverbyteIcsIpStreamLog.hpp"
#include "OverbyteIcsSuperObject.hpp"

#pragma package(smart_init)
#pragma resource "*.dfm"

TSnippets *Snippets;

namespace {

// Disables a snippet's button while it runs. When the snippet's scope ends, however it
// ends (normal return, early return or exception), it sets the optional completion caption
// and re-enables the button: the Delphi demo does both in its finally blocks.
class TButtonBusy
{
public:
    explicit TButtonBusy(TButton *Btn, TLabel *Progress = nullptr, const String &DoneCaption = String())
        : FBtn(Btn), FProgress(Progress), FDoneCaption(DoneCaption) { FBtn->Enabled = false; }
    ~TButtonBusy()
    {
        if (FProgress)
            FProgress->Caption = FDoneCaption;
        FBtn->Enabled = true;
    }
    TButtonBusy(const TButtonBusy &) = delete;
    TButtonBusy &operator=(const TButtonBusy &) = delete;
private:
    TButton *FBtn;
    TLabel *FProgress;
    String FDoneCaption;
};

} // namespace

__fastcall TSnippets::TSnippets(TComponent *Owner)
    : TForm(Owner)
{
}

void __fastcall TSnippets::FormCreate(TObject *Sender)
{
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
    ReportMemoryLeaksOnShutdown = (DebugHook != 0);
}

void TSnippets::AddLogText(const String &Line)
{
    LogWin->Lines->Add(Line);
}

// The IP stream logging and WebSocket components are async, so those snippets fake a
// sync method by pumping messages here until Abort is clicked, the application closes
// or TimeoutMins elapses. OnIdle, if given, runs once per pass after the messages.
void TSnippets::WaitWhileRunning(int TimeoutMins, const std::function<void()> &OnIdle)
{
    const __int64 trgTimerEnd = IcsGetTrgMins64(TimeoutMins);
    while (!FAbortFlag) {
        // Sleep until a message is waiting or 50 ms pass, rather than spinning a CPU core.
        MsgWaitForMultipleObjectsEx(0, nullptr, 50, QS_ALLINPUT, MWMO_INPUTAVAILABLE);
        Application->ProcessMessages();
        if (Application->Terminated || IcsTestTrgTick64(trgTimerEnd))
            break;
        if (OnIdle)
            OnIdle();
    }
}

/* onXferEvent is used by the FTP, HTTP and file copying components for logging and
   progress information, all optional and the components will work without this event.
   But it means you can see what is happening, with varying levels of logging. Also
   allows long running operations to be cancelled by clicking the Abort button, by
   setting Cancel, which ICS passes by reference. */
void __fastcall TSnippets::onXferEvent(TIcsCopyLogLevel LogLevel, String Info, bool &Cancel)
{
    if (LogLevel == LogLevelInfo || LogLevel == LogLevelFile) {
        AddLogText(Info);
        LabelProgress->Caption = Info;
    }
    if (LogLevel == LogLevelProg)
        LabelProgress->Caption = Info.IsEmpty() ? String() : "Progress: " + Info;
    if (LogLevel == LogLevelDiag && ShowDiags->Checked)
        AddLogText(Info);
    if (FAbortFlag)
        Cancel = true;
}

void __fastcall TSnippets::doAbortClick(TObject *Sender)
{
    FAbortFlag = true;
    AddLogText("Abort Clicked");
}

void __fastcall TSnippets::doClearLogClick(TObject *Sender)
{
    LogWin->Lines->Clear();
}

void __fastcall TSnippets::doCloseClick(TObject *Sender)
{
    Close();
}

/* Snippet: View Local Directories - print a directory file listing, optionally
   including all sub-directories, with local or UTC time stamps. This function is used
   by most of the file transfer methods to determine if local files already exist so
   they can be replaced if older or transfers skipped. The snippet will list the default
   temporary directory DirTemp.
   The full file transfer sample is OverbyteIcsXferTst. */
void __fastcall TSnippets::doFileDirViewClick(TObject *Sender)
{
    const String myfilepath = IncludeTrailingPathDelimiter(DirTemp->Text);

    TButtonBusy busy(doFileDirView);
    try {
        std::unique_ptr<TIcsFileCopy> FileCopyClient(new TIcsFileCopy(nullptr));
        AddLogText("Directory Path: " + myfilepath);
        // sub-dirs, UTC stamps, dir names
        AddLogText(FileCopyClient->DispLocFiles(myfilepath, "*/*", FCTypeAllDir, true, false, true));
    }
    catch (Exception &E) {
        AddLogText("Directory Error - " + IcsGetExceptMess(&E));
    }
}

/* Snippet: File Copy One File - copy a single file, optionally replacing an existing
   file if older. Safe Copy means the file is copied with a temporary name and renamed
   upon completion to avoid losing an existing file upon failure. Copy progress
   information is available in onXferEvent. CopyOneFile returns TaskResFail if the file
   or destination are not found with the error in ReqResponse, TaskResOKNew for a
   successful copy, TaskResOKNone if the destination file was the same and the copy was
   skipped, TaskResAbort if the method was aborted.
   The snippet copies a file downloaded into DirTemp by FTP Download One File.
   The full file transfer sample is OverbyteIcsXferTst. */
void __fastcall TSnippets::doFileCopyOneFileClick(TObject *Sender)
{
    const String myfilesource = IncludeTrailingPathDelimiter(DirTemp->Text) + "speed50meg.zip";
    const String myfiletarget = IncludeTrailingPathDelimiter(DirTemp->Text) + "copiedfiles\\speed50meg.zip";
    const TIcsFileCopyRepl myfilereplace = FCReplAlways;   // or FCReplNever, FCReplNewer

    TButtonBusy busy(doFileCopyOneFile, LabelProgress, "File Copy Completed");
    FAbortFlag = false;
    LabelProgress->Caption = "";
    try {
        std::unique_ptr<TIcsFileCopy> FileCopyClient(new TIcsFileCopy(nullptr));
        FileCopyClient->CopyEvent = onXferEvent;
        __int64 newfsize = 0;
        const TIcsTaskResult taskres =
            FileCopyClient->CopyOneFile(myfilesource, myfiletarget, myfilereplace, true, newfsize);  // SafeCopy=true
        AddLogText("Task Result: " + IcsGetTaskResName(taskres));
        AddLogText(FileCopyClient->ReqResponse);
    }
    catch (Exception &E) {
        AddLogText("Copying Error - " + IcsGetExceptMess(&E));
    }
}

/* Snippet: File Copy Multiple Files - copy multiple files, optionally replacing any
   existing files if older. SubDirs means all lower level directories will be copied,
   EmptyDirs means empty directories will be created, CopyType determines how files are
   selected for copying: FCTypeSingle just one, FCTypeMaskDir uses a mask in SrcFName
   like *.zip, FCTypeArchDir selects files by archive flag, FCTypeAllDir copies all
   files, FCTypeDates by date range between CopyLoDT and CopyHiDT. DelDone means the
   source file will be deleted after being copied, DelOldTar means directories will be
   synchronised so target files no longer in the source directory will be deleted,
   IgnorePaths is a comma delimited list of paths that will skip being copied, Safe Copy
   means the file is copied with a temporary name and renamed upon completion. Copy
   progress information is available in onXferEvent. SelCopyFiles returns TaskResFail
   if the source or destination directories are not found with the error in ReqResponse,
   TaskResOKNew for a successful copy, TaskResOKNone if the destination files were the
   same and all copying was skipped, TaskResAbort if the method was aborted.
   SelCopyFiles will copy entire volumes of files, hundreds of thousands, memory
   permitting, provided they are not locked open by the OS.
   The snippet copies all files downloaded into DirTemp into the sub-directory
   copiedfiles, excluding files in that directory.
   The full file transfer sample is OverbyteIcsXferTst. */
void __fastcall TSnippets::doFileCopyMultiFilesClick(TObject *Sender)
{
    const String mypathsource = IncludeTrailingPathDelimiter(DirTemp->Text);
    const String mypathtarget = IncludeTrailingPathDelimiter(DirTemp->Text) + "copiedfiles\\";
    const String myfilemask = "*.*";
    const TIcsFileCopyRepl myfilereplace = FCReplAlways;   // or FCReplNever, FCReplNewer

    TButtonBusy busy(doFileCopyMultiFiles, LabelProgress, "File Copy Completed");
    FAbortFlag = false;
    LabelProgress->Caption = "";
    try {
        std::unique_ptr<TIcsFileCopy> FileCopyClient(new TIcsFileCopy(nullptr));
        FileCopyClient->CopyEvent = onXferEvent;
        FileCopyClient->MultiDir = false;           // true copy multiple specific directories
        FileCopyClient->SrcDir = mypathsource;
        FileCopyClient->SrcFName = myfilemask;
        FileCopyClient->TarDir = mypathtarget;
        FileCopyClient->IgnorePaths = mypathtarget; // ignore copying from our target path
        // CopyType: FCTypeSingle, FCTypeMaskDir, FCTypeArchDir, FCTypeAllDir, FCTypeDates
        FileCopyClient->CopyType = FCTypeMaskDir;
        FileCopyClient->SubDirs = true;             // true copy all sub directories
        FileCopyClient->EmptyDirs = false;          // true copy empty directories
        FileCopyClient->DelDone = false;            // true delete source file after copy
        FileCopyClient->DelOldTar = false;          // true delete target files not in source directories
        FileCopyClient->Repl = myfilereplace;
        FileCopyClient->ReplRO = true;              // true, replace read only files
        FileCopyClient->Safe = false;               // true, copy file with TMP extension, rename when done
        FileCopyClient->IgnoreFileExt = "tmp;ftp";
        const TIcsTaskResult taskres = FileCopyClient->SelCopyFiles(FtpOnlyCheck->Checked);  // main file copy function
        AddLogText("Task Result: " + IcsGetTaskResName(taskres));
        AddLogText(FileCopyClient->ReqResponse);
        if (FtpOnlyCheck->Checked)
            AddLogText("Skipped Download on Request, untick Only check box");
    }
    catch (Exception &E) {
        AddLogText("Copying Error - " + IcsGetExceptMess(&E));
    }
}

/* Snippet: FTP View Directories - print a remote directory listing from an FTP site,
   optionally including all sub-directories. This function is used by most of the FTP
   methods to select files to download or upload, comparing with local files so they
   can be replaced if older or transfers skipped.
   FtpType is FtpTypeNone for no SSL/TLS with port 21, FtpTypeAuthSslBoth to use SSL/TLS
   with the AUTH command with port 21 or FtpTypeConnSslBoth to force SSL/TLS with port
   990. DispFtpDir returns TaskResFail if the FTP site can not be accessed with the error
   in ReqResponse, and TaskResOKNew for a directory listing.
   The snippet accesses the FTP site ics.ftptest.org, which uses the ICS FTP server with
   anonymous authentication, and lists all the files on the site.
   The full file transfer sample is OverbyteIcsXferTst. */
void __fastcall TSnippets::doFtpViewDirsClick(TObject *Sender)
{
    const String myftppath = "/";                   // FTP server path for directories
    const String myftphost = "ics.ftptest.org";     // supports IPv4 and IPv6
    const String myftpusername = "anonymous";       // no uploads
    const String myftppassword = "icssnippets";
    const TFtpType myftptype = FtpTypeAuthSslBoth;  // or FtpTypeNone, FtpTypeConnSslBoth (no SSL or only SSL on port 990)

    TButtonBusy busy(doFtpViewDirs, LabelProgress, "FTP Completed");
    FAbortFlag = false;
    LabelProgress->Caption = "";
    try {
        std::unique_ptr<TIcsFtpMulti> FtpMultiClient(new TIcsFtpMulti(nullptr));
        FtpMultiClient->CopyEvent = onXferEvent;

        // essential FTP parameters
        FtpMultiClient->SocketFamily = sfIPv4;        // or sfIPv6 or sfAny
        FtpMultiClient->HostName1 = myftphost;
        FtpMultiClient->FtpType = myftptype;
        FtpMultiClient->UserName = myftpusername;
        FtpMultiClient->PassWord = myftppassword;
        FtpMultiClient->MaxAttempts = 2;              // logon attempts, may try IPv6 then IPv4
        FtpMultiClient->FailRepeat = 2;               // retries for failed transfers
        FtpMultiClient->PassiveX = true;              // must be after connection type
        FtpMultiClient->FtpSslVerMethod = ftpSslVerBundle;  // or ftpSslVerNone to skip checking certificates
        FtpMultiClient->FtpSslReportChain = false;    // true to list SSL certificates
        FtpMultiClient->SrcDir = myftppath;
        FtpMultiClient->SubDirs = true;               // true list all sub-directories
        FtpMultiClient->BulkMode = BulkModeDownload;  // required

        // connect, login, get features, get directories, log-off, single method
        String dirlisting;
        const TIcsTaskResult taskres = FtpMultiClient->DispFtpDir(dirlisting);
        AddLogText("Task Result: " + IcsGetTaskResName(taskres));
        AddLogText(dirlisting);
    }
    catch (Exception &E) {
        AddLogText("FTP Error - " + IcsGetExceptMess(&E));
    }
}

/* Snippet: FTP Download One File - download a single file from an FTP site, although
   extra single files can easily be added, optionally replacing an existing file if
   older. FtpDownOneFile changes to the correct directory automatically.
   FtpLogon and FtpDownOneFile return TaskResFail if the FTP site can not be accessed
   with the error in ReqResponse, TaskResOKNew for a successful download, or
   TaskResOKNone if the download is the same as local and skipped.
   The snippet accesses ics.ftptest.org with anonymous authentication and downloads
   speed50meg.zip to DirTemp.
   The full file transfer sample is OverbyteIcsXferTst. */
void __fastcall TSnippets::doFtpDownOneFileClick(TObject *Sender)
{
    const String myftppath = "/testing";            // FTP server path for file
    const String myftpfile = "speed50meg.zip";      // FTP file to download
    const String myfiletarget = IncludeTrailingPathDelimiter(DirTemp->Text) + myftpfile;  // where we download to
    const String myftphost = "ics.ftptest.org";     // supports IPv4 and IPv6
    const String myftpusername = "anonymous";       // no uploads
    const String myftppassword = "icssnippets";
    const TFtpType myftptype = FtpTypeAuthSslBoth;  // or FtpTypeNone, FtpTypeConnSslBoth
    const TIcsFileCopyRepl myfilereplace = FCReplAlways;  // or FCReplNever, FCReplNewer

    TButtonBusy busy(doFtpDownOneFile, LabelProgress, "FTP Completed");
    FAbortFlag = false;
    LabelProgress->Caption = "";
    // created outside the try, like the Delphi demo, so FtpLogoff below also runs after an ICS error
    std::unique_ptr<TIcsFtpMulti> FtpMultiClient(new TIcsFtpMulti(nullptr));
    try {
        FtpMultiClient->CopyEvent = onXferEvent;

        // essential FTP parameters
        FtpMultiClient->SocketFamily = sfIPv4;        // or sfIPv6 or sfAny
        FtpMultiClient->HostName1 = myftphost;
        FtpMultiClient->FtpType = myftptype;
        FtpMultiClient->UserName = myftpusername;
        FtpMultiClient->PassWord = myftppassword;
        FtpMultiClient->MaxAttempts = 2;              // logon attempts, may try IPv6 then IPv4
        FtpMultiClient->FailRepeat = 2;               // retries for failed transfers
        FtpMultiClient->PassiveX = true;              // must be after connection type
        FtpMultiClient->FtpSslVerMethod = ftpSslVerBundle;  // or ftpSslVerNone to skip checking certificates
        FtpMultiClient->FtpSslReportChain = false;    // true to list SSL certificates
        FtpMultiClient->SrcDir = "/";                 // required
        FtpMultiClient->BulkMode = BulkModeDownload;  // required

        // connect, login, get features, then download; more single downloads could follow
        TIcsTaskResult taskres = FtpMultiClient->FtpLogon();
        if (taskres == TaskResOKNew)
            taskres = FtpMultiClient->FtpDownOneFile(myftppath, myftpfile, myfiletarget, myfilereplace);
        AddLogText("Task Result: " + IcsGetTaskResName(taskres));
        AddLogText(FtpMultiClient->ReqResponse);
    }
    catch (Exception &E) {
        AddLogText("FTP Error - " + IcsGetExceptMess(&E));
    }
    FtpMultiClient->FtpLogoff();
}

/* Snippet: FTP Download Multiple Files - downloads multiple files from an FTP site,
   optionally including all sub-directories, optionally replacing an existing file if
   older. FtpDownload does everything: logs onto the FTP site, gets remote and local
   directory listings, compares them for files that need to be downloaded, then does
   that, changing directories automatically. It will potentially download thousands of
   files from dozens of directories. CopyType determines how files are selected:
   FCTypeSingle just one, FCTypeMaskDir uses a mask in SrcFName like *.zip, FCTypeAllDir
   downloads all files. FtpDownload returns TaskResFail if the FTP site can not be
   accessed with the error in ReqResponse, TaskResOKNew for a successful download, or
   TaskResOKNone if the download is the same as local and skipped.
   The snippet accesses ics.ftptest.org with anonymous authentication and downloads all
   the files matching *.htm in the test directory to DirTemp.
   The full file transfer sample is OverbyteIcsXferTst. */
void __fastcall TSnippets::doFtpDownMultiFilesClick(TObject *Sender)
{
    const String myftppath = "/test";               // FTP server path for files
    const String myftpfile = "*.htm";               // FTP files to download
    const String myfiletarget = IncludeTrailingPathDelimiter(DirTemp->Text);  // where we download to
    const String myftphost = "ics.ftptest.org";     // supports IPv4 and IPv6
    const String myftpusername = "anonymous";       // no uploads
    const String myftppassword = "icssnippets";
    const TFtpType myftptype = FtpTypeAuthSslBoth;  // or FtpTypeNone, FtpTypeConnSslBoth
    const TIcsFileCopyRepl myfilereplace = FCReplAlways;  // or FCReplNever, FCReplNewer

    TButtonBusy busy(doFtpDownMultiFiles, LabelProgress, "FTP Completed");
    FAbortFlag = false;
    LabelProgress->Caption = "";
    try {
        std::unique_ptr<TIcsFtpMulti> FtpMultiClient(new TIcsFtpMulti(nullptr));
        FtpMultiClient->CopyEvent = onXferEvent;

        // essential FTP parameters
        FtpMultiClient->SocketFamily = sfIPv4;        // or sfIPv6 or sfAny
        FtpMultiClient->HostName1 = myftphost;
        FtpMultiClient->FtpType = myftptype;
        FtpMultiClient->UserName = myftpusername;
        FtpMultiClient->PassWord = myftppassword;
        FtpMultiClient->MaxAttempts = 2;              // logon attempts, may try IPv6 then IPv4
        FtpMultiClient->FailRepeat = 2;               // retries for failed transfers
        FtpMultiClient->PassiveX = true;              // must be after connection type
        FtpMultiClient->FtpSslVerMethod = ftpSslVerBundle;  // or ftpSslVerNone to skip checking certificates
        FtpMultiClient->FtpSslReportChain = false;    // true to list SSL certificates
        FtpMultiClient->SrcDir = myftppath;
        FtpMultiClient->SrcFName = myftpfile;
        FtpMultiClient->TarDir = myfiletarget;
        FtpMultiClient->Repl = myfilereplace;
        // CopyType: FCTypeSingle, FCTypeMaskDir, FCTypeAllDir
        FtpMultiClient->CopyType = FCTypeMaskDir;
        FtpMultiClient->SubDirs = false;              // true look in sub-directories
        FtpMultiClient->DispLDir = true;              // display local file directory
        FtpMultiClient->DispRDir = true;              // display remote FTP directory
        FtpMultiClient->BulkMode = BulkModeDownload;  // required

        // connect, login, get features, list files, download files, logoff
        const TIcsTaskResult taskres = FtpMultiClient->FtpDownload(FtpOnlyCheck->Checked);
        AddLogText("Task Result: " + IcsGetTaskResName(taskres));
        AddLogText(FtpMultiClient->ReqResponse);
        if (FtpOnlyCheck->Checked)
            AddLogText("Skipped Download on Request, untick Only check box");
    }
    catch (Exception &E) {
        AddLogText("FTP Error - " + IcsGetExceptMess(&E));
    }
}

/* Snippet: FTP Upload One File - upload a single file to an FTP site, although extra
   single files can easily be added, optionally replacing an existing file if older.
   FtpUpOneFile changes directory automatically. FtpLogon and FtpUpOneFile return
   TaskResFail if the FTP site can not be accessed with the error in ReqResponse,
   TaskResOKNew for a successful upload, or TaskResOKNone if the upload is the same as
   remote and skipped.
   The snippet accesses ics.ftptest.org; you must request a login by email from
   delphi@magsys.co.uk for uploads. It uploads speed50meg.zip from DirTemp, previously
   downloaded with the FTP Download One File snippet.
   The full file transfer sample is OverbyteIcsXferTst. */
void __fastcall TSnippets::doFtpUpOneFileClick(TObject *Sender)
{
    const String myftppath = "/ftptest23";          // FTP server path for file
    const String myftpfile = "speed50meg.zip";      // FTP file to upload
    const String myfilesource = IncludeTrailingPathDelimiter(DirTemp->Text) + myftpfile;  // what we upload
    const String myftphost = "ics.ftptest.org";     // supports IPv4 and IPv6
    const String myftpusername = "ftptest23";
    const String myftppassword = "xxxxx";
    const TFtpType myftptype = FtpTypeAuthSslBoth;  // or FtpTypeNone, FtpTypeConnSslBoth
    const TIcsFileCopyRepl myfilereplace = FCReplNever;  // or FCReplAlways, FCReplNewer

    TButtonBusy busy(doFtpUpOneFile, LabelProgress, "FTP Completed");
    FAbortFlag = false;
    LabelProgress->Caption = "";
    // created outside the try, like the Delphi demo, so FtpLogoff below also runs after an ICS error
    std::unique_ptr<TIcsFtpMulti> FtpMultiClient(new TIcsFtpMulti(nullptr));
    try {
        FtpMultiClient->CopyEvent = onXferEvent;

        // essential FTP parameters
        FtpMultiClient->SocketFamily = sfIPv4;        // or sfIPv6 or sfAny
        FtpMultiClient->HostName1 = myftphost;
        FtpMultiClient->FtpType = myftptype;
        FtpMultiClient->UserName = myftpusername;
        FtpMultiClient->PassWord = myftppassword;
        FtpMultiClient->MaxAttempts = 2;              // logon attempts, may try IPv6 then IPv4
        FtpMultiClient->FailRepeat = 2;               // retries for failed transfers
        FtpMultiClient->PassiveX = true;              // must be after connection type
        FtpMultiClient->FtpSslVerMethod = ftpSslVerBundle;  // or ftpSslVerNone to skip checking certificates
        FtpMultiClient->FtpSslReportChain = false;    // true to list SSL certificates
        FtpMultiClient->TarDir = "/";                 // required
        FtpMultiClient->BulkMode = BulkModeUpload;    // required

        // connect, login, get features, then upload; more tasks could follow
        TIcsTaskResult taskres = FtpMultiClient->FtpLogon();
        if (taskres == TaskResOKNew)
            taskres = FtpMultiClient->FtpUpOneFile(myfilesource, myftppath, myftpfile, myfilereplace);
        AddLogText("Task Result: " + IcsGetTaskResName(taskres));
        AddLogText(FtpMultiClient->ReqResponse);
    }
    catch (Exception &E) {
        AddLogText("FTP Error - " + IcsGetExceptMess(&E));
    }
    FtpMultiClient->FtpLogoff();
}

/* Snippet: FTP Upload Multiple Files - upload multiple files to an FTP site, optionally
   replacing an existing file if older. FtpUpload does everything: logs onto the FTP
   site, gets remote and local directory listings, compares them for files that need to
   be uploaded, then does that, changing directories automatically. It will potentially
   upload thousands of files in dozens of directories. FtpUpload returns TaskResFail if
   the FTP site can not be accessed with the error in ReqResponse, TaskResOKNew for a
   successful upload, or TaskResOKNone if the upload is the same as remote and skipped.
   The snippet accesses ics.ftptest.org; you must request a login by email from
   delphi@magsys.co.uk for uploads. It uploads all files matching *.htm from DirTemp,
   previously downloaded with the FTP Download Multiple Files snippet.
   The full file transfer sample is OverbyteIcsXferTst. */
void __fastcall TSnippets::doFtpUpMultiFilesClick(TObject *Sender)
{
    const String myftppath = "/ftptest23";          // FTP server path for files
    const String myftpfile = "*.htm";               // file mask to upload
    const String myfilesource = IncludeTrailingPathDelimiter(DirTemp->Text);  // what we upload
    const String myftphost = "ics.ftptest.org";     // supports IPv4 and IPv6
    const String myftpusername = "ftptest23";
    const String myftppassword = "xxxxx";           // the Delphi demo has a real-looking password here
    const TFtpType myftptype = FtpTypeAuthSslBoth;  // or FtpTypeNone, FtpTypeConnSslBoth
    const TIcsFileCopyRepl myfilereplace = FCReplNever;  // or FCReplAlways, FCReplNewer

    TButtonBusy busy(doFtpUpMultiFiles, LabelProgress, "FTP Completed");
    FAbortFlag = false;
    LabelProgress->Caption = "";
    try {
        std::unique_ptr<TIcsFtpMulti> FtpMultiClient(new TIcsFtpMulti(nullptr));
        FtpMultiClient->CopyEvent = onXferEvent;

        // essential FTP parameters
        FtpMultiClient->SocketFamily = sfIPv4;        // or sfIPv6 or sfAny
        FtpMultiClient->HostName1 = myftphost;
        FtpMultiClient->FtpType = myftptype;
        FtpMultiClient->UserName = myftpusername;
        FtpMultiClient->PassWord = myftppassword;
        FtpMultiClient->MaxAttempts = 2;              // logon attempts, may try IPv6 then IPv4
        FtpMultiClient->FailRepeat = 2;               // retries for failed transfers
        FtpMultiClient->PassiveX = true;              // must be after connection type
        FtpMultiClient->FtpSslVerMethod = ftpSslVerBundle;  // or ftpSslVerNone to skip checking certificates
        FtpMultiClient->FtpSslReportChain = false;    // true to list SSL certificates
        // CopyType: FCTypeSingle, FCTypeMaskDir, FCTypeArchDir, FCTypeAllDir, FCTypeDates
        FtpMultiClient->CopyType = FCTypeMaskDir;
        FtpMultiClient->SrcDir = myfilesource;
        FtpMultiClient->SrcFName = myftpfile;
        FtpMultiClient->Repl = myfilereplace;
        FtpMultiClient->SubDirs = false;              // true look in sub-directories
        FtpMultiClient->DispLDir = true;              // display local file directory
        FtpMultiClient->DispRDir = true;              // display remote FTP directory
        FtpMultiClient->TarDir = myftppath;
        FtpMultiClient->BulkMode = BulkModeUpload;    // required

        // connect, login, get features, list files, upload files, log-off
        const TIcsTaskResult taskres = FtpMultiClient->FtpUpload(FtpOnlyCheck->Checked);
        AddLogText("Task Result: " + IcsGetTaskResName(taskres));
        AddLogText(FtpMultiClient->ReqResponse);
        if (FtpOnlyCheck->Checked)
            AddLogText("Skipped Upload on Request, untick Only check box");  // Delphi says "Download" here
    }
    catch (Exception &E) {
        AddLogText("FTP Error - " + IcsGetExceptMess(&E));
    }
}

/* Snippet: HTTP Download List of Files - downloads a list of files from a web site,
   optionally checking existing files if newer. Supports http and https. Download does
   everything: builds a remote listing of the files and a local directory listing,
   compares them for files that need to be downloaded, then does that. Download returns
   TaskResFail if the HTTP site can not be accessed with the error in ReqResponse,
   TaskResOKNew for a successful download, or TaskResOKNone if the download is the same
   as local files and skipped.
   The snippet downloads files from the Magenta Systems HTML test pages on the ICS web
   server at https://www.telecom-tariffs.co.uk/testing/ into DirTemp.
   The full file transfer sample is OverbyteIcsXferTst. */
void __fastcall TSnippets::doHttpDownListClick(TObject *Sender)
{
    // URLList takes one or more source URLs separated by CRLF
    const String myurllist =
        "https://www.telecom-tariffs.co.uk/testing/unicodedemo.htm\r\n"
        "https://www.telecom-tariffs.co.uk/testing/unicodedemo-mchars.htm\r\n"
        "https://www.telecom-tariffs.co.uk/testing/speed20meg.zip\r\n"
        "https://www.telecom-tariffs.co.uk/testing/speed50meg.zip\r\n";
    const String mytardir = IncludeTrailingPathDelimiter(DirTemp->Text);  // where to download
    const TIcsFileCopyRepl myfilereplace = FCReplAlways;  // or FCReplNever, FCReplNewer

    TButtonBusy busy(doHttpDownList, LabelProgress, "HTTP Completed");
    FAbortFlag = false;
    LabelProgress->Caption = "";
    try {
        std::unique_ptr<TIcsHttpMulti> HttpMultiClient(new TIcsHttpMulti(nullptr));
        HttpMultiClient->CopyEvent = onXferEvent;

        // essential HTTP parameters
        HttpMultiClient->SocketFamily = sfIPv4;       // or sfIPv6 or sfAny
        HttpMultiClient->UrlList = myurllist;
        HttpMultiClient->SrcMask = "*.*";             // optional source file mask to restrict downloads
        HttpMultiClient->DownDir = mytardir;          // directory for downloaded files
        HttpMultiClient->ParseHTML = false;           // if true, parse HTML page for links to files
        HttpMultiClient->ParseLevels = 0;             // how many page levels down to parse, 0 only top page
        HttpMultiClient->Repl = myfilereplace;
        HttpMultiClient->LogFiles = true;             // log each file downloaded
        HttpMultiClient->LogProt = ShowDiags->Checked;  // log HTTP protocol
        HttpMultiClient->LogLDir = false;             // log destination directory
        HttpMultiClient->LogRDir = true;              // log created HTTP directory
        HttpMultiClient->HttpSslVerMethod = httpSslVerBundle;  // or httpSslVerNone to skip checking certificates
        HttpMultiClient->SslReportChain = false;      // true to list SSL certificates

        // https://www.telecom-tariffs.co.uk/testing/ needs authentication
        HttpMultiClient->ServerAuth = httpAuthBasic;
        HttpMultiClient->Username = "test";
        HttpMultiClient->Password = "password";

        // list files, compare, download
        const TIcsTaskResult taskres = HttpMultiClient->Download(FtpOnlyCheck->Checked);
        AddLogText("Task Result: " + IcsGetTaskResName(taskres));
        AddLogText(HttpMultiClient->ReqResponse);
        if (FtpOnlyCheck->Checked)
            AddLogText("Skipped Download on Request, untick Only check box");
    }
    catch (Exception &E) {
        AddLogText("HTTP Error - " + IcsGetExceptMess(&E));
    }
}

/* Snippet: HTTP Download Linked Files - downloads multiple files from a web site by
   parsing HTML pages for links, comparing them against a file mask like *.zip and
   building a list. If ParseLevels is 1 or higher, pages found will also be parsed for
   links, so the list of files found can increase rapidly. Once the download list is
   complete, it is optionally checked against existing files to see if newer or can be
   skipped. Supports http and https. This parsing works very well for files
   automatically indexed by HTTP servers (if allowed), in particular CCTV cameras that
   index their memory cards for video recordings, allowing all new recordings to be
   downloaded automatically. Download returns TaskResFail if the HTTP site can not be
   accessed with the error in ReqResponse, TaskResOKNew for a successful download, or
   TaskResOKNone if the download is the same as local files and skipped.
   The snippet parses https://www.telecom-tariffs.co.uk/testing/ and should find three
   zip files that are downloaded into DirTemp.
   The full file transfer sample is OverbyteIcsXferTst. */
void __fastcall TSnippets::doHttpDownLinkedClick(TObject *Sender)
{
    // the server needs authentication; here the user name and password are put in the
    // URL before @, instead of setting ServerAuth, Username and Password
    const String myurl = "https://test:password@www.telecom-tariffs.co.uk/testing/";
    const String mysrcmask = "*.zip";
    const String mytardir = IncludeTrailingPathDelimiter(DirTemp->Text);  // where to download
    const TIcsFileCopyRepl myfilereplace = FCReplAlways;  // or FCReplNever, FCReplNewer

    TButtonBusy busy(doHttpDownLinked, LabelProgress, "HTTP Completed");
    FAbortFlag = false;
    LabelProgress->Caption = "";
    try {
        std::unique_ptr<TIcsHttpMulti> HttpMultiClient(new TIcsHttpMulti(nullptr));
        HttpMultiClient->CopyEvent = onXferEvent;

        // essential HTTP parameters
        HttpMultiClient->SocketFamily = sfIPv4;       // or sfIPv6 or sfAny
        HttpMultiClient->UrlList = myurl;             // one or more source URLs separated by CRLF
        HttpMultiClient->SrcMask = mysrcmask;         // optional source file mask to restrict downloads
        HttpMultiClient->DownDir = mytardir;          // directory for downloaded files
        HttpMultiClient->KeepPath = false;            // if true, use HTTP path for subdirs
        HttpMultiClient->KeepHost = false;            // if true, use HTTP host for subdir
        HttpMultiClient->ParseHTML = true;            // if true, parse HTML page for links to files
        HttpMultiClient->ParseLevels = 1;             // how many page levels down to parse, 0 only top page
        HttpMultiClient->Repl = myfilereplace;
        HttpMultiClient->LogFiles = true;             // log each file downloaded
        HttpMultiClient->LogProt = ShowDiags->Checked;  // log HTTP protocol
        HttpMultiClient->LogLDir = false;             // log destination directory
        HttpMultiClient->LogRDir = true;              // log created HTTP directory
        HttpMultiClient->HttpSslVerMethod = httpSslVerBundle;  // or httpSslVerNone to skip checking certificates
        HttpMultiClient->SslReportChain = false;      // true to list SSL certificates

        // parse pages, list files, compare, download
        const TIcsTaskResult taskres = HttpMultiClient->Download(FtpOnlyCheck->Checked);
        AddLogText("Task Result: " + IcsGetTaskResName(taskres));
        AddLogText(HttpMultiClient->ReqResponse);
        if (FtpOnlyCheck->Checked)
            AddLogText("Skipped Download on Request, untick Only check box");
    }
    catch (Exception &E) {
        AddLogText("HTTP Error - " + IcsGetExceptMess(&E));
    }
}

// progress event for the TSslHttpRest and TSslWebSocketCli components
void __fastcall TSnippets::onSslHttpRestProg(TObject *Sender, TLogOption LogOption, const String Msg)
{
    if (LogOption == loProgress)
        LabelProgress->Caption = Msg;
    else
        AddLogText(Msg);
}

/* Snippet: HTTP REST Json Request - makes an HTTP GET request to a REST server passing
   a UrlEncoded parameter and receiving a Json response which is parsed to extract
   specific data. Uses the TSslHttpRest component in sync mode, but it could be async
   with the result in the OnRestRequestDone event. Returns an HTTP status code, 200 for
   OK, 404 page not found, etc. RestParams will build Json, XML, UrlEncoded or comma
   list parameters, from strings, integers and TDateTime, or you can build them yourself
   and pass them raw. On success, ResponseRaw has a unicode string converted using
   IcsHtmlToStr according to the content, including entities like &pound; and &#9741;.
   If Json content is returned, ResponseJson is a SuperObject that may be parsed.
   TSslHttpRest descends from THttpCli, publishes all its properties and events, and
   bundles SSL configuration and certificate validation with a root bundle, SSL session
   caching, content compression, content code page decoding, persistent cookies, Json
   handling, logging and client SSL certificates.
   The snippet accesses https://jsonplaceholder.typicode.com which provides several
   datasets on which queries can be made.
   The full HTTP REST sample is OverbyteIcsHttpRestTst. */
void __fastcall TSnippets::doHttpRestReqClick(TObject *Sender)
{
    const String myurl = "https://jsonplaceholder.typicode.com/users";
    const String myusername = "Antonette";   // or Samantha

    TButtonBusy busy(doHttpRestReq, LabelProgress, "HTTP Completed");
    LabelProgress->Caption = "";
    try {
        std::unique_ptr<TSslHttpRest> SslHttpRest(new TSslHttpRest(nullptr));
        SslHttpRest->OnHttpRestProg = onSslHttpRestProg;

        // essential and common HTTP parameters; optional ones include SslReportChain,
        // SslRevocation, ServerAuth with Username and Password, AuthBearerToken,
        // HttpUploadStrat, ShowProgress and HttpMemStrategy
        SslHttpRest->SocketFamily = sfAnyIPv4;        // IPv4 and/or IPv6
        SslHttpRest->DebugLevel = DebugHdr;           // DebugNone, DebugConn, DebugParams, DebugSSL, DebugHdr, DebugBody, each logs more
        SslHttpRest->CertVerMethod = CertVerBundle;   // or CertVerNone if you don't care
        SslHttpRest->SslCliSecurity = sslCliSecDefault;

        // TRestParams builds Json, XML, UrlEncoded or comma list parameters
        SslHttpRest->RestParams->AddItem("username", myusername);
        SslHttpRest->RestParams->PContent = PContUrlencoded;   // lots of PContxx options
        const int StatCode = SslHttpRest->RestRequest(httpGET, myurl, false, "");  // sync request, no extra parameters
        AddLogText("HTTP Rest Request Response: " + IntToStr(StatCode));
        if (StatCode == 200) {
            AddLogText(SslHttpRest->ResponseRaw);   // anything; ResponseJson->AsString() for the Json alone

            // ResponseJson is a reference counted SuperObject interface, null unless the
            // response was Json; this server returns an array of users.
            // The Delphi demo assumes the Json array is there; this checks first.
            _di_ISuperObject json = SslHttpRest->ResponseJson;
            if (json && json->AsArray() && json->AsArray()->Length > 0)
                AddLogText("Address: " + json->AsArray()->O[0]->S["address"]);
        }
    }
    catch (Exception &E) {
        AddLogText("HTTP Error - " + IcsGetExceptMess(&E));
    }
}

/* Snippet: HTTP REST Download - makes an HTTP GET request to download a file, with
   optional resume of a partial download. Uses the TSslHttpRest component in sync mode,
   so RestParams could be set if needed, and saves the file with a specified path and
   name. Returns an HTTP status code, 200 for OK, 404 file not found, etc.
   The snippet accesses https://www.telecom-tariffs.co.uk, which uses the ICS web
   server, and downloads a 300MB file.
   The full HTTP REST sample is OverbyteIcsHttpRestTst. */
void __fastcall TSnippets::doHttpRestDownClick(TObject *Sender)
{
    const String myfile = "speed300meg.zip";
    const String myurl = "https://www.telecom-tariffs.co.uk/testing/" + myfile;
    const String mytarfile = IncludeTrailingPathDelimiter(DirTemp->Text) + myfile;  // where to download

    TButtonBusy busy(doHttpRestDown, LabelProgress, "HTTP Completed");
    LabelProgress->Caption = "";
    try {
        std::unique_ptr<TSslHttpRest> SslHttpRest(new TSslHttpRest(nullptr));
        SslHttpRest->OnHttpRestProg = onSslHttpRestProg;

        // essential and common HTTP parameters; optional: SslReportChain, SslRevocation,
        // HttpUploadStrat, AuthBearerToken
        SslHttpRest->SocketFamily = sfAnyIPv4;        // IPv4 and/or IPv6
        SslHttpRest->DebugLevel = DebugHdr;
        SslHttpRest->CertVerMethod = CertVerBundle;
        SslHttpRest->SslCliSecurity = sslCliSecDefault;

        // https://www.telecom-tariffs.co.uk/testing/ needs authentication
        SslHttpRest->ServerAuth = httpAuthBasic;
        SslHttpRest->Username = "test";
        SslHttpRest->Password = "password";

        SslHttpRest->ShowProgress = true;
        SslHttpRest->HttpMemStrategy = HttpStratFile;   // also HttpStratResume for auto resumed large downloads
        SslHttpRest->HttpDownFileName = mytarfile;
        SslHttpRest->HttpDownReplace = true;
        const int StatCode = SslHttpRest->RestRequest(httpGET, myurl, false, "");  // sync request, no extra parameters
        AddLogText("HTTP Rest Request Response: " + IntToStr(StatCode));
        if (StatCode == 200)
            AddLogText("File Download OK, Size " + IntToKbyte(IcsGetFileSize(mytarfile)));
    }
    catch (Exception &E) {
        AddLogText("HTTP Error - " + IcsGetExceptMess(&E));
    }
}

/* Snippet: HTTP Simple Upload File - makes an HTTP POST request to upload a file to a
   special upload web page. Uses the TSslHttpRest component in sync mode, with
   HttpUploadStrat of HttpUploadSimple which causes the entire file to be POSTed as
   binary content to the server, with some parameters to tell the server what to do
   with the file.

   This snippet is designed to upload to the ICS web application server samples and a
   public web site using the same code; the uploadfile.htm page expects FileTitle and
   FileName parameters in the URL and will reject the upload otherwise. The snippet adds
   duplicate Json parameters as an example of nesting parameters of different types.
   Returns an HTTP status code, 200 for OK, 404 page not found, etc. Apart from 200,
   there is no indication the upload completed OK; uploadfile.htm reports the file size
   received.

   This upload technique will manage very large files, tested to 8 Gbyte, provided the
   server accepts that size; the ICS web server defaults to a maximum of 200MB.
   The full HTTP REST sample is OverbyteIcsHttpRestTst. */
void __fastcall TSnippets::doHttpSimpleUploadClick(TObject *Sender)
{
    const String myfile = "speed50meg.zip";
    const String mytitle = "A title for the uploaded file";
    const String myurl = "https://www.telecom-tariffs.co.uk/testing/uploadfile.htm";
    const String mysrcfile = IncludeTrailingPathDelimiter(DirTemp->Text) + myfile;  // what to upload
    if (!FileExists(mysrcfile)) {
        AddLogText("Use HTTP Download List of Files first to get upload file - " + myfile);
        return;
    }

    TButtonBusy busy(doHttpSimpleUpload, LabelProgress, "HTTP Completed");
    LabelProgress->Caption = "";
    try {
        std::unique_ptr<TSslHttpRest> SslHttpRest(new TSslHttpRest(nullptr));
        SslHttpRest->OnHttpRestProg = onSslHttpRestProg;
        // TRestParams is a TCollection: its Owner is only for naming, it never frees it
        std::unique_ptr<TRestParams> MyJsonParams(new TRestParams(nullptr));

        // essential and common HTTP parameters; optional: SslReportChain, SslRevocation,
        // AuthBearerToken
        SslHttpRest->SocketFamily = sfAnyIPv4;        // IPv4 and/or IPv6
        SslHttpRest->DebugLevel = DebugHdr;
        SslHttpRest->CertVerMethod = CertVerBundle;
        SslHttpRest->SslCliSecurity = sslCliSecDefault;

        // https://www.telecom-tariffs.co.uk/testing/ needs authentication
        SslHttpRest->ServerAuth = httpAuthBasic;
        SslHttpRest->Username = "test";
        SslHttpRest->Password = "password";

        // build some simple Json parameters, that will be nested into the URL parameters
        MyJsonParams->PContent = PContJson;
        MyJsonParams->AddItem("FileTitle", mytitle);
        MyJsonParams->AddItem("FileName", myfile);

        // build main parameters that will be encoded and added after the URL;
        // AddItemSO adds a SuperObject instead, if using SuperObject to build Json
        SslHttpRest->RestParams->PContent = PContUrlencoded;
        SslHttpRest->RestParams->AddItem("FileTitle", mytitle);
        SslHttpRest->RestParams->AddItemA("JsonBlock", MyJsonParams->GetParameters(), true);  // true means raw data, no escaping
        SslHttpRest->RestParams->AddItem("FileName", myfile);   // required by the uploadfile.htm page

        // make simple POST upload request with the file as binary content
        SslHttpRest->ShowProgress = true;
        SslHttpRest->HttpMemStrategy = HttpStratMem;
        SslHttpRest->HttpUploadStrat = HttpUploadSimple;   // POST content is binary file
        SslHttpRest->HttpUploadFile = mysrcfile;
        const int StatCode = SslHttpRest->RestRequest(httpPOST, myurl, false, "");  // sync request, use RestParams
        AddLogText("HTTP Rest Request Response: " + IntToStr(StatCode));
        if (StatCode == 200)
            AddLogText("File Upload OK");   // ResponseRaw may have the web page's own report
    }
    catch (Exception &E) {
        AddLogText("HTTP Error - " + IcsGetExceptMess(&E));
    }
}

/* Snippet: HTTP Form Upload File - makes an HTTP POST request to upload a file to a
   special upload web page. Uses the TSslHttpRest component in sync mode, using MIME
   multipart/form-data parameters similar to uploading from a web page form using a
   browser.

   This snippet is designed to upload to the ICS web application server samples and a
   public web site using the same code; the uploadfile.htm page expects FileTitle and
   FileName parameters in the form-data and will reject the upload otherwise. The binary
   upload file is specified using AddItemFile with the full disk file name and an
   optional file size, which will be checked if left as 0. The snippet adds duplicate
   Json parameters as an example of nesting parameters of different types. Returns an
   HTTP status code, 200 for OK, 404 page not found, etc. Apart from 200, there is no
   indication the upload completed OK; uploadfile.htm reports the file size received.
   Multiple files may be added, with different names.

   This upload technique will manage very large files, tested to 8 Gbyte, provided the
   server accepts that size; the ICS web server defaults to a maximum of 200MB. Beware
   preparing the form-data content involves copying the binary file, which may take a
   minute or more for gigabyte files, and the server also has to copy the file from the
   form stream, which might delay the upload response by a minute or more, causing sync
   requests to time out; Simple Upload avoids this copying and is better for very large
   files.
   The full HTTP REST sample is OverbyteIcsHttpRestTst. */
void __fastcall TSnippets::doHttpFormUploadClick(TObject *Sender)
{
    const String myfile = "speed50meg.zip";
    const String mytitle = "A title for the uploaded file";
    const String myurl = "https://www.telecom-tariffs.co.uk/testing/uploadfile.htm";
    const String mysrcfile = IncludeTrailingPathDelimiter(DirTemp->Text) + myfile;  // what to upload
    if (!FileExists(mysrcfile)) {
        AddLogText("Use HTTP Download List of Files first to get upload file - " + myfile);
        return;
    }

    TButtonBusy busy(doHttpFormUpload, LabelProgress, "HTTP Completed");
    LabelProgress->Caption = "";
    try {
        std::unique_ptr<TSslHttpRest> SslHttpRest(new TSslHttpRest(nullptr));
        SslHttpRest->OnHttpRestProg = onSslHttpRestProg;
        std::unique_ptr<TRestParams> MyJsonParams(new TRestParams(nullptr));

        // essential and common HTTP parameters; optional: SslReportChain, SslRevocation,
        // AuthBearerToken
        SslHttpRest->SocketFamily = sfAnyIPv4;        // IPv4 and/or IPv6
        SslHttpRest->DebugLevel = DebugHdr;
        SslHttpRest->CertVerMethod = CertVerBundle;
        SslHttpRest->SslCliSecurity = sslCliSecDefault;

        // https://www.telecom-tariffs.co.uk/testing/ needs authentication
        SslHttpRest->ServerAuth = httpAuthBasic;
        SslHttpRest->Username = "test";
        SslHttpRest->Password = "password";

        // build some simple Json parameters, that will be nested in the form-data parameters
        MyJsonParams->PContent = PContJson;
        MyJsonParams->AddItem("FileTitle", mytitle);
        MyJsonParams->AddItem("FileName", myfile);

        // build main parameters that become MIME multipart/form-data POST content;
        // more files can be added with further AddItemFile calls using different names
        SslHttpRest->RestParams->PContent = PContFormData;
        SslHttpRest->RestParams->AddItem("FileTitle", mytitle);
        SslHttpRest->RestParams->AddItemA("JsonBlock", MyJsonParams->GetParameters(), true);  // true means raw data, no escaping
        SslHttpRest->RestParams->AddItemFile("FileName", mysrcfile, 0);   // the binary file to upload, optional file size
        // String() is needed: a bare string literal would pick AddItem's bool overload
        SslHttpRest->RestParams->AddItem("Submit", String("SubmitFile"));

        // make simple POST request with RestParams containing form-data and the binary file
        SslHttpRest->ShowProgress = true;
        SslHttpRest->HttpMemStrategy = HttpStratMem;
        SslHttpRest->HttpUploadStrat = HttpUploadNone;   // file is part of the RestParams
        const int StatCode = SslHttpRest->RestRequest(httpPOST, myurl, false, "");  // sync request, use RestParams
        AddLogText("HTTP Rest Request Response: " + IntToStr(StatCode));
        if (StatCode == 200)
            AddLogText("File Upload OK");   // ResponseRaw may have the web page's own report
    }
    catch (Exception &E) {
        AddLogText("HTTP Error - " + IcsGetExceptMess(&E));
    }
}

// the next four event handlers are for the TIcsIpStrmLog IP stream logging components
void __fastcall TSnippets::onCliLogRecvEvent(TObject *Sender, int Socnr, const String Line)
{
    AddLogText("IP Log Client: Received Data - " + Line);
}

void __fastcall TSnippets::onCliLogProgEvent(TObject *Sender, int Socnr, TLogOption LogOption, const String Msg)
{
    AddLogText("IP Log Client: " + Msg);
}

void __fastcall TSnippets::onSrvLogRecvEvent(TObject *Sender, int Socnr, const String Line)
{
    AddLogText("IP Log Server: Received Data - " + Line);
}

void __fastcall TSnippets::onSrvLogProgEvent(TObject *Sender, int Socnr, TLogOption LogOption, const String Msg)
{
    AddLogText("IP Log Server: " + Msg);
}

/* Snippet: Local Socket Traffic - send simple text traffic between two sockets on the
   same PC, using client server concepts. Uses the TIcsIpStrmLog IP stream logging
   component, a high level version of the basic TSslWSocket and TSslWSocketServer
   components combined together and configured for TCP Client, TCP Server, UDP Client
   or UDP Server protocols. The component supports multiple client sockets, so may be
   used to send data to two or more remote servers at the same time. For TCP and UDP
   clients, the component will optionally ping the remote computer before opening an IP
   connection, to allow faster failure retries and some confirmation that UDP may work.
   TCP client provides repeated connection retry attempts, including re-establishing a
   lost connection. UDP client will optionally keep pinging the remote during a
   connection to ensure it's still there. UDP server sends data to the IP address and
   port from which it last received data. TCP server supports multiple remote clients.
   Received data is parsed for various line endings, optionally removing control
   characters, and an event is triggered for each received line. For efficiency,
   TIcsIpStrmLog needs to send lines or packets of data with a known line ending that
   triggers the receive event; the default is LF for lines of text, and sending can
   add CRLF automatically. The line end can be changed to another character, or to a
   fixed length binary packet. TIcsIpStrmLog only works in async mode, using three main
   events: RecvEvent for received data, ChangeEvent for connection and disconnection
   and ProgEvent for logging. TIcsIpStrmLog supports SSL/TLS if needed; TCP Client is
   automatic, TCP Server needs an SSL server certificate and private key, which it can
   order automatically from Let's Encrypt for public servers.

   This snippet uses two TIcsIpStrmLog components configured as TCP Client and TCP
   Server on the same IP address and port, so they send data between themselves
   (usually it would be between two different applications). Because TIcsIpStrmLog is
   async, the snippet waits in a message loop with a five minute timeout, with the
   client and server each sending a line of data every two seconds to simulate traffic.
   Clicking the Abort button stops it early.
   The full IP stream logging sample is OverbyteIcsIpStmLogTst. */
void __fastcall TSnippets::doSocketLocalClick(TObject *Sender)
{
    const String myIpAddress = "127.0.0.1";   // loop back
    const int myIpPort = 34567;               // random
    const String myCliData = "Test data from the IP Streaming Log client component";
    const String mySrvData = "Test data from the IP Streaming Log server component";

    TButtonBusy busy(doSocketLocal, LabelProgress, "IP Logging Completed");
    // Created outside the try so an error is logged before the components are destroyed
    // (their destructors stop logging and report it), as in the Delphi demo. unique_ptr
    // also replaces the Delphi Assigned() checks, which tested uninitialised variables if
    // Create raised. Destroyed in reverse order: server first, then client, as in Delphi.
    std::unique_ptr<TIcsIpStrmLog> IpLogClient(new TIcsIpStrmLog(nullptr));
    std::unique_ptr<TIcsIpStrmLog> IpLogServer(new TIcsIpStrmLog(nullptr));
    try {

        // create IP stream logging local server component and start listening
        IpLogServer->onLogRecvEvent = onSrvLogRecvEvent;
        IpLogServer->onLogProgEvent = onSrvLogProgEvent;
        IpLogServer->LogProtocol = logprotTcpServer;   // could be UDP
        IpLogServer->ForceSsl = false;
        IpLogServer->SrvTimeoutSecs = 60;
        IpLogServer->SrvIcsHosts->Clear();
        TIcsHost *host = static_cast<TIcsHost *>(IpLogServer->SrvIcsHosts->Add());  // only need one host
        host->HostEnabled = true;
        host->BindIpAddr = myIpAddress;
        host->BindNonPort = myIpPort;   // Delphi converts a string with ICS atoi
        host->BindSslPort = 0;
        host->HostTag = "LocalServer";
        AddLogText("IP Log Server: Starting on " + myIpAddress + ":" + IntToStr(myIpPort));
        if (!IpLogServer->StartLogging()) {
            AddLogText("IP Log Server: Failed to Start");
            return;
        }
        AddLogText("IP Log Server: Started OK");

        // create IP stream logging client component and connect to the local server.
        // Defaults that usually need no setting, shown for reference: PingWaitSecs 5,
        // AutoReconnect true, RetryAttempts 0 (never stop, -1 none), RetryWaitSecs 10.
        // These also apply in server mode: LineEndType lineendLF (or lineendCR,
        // lineendCustom, lineendPacket), MaxLineLen 132, RawData false, UseUtf8 true,
        // StripControls true, AddCRLF true (added by SendLogLine).
        IpLogClient->MaxSockets = 1;
        IpLogClient->onLogRecvEvent = onCliLogRecvEvent;
        IpLogClient->onLogProgEvent = onCliLogProgEvent;
        IpLogClient->LogProtocol = logprotTcpClient;   // could be UDP
        IpLogClient->ForceSsl = false;
        IpLogClient->RemoteHost = myIpAddress;     // SetRemotes sends to multiple hosts/ports, up to MaxSockets
        IpLogClient->RemoteIpPort = IntToStr(myIpPort);
        IpLogClient->CheckPing = false;   // TCP connect failure usually waits 30 secs or more, ping can time out in a few seconds
        IpLogClient->StartLogging();      // connects async, event when done
        AddLogText("IP Log Client: Connecting to Server");

        // TIcsIpStrmLog is event driven: data arrives in onLogRecvEvent and is sent with
        // SendLogLine. Here we wait up to five minutes sending a line each way every two
        // seconds. The lambda captures the components and the trigger times by reference
        // and runs on each pass of the wait loop.
        AddLogText("Click Abort to stop Local IP Logging snippet");
        FAbortFlag = false;
        __int64 trgCliData = IcsGetTrgSecs64(2);
        __int64 trgSrvData = IcsGetTrgSecs64(1);
        WaitWhileRunning(5, [&]() {
            // see if client is connected and send data to server
            if (IcsTestTrgTick64(trgCliData)) {
                trgCliData = IcsGetTrgSecs64(2);
                if (IpLogClient->AnyStateOK && !myCliData.IsEmpty())
                    IpLogClient->SendLogLine(TimeToStr(Time()) + " - " + myCliData);
            }
            // see if server has any clients connected and send data back to them
            if (IcsTestTrgTick64(trgSrvData)) {
                trgSrvData = IcsGetTrgSecs64(2);
                if (IpLogServer->CurSockets > 0 && !mySrvData.IsEmpty())
                    IpLogServer->SendLogLine(TimeToStr(Time()) + " - " + mySrvData);
            }
        });

        AddLogText("Stopping Logging");
        IpLogClient->StopLogging();
        IpLogServer->StopLogging();
    }
    catch (Exception &E) {
        AddLogText("IP Logging - " + IcsGetExceptMess(&E));
    }
}

/* Snippet: Remote Socket Traffic - receive simple text traffic from a remote TCP
   server, optionally using SSL/TLS. Receiving and sending logging streams to and from
   remote network appliances and applications is a common requirement, and the reason
   the component is called TIcsIpStrmLog - IP Stream Logging.
   This snippet uses a TIcsIpStrmLog component configured as TCP Client, which accesses
   an application called ComGen on a public server; ComGen itself uses TIcsIpStrmLog in
   TCP Server mode to generate lines of text for testing purposes, see
   https://www.magsys.co.uk/comcap/. The snippet can connect to non-SSL or SSL ports.
   Because TIcsIpStrmLog is async, the snippet waits in a message loop with a five
   minute timeout while traffic is received. Clicking the Abort button stops it early.
   The full IP stream logging sample is OverbyteIcsIpStmLogTst. */
void __fastcall TSnippets::doSocketRemoteClick(TObject *Sender)
{
    const String myIpAddress = "comgen.ftptest.co.uk";
    const String myIpPort = "21501";   // uses SSL; 21502 is the same without SSL

    TButtonBusy busy(doSocketRemote, LabelProgress, "IP Logging Completed");
    // outside the try for the same reason as in the Local Socket Traffic snippet
    std::unique_ptr<TIcsIpStrmLog> IpLogClient(new TIcsIpStrmLog(nullptr));
    try {

        // the defaults listed in the Local Socket Traffic snippet apply here too, plus
        // LogSslRevocation false and LogSslReportChain false
        IpLogClient->MaxSockets = 1;
        IpLogClient->onLogRecvEvent = onCliLogRecvEvent;
        IpLogClient->onLogProgEvent = onCliLogProgEvent;
        IpLogClient->LogProtocol = logprotTcpClient;   // could be UDP
        IpLogClient->ForceSsl = true;
        IpLogClient->RemoteHost = myIpAddress;     // SetRemotes sends to multiple hosts/ports, up to MaxSockets
        IpLogClient->RemoteIpPort = myIpPort;
        IpLogClient->CheckPing = false;   // TCP connect failure usually waits 30 secs or more, ping can time out in a few seconds
        IpLogClient->LogSslVerMethod = logSslVerBundle;
        IpLogClient->StartLogging();      // connects async, event when done
        AddLogText("IP Log Client: Connecting to Server");

        // data arrives in onLogRecvEvent; wait up to five minutes while it does
        AddLogText("Click Abort to stop Remote IP Logging snippet");
        FAbortFlag = false;
        WaitWhileRunning(5);

        AddLogText("Stopping Logging");
        IpLogClient->StopLogging();
    }
    catch (Exception &E) {
        AddLogText("IP Logging - " + IcsGetExceptMess(&E));
    }
}

// the next three event handlers are for the TSslWebSocketCli component
void __fastcall TSnippets::onSslWebSocketCliWSConnected(TObject *Sender)
{
    TSslWebSocketCli *cli = static_cast<TSslWebSocketCli *>(Sender);   // only ever a TSslWebSocketCli
    if (cli->IsWSConnected())
        LabelProgress->Caption = "Websocket Connected OK to: " + cli->URL;
    else {
        LabelProgress->Caption = "Websocket Failed to Connect to: " + cli->URL;
        FAbortFlag = true;   // break wait loop
    }
}

void __fastcall TSnippets::onSslWebSocketCliWSDisconnected(TObject *Sender)
{
    LabelProgress->Caption = "Websocket Session Disconnected";
    FAbortFlag = true;   // break wait loop
}

void __fastcall TSnippets::onSslWebSocketCliWSFrameRcvd(TSslWebSocketCli *Sender, const String APacket,
                                                       TWebSocketReceivedFrame *&AFrame)
{
    if (!AFrame)
        return;
    LabelProgress->Caption = "WebSocket Frames in/out: " + IntToStr(Sender->WSFrameCounter);
    if (AFrame->Kind == wsfkText)
        AddLogText(APacket);
}

/* Snippet: WebSocket Client - connect to a remote WebSocket server to send and receive
   data, optionally using SSL/TLS.
   WebSocket is a full duplex TCP protocol for web servers to support interactive web
   pages, typically dynamic updating such as chat sessions, spell checkers as you type,
   search hints, etc. WebSocket extends the HTTP protocol and can be carried through HTTP
   proxies using the same ports as HTTP. The protocol includes ping/pong keep alive so
   long lived connections are not dropped.
   The snippet uses the TSslWebSocketCli component to connect to
   wss://www.telecom-tariffs.co.uk/WebSocket/Serverinfo, a WebSocket server supporting
   the https://www.telecom-tariffs.co.uk/serverinfow.htm page, which is updated every 10
   seconds using the ICS WebSocket server component. HTML received by the WebSocket is
   normally plugged into the web page, but is just displayed raw here. Because
   TSslWebSocketCli is async, the snippet waits in a message loop with a five minute
   timeout while traffic is received. Clicking the Abort button stops it early.
   The full WebSocket client sample is OverbyteIcsHttpRestTst. */
void __fastcall TSnippets::doWebSocketClick(TObject *Sender)
{
    // this server streams HTML information every 10 seconds to update a web page
    const String myurl = "wss://www.telecom-tariffs.co.uk/WebSocket/Serverinfo";

    TButtonBusy busy(doWebSocket);
    LabelProgress->Caption = "";
    try {
        std::unique_ptr<TSslWebSocketCli> SslWebSocketCli(new TSslWebSocketCli(nullptr));
        SslWebSocketCli->OnHttpRestProg = onSslHttpRestProg;
        SslWebSocketCli->OnWSFrameRcvd = onSslWebSocketCliWSFrameRcvd;
        SslWebSocketCli->OnWSConnected = onSslWebSocketCliWSConnected;
        SslWebSocketCli->OnWSDisconnected = onSslWebSocketCliWSDisconnected;

        // essential and common HTTP parameters; optional: SslReportChain, SslRevocation,
        // ServerAuth with Username and Password, AuthBearerToken
        SslWebSocketCli->SocketFamily = sfAnyIPv4;    // IPv4 and/or IPv6
        SslWebSocketCli->DebugLevel = DebugHdr;
        SslWebSocketCli->CertVerMethod = CertVerBundle;
        SslWebSocketCli->SslCliSecurity = sslCliSecDefault;
        SslWebSocketCli->WSPingSecs = 0;              // do we need keep-alive ping/pongs

        // A sync WSConnect returns true only when the WebSocket session is ready. The
        // Delphi demo ignores the result and clears its abort flag afterwards, so after a
        // failed connection it waits the full five minutes.
        FAbortFlag = false;
        if (!SslWebSocketCli->WSConnect(myurl, false)) {
            AddLogText("WebSocket failed to connect to " + myurl);
            return;
        }

        // the client now listens until stopped; wait up to five minutes
        AddLogText("Click Abort to stop WebSocket Client snippet");
        WaitWhileRunning(5);
        SslWebSocketCli->WSClose(wscrNormalClosure, "Closed by user");
    }
    catch (Exception &E) {
        AddLogText("WebSocket Error - " + IcsGetExceptMess(&E));
    }
}

// logging event for the TIcsMailQueue component
void __fastcall TSnippets::onIcsMailQueueLogEvent(TMailLogLevel LogLevel, const String Info)
{
    AddLogText(Info);
}

/* Snippet: Send Email using Mail Queue - runs a mail queue to send multiple emails with
   extended retries over many hours or days, supporting multiple SMTP relay servers or
   looking up MX servers, so the application need not handle retries. Mail is queued to
   disk, so retries will continue if the application is restarted.
   TIcsMailQueue prepares, queues and sends email. Preparing the email is done using the
   ICS THtmlSmtpCli component, so it may be plain text or HTML email with one or more
   file attachments. Once the mail properties in QuHtmlSmtp have been specified, it is
   queued using QueueMail, which saves it to an EML RFC2822 spool file from where a
   thread will send it.
   This snippet sends email to a Magenta Systems junk mailbox that does not require
   authentication; normally you would send to your ISP or corporate relay mail server
   with authentication, which can be done by changing the properties below. The snippet
   queues mail in a temporary directory, mailqueuework. The mail queue is started, a
   single email is queued and WaitSendandStop is called, which waits up to 10 minutes
   for the email to be sent; normally it happens in a few seconds. Usually the mail
   queue would be left running to handle new delivery attempts for failed emails and to
   accept new emails. The mail queue itself can be accessed for display and to cancel
   mail before it is delivered.
   The full Mail Queue sample is OverbyteIcsMailQuTst. */
void __fastcall TSnippets::doEmailMailQuClick(TObject *Sender)
{
    const String mysmptserver = "mail.magsys.co.uk";                   // also hosts ftptest.org
    const String myemailto = "\"Junk Testing\" <testing@ftptest.org>";  // no authentication needed for this account's SMTP server
    const String myemailfrom = "\"ICS Snippets\" <icsnippets@ftptest.org>";
    const String mymailqudir = IncludeTrailingPathDelimiter(DirTemp->Text) + "mailqueuework";

    if (!ForceDirectories(mymailqudir)) {
        AddLogText("Failed to Create Mail Queue Directory: " + mymailqudir);
        return;
    }

    TButtonBusy busy(doEmailMailQu);
    // outside the try: the queue's destructor stops it and logs, which should follow any
    // error message, as in the Delphi demo
    std::unique_ptr<TIcsMailQueue> IcsMailQueue(new TIcsMailQueue(nullptr));
    try {
        IcsMailQueue->LogEvent = onIcsMailQueueLogEvent;

        // optional: SslRevocation, SslReportChain; defaults that need no setting:
        // RetryList "5,5,10,10,30,30,60,90,300,300,300,300" (minutes between attempts),
        // ArchiveSent true (keep copies of sent mail), DeleteFailed false, QuStartDelay 2
        IcsMailQueue->SslVerMethod = MailSslVerBundle;  // or MailSslVerNone
        IcsMailQueue->SslRootFile = "";                 // blank uses internal bundle
        IcsMailQueue->LogQuSent = true;                 // create log of sent email
        IcsMailQueue->Debug = true;
        IcsMailQueue->MailQuDir = mymailqudir;

        // relay is generally your ISP's or corporate email server, specific allows
        // individual servers for each email, MX lookup uses DNS to find the final
        // delivery mail server but that might expect reverse DNS matching the SignOn
        IcsMailQueue->SmtpMethod = MailSmtpRelay;       // or MailSmtpSpecific, MailSmtpMxLookup
        IcsMailQueue->MxSrvUseSsl = false;

        // add one or more email servers, not necessary if using specific or MX lookup;
        // with several, each send attempt tries each one in turn
        IcsMailQueue->MailServers->Clear();
        TMailServer *server = static_cast<TMailServer *>(IcsMailQueue->MailServers->Add());
        server->Port = "587";                 // 25 or 465 (SSL only) or 587
        server->Host = mysmptserver;
        server->AuthType = smtpAuthNone;      // smtpAuthAutoSelect, smtpAuthPlain, etc with UserName and Password
        server->Username = "";
        server->Password = "";
        server->SslType = smtpTlsExplicit;    // explicit means same port as non-SSL, implicit means dedicated SSL port 465
        server->SignOn = "ICS Snippet";
        server->SslCliSecurity = sslCliSecDefault;
        server->RetryWithoutSsl = true;

        IcsMailQueue->Active = true;
        if (!IcsMailQueue->Active) {
            AddLogText("Failed to Start Mail Queue");
            return;
        }

        // now queue one email, could be hundreds
        THtmlSmtpCli *smtp = IcsMailQueue->QuHtmlSmtp;
        String FriendlyName;
        smtp->EmailFiles->Clear();
        smtp->Allow8bitChars = false;        // true prevents MIME encoding of unicode headers
        smtp->CharSet = "utf-8";             // so full unicode is supported
        smtp->FoldHeaders = true;
        smtp->HdrFrom = myemailfrom;
        smtp->FromName = IcsParseEmail(myemailfrom, FriendlyName);
        smtp->HdrCc = myemailfrom;
        smtp->HdrReplyTo = smtp->FromName;
        smtp->HdrSubject = "Testing email from ICS Snippets";
        smtp->ContentType = smtpPlainText;   // or smtpHtml with an HTML body in HtmlText, plus attachments, images, etc
        smtp->PlainText->Text = "A very short email message.";

        // the same email can go to hundreds of recipient addresses, but they all get the
        // same HdrTo; for mailing lists queue each mail separately with its own HdrTo
        smtp->RcptName->Clear();
        smtp->RcptName->Add(IcsParseEmail(myemailto, FriendlyName));
        smtp->HdrTo = myemailto;

        // finally queue this email
        const int Item = IcsMailQueue->QueueMail();
        if (Item == 0)
            AddLogText("Failed to Queue Mail - " + smtp->ErrorMessage);
        else
            AddLogText("Mail Queued OK as Item " + IntToStr(Item));

        // mail is now sent in the background by a thread; wait until the queue is empty.
        // Abort does not work here, so this might wait 10 minutes for two retries.
        IcsMailQueue->WaitSendandStop(630);
    }
    catch (Exception &E) {
        AddLogText("Mail Queue Error - " + IcsGetExceptMess(&E));
    }
}

/* Snippet: HTTP REST Get and Post Parameters Requests - two snippets that make HTTP GET
   and POST requests to an ICS REST server passing several UrlEncoded parameters and
   receiving back a page displaying those parameters in raw and decoded format,
   primarily for debugging purposes. Uses the TSslHttpRest component in sync mode, but
   it could be async with the result in the OnRestRequestDone event. Returns an HTTP
   status code, 200 for OK, 404 page not found, etc. RestParams will build Json, XML,
   UrlEncoded or comma list parameters, from strings, integers and TDateTime, or you can
   build them yourself and pass them raw. On success, ResponseRaw has a unicode string
   converted using IcsHtmlToStr according to the content, including entities like
   &pound; and &#9741;. This ICS server needs authentication, which is hardcoded here.
   The full HTTP REST sample is OverbyteIcsHttpRestTst. */
void __fastcall TSnippets::doHttpGetParamsClick(TObject *Sender)
{
    const String myurl = "https://www.telecom-tariffs.co.uk/testing/postinfo.htm";

    TButtonBusy busy(doHttpGetParams, LabelProgress, "HTTP Completed");   // the Delphi demo disables doHttpRestReq here
    LabelProgress->Caption = "";
    try {
        std::unique_ptr<TSslHttpRest> SslHttpRest(new TSslHttpRest(nullptr));
        SslHttpRest->OnHttpRestProg = onSslHttpRestProg;

        // essential and common HTTP parameters
        SslHttpRest->SocketFamily = sfAny;            // IPv4 and/or IPv6
        SslHttpRest->DebugLevel = DebugHdr;           // DebugNone, DebugConn, DebugParams, DebugSSL, DebugHdr, DebugBody, each logs more
        SslHttpRest->CertVerMethod = CertVerBundle;   // or CertVerNone if you don't care

        // https://www.telecom-tariffs.co.uk/testing/ needs authentication
        SslHttpRest->ServerAuth = httpAuthBasic;
        SslHttpRest->Username = "test";
        SslHttpRest->Password = "password";

        // AddItem is overloaded for String, int, double and bool. A bare string literal
        // converts to bool before String in C++, so string values are wrapped in String().
        SslHttpRest->RestParams->PContent = PContUrlencoded;   // lots of PContxx options, Json, XML
        SslHttpRest->RestParams->AddItem("myparam1", String("string1"));
        SslHttpRest->RestParams->AddItem("myparam2", String("string2"));
        SslHttpRest->RestParams->AddItem("myparam3", 123456);
        SslHttpRest->RestParams->AddItem("myparam4", true);
        SslHttpRest->RestParams->AddItemDT("myparam5", Now());
        const int StatCode = SslHttpRest->RestRequest(httpGET, myurl, false, "");  // sync request, no extra parameters
        AddLogText("HTTP Rest Request Response: " + IntToStr(StatCode));
        if (StatCode == 200)
            AddLogText(SslHttpRest->ResponseRaw);
    }
    catch (Exception &E) {
        AddLogText("HTTP Error - " + IcsGetExceptMess(&E));
    }
}

void __fastcall TSnippets::doHttpPostParamsClick(TObject *Sender)
{
    const String myurl = "https://www.telecom-tariffs.co.uk/testing/postinfo.htm";

    TButtonBusy busy(doHttpPostParams, LabelProgress, "HTTP Completed");   // the Delphi demo disables doHttpRestReq here
    LabelProgress->Caption = "";
    try {
        std::unique_ptr<TSslHttpRest> SslHttpRest(new TSslHttpRest(nullptr));
        SslHttpRest->OnHttpRestProg = onSslHttpRestProg;

        // essential and common HTTP parameters
        SslHttpRest->SocketFamily = sfAny;            // IPv4 and/or IPv6
        SslHttpRest->DebugLevel = DebugHdr;
        SslHttpRest->CertVerMethod = CertVerBundle;

        // https://www.telecom-tariffs.co.uk/testing/ needs authentication
        SslHttpRest->ServerAuth = httpAuthBasic;
        SslHttpRest->Username = "test";
        SslHttpRest->Password = "password";

        // String() for the same overload reason as in the Get Parameters snippet
        SslHttpRest->RestParams->PContent = PContUrlencoded;
        SslHttpRest->RestParams->AddItem("myparam1", String("string1"));
        SslHttpRest->RestParams->AddItem("myparam2", String("string2"));
        SslHttpRest->RestParams->AddItem("myparam3", 123456);
        SslHttpRest->RestParams->AddItem("myparam4", true);
        SslHttpRest->RestParams->AddItemDT("myparam5", Now());
        const int StatCode = SslHttpRest->RestRequest(httpPOST, myurl, false, "");  // sync request, RestParams as content
        AddLogText("HTTP Rest Request Response: " + IntToStr(StatCode));
        if (StatCode == 200)
            AddLogText(SslHttpRest->ResponseRaw);
    }
    catch (Exception &E) {
        AddLogText("HTTP Error - " + IcsGetExceptMess(&E));
    }
}
