/*
  ICS MIME Decode demo - C++Builder translation of the Delphi demo OverbyteIcsMimeDemo1.pas
  (demos-delphi-vcl), ICS V9.8.

  Exercises the ICS MIME helpers: TMimeDecodeW (event driven decoding of a MIME file),
  TMimeDecodeEx (decoding into arrays, no events), Base64, MIME header encoding and decoding,
  TMimeTypesList look-ups, HTTP header parsing and RFC 5987 HTTP header parameters.

  How this C++ version differs from the Delphi original, apart from syntax:
  - The inline (UU/yEnc) decode file stream and the temporary string list are held in
    std::unique_ptr; the ini file object too.
  - The demos-data folder is found from the exe folder for both the test file and mime.types.
  - Inline decoded data is appended to the last memo line (see MimeDecode1InlineDecodeLine).
*/

#include <vcl.h>
#pragma hdrstop

#include <initializer_list>

#include "OverbyteIcsMimeDemo1.h"
#include "OverbyteIcsIniFiles.hpp"
#include "OverbyteIcsUtils.hpp"
#include "OverbyteIcsHtmlUtils.hpp"

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

TMimeDecodeForm *MimeDecodeForm;

namespace {

const String SectionData   = "Data";
const String SectionWindow = "Window";
const String KeyTop        = "Top";
const String KeyLeft       = "Left";
const String KeyWidth      = "Width";
const String KeyHeight     = "Height";
const String KeyFile       = "FileName";
const String KeyText       = "TextEdit";
const String KeyIgnore     = "IgnoreBlankParts";
const String KeyLooseRFC   = "LooseRFC";

// A few encoded header lines (koi8-r is Cyrillic, GB2312 is Chinese, 8859-1 is Latin/Western
// European, 8859-5 is Latin/Cyrillic, windows-1256 is Arabic, see OverbyteIcsCharsetUtils).
// \001 is the Pascal #1; octal escapes stop after three digits, so a following letter is safe.
const char *const TestHeaders[] = {
    "a\001\001\001",
    "a\001\001\001b",
    "\"test test test test test test test\001\001\001\001\001\001test test\"",
    "=?ISO-8859-1?Q?a?=\001\001\001=?ISO-8859-1?Q?b?=",
    "=?ISO-8859-1?Q?a?=\r\n\t=?ISO-8859-1?Q?b?=",
    "=?ISO-8859-1?Q?a?=\r\n =?ISO-8859-1?Q?b?=",
    "=?ISO-8859-1?Q?a?=\001\001\001<foo@bar.com>",
    "\n\n=?ISO-8859-1?Q?a?= b\001\001\001=?ISO-8859-1?Q?c?=",
    "=?US-ASCII?Q?Keith_Moore?= <moore@cs.utk.edu>",
    "=?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>",
    "=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=",
    "=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=",
    "=?ISO-8859-1?Q?a?= b=?ISO-8859-1?Q?a_b?=",
    "=?ISO-8859-1?Q?a?=  =?ISO-8859-1?Q?b?=",
    "=?iso-8859-1?q?Fred=20Mace?= <fredmace@yahoo.co.uk>",
    "=?ISO-8859-1?Q?Re=3A_=5Btwsocket=5D?= Encoded mail headers",
    "=?ISO-8859-1?B?aGk=?=",
    "=?GB2312?B?yczO8cOz0tc=?= <hk@163.com>",
    "=?GB2312?B?sOzA7c/juNvTosPAufq8yrmry77Qrbvh?=",
    "=?utf-8?B?TWljcm9zb2Z0IE91dGxvb2sgVGVzdCBNZXNzYWdl?=",
    "=?ISO-8859-1?b?UmU6S2V5cyB0byBzdGF5aW5nIHlvdW5n?=",
    "=?iso-8859-5?B?+szB1MEg4s/U18nOwQ==?= <dnevnik@liveinternet.ru>",
    "\"eap2s@mtsu.eduruss.txt}\" <dunman@magsys.co.uk>",
    "=?koi8-r?B?9C7yLiDs1cLRztPLycog?= <yujiko@barryland.com>",
    "=?koi8-r?B?NSDUy8EuIMTM0SDP0sfBzsnawdTP0s/XICDQ0sHaxM7Jy8/XIMkgzQ==?=",
    "=?windows-1252?Q?Don=92t_be_Blue!_Heat_up_October_with_these_Scorching_Deals_Only_at_Screwfix_?=",
    "=?iso-8859-1?Q?Integral_512MB_Pen_Drives_For_Just_=A31.29?=",
    "=?windows-1256?Q?=CF=E6=D1=C8=ED=E4_=E3=CF=C7=D1=C8=D3=CA=E5_=E6_=D3=C7=E4=CA=D1=C7=E1?=",
    "=?utf-8?B?QW5ndXMgZmFpdCBsYSBmw6p0ZSDDoCBGcmFuw6dvaXMgcXVhbmQgbCfDqXTDqSBhcnJpdmU=?=",
    "=?utf-8?Q?Angus_fait_la_f=C3=AAte_=C3=A0_Fran=C3=A7ois_quand_l'=C3=A9t=C3=A9_arrive?=",
    "=?iso-8859-1?B?QW5ndXMgZmFpdCBsYSBm6nRlIOAgRnJhbudvaXMgcXVhbmQgbCfpdOkgYXJyaXZl?=",
    "=?iso-8859-1?Q?Angus_fait_la_f=EAte_=E0_Fran=E7ois_quand_l'=E9t=E9_arrive?=",
    "=?utf-8?Q?Unicode_testing_-_Angus_fait_la_?==?utf-8?Q?f=C3=AAte_=C3=A0_Fran=C3=A7ois_q"
        "uand_l'=C3=A9t=C3=A9_arri?==?utf-8?Q?ve_=28Greek:_=C5=B4=C6=9F=C6=B1=CE=A3=CE=A8=CE=A9=29?=",
    "=?utf-8?Q?Unicode_testing_-_Angus_fait_la_?=\r\n\t"
        "=?utf-8?Q?f=C3=AAte_=C3=A0_Fran=C3=A7ois_quand_l'=C3=A9t=C3=A9_arri?=\r\n\t"
        "=?utf-8?Q?ve_=28Greek:_=C5=B4=C6=9F=C6=B1=CE=A3=CE=A8=CE=A9=29?=",
    "\"=?utf-8?B?VGFXYSBmw7xyIEhHLVNWIDM1MiB2b20gMjAuMDYuMjAyNSAwOV80OV8w?==?utf-8?B?OC5wZGY=?=\"",
};
static_assert(sizeof(TestHeaders) / sizeof(TestHeaders[0]) == 35, "TotHeaders is 35 in the Delphi demo");

// A few HTTP request and response headers, mostly with several arguments to parse.
// The trailing comments give the Delphi demo's 1-based numbers, which the parse tests use.
const char *const TestHttpHdrs[] = {
    "Accept: text/html",
    "Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*",
    "Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8",    // 3
    "Accept-Encoding: gzip, deflate",
    "Allow: GET, POST, HEAD",
    "Content-Security-Policy: default-src 'self' http://example1.com; connect-src 'none';",    // 6
    "Content-Security-Policy: connect-src http://example1.com/;\r\nscript-src http://example2.com/",    // probably illegal
    "Content-Disposition: form-data; name=\"myFile\"; filename=\"foo.txt\"",
    "Content-Type: text/plain",
    "Content-Type: text/html; charset=\"iso-8859-1\"",     // 10
    "Content-Type: text/html; charset='utf-8'",            // 11
    "Content-Type: json/xml; charset=utf-8",               // 12
    "Content-Type: text/xml",
    "Content-Type: text/css",
    "Content-Type: application/json; charset=UTF-8",
    "Content-Type: application/x-www-form-urlencoded; charset=UTF-8",
    "Content-Type: application/binary",
    "Content-Type: application/dns-json",
    "Content-Type: application/xml",
    "Content-Type: application/javascript",
    "application/json",
    "Content-Type: image/gif",
    "Content-Type: audio/x-wav",
    "Content-Type: multipart/form-data; boundary=something",
    "Content-Type: application/json;charset=UTF-8;odata.metadata=minimal",    // 25
    "Cookie: name=value",
    "Cookie: name=value; name2=value2; name3=value3",
    "Keep-Alive: timeout=5, max=1000",
    "Set-Cookie: WebAppTelecom-Logon=_NONE_; EXPIRES=Sat, 29 Jun 2024 00:00:00; DOMAIN=telecom-tariffs.co.uk; PATH=/; Secure; HttpOnly",  // 29
    "Strict-Transport-Security: max-age=31536000; includeSubDomains",
    "X-Content-Type-Options: nosniff",
    "X-XSS-Protection: 1;mode=block",
    "Authorization: Basic YW5ndXM3OmZnaA==",    // 33
    "Authorization: Digest username=\"Mufasa\",realm=\"testrealm@host.com\",nonce=\"dcd98c093\",uri=\"/dir/index.html\",qop=auth,nc=00000001,cnonce=\"0a4f113b\",response=\"6629f07c4ef1\", opaque=\"5cc71e9517f40e41\"",   // 34
    "Authorization: Digest username=\"Mufasa\",realm=\"http-auth@example.org\",uri=\"/dir/index.html\", algorithm=SHA-256,nonce=\"7ypf/xlj90v\",nc=00000001,cnonce=\"f2/wEemxURZJ\", qop=auth, response=\"8c00eebdbec\",opaque=\"FQhe/qtdS\"",
    "WWW-Authenticate: Basic Realm=\"Business Telecom Tariffs (all lower case please)\"",    // 36
    "WWW-Authenticate: Digest realm=\"DemoAuthAll\",qop=\"auth\",algorithm=\"SHA-256\",nonce=\"9XVVN7HS5UBmYgcfFjQV2eHI7UTYBLc3\",opaque=\"BZW1LWLuuz7ra1yl2ep7DvTA3KUIZ1LV3u\"",
    "WWW-Authenticate: Digest realm=\"DemoAuthAll\",qop=\"auth\",algorithm=\"MD5\",nonce=\"9XVVN7HS5UBoBpM4gto3vFXB52oOGtvy\",opaque=\"0QMT7tIpbMzS46c1RjYbjyx2FbbKil6Q7G\"",
    "WWW-Authenticate: Basic Realm=\"DemoAuthAll\"",    // 39
};
static_assert(sizeof(TestHttpHdrs) / sizeof(TestHttpHdrs[0]) == 39, "TotHttpHdrs is 39 in the Delphi demo");

// The Delphi demo numbers the HTTP headers from 1.
String HttpHdr(int Number)
{
    return TestHttpHdrs[Number - 1];
}

} // namespace

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

void __fastcall TMimeDecodeForm::FormCreate(TObject *Sender)
{
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
    Font = Screen->IconFont;
    // The form owns the memo and frees it.
    Memo1 = new TMemo(this);
    Memo1->ParentFont = true;
    Memo1->Parent = this;
    Memo1->Align = alClient;
    Memo1->ScrollBars = ssBoth;
    Memo1->WordWrap = false;
    FIniFileName = GetIcsIniFileName();
    Memo1->Clear();
}

// demos-data is three levels up from Win64x\<config> (Delphi: four, from bin\<platform>\<config>).
String TMimeDecodeForm::DemosDataPath(const String &FileName)
{
    return IcsAbsolutisePath(ExtractFilePath(ParamStr(0)) + "..\\..\\..\\demos-data\\" + FileName);
}

void __fastcall TMimeDecodeForm::FormShow(TObject *Sender)
{
    if (FInitialized)
        return;
    FInitialized = true;
    {
        // PreserveAnsi must be passed in C++ (the Pascal default TRUE is not in the .hpp)
        std::unique_ptr<TIcsIniFile> ini(new TIcsIniFile(FIniFileName, true));
        Top = ini->ReadInteger(SectionWindow, KeyTop, Top);
        Left = ini->ReadInteger(SectionWindow, KeyLeft, Left);
        Width = ini->ReadInteger(SectionWindow, KeyWidth, Width);
        Height = ini->ReadInteger(SectionWindow, KeyHeight, Height);
        FileEdit->Text = ini->ReadString(SectionData, KeyFile, "");
        TextEdit->Text = ini->ReadString(SectionData, KeyText, "some text to encode");
        IgnoreBlankParts->Checked = ini->ReadBool(SectionData, KeyIgnore, IgnoreBlankParts->Checked);
        LooseRFC->Checked = ini->ReadBool(SectionData, KeyLooseRFC, LooseRFC->Checked);
    }
    if (FileEdit->Text.IsEmpty())
        FileEdit->Text = DemosDataPath("mime-demo1.txt");
}

void __fastcall TMimeDecodeForm::FormClose(TObject *Sender, TCloseAction &Action)
{
    std::unique_ptr<TIcsIniFile> ini(new TIcsIniFile(FIniFileName, true));
    ini->WriteInteger(SectionWindow, KeyTop, Top);
    ini->WriteInteger(SectionWindow, KeyLeft, Left);
    ini->WriteInteger(SectionWindow, KeyWidth, Width);
    ini->WriteInteger(SectionWindow, KeyHeight, Height);
    ini->WriteString(SectionData, KeyFile, FileEdit->Text);
    ini->WriteString(SectionData, KeyText, TextEdit->Text);
    ini->WriteBool(SectionData, KeyIgnore, IgnoreBlankParts->Checked);
    ini->WriteBool(SectionData, KeyLooseRFC, LooseRFC->Checked);
    ini->UpdateFile();
}

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

// Shows FLineBuf. ANSI text in a charset other than the system one is converted to
// Unicode with the part's code page.
void TMimeDecodeForm::DisplayBuffer()
{
    if (MimeDecode1->IsTextpart &&
        (MimeDecode1->PartCodePage == MimeDecode1->DefaultCodePage ||
         IsValidCodePage(MimeDecode1->PartCodePage)))
        Memo1->Lines->Add(AnsiToUnicode(FLineBuf, MimeDecode1->PartCodePage));
    else
        Memo1->Lines->Add(String(FLineBuf));
}

void __fastcall TMimeDecodeForm::DecodeButtonClick(TObject *Sender)
{
    Memo1->Clear();
    Update();
    MimeDecode1->LooseRFC = LooseRFC->Checked;
    MimeDecode1->DecodeFile(FileEdit->Text);
}

void __fastcall TMimeDecodeForm::MimeDecode1PartBegin(TObject *Sender)
{
    Display("--------- PART " + IntToStr(MimeDecode1->PartNumber) + " BEGIN ----------");
    FCharCnt = 0;
}

void __fastcall TMimeDecodeForm::MimeDecode1PartEnd(TObject *Sender)
{
    if (FCharCnt > 0) {
        FLineBuf[FCharCnt] = '\0';
        DisplayBuffer();
        FCharCnt = 0;
    }
    Display("--------- PART " + IntToStr(MimeDecode1->PartNumber) + " END ----------");
    FFileStream.reset();   // close the inline decode file, if any
}

// Decoded data arrives here. Assumes text data organised in lines: collects bytes into
// FLineBuf until LF (CR is dropped) and shows each line.
void __fastcall TMimeDecodeForm::MimeDecode1PartLine(TObject *Sender, void *Data, int DataLen)
{
    const char *bytes = static_cast<const char *>(Data);
    const int lastIndex = static_cast<int>(sizeof(FLineBuf)) - 1;   // Pascal High(FLineBuf)
    for (int i = 0; i < DataLen; ++i) {
        if (bytes[i] == '\r')
            continue;
        if (bytes[i] == '\n') {
            FLineBuf[FCharCnt] = '\0';
            DisplayBuffer();
            FCharCnt = 0;
        }
        else
            FLineBuf[FCharCnt++] = bytes[i];
        if (FCharCnt >= lastIndex - 1) {
            // buffer full: show what has been collected so far
            FLineBuf[lastIndex - 1] = '\0';
            DisplayBuffer();
            FCharCnt = 0;
        }
    }
}

void __fastcall TMimeDecodeForm::MimeDecode1PartHeaderLine(TObject *Sender)
{
    Display("Part header: " + String(UnfoldHdrValue(MimeDecode1->CurrentData)));
}

void __fastcall TMimeDecodeForm::MimeDecode1HeaderLine(TObject *Sender)
{
    Display("Msg header: " + String(UnfoldHdrValue(MimeDecode1->CurrentData)));
}

void __fastcall TMimeDecodeForm::MimeDecode1HeaderBegin(TObject *Sender)
{
    Display("--------- HEADER BEGIN ----------");
}

void __fastcall TMimeDecodeForm::MimeDecode1HeaderEnd(TObject *Sender)
{
    Display("\r\n--- Some Msg header: lines MIME inline decoded to Unicode ---\r\n"
            "\tFROM:\t" + MimeDecode1->FromW + "\r\n"
            "\tTO:\t" + MimeDecode1->DestW + "\r\n"
            "\tSUBJECT:\t" + MimeDecode1->SubjectW + "\r\n");
    Display("--------- HEADER END ----------");
}

void __fastcall TMimeDecodeForm::MimeDecode1PartHeaderBegin(TObject *Sender)
{
    Display("--------- PART " + IntToStr(MimeDecode1->PartNumber) + " HEADER BEGIN ----------");
    if (MimeDecode1->PartLevel > 0)
        Display("--------- EMBEDDED PART LEVEL " + IntToStr(MimeDecode1->PartLevel) + " ---------");
}

void __fastcall TMimeDecodeForm::MimeDecode1PartHeaderEnd(TObject *Sender)
{
    Display("--------- PART " + IntToStr(MimeDecode1->PartNumber) + " HEADER END ----------");
}

void __fastcall TMimeDecodeForm::MimeDecode1InlineDecodeBegin(TObject *Sender, AnsiString Filename)
{
    FFileName = String(Filename);
    Display("--------- INLINE begin. Filename is '" + FFileName + "'");
    Display("");
    FFileStream.reset();   // close the previous file, if any
    // The file name comes from the message: keep only its name part so it cannot point
    // outside the current folder (the Delphi demo uses it as it is).
    FFileStream.reset(new TFileStream("MimeFile_" + ExtractFileName(FFileName), fmCreate));
}

void __fastcall TMimeDecodeForm::MimeDecode1InlineDecodeEnd(TObject *Sender, AnsiString Filename)
{
    Display("--------- INLINE end");
    FFileStream.reset();
}

void __fastcall TMimeDecodeForm::MimeDecode1InlineDecodeLine(TObject *Sender, void *Line, int Len)
{
    if (Line == nullptr || Len <= 0)
        return;
    if (FFileStream)
        FFileStream->WriteBuffer(Line, Len);

    // The decoded bytes are shown as ANSI text, appended to the last memo line (the blank
    // line written by InlineDecodeBegin). The Delphi demo copies the bytes into a UnicodeString
    // (half filled), reads Lines[Count - 2] even when Count is 1, and drops a line on each call.
    String dataLine = String(AnsiString(static_cast<const char *>(Line), Len));
    TStrings *lines = Memo1->Lines;
    if (lines->Count < 1)
        lines->Add(dataLine);
    else
        lines->Strings[lines->Count - 1] = lines->Strings[lines->Count - 1] + dataLine;
}

void __fastcall TMimeDecodeForm::Decode64ButtonClick(TObject *Sender)
{
    String buf = IcsBase64Decode(TextEdit->Text);
    TextEdit->Text = buf;
    // Control and non-ASCII characters are shown as $hh.
    String txt;
    for (int i = 1; i <= buf.Length(); ++i) {
        if (buf[i] <= L'!' || buf[i] > L'~')
            txt += "$" + IntToHex(static_cast<int>(buf[i]), 2);
        else
            txt += buf[i];
    }
    Memo1->Lines->Add(txt);
}

void __fastcall TMimeDecodeForm::Encode64ButtonClick(TObject *Sender)
{
    String buf = IcsBase64Encode(TextEdit->Text);
    TextEdit->Text = buf;
    Memo1->Lines->Add(buf);
}

void __fastcall TMimeDecodeForm::DecAutoHeaderButtonClick(TObject *Sender)
{
    Display("Auto decoding test MIME Encoded Header Lines");
    for (const char *header : TestHeaders) {
        AnsiString charSet;
        AnsiString unfolded = UnfoldHdrValue(AnsiString(header));
        String uniStr = DecodeMimeInlineValueEx(unfolded, charSet);
        Display("Raw Header: " + String(header));
        Display("Unfolded Header: " + String(unfolded));
        Display("Unicode Header: " + uniStr + " [CharSet=" + LowerCase(String(charSet)) + "]");
        Display("");
    }
}

void __fastcall TMimeDecodeForm::DecOneHeaderButtonClick(TObject *Sender)
{
    AnsiString charSet;
    Display("Raw Header: " + TextEdit->Text);
    String uniStr = DecodeMimeInlineValueEx(AnsiString(TextEdit->Text), charSet);
    Display("Unicode Header: " + uniStr + " [CharSet=" + String(charSet) + "]");
    Display("");
}

void __fastcall TMimeDecodeForm::EncodeOneHdrButtonClick(TObject *Sender)
{
    const String text = TextEdit->Text;
    // Show the UTF-8 bytes read as ANSI characters.
    RawByteString utf8 = StringToUtf8(text);
    Display("Raw Text: " + text);
    Display("UTF-8 Text: " + String(AnsiString(utf8.c_str(), utf8.Length())));
    Display("UTF-8 Binary: " + String(HdrEncodeInLineEx(text, SpecialsRFC822, L'B', CP_UTF8, 72, false)));
    Display("UTF-8 Quoted: " + String(HdrEncodeInLineEx(text, SpecialsRFC822, L'Q', CP_UTF8, 72, false)));
    Display("ISO-8859-1 Binary: " + String(HdrEncodeInLineEx(text, SpecialsRFC822, L'B', 28591, 72, false)));
    Display("ISO-8859-1 Quoted: " + String(HdrEncodeInLineEx(text, SpecialsRFC822, L'Q', 28591, 72, false)));
    Display("Current Binary: " + String(HdrEncodeInLineEx(text, SpecialsRFC822, L'B', CP_ACP, 72, false)));
    Display("Current Quoted: " + String(HdrEncodeInLineEx(text, SpecialsRFC822, L'Q', CP_ACP, 72, false)));
}

// Decodes without events into arrays: header lines in WideHeaders, parts in PartInfos.
void __fastcall TMimeDecodeForm::DecodeFileExButtonClick(TObject *Sender)
{
    Display("MIME Decoding " + FileEdit->Text);
    MimeDecodeEx1->MaxParts = 999;                                // we want lots of parts
    MimeDecodeEx1->SkipBlankParts = IgnoreBlankParts->Checked;    // but maybe not empty ones
    MimeDecodeEx1->LooseRFC = LooseRFC->Checked;
    MimeDecodeEx1->DecodeFileEx(FileEdit->Text);
    Display("Total header lines found: " + IntToStr(MimeDecodeEx1->TotHeaders) +
            ", Length " + IntToStr(MimeDecodeEx1->DecodeW->LengthHeader));
    Display("Wide Subject: " + MimeDecodeEx1->DecodeW->SubjectW);
    if (MimeDecodeEx1->TotHeaders == 0)
        return;
    for (int i = 0; i < MimeDecodeEx1->TotHeaders; ++i)
        Display(MimeDecodeEx1->WideHeaders[i]);
    if (MimeDecodeEx1->DecParts == 0) {
        Display("No parts found to decode");
        return;
    }
    // Each part's content is in PartInfos[i].PartStream (only its summary is shown here); it
    // could be saved with PartStream->SaveToFile or loaded into another component.
    for (int i = 0; i < MimeDecodeEx1->DecParts; ++i)
        Display(MimeDecodeEx1->PartInfos[i].PInfo);
    MimeDecodeEx1->Reset();   // free the part streams
}

void __fastcall TMimeDecodeForm::ClearButtonClick(TObject *Sender)
{
    Memo1->Clear();
}

void TMimeDecodeForm::ListMimeTypes()
{
    Display("Total Mime Extensions: " + IntToStr(MimeTypesList1->CountExtn()) +
            ", Total Content Types: " + IntToStr(MimeTypesList1->CountContent()));
    std::unique_ptr<TStringList> tempList(new TStringList);
    MimeTypesList1->GetContentTypes(tempList.get());
    tempList->Sort();
    Display(tempList->Text);
}

void __fastcall TMimeDecodeForm::doCreateMimeListClick(TObject *Sender)
{
    MimeTypesList1->MimeTypeSrc = MTypeList;
    MimeTypesList1->LoadTypeList();
    Display("Mime Types List Loaded from Defaults");
    ListMimeTypes();
}

// Decodes HTTP header values with UTF-8 and percent encoding to Unicode.
void __fastcall TMimeDecodeForm::doDecHttpParamClick(TObject *Sender)
{
    auto testDecode = [this](const String &s) {
        Display("Http Param: " + s + " > Decoded: " + IcsDecodeHttpParam(s));
    };
    auto test2Decode = [this](const String &s, const String &name) {
        Display("Http Params: " + s + " > Decoded: " + IcsDecHttp2Params(s, name));
    };
    testDecode("iso-8859-1'en'%A3%20rates");
    testDecode("UTF-8'file%20name.jpg");
    testDecode("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates");
    test2Decode("bar; title=\"EURO exchange rates\"", "title");
    test2Decode("bar; title=\"EURO exchange rates\"; title*=utf-8''%e2%82%ac%20exchange%20rates", "title");
}

// Encodes non-ASCII HTTP header values with UTF-8 and percent encoding.
void __fastcall TMimeDecodeForm::doEncHttpParamClick(TObject *Sender)
{
    if (TextEdit->Text.IsEmpty())
        return;
    String s = IcsEncodeHttpParam(TextEdit->Text);
    Display("Http Param Encoded: " + s);
    Display("Http Param Decoded: " + IcsDecodeHttpParam(s));

    s = IcsEncHttp2Params("filename", TextEdit->Text);
    Display("Http Double Params Encoded: " + s);
    Display("Http Double Params Decoded: " + IcsDecHttp2Params(s, "filename"));
}

void __fastcall TMimeDecodeForm::doMimefromRegClick(TObject *Sender)
{
    const unsigned __int64 tickStart = GetTickCount64();
    MimeTypesList1->MimeTypeSrc = MTypeOS;
    if (!MimeTypesList1->LoadTypeList()) {
        Display("Failed to Load Mime Types from Windows Registry");
        return;
    }
    Display("Mime Types List Loaded from Windows Registry, took " +
            IntToStr(static_cast<__int64>(GetTickCount64() - tickStart)) + "ms");
    MimeTypesList1->AddContentType(".zip", "application/octet-stream");   // correct a Windows type we don't like
    ListMimeTypes();
}

void __fastcall TMimeDecodeForm::doMimeFromTypesClick(TObject *Sender)
{
    const unsigned __int64 tickStart = GetTickCount64();
    // The Delphi demo uses a path relative to the current directory, so it only loads when
    // started from the exe folder.
    MimeTypesList1->MimeTypesFile = DemosDataPath("mime.types");
    MimeTypesList1->MimeTypeSrc = MTypeMimeFile;
    if (!MimeTypesList1->LoadTypeList()) {
        Display("Failed to Load Mime Types from File: " + MimeTypesList1->MimeTypesFile);
        return;
    }
    Display("Mime Types List Loaded from  File: " + MimeTypesList1->MimeTypesFile +
            ", took " + IntToStr(static_cast<__int64>(GetTickCount64() - tickStart)) + "ms");
    ListMimeTypes();
}

void __fastcall TMimeDecodeForm::doMimeTestContentClick(TObject *Sender)
{
    const char *const contents[] = {
        "text/html", "image/jpeg", "application/pdf", "application/zip",
        "application/x-msdownload", "application/postscript", "application/octet-stream",
        "application/binary",
    };
    for (const char *content : contents)
        Display(String(content) + " Look-up: " + MimeTypesList1->TypeGetExtn(content));
    Display("");
}

void __fastcall TMimeDecodeForm::doMimeTestExtnClick(TObject *Sender)
{
    const int tests = 500000;
    const char *const extensions[] = { ".htm", ".zip", ".rtf", ".jpeg", ".iso", ".eps", ".xxx" };
    for (const char *extn : extensions) {
        const String ext = extn;
        const unsigned __int64 tickStart = GetTickCount64();
        String s;
        for (int i = 0; i < tests; ++i)
            s = MimeTypesList1->TypeFromExtn(ext);
        Display(IntToStr(tests) + " " + ext + " Look-ups: " + s +
                ", took " + IntToStr(static_cast<__int64>(GetTickCount64() - tickStart)) + "ms");
    }
    Display("");
}

void __fastcall TMimeDecodeForm::doTestHttpHdrsClick(TObject *Sender)
{
    // Parses one header into its name and arguments; an empty separator lets ICS choose.
    auto parse1 = [this](const String &hdr, const String &sep) {
        Display("Parsing: " + hdr);
        String hdrName;
        THdrArgs hdrArgs;
        // The Delphi demo never passes Sep, so its "known separators" pass repeats the automatic one.
        const int tot = IcsPaseHttpHdr(hdr, hdrName, hdrArgs, sep);
        if (tot == 0)
            Display("No arguments found, header name: " + hdrName);
        else {
            Display(IntToStr(tot) + " arguments found, header name: " + hdrName);
            for (int j = 0; j < tot; ++j)
                Display("Arg " + IntToStr(j + 1) + ": name: " + hdrArgs[j].HName +
                        ": value: " + hdrArgs[j].HValue);
        }
        Display("");
    };

    for (const char *hdr : TestHttpHdrs)   // parse all with the automatic separator
        parse1(hdr, "");

    Display("Repeating some with known separators");
    parse1(HttpHdr(3), ",");
    parse1(HttpHdr(4), ";");
    parse1(HttpHdr(6), " ");
    parse1(HttpHdr(29), "=");   // set-cookie
    parse1(HttpHdr(33), ";");
    parse1(HttpHdr(34), ";");
    parse1(HttpHdr(36), ";");
    parse1(HttpHdr(39), "=");

    Display("Finding charset and MIME from Content-Type headers");
    for (int number : { 10, 11, 12, 25 }) {
        Display(IcsFindHdrArg(HttpHdr(number), "charset"));
        Display(IcsFindHdrArg(HttpHdr(number), ""));   // MIME type
    }
}

void __fastcall TMimeDecodeForm::SelectFileClick(TObject *Sender)
{
    OpenDialog->InitialDir = ExtractFileDir(FileEdit->Text);
    if (OpenDialog->Execute())
        FileEdit->Text = OpenDialog->FileName;
}

void __fastcall TMimeDecodeForm::CloseButtonClick(TObject *Sender)
{
    Close();
}
