unit uBafWebModule;

// this code is under the BAF fair use license (BFUL) - https://bafbal.de/index.php?title=Bful
// FTP, SFTP, E-Mail, http
// https://bafbal.de/index.php?title=Modul_Web

interface

uses System.Math, System.SysUtils, System.Classes, uBafTypes, uBafInterpreter,
    ScSMTPClient, ScMailMessage, ScSMTPUtils, ScFtpClient, foBafDialog,
    ScSFtpClient, ScSSHClient, ScTypes, ScSSHUtils, ScBridge, ScSFTPUtils,
    ScHttp, uOsStuff, CLRClasses, FMX.Graphics, System.UITypes, System.IOUtils,
    System.NetEncoding, ScUtils;

type
  TBafWebModule = class(TBafInterpreterCustomModule)
  protected // #email_
    FMailClient: TScSMTPClient;
    FEmailLog: boolean;
    procedure EmailInit;
    procedure EmailSend;
    procedure SMTPClientAfterConnect(Sender: TObject);
    procedure SMTPClientAfterDisconnect(Sender: TObject);
    procedure SMTPClientError(Sender: TObject;
        ErrorCode: Integer; const ErrorMessage: string; var Fail: Boolean);
    procedure SMTPClientReadReply(Sender: TObject; const Line: string);
    procedure SMTPClientRecipientError(Sender: TObject;
        const RecipientAddress: string; ReplyCode: Integer;
        const ReplyText: string; var Skip: Boolean);
    procedure SMTPClientSendCommand(Sender: TObject; const Line: string);
  protected // #ftp_
    FFtpClient: TScFTPClient;
    procedure FtpConnect;
    procedure FtpDisconnect;
    procedure FtpDownload;
    procedure FtpUpload;
    procedure FtpRename;
    procedure FtpDelete;
    procedure FtpMakeDir;
    procedure FtpRemoveDir;
    procedure FtpChangeDir;
    function FtpSize(AParams: TStrings): string;
    function FtpList(AParams: TStrings): string;
  protected // #sftp_
    FScFileStorage: TScFileStorage;
    FScSshClient: TScSshClient;
    FSFtpClient: TScSFTPClient;
    FSFtpConnectAlwaysValidate: boolean;
    procedure SFtpConnect;
    procedure SFtpDisconnect;
    procedure SFtpDownload;
    procedure SFtpUpload;
    procedure SFtpRename;
    procedure SFtpDelete;
    procedure SFtpMakeDir;
    procedure SFtpRemoveDir;
    procedure SFtpChangeDir;
    function SFtpSize(AParams: TStrings): string;
    function SFtpList(AParams: TStrings): string;
    procedure ScSSHClientServerKeyValidate(Sender: TObject;
      NewServerKey: TScKey; var Accept: Boolean);
    procedure DoServerKeyValidate(AFileStorage: TScFileStorage;
      const AHostKeyName: string; ANewServerKey: TScKey; var AAccept: Boolean);
  protected
    procedure BafHttpRequest;
    procedure BafHttpLoopJson;
    function GetStatusCodeNum(ACode: TScHttpStatusCode ): string;
  public
    constructor Create; override;
    destructor Destroy; override;
    function InterpretLine(AExecInter: TBafCustomInterpreter): boolean; override;
    function ReplaceFunction(ACommand: string; AParams: TStrings; var AResult: string): boolean; override;
    class procedure BafField2Header(ALine: string; ARequest: TScHttpWebRequest;
        AExecInter: TBafCustomInterpreter);
    class function GetBitmapFromUrl(AUrl: string; AIgnoreServerCertificate: boolean;
        var ABitmap: TBitmap; AInter: TObject): boolean;
    class function ExecHttp(AUrl, AMethod, ARequest, AEncoding: string;
        AIgnoreServerCertificate: boolean; AInter: TObject;
        var AResponse: string): boolean;
    class function GetRequestMethode(AName: string): TScRequestMethod;
    class function RequestMethode2Name(AMethod: TScRequestMethod): string;
    class function CheckDebugRequest(ARequest: TScHttpWebRequest; ARequestText: string): boolean;
    class function CheckDebugResponse(AResponseText: string): boolean;

  end;


implementation

uses uBafCrypt, dmMain, foMain, uBafJsonModule, foBafDbDebug, uBafXmlModule;

{ TBafWebModule }

class procedure TBafWebModule.BafField2Header(ALine: string;
  ARequest: TScHttpWebRequest; AExecInter: TBafCustomInterpreter);
var
  i, p1, p2, LProgress: integer;
  LData: string;
  LInQuote: boolean;
begin
  LProgress := 0;
  p1 := 0;
  p2 := 0;
  LInQuote := false;
  ALine := ALine + ' ';
  for i := 1 to Length(ALine) do begin
    if ALine[i] = '"' then
      LInQuote := not LInQuote;
    if not LInQuote then begin
      if (LProgress = 0) and (ALine[i] = 'f') and (i < Length(ALine)) and (ALine[i + 1] = '_')
        and (i > 1) and (ALine[i - 1] = ' ') then begin
        LProgress := 1;     // a column definition starts
        p1 := i + 2;
      end
      else if (LProgress = 1) and (ALine[i] = '=') then begin
        LProgress := 2;     // after the equal sign
        p2 := i;
      end
      else if (LProgress = 2) and (i = p2 + 1) and (ALine[i] = '"') then
        LProgress := 3     // is quoted
      else if (LProgress = 2) and (ALine[i] = ' ') then begin
        if ALine[P2 + 1] = '"' then
          LData := copy(ALine, p2 + 2, i - p2 - 3)
        else
          LData := copy(ALine, p2 + 1, i - p2 - 1);
        LData := AExecInter.ReplaceFunctions(LData);
        ARequest.Headers.Add(copy(ALine, p1, p2 - p1), LData);
        LProgress := 0;
      end
      else if (LProgress = 3) and (ALine[i] = '"') then begin
        raise Exception.Create('TBafWebModule.BafField2Header LProgress 3');
//        LData := copy(ALine, p2 + (LProgress - 1), i - p2 - (LProgress - 1));
//        LData := AExecInter.ReplaceFunctions(LData);
//        ARequest.Headers.Add(copy(ALine, p1, p2 - p1), LData);
//        LProgress := 0;
      end;
    end; // if not LInQuote then begin
  end; // for i := 1 to

end;



procedure TBafWebModule.BafHttpLoopJson;
var
  LResponse: TScHttpWebResponse;
  LRequest: TScHttpWebRequest;
  s, LEncReq, LEncRes, LLineP, LRequestText: string;
  LBuf: TBytes;
  LNum, LMax, LPageCount, LResp: integer;
  LError, LReady: boolean;
  LRoot, LNode: TBafJSONNode;

  procedure lokParams;
  begin
    LRequest.ReadWriteTimeout := FindParamIntegerReplaced(LLineP, 'to', 15);
    s := FindParamStringReplaced(LLineP, 'y', '');
    LRequest.Method := GetRequestMethode(s);
    LRequest.ContentType := FindParamStringReplaced(LLineP, 'cy', '');
    LRequest.Accept := FindParamStringReplaced(LLineP, 'acc', '');
    if FindParamBooleanReplaced(LLineP, 'isc', false) then begin
      LRequest.SSLOptions.IgnoreServerCertificateConstraints := true;
      LRequest.SSLOptions.IgnoreServerCertificateInsecurity := true;
      LRequest.SSLOptions.IgnoreServerCertificateValidity := true;
    end;
    if LRequest.Method in [rmPOST, rmPUT] then begin
      LEncReq := FindParamStringReplaced(LLineP, 'e_req', 'utf8');
      LRequestText := FindParamStringReplaced(LLineP, 'request', '');
      LBuf := BafGetEncoding(LEncReq).GetBytes(LRequestText);
      LRequest.ContentLength := Length(LBuf);
      LRequest.WriteBuffer(LBuf);
    end;
  end; // lokParams

  procedure lokResponse;
  var
    Buf: TBytes;
    LCodePage: integer;
  begin
    LError := false;
    s := '';
    LResponse := nil;
    try
      LResponse := LRequest.GetResponse;
    except
      on E:HttpException do begin
        s := E.ServerMessage;
        if s = '' then
          s := E.Message;
        LError := true;
        exit;
      end;
      on E: Exception do begin
        s := E.Message;
        LError := true;
        exit;
      end;
    end;
    if LResponse.WaitForData(FindParamIntegerReplaced('wait', 1000)) then begin
      LEncRes := FindParamStringReplaced('e_res', 'utf8');
      if LEncRes = 'utf8' then
        s := LResponse.ReadAsString
      else begin
        buf := LResponse.ReadAsBytes;
        s := BafGetEncoding(LEncRes).GetString(Buf, 0, Length(Buf));
      end;
    end;
    if FindParamBooleanReplaced('cu8', false) then
      s := Utf8ToAnsi(s);
    LResponse.Free;
  end; // procedure lokResponse

  procedure lokParse;
  begin
    if LPageCount = 1 then
      TBafJsonModule.Parse(LRoot, s)
    else begin
      LNode := TBafJSONNode.Create(nil);
      try
        TBafJsonModule.Parse(LNode, s);
        LRoot.Merge(LNode, true);
      finally
        FreeAndNil(LNode);
      end;
    end;
  end; // procedure lokParse

begin
  LLineP := FExecInter.LineP;
  LRequest := TScHttpWebRequest.Create(nil);
  try
    LRequest.RequestUri := FindParamStringReplaced(LLineP, 'url', '');
    lokParams;
    LMax := FindParamIntegerReplaced('m', MaxInt);
    LResp := FindParamIntegerReplaced('response', 1);
    LRoot := (FInter.GetModule('json') as TBafJsonModule).GetParsedJson(LResp);
    LReady := false;
    TBafWebModule.BafField2Header(FExecInter.LineP, LRequest, FExecInter);
    LPageCount := 0;
    repeat
      inc(LPageCount);
      if TBafWebModule.CheckDebugRequest(LRequest, LRequestText) then begin
        lokResponse;
        if TBafWebModule.CheckDebugResponse(s) then begin
          lokParse;
          LReady := FindParamBooleanReplaced(LLineP, 'ready', false);
          if not LReady then begin
            FreeAndNil(LRequest);
            LRequest := TScHttpWebRequest.Create(nil);
            lokParams;
            TBafWebModule.BafField2Header(FExecInter.LineP, LRequest, FExecInter);
            LRequest.RequestUri := FindParamStringReplaced(LLineP, 'nurl', '');
            if LRequest.RequestUri = '' then
              LReady := true;
          end;
        end;
      end;
    until LReady or LError or (LPageCount = LMax);
    if LError and FindParamBooleanReplaced('se', true) then
      TfrmBafDialog.ShowMessage(dataMain.ProgName, s, nil);
  finally
    LRequest.Free;
  end;
// procedure TBafWebModule.BafHttpLoopJson
end;

procedure TBafWebModule.BafHttpRequest;
var
  LResponse: TScHttpWebResponse;
  LRequest: TScHttpWebRequest;
  LStatusCode: TScHttpStatusCode;
  s, LResp, LEncReq, LEncRes, LRequestText, LStatusDescription, LStatus,
      LSCode: string;
  LBuf: TBytes;
  LNum: integer;
  LError: boolean;

  procedure lokParams;
  begin
    LRequest.RequestUri := FindParamStringReplaced('url', '');
    LRequest.ReadWriteTimeout := FindParamIntegerReplaced('to', 15);
    s := FindParamStringReplaced('y', '');
    LRequest.Method := GetRequestMethode(s);
    LRequest.ContentType := FindParamStringReplaced('cy', '');
    LRequest.Accept := FindParamStringReplaced('acc', '');

    if FindParamBooleanReplaced('isc', false) then begin
      LRequest.SSLOptions.IgnoreServerCertificateConstraints := true;
      LRequest.SSLOptions.IgnoreServerCertificateInsecurity := true;
      LRequest.SSLOptions.IgnoreServerCertificateValidity := true;
    end;

    if LRequest.Method in [rmPOST, rmPUT, rmPATCH, rmOPTIONS, rmTRACE, rmCONNECT] then begin
      LEncReq := FindParamStringReplacedLower('e_req', 'utf8');
      LRequestText := FindParamStringReplaced('request', '');
      if (LEncReq = 'file') or (LEncReq = 'file_base64') then begin
        LBuf := TFile.ReadAllBytes(LRequestText);
        if LEncReq = 'file_base64' then
          LBuf := TNetEncoding.Base64String.Encode(LBuf);
      end
      else
        LBuf := BafGetEncoding(LEncReq).GetBytes(LRequestText);
      LRequest.ContentLength := Length(LBuf);
      LRequest.WriteBuffer(LBuf);
    end;
  end; // lokParams

  procedure lokResponse;
  var
    Buf: TBytes;
    LCodePage: integer;
  begin
    LError := false;
    s := '';
    LResponse := nil;
    try
      LResponse := LRequest.GetResponse;
    except
      on E: HttpException do begin
        s := E.ServerMessage;
        if s = '' then
          s := E.Message;
        LError := true;
        exit;
      end;
      on E: Exception do begin
        s := E.Message;
        LError := true;
        exit;
      end;
    end;
    if LResponse.WaitForData(FindParamIntegerReplaced('wait', 1000)) then begin
      LEncRes := FindParamStringReplaced('e_res', 'utf8');
//      LCodePage := FindParamIntegerReplaced('codepage', 0);
      if LEncRes = 'utf8' then
        s := LResponse.ReadAsString
      else begin
        buf := LResponse.ReadAsBytes;
        s := BafGetEncoding(LEncRes).GetString(Buf, 0, Length(Buf));
      end;
    end;
    if FindParamBooleanReplaced('cu8', false) then
      s := Utf8ToAnsi(s);
    LStatusDescription := LResponse.StatusDescription;
    LStatusCode := LResponse.StatusCode;
    LResponse.Free;
  end; // procedure lokResponse

begin
  LRequest := TScHttpWebRequest.Create(nil);
  try
    lokParams;
    TBafWebModule.BafField2Header(FExecInter.LineP, LRequest, FExecInter);
    if TBafWebModule.CheckDebugRequest(LRequest, LRequestText) then begin
      lokResponse;
      if TBafWebModule.CheckDebugResponse(s) then begin
        FExecInter.SetVarOrValue('response', s);
        FExecInter.SetVarOrValue('status', LStatusDescription);
        FExecInter.SetVarOrValue('scode', GetStatusCodeNum(LStatusCode));

        if LError and FindParamBooleanReplaced('se', true) then
          TfrmBafDialog.ShowMessage(dataMain.ProgName, s, nil);
      end;
    end;
  finally
    LRequest.Free;
  end;
// procedure TBafWebModule.BafHttpRequest
end;

class function TBafWebModule.CheckDebugRequest(ARequest: TScHttpWebRequest;
    ARequestText: string): boolean;
var
  i: integer;
begin
  result := true;
  if Assigned(frmMain) and frmMain.cbRequest.IsChecked then begin
    frmBafDbDebug.cbLog.Text := 'Debug Request';
    frmBafDbDebug.cbLog.IsChecked := true;
    frmBafDbDebug.memLog.Lines.Text := 'URL: ' + ARequest.RequestUri;
    frmBafDbDebug.memLog.Lines.Add('TimeOut: ' + IntToStr(ARequest.ReadWriteTimeout));
    frmBafDbDebug.memLog.Lines.Add('Method: ' + TBafWebModule.RequestMethode2Name(ARequest.Method));
    frmBafDbDebug.memLog.Lines.Add('ContentType: ' + ARequest.ContentType);
    frmBafDbDebug.memLog.Lines.Add('Accept: ' + ARequest.Accept);
//    frmBafDbDebug.memLog.Lines.Add('---------------------------------------------------');
    for i := 0 to ARequest.Headers.Count - 1 do
      frmBafDbDebug.memLog.Lines.Add(ARequest.Headers.Text);
    frmBafDbDebug.memLog.Lines.Add('---------------------------------------------------');
    frmBafDbDebug.memLog.Lines.AddStrings(ARequestText);

    result := (frmBafDbDebug.ShowModal = mrOk);
    frmMain.cbRequest.IsChecked := frmBafDbDebug.cbLog.IsChecked;
  end;

end;

class function TBafWebModule.CheckDebugResponse(AResponseText: string): boolean;
var
  i: integer;
  LJson: TBafJsonNode;
  LXml: TBafXmlNode;

  procedure lokJSON;
  begin
    LJson := TBafJsonNode.Create(nil);
    try
      TBafJsonModule.Parse(LJson, AResponseText);
      frmBafDbDebug.memLog.Lines.Text := LJson.GetText(jmFormatted, 0);
    finally
      LJson.Free;
    end;
  end; // procedure lokJSON

  procedure lokXML;
  begin
    LXml := TBafXmlNode.Create(nil);
    try
      TBafXmlModule.Parse(LXml, AResponseText);
      frmBafDbDebug.memLog.Lines.Text := LXml.GetText(xmFormatted, 0);
    finally
      LXml.Free;
    end;
  end; // procedure lokXML

begin
  result := true;
  if Assigned(frmMain) and frmMain.cbResponse.IsChecked then begin
    if AResponseText[1] = '{' then
      lokJSON
    else if AResponseText[1] = '<' then
      lokXML
    else
      frmBafDbDebug.memLog.Lines.AddStrings(AResponseText);

    result := (frmBafDbDebug.ShowModal = mrOk);
    frmMain.cbResponse.IsChecked := frmBafDbDebug.cbLog.IsChecked;
  end;
end;

constructor TBafWebModule.Create;
begin
  inherited;

end;

destructor TBafWebModule.Destroy;
begin
  FreeAndNil(FMailClient);
  FreeAndNil(FFtpClient);
  inherited;
end;

procedure TBafWebModule.DoServerKeyValidate(AFileStorage: TScFileStorage;
  const AHostKeyName: string; ANewServerKey: TScKey; var AAccept: Boolean);
var
  LKey: TScKey;
  LMessage, LFinger: string;
begin
  LKey := AFileStorage.Keys.FindKey(AHostKeyName);
  if (LKey = nil) or not LKey.Ready then begin
    ANewServerKey.GetFingerPrint(haMD5, LFinger);
    LMessage := 'Server is not autheticated yet.'#13#10 +
           'Fingerprint for the server: ' + LFinger + '.'#13#10 +
           'key length: ' + IntToStr(ANewServerKey.BitCount) + ' bits.'#13#10 +
           'Do you trust the connection?';
    if TfrmBafDialog.DialogYesNow('Confirmation', LMessage, frmMain) then begin
      LKey := TScKey.Create(nil);
      try
        LKey.Assign(ANewServerKey);
        LKey.KeyName := AHostKeyName;
        AFileStorage.Keys.Add(LKey);
      except
        LKey.Free;
        raise;
      end;

      AAccept := True;
    end;
  end;

end;

procedure TBafWebModule.EmailInit;
var
  LMode, s: string;
begin
  if FMailClient = nil then begin
    FMailClient := TScSMTPClient.Create(nil);
    FMailClient.AfterConnect := SMTPClientAfterConnect;
    FMailClient.AfterDisconnect := SMTPClientAfterDisconnect;
    FMailClient.OnError := SMTPClientError;
    FMailClient.OnReadReply := SMTPClientReadReply;
    FMailClient.OnRecipientError := SMTPClientRecipientError;
    FMailClient.OnSendCommand := SMTPClientSendCommand;
    FMailClient.AfterConnect := SMTPClientAfterConnect;
    FMailClient.AfterConnect := SMTPClientAfterConnect;
  end
  else
    FMailClient.Disconnect;
  FMailClient.Uri := FindParamStringReplaced('host', '');
  FMailClient.Port := FindParamIntegerReplaced('port', FMailClient.Port);
  s := IntToStr(FMailClient.Port);
  LMode := FindParamStringLower('tls', 'itls');
  if LMode = 'dtls' then
    FMailClient.TLSMode := tmDisableTLS
  else if LMode = 'itls' then
    FMailClient.TLSMode := tmImplicitTLS
  else if LMode = 'retls' then
    FMailClient.TLSMode := tmRequireExplicitTLS
  else if LMode = 'aetls' then
    FMailClient.TLSMode := tmAllowExplicitTLS;
  FMailClient.Username := FindParamStringReplaced('usr', '');
  if FindParamBooleanReplaced('pwc', false) then
    FMailClient.Password := BafDecrypt(FindParamStringReplaced('pw', ''))
  else
    FMailClient.Password := FindParamStringReplaced('pw', '');
  FMailClient.UseSASLMechanisms := FindParamBooleanReplaced('sasl', false);
  if FindParamBooleanReplaced('isc', false) then begin
    FMailClient.SSLOptions.IgnoreServerCertificateConstraints := true;
    FMailClient.SSLOptions.IgnoreServerCertificateInsecurity := true;
    FMailClient.SSLOptions.IgnoreServerCertificateValidity := true;
  end;
  FMailClient.Timeout := FindParamIntegerReplaced('to', 15);
  FEmailLog := FindParamBooleanReplaced('log', false);
end;

procedure TBafWebModule.EmailSend;
var
  LMess: TScMailMessage;

  procedure lokAttachement;
  var
    LAttach: TScAttachment;
    i, LCount, LFilenameList: integer;
    LText: TStringList;
    LFileName: string;
  begin
    LFilenameList := FindParamIntegerReplaced('fn_list', 0);
    if LFilenameList > 0 then begin
      LText := FInter.GetTextStringList(LFilenameList);
      for i := 0 to LText.Count - 1 do begin
        LFileName := Trim(LText[i]);
        if LFileName <> '' then
          LAttach := TScAttachment.Create(LMess.Attachments, LFileName);
      end;
    end // if LFilenameList > 0
    else begin
      LCount := FindParamIntegerReplaced('cnt', 0);
      for i := 1 to LCount do begin
        LAttach := TScAttachment.Create(LMess.Attachments,
          FindParamStringReplaced('fn' + IntToStr(i), ''));
      end;
    end; // else LFilenameList > 0
  end; // procedure lokAttachement

begin
  if not FMailClient.Active then
    FMailClient.Connect;
  if FMailClient.Authenticate then begin
    LMess := TScMailMessage.Create;
    try
      LMess.Encoding := meMIME;
      LMess.From.AsString := FindParamStringReplaced('from', '');
      LMess.ReplyTo.AsString := FindParamStringReplaced('reply', '');
      LMess.ToAddress.AsString := FindParamStringReplaced('to', '');
      LMess.CC.AsString := FindParamStringReplaced('cc', '');
      LMess.BCC.AsString := FindParamStringReplaced('bcc', '');
      LMess.Subject := FindParamStringReplaced('subject', '');
//      LMess.BodyEncoding := 'multipart';
      LMess.Body.Text := FindParamStringReplaced('text', '');
      lokAttachement;
      LMess.ContentType := FindParamStringReplaced('cy', '');  // before ContentCharset !!!
      LMess.ContentCharset := FindParamStringReplaced('ccs', '');
      LMess.ContentTransferEncoding := FindParamStringReplaced('cte', '');
      if FindParamBooleanReplaced('headers2clipboard', false) then
        Clipboard.AsText :=  LMess.Headers.Text;
      FMailClient.Send(LMess);
    finally
      LMess.Free;
    end;
  end
  else
    FInter.DoLog('E', '#email_send, authenification failed');
// procedure TBafWebModule.EmailSend
end;

class function TBafWebModule.ExecHttp(AUrl, AMethod, ARequest, AEncoding: string;
    AIgnoreServerCertificate: boolean; AInter: TObject;
    var AResponse: string): boolean;
var
  LResponse: TScHttpWebResponse;
  LRequest: TScHttpWebRequest;
  LBuf: TBytes;
begin
  result := false;
  try
    LRequest := TScHttpWebRequest.Create(nil);
    try
      LRequest.Method := TBafWebModule.GetRequestMethode(AMethod);
      LRequest.RequestUri := AUrl;
      if AIgnoreServerCertificate then begin
        LRequest.SSLOptions.IgnoreServerCertificateConstraints := true;
        LRequest.SSLOptions.IgnoreServerCertificateInsecurity := true;
        LRequest.SSLOptions.IgnoreServerCertificateValidity := true;
      end;
      Clipboard.AsText := AUrl + #13#10 + ARequest;
      if LRequest.Method in [rmPOST, rmPUT, rmPATCH] then begin
        LBuf := BafGetEncoding(AEncoding).GetBytes(ARequest);
        LRequest.ContentLength := Length(LBuf);
        LRequest.WriteBuffer(LBuf);
      end;
      if TBafWebModule.CheckDebugRequest(LRequest, ARequest) then begin
        LResponse := LRequest.GetResponse();
        try
          AResponse := LResponse.ReadAsString;
          if TBafWebModule.CheckDebugResponse(AResponse) then
            result := true;
        finally
          LResponse.Free;
        end;
      end;
    finally
      LRequest.Free;
    end;
  except
    on E: Exception do
      (AInter as TBafInterpreter).DoLog('W', 'Exception in ExecHttp '
          + '(' + AUrl + '): ' + E.Message);
  end;
end;

procedure TBafWebModule.FtpChangeDir;
var
  LPath: string;
begin
  if FindParamStringLower('y', '') = 'up' then
    FFtpClient.ChangeDirUp
  else begin
    LPath := FindParamStringReplaced('n', '');
    FFtpClient.ChangeDir(LPath);
  end;
end;

procedure TBafWebModule.FtpConnect;
var
  LMode: string;
begin
  if FFtpClient = nil then
    FFtpClient := TScFTPClient.Create(nil);
  if FFtpClient.Active then
    FFtpClient.Disconnect;
  FFtpClient.HostName := FindParamStringReplaced('host', '');
  FFtpClient.Port := FindParamIntegerReplaced('prt', 21);
  LMode := FindParamStringReplacedLower('y', '');
  if LMode = 'disable' then
    FFtpClient.TLSMode := tmDisableTLS
  else if LMode = 'implicit' then
    FFtpClient.TLSMode := tmImplicitTLS
  else if LMode = 'require' then
    FFtpClient.TLSMode := tmRequireExplicitTLS
  else
    FFtpClient.TLSMode := tmAllowExplicitTLS;
  FFtpClient.EncryptDataChannel := FindParamBooleanReplaced('crpt', true);
  if FindParamBooleanReplaced('pwc', false) then
    FFtpClient.Password := BafDecrypt(FindParamStringReplaced('pw', ''))
  else
    FFtpClient.Password := FindParamStringReplaced('pw', '');
  FFtpClient.Username := FindParamStringReplaced('usr', '');
  FFtpClient.TransferType := ttBinary;
  FFtpClient.UsePassive := FindParamBooleanReplaced('up', false);
  if FindParamBooleanReplaced('isc', false) then begin
    FFtpClient.SSLOptions.IgnoreServerCertificateConstraints := true;
    FFtpClient.SSLOptions.IgnoreServerCertificateInsecurity := true;
    FFtpClient.SSLOptions.IgnoreServerCertificateValidity := true;
    FFtpClient.SSLOptions.TrustSelfSignedCertificate := true;
    FFtpClient.SSLOptions.TrustServerCertificate := true;
  end;
  FFtpClient.Timeout := FindParamIntegerReplaced('to', 15);
  FFtpClient.Connect;
  FFtpClient.Login;
end;

procedure TBafWebModule.FtpDelete;
var
  LFile: string;
begin
  LFile := FindParamStringReplaced('fn', '');
  FFtpClient.Delete(LFile);
end;

procedure TBafWebModule.FtpDisconnect;
begin
  FFtpClient.Disconnect;
end;

procedure TBafWebModule.FtpDownload;
var
  LSource, LDest: string;
  LOverwrite: boolean;
begin
  LOverwrite := not FindParamBooleanReplaced('ie', false);
  LSource := FindParamStringReplaced('src', '');
  LDest := FindParamStringReplaced('dst', '');
  FFtpClient.Download(LSource, LDest, LOverwrite);
end;

function TBafWebModule.FtpList(AParams: TStrings): string;
var
  sl: TStringList;
  LDetails: boolean;
  s: string;
begin
  if AParams.Count > 0 then begin
    LDetails := false;
    if AParams.Count > 1 then begin
      s := AnsiLowerCase(AParams[1]);
      LDetails := (s = 'det') or (s = 'details');
    end;
    sl := TStringList.Create;
    try
      s := AnsiLowerCase(AParams[0]);
      if LDetails then
        FFtpClient.ListDirDetails(sl, s)
      else
        FFtpClient.ListDir(sl, s);
      result := sl.Text;
    finally
      sl.Free;
    end;
  end
  else
    result := 'param 1 missing';
end;

procedure TBafWebModule.FtpMakeDir;
var
  LPath: string;
begin
  LPath := FindParamStringReplaced('n', '');
  FFtpClient.MakeDir(LPath);
end;

procedure TBafWebModule.FtpRemoveDir;
var
  LPath: string;
begin
  LPath := FindParamStringReplaced('n', '');
  FFtpClient.RemoveDir(LPath);
end;

procedure TBafWebModule.FtpRename;
var
  LSource, LDest: string;
begin
  LSource := FindParamStringReplaced('src', '');
  LDest := FindParamStringReplaced('dst', '');
  FFtpClient.Rename(LSource, LDest);
end;

function TBafWebModule.FtpSize(AParams: TStrings): string;
begin
  if AParams.Count > 0 then
    result := IntToStr(FFtpClient.Size(AParams[0]))
  else
    result := '-1';
end;

procedure TBafWebModule.FtpUpload;
var
  LSource, LDest: string;
  LOverwrite: boolean;
begin
  LOverwrite := not FindParamBooleanReplaced('ie', false);
  LSource := FindParamStringReplaced('src', '');
  LDest := FindParamStringReplaced('dst', '');
  FFtpClient.Upload(LSource, LDest);
end;

class function TBafWebModule.GetBitmapFromUrl(AUrl: string;
    AIgnoreServerCertificate: boolean; var ABitmap: TBitmap;
    AInter: TObject): boolean;
var
  LResponse: TScHttpWebResponse;
  LRequest: TScHttpWebRequest;
  LStream: TMemoryStream;
  LCount: integer;
begin
  result := false;
  try
    LRequest := TScHttpWebRequest.Create(nil);
    try
      LRequest.Method := rmGET;
      LRequest.RequestUri := AUrl;
      if AIgnoreServerCertificate then begin
        LRequest.SSLOptions.IgnoreServerCertificateConstraints := true;
        LRequest.SSLOptions.IgnoreServerCertificateInsecurity := true;
        LRequest.SSLOptions.IgnoreServerCertificateValidity := true;
      end;
      LResponse := LRequest.GetResponse();
      try
        LStream := TMemoryStream.Create;
        try
          LCount := LResponse.ReadToStream(LStream);
          result := LCount > 0;
          if result then begin
            LStream.Position := 0;
            ABitmap.LoadFromStream(LStream);
          end;
        finally
          LStream.Free;
        end;
      finally
        LResponse.Free;
      end;
    finally
      LRequest.Free;
    end;
  except
    on E: Exception do
      (AInter as TBafInterpreter).DoLog('W', 'Exception in GetBitmapFromUrl '
          + '(' + AUrl + '): ' + E.Message);
  end;
end;

class function TBafWebModule.GetRequestMethode(AName: string): TScRequestMethod;
begin
  AName := AnsiLowerCase(AName);
  if AName = 'get' then
    result := rmGet
  else if AName = 'post' then
    result := rmPOST
  else if AName = 'put' then
    result := rmPUT
  else if AName = 'delete' then
    result := rmDELETE
  else if AName = 'head' then
    result := rmHEAD
  else if AName = 'options' then
    result := rmOPTIONS
  else if AName = 'trace' then
    result := rmTRACE
  else if AName = 'connect' then
    result := rmCONNECT
  else if AName = 'patch' then
    result := rmPATCH
  ;
end;

function TBafWebModule.GetStatusCodeNum(ACode: TScHttpStatusCode): string;
begin
  case ACode of
    scAccepted: result := '202';
//    scAmbiguous: result := '300';
    scBadGateway: result := '502';
    scBadRequest: result := '400';
    scConflict: result := '409';
    scContinue: result := '100';
    scCreated: result := '201';
    scExpectationFailed: result := '417';
    scForbidden: result := '403';
    scFound: result := '302';
    scGatewayTimeout: result := '504';
    scGone: result := '410';
    scHttpVersionNotSupported: result := '505';
    scInternalServerError: result := '500';
    scLengthRequired: result := '411';
    scMethodNotAllowed: result := '405';
//    scMoved: result := '301';
    scMovedPermanently: result := '301';
    scMultipleChoices: result := '300';
    scNoContent: result := '204';
    scNonAuthoritativeInformation: result := '203';
    scNotAcceptable: result := '406';
    scNotFound: result := '404';
    scNotImplemented: result := '404';
    scNotModified: result := '304';
    scOK: result := '200';
    scPartialContent: result := '206';
    scPaymentRequired: result := '402';
    scPreconditionFailed: result := '412';
    scProxyAuthenticationRequired: result := '407';
    scRedirect: result := '302';
    scRedirectKeepVerb: result := '307';
    scRedirectMethod: result := '303';
    scRequestedRangeNotSatisfiable: result := '416';
    scRequestEntityTooLarge: result := '413';
    scRequestTimeout: result := '408';
    scRequestUriTooLong: result := '414';
    scResetContent: result := '205';
    scSeeOther: result := '303';
    scServiceUnavailable: result := '503';
    scSwitchingProtocols: result := '101';
    scTemporaryRedirect: result := '307';
    scUnauthorized: result := '401';
    scUnsupportedMediaType: result := '415';
    scUnused: result := '306';
    scUpgradeRequired: result := '426';
    scUseProxy: result := '305';
    scUnknown: result := '0';
  end;
end;

function TBafWebModule.InterpretLine(AExecInter: TBafCustomInterpreter): boolean;
var
  LInter: TBafCustomInterpreter;
begin
  LInter := FExecInter;
  try
    FExecInter := AExecInter;

    result := true;
    if FExecInter.LineF = '#email_init' then EmailInit
    else if FExecInter.LineF = '#email_send' then EMailSend
    else if FExecInter.LineF = '#email_disconnect' then FMailClient.Disconnect

    else if FExecInter.LineF = '#ftp_connect' then FtpConnect
    else if FExecInter.LineF = '#ftp_disconnect' then FtpDisconnect
    else if FExecInter.LineF = '#ftp_upload' then FtpUpload
    else if FExecInter.LineF = '#ftp_download' then FtpDownload
    else if FExecInter.LineF = '#ftp_rename' then FtpRename
    else if FExecInter.LineF = '#ftp_delete' then FtpDelete
    else if FExecInter.LineF = '#ftp_mkdir' then FtpMakeDir
    else if FExecInter.LineF = '#ftp_remdir' then FtpRemoveDir
    else if FExecInter.LineF = '#ftp_chgdir' then FtpChangeDir

    else if FExecInter.LineF = '#sftp_connect' then SFtpConnect
    else if FExecInter.LineF = '#sftp_disconnect' then SFtpDisconnect
    else if FExecInter.LineF = '#sftp_upload' then SFtpUpload
    else if FExecInter.LineF = '#sftp_download' then SFtpDownload
    else if FExecInter.LineF = '#sftp_rename' then SFtpRename
    else if FExecInter.LineF = '#sftp_delete' then SFtpDelete
    else if FExecInter.LineF = '#sftp_mkdir' then SFtpMakeDir
    else if FExecInter.LineF = '#sftp_remdir' then SFtpRemoveDir
    else if FExecInter.LineF = '#sftp_chgdir' then SFtpChangeDir

    else if FExecInter.LineF = '#http_request' then BafHttpRequest             // also an idea for REST
    else if FExecInter.LineF = '#http_loop_json' then BafHttpLoopJson          // data from more then one page

    else result := false;
  finally
    FExecInter := LInter;
  end;
end;

function TBafWebModule.ReplaceFunction(ACommand: string; AParams: TStrings;
  var AResult: string): boolean;
begin
  inherited;
  result := true;
  if ACommand = '$FTP_SIZE' then AResult := FtpSize(AParams)
  else if ACommand = '$FTP_LIST' then AResult := FtpList(AParams)

  else if ACommand = '$SFTP_SIZE' then AResult := SFtpSize(AParams)
  else if ACommand = '$SFTP_LIST' then AResult := SFtpList(AParams)


  else result := false;
end;

class function TBafWebModule.RequestMethode2Name(AMethod: TScRequestMethod): string;
begin
  case AMethod of
    rmGET: result := 'GET';
    rmPOST: result := 'POST';
    rmPUT: result := 'PUT';
    rmDELETE: result := 'DELETE';
    rmHEAD: result := 'HEAD';
    rmOPTIONS: result := 'OPTIONS';
    rmTRACE: result := 'TRACE';
    rmCONNECT: result := 'CONNECT';
    rmPATCH: result := 'PATCH';
  end;
end;

procedure TBafWebModule.ScSSHClientServerKeyValidate(Sender: TObject;
  NewServerKey: TScKey; var Accept: Boolean);
var
  CurHostKeyName: string;
begin
  if FSFtpConnectAlwaysValidate then
    Accept := true
  else begin
    if FScSSHClient.HostKeyName = '' then
      CurHostKeyName := FScSSHClient.HostName
    else
      CurHostKeyName := FScSSHClient.HostKeyName;
    DoServerKeyValidate(FScFileStorage, CurHostKeyName, NewServerKey, Accept);
  end;
end;

procedure TBafWebModule.SFtpChangeDir;
var
  LPath: string;
begin
//  if FindParamStringLower('y', '') = 'up' then
//    FSFtpClient.ChangeDirUp
//  else begin
//    LPath := FindParamStringReplaced('n', '');
//    FSFtpClient.ChangeDir(LPath);
//  end;
end;

procedure TBafWebModule.SFtpConnect;
const
  KEA_STANDARD = 'curve25519-sha256,curve25519-sha256@libssh.org,curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256';
  KEA_ALL = 'curve25519-sha256,curve25519-sha256@libssh.org,curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1';
var
  LTyp, LKea: string;
begin
  FSFtpConnectAlwaysValidate := FindParamBooleanReplaced('av', false);
  if FScFileStorage = nil then begin
    FScFileStorage := TScFileStorage.Create(nil);
    FScFileStorage.Path := IncludeTrailingPathDelimiter(dataMain.Root) + 'key';
  end;
  if FScSshClient = nil then begin
    FScSshClient := TScSSHClient.Create(nil);
    FScSshClient.KeyStorage := FScFileStorage;
    FScSshClient.OnServerKeyValidate := ScSSHClientServerKeyValidate;
  end;
  if FSFtpClient = nil then begin
    FSFtpClient := TScSFTPClient.Create(nil);
    FSFtpClient.SSHClient := FScSshClient;
  end;
  if FSFtpClient.Active then
    FSFtpClient.Disconnect;
  if FScSshClient.Connected then
    FScSshClient.Disconnect;
  FScSshClient.HostName := FindParamStringReplaced('host', '');
  FScSshClient.Port := FindParamIntegerReplaced('prt', 22);
  FScSshClient.User := FindParamStringReplaced('usr', '');
  LTyp := FindParamStringLower('y', 'pw');
  if LTyp = 'pw' then begin
    FScSshClient.Authentication := atPassword;
    if FindParamBooleanReplaced('pwc', false) then
      FScSshClient.Password := BafDecrypt(FindParamStringReplaced('pw', ''))
    else
      FScSshClient.Password := FindParamStringReplaced('pw', '');
  end
  else if LTyp = 'pk' then begin
    FScSshClient.Authentication := atPublicKey;
    FScSshClient.PrivateKeyName := FindParamStringReplaced('private', '');
    if FScFileStorage.Keys.FindKey(FScSSHClient.PrivateKeyName) = nil then
      raise EScError.Create('Private key can not be empty');
  end;
  FSFtpClient.Timeout := FindParamIntegerReplaced('to', 15);

  LKea := FindParamStringReplacedLower('kea', 'standard');
  if LKea = 'standard' then
    FScSshClient.KeyExchangeAlgorithms.AsString := KEA_STANDARD
  else if LKea = 'all' then
    FScSshClient.KeyExchangeAlgorithms.AsString := KEA_ALL
  else
    FScSshClient.KeyExchangeAlgorithms.AsString := LKea;
  FScSshClient.Connect;
  FSFtpClient.Initialize;
end;

procedure TBafWebModule.SFtpDelete;
var
  LFile: string;
begin
  LFile := FindParamStringReplaced('fn', '');
  FSFtpClient.RemoveFile(LFile);
end;

procedure TBafWebModule.SFtpDisconnect;
begin
  FSFtpClient.Disconnect;
  FScSshClient.Disconnect;
end;

procedure TBafWebModule.SFtpDownload;
var
  LSource, LDest: string;
  LOverwrite: boolean;
begin
  LOverwrite := not FindParamBooleanReplaced('ie', false);
  LSource := FindParamStringReplaced('src', '');
  LDest := FindParamStringReplaced('dst', '');
  FSFtpClient.DownloadFile(LSource, LDest, LOverwrite);
end;

function TBafWebModule.SFtpList(AParams: TStrings): string;
var
  sl: TStringList;
  LDetails: boolean;
  s: string;

  procedure lokOpenList;
  var
    LHandle: TScSFTPFileHandle;
    LList: TCRObjectList;
    i: integer;
    LInfo: TScSFTPFileInfo;
  begin
    LList := TCRObjectList.Create;
    try
      LHandle := FSFtpClient.OpenDirectory(s);
      try
        FSFtpClient.ReadDirectoryToList(LHandle, LList);
        for i := 0 to LList.Count - 1 do begin
          if TObject(LList[i]) is TScSFTPFileInfo then begin
            LInfo := TScSFTPFileInfo(LList[i]);
            if LDetails then
              sl.Add(LInfo.Longname)
            else
              sl.Add(LInfo.Filename);
          end;
        end;
      finally
        FSFtpClient.CloseHandle(LHandle);
      end;
    finally
      LList.Free;
    end;
  end; // procedure lokOpenList

begin
  if AParams.Count > 0 then begin
    LDetails := false;
    if AParams.Count > 1 then begin
      s := AParams[1];
      LDetails := (s = 'det') or (s = 'details');
    end;
    sl := TStringList.Create;
    try
      s := AParams[0];
      lokOpenList;
      result := sl.Text;
    finally
      sl.Free;
    end;
  end
  else
    result := 'param 1 missing';
end;

procedure TBafWebModule.SFtpMakeDir;
var
  LPath: string;
begin
  LPath := FindParamStringReplaced('n', '');
  FSFtpClient.MakeDirectory(LPath);
end;

procedure TBafWebModule.SFtpRemoveDir;
var
  LPath: string;
begin
  LPath := FindParamStringReplaced('n', '');
  FSFtpClient.RemoveDirectory(LPath);
end;

procedure TBafWebModule.SFtpRename;
var
  LSource, LDest: string;
begin
  LSource := FindParamStringReplaced('src', '');
  LDest := FindParamStringReplaced('dst', '');
  FSFtpClient.RenameFile(LSource, LDest);
end;

function TBafWebModule.SFtpSize(AParams: TStrings): string;
var
  LHandle: TScSFTPFileHandle;
  LAttributes: TScSFTPFileAttributes;
begin
  if AParams.Count > 0 then begin
    LHandle := FSFtpClient.OpenFile(AParams[0], []);
    try
      LAttributes := TScSFTPFileAttributes.Create;
      try
        FSFtpClient.RetrieveAttributesByHandle(LAttributes, LHandle, [aSize]);
        result := IntToStr(LAttributes.Size);
      finally
        LAttributes.Free;
      end;
    finally
      FSFtpClient.CloseHandle(LHandle);
    end;
  end
  else
    result := '-1';
end;

procedure TBafWebModule.SFtpUpload;
var
  LSource, LDest: string;
  LOverwrite: boolean;
begin
  LOverwrite := not FindParamBooleanReplaced('ie', false);
  LSource := FindParamStringReplaced('src', '');
  LDest := FindParamStringReplaced('dst', '');
  FSFtpClient.UploadFile(LSource, LDest, LOverwrite);
end;

procedure TBafWebModule.SMTPClientAfterConnect(Sender: TObject);
begin
  if FEmailLog then
    FInter.DoLog('I', 'email_send connected');
end;

procedure TBafWebModule.SMTPClientAfterDisconnect(Sender: TObject);
begin
  if FEmailLog then
    FInter.DoLog('I', 'email_send disconnected');
end;

procedure TBafWebModule.SMTPClientError(Sender: TObject; ErrorCode: Integer;
  const ErrorMessage: string; var Fail: Boolean);
begin
  if FEmailLog then
    FInter.DoLog('I', 'email_send error: ' + ErrorMessage);
end;

procedure TBafWebModule.SMTPClientReadReply(Sender: TObject;
  const Line: string);
begin
  if FEmailLog then
    FInter.DoLog('I', 'email_send reply: ' + Line);
end;

procedure TBafWebModule.SMTPClientRecipientError(Sender: TObject;
  const RecipientAddress: string; ReplyCode: Integer; const ReplyText: string;
  var Skip: Boolean);
begin
  if FEmailLog then
    FInter.DoLog('I', 'email_send recipient error: ' + IntToStr(ReplyCode)
      + ' - ' + ReplyText);
end;

procedure TBafWebModule.SMTPClientSendCommand(Sender: TObject;
  const Line: string);
begin
  if FEmailLog then
    FInter.DoLog('I', 'email_send send command: ' + Line);
end;

end.



    (CPID: 37; CPName: 'IBM037'),
    (CPID: 437; CPName: 'IBM437'),
    (CPID: 500; CPName: 'IBM500'),
    (CPID: 708; CPName: 'ASMO-708'),
    (CPID: 720; CPName: 'DOS-720'),
    (CPID: 737; CPName: 'ibm737'),
    (CPID: 775; CPName: 'ibm775'),
    (CPID: 850; CPName: 'ibm850'),
    (CPID: 852; CPName: 'ibm852'),
    (CPID: 855; CPName: 'IBM855'),
    (CPID: 857; CPName: 'ibm857'),
    (CPID: 858; CPName: 'IBM00858'),
    (CPID: 860; CPName: 'IBM860'),
    (CPID: 861; CPName: 'ibm861'),
    (CPID: 862; CPName: 'DOS-862'),
    (CPID: 863; CPName: 'IBM863'),
    (CPID: 864; CPName: 'IBM864'),
    (CPID: 865; CPName: 'IBM865'),
    (CPID: 866; CPName: 'cp866'),
    (CPID: 869; CPName: 'ibm869'),
    (CPID: 870; CPName: 'IBM870'),
    (CPID: 874; CPName: 'windows-874'),
    (CPID: 875; CPName: 'cp875'),
    (CPID: 932; CPName: 'shift_jis'),
    (CPID: 936; CPName: 'gb2312'),
    (CPID: 949; CPName: 'ks_c_5601-1987'),
    (CPID: 950; CPName: 'big5'),
    (CPID: 1026; CPName: 'IBM1026'),
    (CPID: 1047; CPName: 'IBM01047'),
    (CPID: 1140; CPName: 'IBM01140'),
    (CPID: 1141; CPName: 'IBM01141'),
    (CPID: 1142; CPName: 'IBM01142'),
    (CPID: 1143; CPName: 'IBM01143'),
    (CPID: 1144; CPName: 'IBM01144'),
    (CPID: 1145; CPName: 'IBM01145'),
    (CPID: 1146; CPName: 'IBM01146'),
    (CPID: 1147; CPName: 'IBM01147'),
    (CPID: 1148; CPName: 'IBM01148'),
    (CPID: 1149; CPName: 'IBM01149'),
    (CPID: 1200; CPName: 'utf-16'),
    (CPID: 1201; CPName: 'unicodeFFFE'),
    (CPID: 1250; CPName: 'windows-1250'),
    (CPID: 1251; CPName: 'windows-1251'),
    (CPID: 1252; CPName: 'Windows-1252'),
    (CPID: 1253; CPName: 'windows-1253'),
    (CPID: 1254; CPName: 'windows-1254'),
    (CPID: 1255; CPName: 'windows-1255'),
    (CPID: 1256; CPName: 'windows-1256'),
    (CPID: 1257; CPName: 'windows-1257'),
    (CPID: 1258; CPName: 'windows-1258'),
    (CPID: 1361; CPName: 'Johab'),
    (CPID: 10000; CPName: 'macintosh'),
    (CPID: 10001; CPName: 'x-mac-japanese'),
    (CPID: 10002; CPName: 'x-mac-chinesetrad'),
    (CPID: 10003; CPName: 'x-mac-korean'),
    (CPID: 10004; CPName: 'x-mac-arabic'),
    (CPID: 10005; CPName: 'x-mac-hebrew'),
    (CPID: 10006; CPName: 'x-mac-greek'),
    (CPID: 10007; CPName: 'x-mac-cyrillic'),
    (CPID: 10008; CPName: 'x-mac-chinesesimp'),
    (CPID: 10010; CPName: 'x-mac-romanian'),
    (CPID: 10017; CPName: 'x-mac-ukrainian'),
    (CPID: 10021; CPName: 'x-mac-thai'),
    (CPID: 10029; CPName: 'x-mac-ce'),
    (CPID: 10079; CPName: 'x-mac-icelandic'),
    (CPID: 10081; CPName: 'x-mac-turkish'),
    (CPID: 10082; CPName: 'x-mac-croatian'),
    (CPID: 12000; CPName: 'utf-32'),
    (CPID: 12001; CPName: 'utf-32BE'),
    (CPID: 20000; CPName: 'x-Chinese-CNS'),
    (CPID: 20001; CPName: 'x-cp20001'),
    (CPID: 20002; CPName: 'x-Chinese-Eten'),
    (CPID: 20003; CPName: 'x-cp20003'),
    (CPID: 20004; CPName: 'x-cp20004'),
    (CPID: 20005; CPName: 'x-cp20005'),
    (CPID: 20105; CPName: 'x-IA5'),
    (CPID: 20106; CPName: 'x-IA5-German'),
    (CPID: 20107; CPName: 'x-IA5-Swedish'),
    (CPID: 20108; CPName: 'x-IA5-Norwegian'),
    (CPID: 20127; CPName: 'us-ascii'),
    (CPID: 20261; CPName: 'x-cp20261'),
    (CPID: 20269; CPName: 'x-cp20269'),
    (CPID: 20273; CPName: 'IBM273'),
    (CPID: 20277; CPName: 'IBM277'),
    (CPID: 20278; CPName: 'IBM278'),
    (CPID: 20280; CPName: 'IBM280'),
    (CPID: 20284; CPName: 'IBM284'),
    (CPID: 20285; CPName: 'IBM285'),
    (CPID: 20290; CPName: 'IBM290'),
    (CPID: 20297; CPName: 'IBM297'),
    (CPID: 20420; CPName: 'IBM420'),
    (CPID: 20423; CPName: 'IBM423'),
    (CPID: 20424; CPName: 'IBM424'),
    (CPID: 20833; CPName: 'x-EBCDIC-KoreanExtended'),
    (CPID: 20838; CPName: 'IBM-Thai'),
    (CPID: 20866; CPName: 'koi8-r'),
    (CPID: 20871; CPName: 'IBM871'),
    (CPID: 20880; CPName: 'IBM880'),
    (CPID: 20905; CPName: 'IBM905'),
    (CPID: 20924; CPName: 'IBM00924'),
    (CPID: 20932; CPName: 'EUC-JP'),
    (CPID: 20936; CPName: 'x-cp20936'),
    (CPID: 20949; CPName: 'x-cp20949'),
    (CPID: 21025; CPName: 'cp1025'),
    (CPID: 21866; CPName: 'koi8-u'),
    (CPID: 28591; CPName: 'iso-8859-1'),
    (CPID: 28592; CPName: 'iso-8859-2'),
    (CPID: 28593; CPName: 'iso-8859-3'),
    (CPID: 28594; CPName: 'iso-8859-4'),
    (CPID: 28595; CPName: 'iso-8859-5'),
    (CPID: 28596; CPName: 'iso-8859-6'),
    (CPID: 28597; CPName: 'iso-8859-7'),
    (CPID: 28598; CPName: 'iso-8859-8'),
    (CPID: 28599; CPName: 'iso-8859-9'),
    (CPID: 28603; CPName: 'iso-8859-13'),
    (CPID: 28605; CPName: 'iso-8859-15'),
    (CPID: 29001; CPName: 'x-Europa'),
    (CPID: 38598; CPName: 'iso-8859-8-i'),
    (CPID: 50220; CPName: 'iso-2022-jp'),
    (CPID: 50221; CPName: 'csISO2022JP'),
    (CPID: 50222; CPName: 'iso-2022-jp'),
    (CPID: 50225; CPName: 'iso-2022-kr'),
    (CPID: 50227; CPName: 'x-cp50227'),
    (CPID: 51932; CPName: 'euc-jp'),
    (CPID: 51936; CPName: 'EUC-CN'),
    (CPID: 51949; CPName: 'euc-kr'),
    (CPID: 52936; CPName: 'hz-gb-2312'),
    (CPID: 54936; CPName: 'GB18030'),
    (CPID: 57002; CPName: 'x-iscii-de'),
    (CPID: 57003; CPName: 'x-iscii-be'),
    (CPID: 57004; CPName: 'x-iscii-ta'),
    (CPID: 57005; CPName: 'x-iscii-te'),
    (CPID: 57006; CPName: 'x-iscii-as'),
    (CPID: 57007; CPName: 'x-iscii-or'),
    (CPID: 57008; CPName: 'x-iscii-ka'),
    (CPID: 57009; CPName: 'x-iscii-ma'),
    (CPID: 57010; CPName: 'x-iscii-gu'),
    (CPID: 57011; CPName: 'x-iscii-pa'),
    (CPID: 65000; CPName: 'utf-7'),
    (CPID: 65001; CPName: 'utf-8')
