/*
  ICS SSL FTP client test - C++Builder translation of the Delphi demo OverbyteIcsSslFtpTst1.pas
  (demos-delphi-vcl), ICS V9.8.

  A graphical FTP client (RFC 959) built on TSslFtpClient, with plain FTP, AUTH TLS/SSL and
  implicit SSL. Each button runs one FTP command, either synchronously or asynchronously as the
  "Sync" check box selects; directory listings are shown in a second form (OverbyteIcsSslFtpTst2).

  How this C++ version differs from the Delphi original, apart from syntax:
  - ExecuteCmd takes a DirListing flag instead of comparing method addresses to find the
    directory commands.
  - FTrustedList, FNotTrusted, FQueryingTrusted and the WM_SSL_NOT_TRUSTED handler are not
    translated: the certificate check that posted the message is commented out in the Delphi
    (done in TWSocket since V9.6), so nothing reaches them. The unused BigConsole helper and
    WM_TMP_SOMETHING are not translated either.
  - The empty FtpClient1SessionConnected, FtpClient1SessionClosed, FtpClient1SslVerifyPeer,
    FtpClient1SslHandshakeDone and ClearTraceButtonClick handlers are not translated and their
    .dfm bindings are removed; FormDestroy is replaced by the destructor and std::unique_ptr.
  - Delphi demo bugs are fixed here; each fix has a one-line comment.
*/

#include <vcl.h>
#pragma hdrstop

#include <memory>
#include <vector>

#include "OverbyteIcsSslFtpTst1.h"
#include "OverbyteIcsSslFtpTst2.h"
#include "OverbyteIcsCliCertDlg.h"
#include "OverbyteIcsIniFiles.hpp"
#include "OverbyteIcsUtils.hpp"

#pragma package(smart_init)
#pragma resource "*.dfm"

TFtpReceiveForm *FtpReceiveForm;

namespace {

const wchar_t *const TempFileName = L"FTPDIR.TXT";

// Ini file layout
const wchar_t *const SectionData        = L"Data";
const wchar_t *const KeyHostName        = L"HostName";
const wchar_t *const KeyUserName        = L"UserName";
const wchar_t *const KeyPassWord        = L"PassWord";
const wchar_t *const KeyAccount         = L"Account";
const wchar_t *const KeyHostDir         = L"HostDir";
const wchar_t *const KeyPort            = L"Port";
const wchar_t *const KeyHostFile        = L"HostFile";
const wchar_t *const KeyLocalFile       = L"LocalFile";
const wchar_t *const KeyResumeAt        = L"ResumeAt";
const wchar_t *const KeyPortStart       = L"DataPortRangeStart";
const wchar_t *const KeyPortEnd         = L"DataPortRangeEnd";
const wchar_t *const KeyPassive         = L"PassiveMode";
const wchar_t *const KeyDisplay         = L"DisplayData";
const wchar_t *const KeySync            = L"SyncMode";
const wchar_t *const KeyNoResume        = L"NoAutoResumeAt";
const wchar_t *const KeyBinary          = L"BinaryMode";
const wchar_t *const KeyCertFile        = L"CertFile";
const wchar_t *const KeyPassPhrase      = L"PassPhrase";
const wchar_t *const KeyPrivKeyFile     = L"PrivKeyFile";
const wchar_t *const KeyCAFile          = L"CAFile";
const wchar_t *const KeyCAPath          = L"CAPath";
const wchar_t *const KeyVerifyPeer      = L"VerifyPeer";
const wchar_t *const KeyAcceptableHosts = L"AcceptableHosts";
const wchar_t *const KeyProtLevel       = L"ProtLevel";
const wchar_t *const KeyPbszSize        = L"PbszSize";
const wchar_t *const SectionWindow      = L"Window";
const wchar_t *const KeyTop             = L"Top";
const wchar_t *const KeyLeft            = L"Left";
const wchar_t *const KeyWidth           = L"Width";
const wchar_t *const KeyHeight          = L"Height";
const wchar_t *const KeySessCache       = L"SessCache";
const wchar_t *const KeySslType         = L"SslType";
const wchar_t *const KeySslPort         = L"SslPort";
const wchar_t *const KeyMaxKB           = L"MaxKB";

// Runs a function when it goes out of scope, like a Delphi "finally" block. The lambda passed
// to MakeScopeExit captures by reference, so it sees the variables as they are at that time.
template <typename F>
struct TScopeExit
{
    F Fn;
    ~TScopeExit() { Fn(); }
};

template <typename F>
TScopeExit<F> MakeScopeExit(F Fn)
{
    return TScopeExit<F>{Fn};
}

}

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

__fastcall TFtpReceiveForm::~TFtpReceiveForm()
{
    // Free the FTP client before FClientCerts, which holds the certificate it may have been given.
    DestroyComponents();
}

void __fastcall TFtpReceiveForm::FormCreate(TObject *Sender)
{
    ReportMemoryLeaksOnShutdown = (DebugHook != 0);
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
    DisplayMemo->Clear();
    InfoLabel->Caption = "";
    StateLabel->Caption = "";
    FIniFileName = GetIcsIniFileName();
}

void __fastcall TFtpReceiveForm::FormShow(TObject *Sender)
{
    if (FInitialized)
        return;
    FInitialized = true;
    {
        std::unique_ptr<TIcsIniFile> IniFile(new TIcsIniFile(FIniFileName, true));
        HostNameEdit->Text = IniFile->ReadString(SectionData, KeyHostName, "ftp.simtel.net");
        PortEdit->Text = IniFile->ReadString(SectionData, KeyPort, "ftp");
        UserNameEdit->Text = IniFile->ReadString(SectionData, KeyUserName, "anonymous");
        PassWordEdit->Text = IniFile->ReadString(SectionData, KeyPassWord, "your.name@your.company.com");
        AccountEdit->Text = IniFile->ReadString(SectionData, KeyAccount, "");
        HostDirEdit->Text = IniFile->ReadString(SectionData, KeyHostDir, "/pub/simtelnet");
        HostFileEdit->Text = IniFile->ReadString(SectionData, KeyHostFile, "index.html");
        LocalFileEdit->Text = IniFile->ReadString(SectionData, KeyLocalFile, "c:\\temp\\index.htm");
        ResumeAtEdit->Text = IniFile->ReadString(SectionData, KeyResumeAt, "0");
        DataPortRangeStartEdit->Text = IniFile->ReadString(SectionData, KeyPortStart, "0");
        DataPortRangeEndEdit->Text = IniFile->ReadString(SectionData, KeyPortEnd, "0");
        PassiveCheckBox->Checked = (IniFile->ReadInteger(SectionData, KeyPassive, 0) != 0);
        DisplayCheckBox->Checked = (IniFile->ReadInteger(SectionData, KeyDisplay, 0) != 0);
        SyncCheckBox->Checked = (IniFile->ReadInteger(SectionData, KeySync, 0) != 0);
        NoAutoResumeAtCheckBox->Checked = (IniFile->ReadInteger(SectionData, KeyNoResume, 0) != 0);
        BinaryCheckBox->Checked = (IniFile->ReadInteger(SectionData, KeyBinary, 0) != 0);
        MaxKbEdit->Text = IniFile->ReadString(SectionData, KeyMaxKB, "0");

        Width = IniFile->ReadInteger(SectionWindow, KeyWidth, Width);
        Height = IniFile->ReadInteger(SectionWindow, KeyHeight, Height);
        Top = IniFile->ReadInteger(SectionWindow, KeyTop, (Screen->Height - Height) / 2);
        Left = IniFile->ReadInteger(SectionWindow, KeyLeft, (Screen->Width - Width) / 2);

        CertFileEdit->Text = IniFile->ReadString(SectionData, KeyCertFile, "01cert.pem");
        PrivKeyFileEdit->Text = IniFile->ReadString(SectionData, KeyPrivKeyFile, "01key.pem");
        PassPhraseEdit->Text = IniFile->ReadString(SectionData, KeyPassPhrase, "password");
        CAFileEdit->Text = IniFile->ReadString(SectionData, KeyCAFile, "TrustedCABundle.pem");
        CAPathEdit->Text = IniFile->ReadString(SectionData, KeyCAPath, "TrustedCaStore");
        AcceptableHostsEdit->Text = IniFile->ReadString(SectionData, KeyAcceptableHosts,
                                                        "www.overbyte.be;www.borland.com");
        VerifyPeerCheckBox->Checked = (IniFile->ReadInteger(SectionData, KeyVerifyPeer, 0) != 0);
        ProtLevelEdit->Text = IniFile->ReadString(SectionData, KeyProtLevel, "P");
        PbszSizeEdit->Text = IniFile->ReadString(SectionData, KeyPbszSize, "0");
        SessCacheCheckBox->Checked = IniFile->ReadBool(SectionData, KeySessCache, false);
        SslTypeComboBox->ItemIndex = IniFile->ReadInteger(SectionData, KeySslType, 0);
        SslPortEdit->Text = IniFile->ReadString(SectionData, KeySslPort, "990");
    }

    const TWSAData Data = WinsockInfo();
    Display("Winsock version " + IntToStr(static_cast<int>(LOBYTE(Data.wHighVersion))) + "." +
            IntToStr(static_cast<int>(HIBYTE(Data.wHighVersion))));
    Display(String(IcsStrPas(Data.szDescription)));
    Display(String(IcsStrPas(Data.szSystemStatus)));
}

void __fastcall TFtpReceiveForm::FormClose(TObject *Sender, TCloseAction &Action)
{
    std::unique_ptr<TIcsIniFile> IniFile(new TIcsIniFile(FIniFileName, true));
    IniFile->WriteString(SectionData, KeyHostName, HostNameEdit->Text);
    IniFile->WriteString(SectionData, KeyPort, PortEdit->Text);
    IniFile->WriteString(SectionData, KeyUserName, UserNameEdit->Text);
    IniFile->WriteString(SectionData, KeyPassWord, PassWordEdit->Text);
    IniFile->WriteString(SectionData, KeyAccount, AccountEdit->Text);
    IniFile->WriteString(SectionData, KeyHostDir, HostDirEdit->Text);
    IniFile->WriteString(SectionData, KeyHostFile, HostFileEdit->Text);
    IniFile->WriteString(SectionData, KeyLocalFile, LocalFileEdit->Text);
    IniFile->WriteString(SectionData, KeyResumeAt, ResumeAtEdit->Text);
    IniFile->WriteString(SectionData, KeyPortStart, DataPortRangeStartEdit->Text);
    IniFile->WriteString(SectionData, KeyPortEnd, DataPortRangeEndEdit->Text);
    IniFile->WriteInteger(SectionData, KeyPassive, PassiveCheckBox->Checked ? 1 : 0);
    IniFile->WriteInteger(SectionData, KeyDisplay, DisplayCheckBox->Checked ? 1 : 0);
    IniFile->WriteInteger(SectionData, KeySync, SyncCheckBox->Checked ? 1 : 0);
    IniFile->WriteInteger(SectionData, KeyNoResume, NoAutoResumeAtCheckBox->Checked ? 1 : 0);
    IniFile->WriteInteger(SectionData, KeyBinary, BinaryCheckBox->Checked ? 1 : 0);
    IniFile->WriteString(SectionData, KeyMaxKB, MaxKbEdit->Text);
    IniFile->WriteInteger(SectionData, KeyVerifyPeer, VerifyPeerCheckBox->Checked ? 1 : 0);
    IniFile->WriteString(SectionData, KeyCertFile, CertFileEdit->Text);
    IniFile->WriteString(SectionData, KeyPrivKeyFile, PrivKeyFileEdit->Text);
    IniFile->WriteString(SectionData, KeyPassPhrase, PassPhraseEdit->Text);
    IniFile->WriteString(SectionData, KeyCAFile, CAFileEdit->Text);
    IniFile->WriteString(SectionData, KeyCAPath, CAPathEdit->Text);
    IniFile->WriteString(SectionData, KeyAcceptableHosts, AcceptableHostsEdit->Text);
    IniFile->WriteString(SectionData, KeyProtLevel, ProtLevelEdit->Text);
    IniFile->WriteString(SectionData, KeyPbszSize, PbszSizeEdit->Text);
    IniFile->WriteBool(SectionData, KeySessCache, SessCacheCheckBox->Checked);
    IniFile->WriteInteger(SectionData, KeySslType, SslTypeComboBox->ItemIndex);
    IniFile->WriteString(SectionData, KeySslPort, SslPortEdit->Text);

    IniFile->WriteInteger(SectionWindow, KeyTop, Top);
    IniFile->WriteInteger(SectionWindow, KeyLeft, Left);
    IniFile->WriteInteger(SectionWindow, KeyWidth, Width);
    IniFile->WriteInteger(SectionWindow, KeyHeight, Height);
    IniFile->UpdateFile();
}

// Appends a line; when the memo passes 200 lines the oldest 50 are removed.
void TFtpReceiveForm::Display(const String &Msg)
{
    DisplayMemo->Lines->BeginUpdate();
    auto EndUpdate = MakeScopeExit([this] {
        DisplayMemo->Lines->EndUpdate();
        SendMessage(DisplayMemo->Handle, EM_SCROLLCARET, 0, 0);
    });
    if (DisplayMemo->Lines->Count > 200) {
        for (int I = 0; I < 50; I++)
            DisplayMemo->Lines->Delete(0);
    }
    DisplayMemo->Lines->Add(Msg);
}

void __fastcall TFtpReceiveForm::DisplayHandler(TObject *Sender, String &Msg)
{
    Display(Msg);
}

void __fastcall TFtpReceiveForm::ExitButtonClick(TObject *Sender)
{
    Close();
}

void __fastcall TFtpReceiveForm::FtpClient1Progress64(TObject *Sender, __int64 Count, bool &Abort)
{
    FProgressCount = Count;
    // Update the screen only once a second (64-bit ticks: Delphi's GetTickCount wraps).
    if (FLastProgress < GetTickCount64()) {
        FLastProgress = GetTickCount64() + 1000;
        InfoLabel->Caption = IntToStr(FProgressCount);
        InfoLabel->Repaint();
    }
}

// Shows a downloaded directory listing in the directory form.
void TFtpReceiveForm::DisplayFile(const String &FileName)
{
    // When the data is displayed on the fly, no file is written.
    if (DisplayCheckBox->Checked)
        return;
    try {
        std::unique_ptr<TFileStream> Strm(new TFileStream(FileName, fmOpenRead));
        std::vector<char> Buf(static_cast<size_t>(Strm->Size));
        if (!Buf.empty())
            Strm->Read(Buf.data(), static_cast<int>(Buf.size()));
        const RawByteString S(Buf.data(), static_cast<int>(Buf.size()));
        // Auto-detect UTF-8 if the option is set.
        unsigned ACodePage = FtpClient1->CodePage;
        if (FtpClient1->CodePage != CP_UTF8 &&
            FtpClient1->Options.Contains(Overbyteicstypes::ftpAutoDetectCodePage) &&
            CharsetDetect(S) == cdrUtf8)
            ACodePage = CP_UTF8;
        DirectoryForm->DirListBox->Items->Text = AnsiToUnicode(S, ACodePage);
    }
    catch (Exception &) {
        DirectoryForm->DirListBox->Clear();
    }
    DirectoryForm->ShowModal();
}

void __fastcall TFtpReceiveForm::FtpClient1RequestDone(TObject *Sender, TFtpRequest RqType, System::Word ErrCode)
{
    Display("Request " + IntToStr(static_cast<int>(RqType)) + " Done.");
    Display("StatusCode = " + IntToStr(FtpClient1->StatusCode));
    Display("LastResponse was : '" + FtpClient1->LastResponse + "'");
    if (ErrCode == 0)
        Display("No error");
    else
        Display("Error = " + IntToStr(ErrCode) + " (" + FtpClient1->ErrorMessage + ")");

    InfoLabel->Caption = IntToStr(FProgressCount);   // the last progress value

    if (ErrCode != 0)
        return;
    switch (RqType) {
    case ftpOptsAsync:
        if (FtpClient1->StatusCode == 200) {
            if (FtpClient1->NewOpts == "UTF8 ON")
                FtpClient1->CodePage = CP_UTF8;
            else if (FtpClient1->NewOpts == "UTF8 OFF")
                FtpClient1->CodePage = CP_ACP;
        }
        break;
    case ftpDirAsync:
    case ftpDirectoryAsync:
    case ftpLsAsync:
    case ftpListAsync:
        DisplayFile(TempFileName);
        break;
    case ftpSizeAsync:
        Display("File size is " + IntToStr(FtpClient1->SizeResult) + " bytes");
        break;
    case ftpPwdAsync:
    case ftpMkdAsync:
    case ftpCDupAsync:
    case ftpCwdAsync:
        Display("Directory is \"" + FtpClient1->DirResult + "\"");
        break;
    case ftpGetAsync:
        InfoLabel->Caption = InfoLabel->Caption + " [" + IntToStr(IcsGetFileSize(FtpClient1->LocalFileName)) + "]";
        break;
    default:
        break;
    }
}

void __fastcall TFtpReceiveForm::FtpClient1StateChange(TObject *Sender)
{
    StateLabel->Caption = IntToStr(static_cast<int>(FtpClient1->State));
}

// Copies the settings from the form to the FTP component, then runs the synchronous or the
// asynchronous version of a command, as the Sync check box selects. DirListing is true for the
// directory commands, which download the listing to a temporary file.
void TFtpReceiveForm::ExecuteCmd(TSyncCmd SyncCmd, TAsyncCmd AsyncCmd, bool DirListing)
{
    Display("Executing Requested Command");
    FLastProgress = 0;
    FProgressCount = 0;

    FtpClient1->HostName = HostNameEdit->Text;
    FtpClient1->Port = PortEdit->Text;
    FtpClient1->DataPortRangeStart = static_cast<unsigned>(StrToInt(Trim(DataPortRangeStartEdit->Text)));
    FtpClient1->DataPortRangeEnd = static_cast<unsigned>(StrToInt(Trim(DataPortRangeEndEdit->Text)));
    FtpClient1->UserName = UserNameEdit->Text;
    FtpClient1->PassWord = PassWordEdit->Text;
    FtpClient1->Account = AccountEdit->Text;
    FtpClient1->HostDirName = HostDirEdit->Text;
    FtpClient1->HostFileName = HostFileEdit->Text;
    FtpClient1->LocalFileName = LocalFileEdit->Text;
    FtpClient1->Passive = PassiveCheckBox->Checked;
    FtpClient1->Binary = BinaryCheckBox->Checked;
    FtpClient1->ResumeAt = StrToInt64(ResumeAtEdit->Text);   // 64-bit, so a resume past 2 GB works
    FtpClient1->DisplayFileFlag = DisplayCheckBox->Checked;
    FtpClient1->OnDisplay = DisplayHandler;

    // What TWSocket logs through the OnDisplay event.
    FtpClient1->WSDebugOptions = DefWSDebugClient;

    SslContext1->SslVerifyPeer = VerifyPeerCheckBox->Checked;
    SslContext1->UseSharedCAStore = true;

    // A client certificate and key are only needed when the server asks for one (very rare).
    if (!PrivKeyFileEdit->Text.IsEmpty() || !CertFileEdit->Text.IsEmpty()) {
        if (!FileExists(PrivKeyFileEdit->Text) || !FileExists(CertFileEdit->Text)) {
            Display("Client SSL certificate and key must exist, if specified and required");
            return;
        }
        SslContext1->SslCertFile = CertFileEdit->Text;
        SslContext1->SslPassPhrase = PassPhraseEdit->Text;
        SslContext1->SslPrivKeyFile = PrivKeyFileEdit->Text;
    }
    // The CA file and path edits are no longer used; the shared CA store verifies the chain.
    FtpClient1->SslCertVerMethod = SslContext1->SslVerifyPeer ? CertVerBundle : CertVerNone;
    FtpClient1->SslAllowSelfSign = true;   // self-signed testing certificates are allowed

    FtpClient1->ProtLevel = ProtLevelEdit->Text;
    FtpClient1->PBSZSize = 0;   // the value is never actually sent

    if (DirListing) {
        DeleteFile(TempFileName);
        FtpClient1->LocalFileName = TempFileName;
    }

    if (NoAutoResumeAtCheckBox->Checked)
        FtpClient1->Options = FtpClient1->Options << ftpNoAutoResumeAt;
    else
        FtpClient1->Options = FtpClient1->Options >> ftpNoAutoResumeAt;

    if (SslTypeComboBox->ItemIndex > -1) {
        FtpClient1->SslType = static_cast<TFtpCliSslType>(SslTypeComboBox->ItemIndex);
        if (FtpClient1->SslType == sslTypeImplicit)
            FtpClient1->Port = SslPortEdit->Text;
    }
    const int Bandwidth = StrToIntDef(MaxKbEdit->Text, 0);
    if (Bandwidth > 0) {
        FtpClient1->BandwidthLimit = Bandwidth * 1024;
        FtpClient1->Options = FtpClient1->Options << ftpBandwidthControl;
    }
    else
        FtpClient1->Options = FtpClient1->Options >> ftpBandwidthControl;

    if (SyncCheckBox->Checked)
        Display(SyncCmd() ? "Command Success" : "Command Failure");
    else
        AsyncCmd();
}

void __fastcall TFtpReceiveForm::OpenAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Open, FtpClient1->OpenAsync);
}

void __fastcall TFtpReceiveForm::QuitAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Quit, FtpClient1->QuitAsync);
}

void __fastcall TFtpReceiveForm::CwdAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Cwd, FtpClient1->CwdAsync);
}

void __fastcall TFtpReceiveForm::UserAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->User, FtpClient1->UserAsync);
}

void __fastcall TFtpReceiveForm::PassAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Pass, FtpClient1->PassAsync);
}

void __fastcall TFtpReceiveForm::AcctAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->acct, FtpClient1->AcctAsync);   // the sync method is spelled "acct" in the .hpp
}

void __fastcall TFtpReceiveForm::ProtButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Prot, FtpClient1->ProtAsync);
}

void __fastcall TFtpReceiveForm::PbszButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Pbsz, FtpClient1->PbszAsync);
}

void __fastcall TFtpReceiveForm::ConnectAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Connect, FtpClient1->ConnectAsync);
}

void __fastcall TFtpReceiveForm::GetAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Get, FtpClient1->GetAsync);
}

void __fastcall TFtpReceiveForm::ReceiveAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Receive, FtpClient1->ReceiveAsync);
}

void __fastcall TFtpReceiveForm::AbortAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Abort, FtpClient1->AbortAsync);
}

void __fastcall TFtpReceiveForm::DirAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Dir, FtpClient1->DirAsync, true);
}

void __fastcall TFtpReceiveForm::DirectoryAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Directory, FtpClient1->DirectoryAsync, true);
}

void __fastcall TFtpReceiveForm::LsAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Ls, FtpClient1->LsAsync, true);
}

void __fastcall TFtpReceiveForm::ListAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->List, FtpClient1->ListAsync, true);
}

void __fastcall TFtpReceiveForm::SystAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Syst, FtpClient1->SystAsync);
}

void __fastcall TFtpReceiveForm::SystemAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->System, FtpClient1->SystemAsync);
}

void __fastcall TFtpReceiveForm::FileSizeAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->FileSize, FtpClient1->FileSizeAsync);
}

void __fastcall TFtpReceiveForm::SizeAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Size, FtpClient1->SizeAsync);
}

void __fastcall TFtpReceiveForm::MkdAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Mkd, FtpClient1->MkdAsync);
}

void __fastcall TFtpReceiveForm::MkdirAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Mkdir, FtpClient1->MkdirAsync);
}

void __fastcall TFtpReceiveForm::RmdAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Rmd, FtpClient1->RmdAsync);
}

void __fastcall TFtpReceiveForm::RmdirAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Rmdir, FtpClient1->RmdirAsync);
}

void __fastcall TFtpReceiveForm::RenAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Ren, FtpClient1->RenAsync);
}

void __fastcall TFtpReceiveForm::RenameAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Rename, FtpClient1->RenameAsync);
}

void __fastcall TFtpReceiveForm::DeleAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Dele, FtpClient1->DeleAsync);
}

void __fastcall TFtpReceiveForm::DeleteAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Delete, FtpClient1->DeleteAsync);
}

void __fastcall TFtpReceiveForm::PwdAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Pwd, FtpClient1->PwdAsync);
}

void __fastcall TFtpReceiveForm::QuoteAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Quote, FtpClient1->QuoteAsync);
}

void __fastcall TFtpReceiveForm::DoQuoteAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->DoQuote, FtpClient1->DoQuoteAsync);
}

void __fastcall TFtpReceiveForm::PutAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Put, FtpClient1->PutAsync);
}

void __fastcall TFtpReceiveForm::TransmitAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Transmit, FtpClient1->TransmitAsync);
}

void __fastcall TFtpReceiveForm::TypeSetAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->TypeSet, FtpClient1->TypeSetAsync);
}

void __fastcall TFtpReceiveForm::RestGetAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->RestGet, FtpClient1->RestGetAsync);
}

void __fastcall TFtpReceiveForm::RestartGetAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->RestartGet, FtpClient1->RestartGetAsync);
}

void __fastcall TFtpReceiveForm::CDupAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->CDup, FtpClient1->CDupAsync);
}

void __fastcall TFtpReceiveForm::ClearButtonClick(TObject *Sender)
{
    DisplayMemo->Clear();
}

void __fastcall TFtpReceiveForm::AppendAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Append, FtpClient1->AppendAsync);
}

void __fastcall TFtpReceiveForm::AppendFileAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->AppendFile, FtpClient1->AppendFileAsync);
}

void __fastcall TFtpReceiveForm::AbortXferAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->AbortXfer, FtpClient1->AbortXferAsync);
}

void __fastcall TFtpReceiveForm::AuthButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Auth, FtpClient1->AuthAsync);
}

void __fastcall TFtpReceiveForm::CccButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Ccc, FtpClient1->CccAsync);
}

void __fastcall TFtpReceiveForm::FeatButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->Feat, FtpClient1->FeatAsync);
}

// Several transfers in one session: connects if needed, then puts the file repeatedly (in sync
// mode) until a transfer does not end with 226.
void __fastcall TFtpReceiveForm::Button1Click(TObject *Sender)
{
    SyncCheckBox->Checked = true;
    if (!FtpClient1->Connected) {
        FtpClient1->HostName = HostNameEdit->Text;
        FtpClient1->Port = PortEdit->Text;
        FtpClient1->UserName = UserNameEdit->Text;
        FtpClient1->PassWord = PassWordEdit->Text;
        FtpClient1->DisplayFileFlag = DisplayCheckBox->Checked;
        FtpClient1->OnDisplay = DisplayHandler;
        ExecuteCmd(FtpClient1->Connect, FtpClient1->ConnectAsync);
        if (FtpClient1->LastResponse.SubString(1, 3) != "230")
            return;
    }
    int Count = 0;
    do {
        DisplayMemo->Clear();
        Display("Count=" + IntToStr(++Count));
        FtpClient1->HostDirName = HostDirEdit->Text;
        FtpClient1->HostFileName = HostFileEdit->Text;
        FtpClient1->LocalFileName = LocalFileEdit->Text;
        FtpClient1->Binary = BinaryCheckBox->Checked;
        FtpClient1->Passive = PassiveCheckBox->Checked;
        FtpClient1->DisplayFileFlag = DisplayCheckBox->Checked;
        FtpClient1->OnDisplay = DisplayHandler;
        ExecuteCmd(FtpClient1->Put, FtpClient1->PutAsync);
    } while (FtpClient1->LastResponse.SubString(1, 3) == "226");
}

void __fastcall TFtpReceiveForm::RestPutAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->RestPut, FtpClient1->RestPutAsync);
}

void __fastcall TFtpReceiveForm::RestartPutAsyncButtonClick(TObject *Sender)
{
    ExecuteCmd(FtpClient1->RestartPut, FtpClient1->RestartPutAsync);
}

// Uploads from a TStream instead of a file name (synchronous).
void __fastcall TFtpReceiveForm::TransmitUsingStreamButtonClick(TObject *Sender)
{
    std::unique_ptr<TStream> MyStream(new TFileStream(LocalFileEdit->Text, fmOpenRead));
    // Declared after MyStream, so it detaches the stream before the stream is freed.
    auto Detach = MakeScopeExit([this] { FtpClient1->LocalStream = nullptr; });
    FtpClient1->HostName = HostNameEdit->Text;
    FtpClient1->Port = PortEdit->Text;
    FtpClient1->UserName = UserNameEdit->Text;
    FtpClient1->PassWord = PassWordEdit->Text;
    FtpClient1->HostDirName = HostDirEdit->Text;
    FtpClient1->HostFileName = HostFileEdit->Text;
    FtpClient1->LocalStream = MyStream.get();
    FtpClient1->Binary = BinaryCheckBox->Checked;
    FtpClient1->DisplayFileFlag = DisplayCheckBox->Checked;
    FtpClient1->OnDisplay = DisplayHandler;
    if (!ResumeAtEdit->Text.IsEmpty())
        FtpClient1->ResumeAt = StrToInt64(ResumeAtEdit->Text);
    FtpClient1->Transmit();
}

// Downloads into a TStream instead of a file name (synchronous).
void __fastcall TFtpReceiveForm::ReceiveUsingStreamButtonClick(TObject *Sender)
{
    std::unique_ptr<TStream> MyStream(new TFileStream(LocalFileEdit->Text, fmCreate));
    // Declared after MyStream, so it detaches the stream before the stream is freed.
    auto Detach = MakeScopeExit([this] { FtpClient1->LocalStream = nullptr; });
    FtpClient1->HostName = HostNameEdit->Text;
    FtpClient1->Port = PortEdit->Text;
    FtpClient1->UserName = UserNameEdit->Text;
    FtpClient1->PassWord = PassWordEdit->Text;
    FtpClient1->HostDirName = HostDirEdit->Text;
    FtpClient1->HostFileName = HostFileEdit->Text;
    FtpClient1->LocalStream = MyStream.get();
    FtpClient1->Binary = BinaryCheckBox->Checked;
    FtpClient1->DisplayFileFlag = DisplayCheckBox->Checked;
    FtpClient1->OnDisplay = DisplayHandler;
    if (!ResumeAtEdit->Text.IsEmpty())
        FtpClient1->ResumeAt = StrToInt64(ResumeAtEdit->Text);
    FtpClient1->Receive();
}

// Just a test; real applications should use AUTH TLS/SSL instead.
void __fastcall TFtpReceiveForm::SslRenegotiateButtonClick(TObject *Sender)
{
    if (!FtpClient1->ControlSocket->SslStartRenegotiation())
        throw Exception("SslRenegotiation can be started only on an existing secure Sslv3 or better connection!");
}

// One complete upload in sync mode, checked by comparing the file size on the server.
bool TFtpReceiveForm::SendFile()
{
    try {
        FtpClient1->Abort();   // whatever happened, abort
        FtpClient1->LocalFileName = LocalFileEdit->Text;
        FtpClient1->HostDirName = HostDirEdit->Text;
        FtpClient1->HostFileName = HostFileEdit->Text;
        FtpClient1->HostName = HostNameEdit->Text;
        FtpClient1->Port = PortEdit->Text;
        FtpClient1->UserName = UserNameEdit->Text;
        FtpClient1->PassWord = PassWordEdit->Text;
        FtpClient1->Passive = PassiveCheckBox->Checked;
        FtpClient1->Binary = BinaryCheckBox->Checked;
        FtpClient1->Timeout = 30;
        FtpClient1->ShareMode = ftpShareDenyNone;
        if (!FtpClient1->Connect())
            throw Exception("FtpClient1.Connect failed: " + FtpClient1->ErrorMessage);
        if (!FtpClient1->HostDirName.IsEmpty() && !FtpClient1->Cwd())
            throw Exception("FtpClient1.Cwd failed: " + FtpClient1->ErrorMessage);
        if (!FtpClient1->TypeSet())
            throw Exception("FtpClient1.TypeSet failed: " + FtpClient1->ErrorMessage);
        if (!FtpClient1->Put())
            throw Exception("FtpClient1.Put failed: " + FtpClient1->ErrorMessage);
        if (FtpClient1->StatusCode != 226)
            throw Exception("FtpClient1.Put failed: " + FtpClient1->LastResponse);
        if (!FtpClient1->Size())
            throw Exception("FtpClient1.Size failed: " + FtpClient1->ErrorMessage);
        // IcsGetFileSize is 64-bit and gives -1 for a missing file; Delphi's local GetFileSize is 32-bit.
        const __int64 LocalSize = IcsGetFileSize(FtpClient1->LocalFileName);
        const bool Result = (FtpClient1->SizeResult == LocalSize);
        if (!Result)
            Display("Incorrect file size on server (S=" + IntToStr(FtpClient1->SizeResult) +
                    " L=" + IntToStr(LocalSize) + ")");
        if (!FtpClient1->Quit())
            throw Exception("FtpClient1.Quit failed: " + FtpClient1->ErrorMessage);
        return Result;
    }
    catch (Exception &E) {
        Display("Exception " + E.ClassName() + ": " + E.Message);
        return false;
    }
}

// Uploads the file again and again until it fails or the button (now "STOP") is pressed again.
void __fastcall TFtpReceiveForm::StressPutButtonClick(TObject *Sender)
{
    if (FRunning) {
        FRunning = false;
        Display("Stress Put interrupted.");
        StressPutButton->Caption = "Stress Put";
        return;
    }
    FRunning = true;
    auto Finished = MakeScopeExit([this] {
        FRunning = false;
        Display("Stress Put finished.");
        StressPutButton->Caption = "Stress Put";
    });
    Display("Stress Put started.");
    StressPutButton->Caption = "STOP";
    int Count = 0;
    while (FRunning && !Application->Terminated) {
        Display("STRESS COUNT = " + IntToStr(++Count));
        if (!SendFile())
            break;
        Application->ProcessMessages();
    }
}

void __fastcall TFtpReceiveForm::FtpClient1DisplayFile(TObject *Sender, String &Msg)
{
    Display("DATA: \"" + Msg + "\"");
}

// Client-side SSL session caching through these two events; see the HttpsTst demo for a real
// external cache.
void __fastcall TFtpReceiveForm::FtpClient1SslCliNewSession(TObject *Sender, void *SslSession, bool WasReused,
                                                            bool &IncRefCount)
{
    if (!SessCacheCheckBox->Checked)
        return;
    FCachedSession = SslSession;
    IncRefCount = true;
    if (WasReused)
        Display("! SSL Session reused");
}

void __fastcall TFtpReceiveForm::FtpClient1SslCliGetSession(TObject *Sender, void *&SslSession, bool &FreeSession)
{
    if (!SessCacheCheckBox->Checked) {
        FCachedSession = nullptr;
        return;
    }
    SslSession = FCachedSession;
    FreeSession = true;
}

// A simple test of the SslCliCertRequest event, triggered only when CertFileEdit is empty, the
// server asked for a client certificate and the SSL session was not reused. The certificate
// files are looked for in the current directory.
void __fastcall TFtpReceiveForm::FtpClient1SslCliCertRequest(TObject *Sender, TX509Base *&Cert)
{
    // The Delphi .dpr never creates ClientCertDlg, so this event would use a nil form.
    if (!ClientCertDlg)
        Application->CreateForm(__classid(TClientCertDlg), &ClientCertDlg);
    if (!FClientCerts) {
        // A pool of client certificates; if a file fails to load the list is freed and the
        // exception goes on to the caller, as in Delphi.
        ClientCertDlg->CertListBox->Clear();
        std::unique_ptr<TX509List> Certs(new TX509List(nullptr, true));
        TX509Base *X = Certs->Add();
        X->LoadFromPemFile("01cert.pem");
        X->PrivateKeyLoadFromPemFile("01key.pem", "password");
        ClientCertDlg->CertListBox->Items->Add(X->SubjectOneLine);
        X = Certs->Add();
        X->LoadFromPemFile("client.pem", true, String("password"));
        ClientCertDlg->CertListBox->Items->Add(X->SubjectOneLine);
        FClientCerts = std::move(Certs);
    }
    ClientCertDlg->CertListBox->ItemIndex = 0;
    if (ClientCertDlg->ShowModal() == mrOk)
        Cert = FClientCerts->Items[ClientCertDlg->CertListBox->ItemIndex];
}

void __fastcall TFtpReceiveForm::OptsAsyncButtonClick(TObject *Sender)
{
    FtpClient1->NewOpts = OptsEdit->Text;
    ExecuteCmd(FtpClient1->Opts, FtpClient1->OptsAsync);
}
