Mtp - Memory Transfer Protocol. Client e Server

Nello scrivere Scintilla  ho incontrato la necessità di trasferire molto velocemente gli screenshot del client al server.D'altra parte anche il server spedisce a client un piccolo file di testo quindi in realtà le necessità erano due: una era quella di attivare una trasmissione ricezione di memoria o file da un client verso un server, l'altra era quella di trasferire molto velocemente quantità di byte elevate.
 Ho quindi scritto queste due classi, MtpServer e MtpClient,  derivandole  dalle classi presenti negli Overbyte ICS. Si tratta di una reinterpretazione  delle già presenti classi Tftpserver e tftpClient,  ma partendo dal presupposto che non vi è alcuna necessità di spogliare directory, ottenere informazioni su files e addirittura autenticarsi. In pratica, scremando un server FTP e un ftp client, si ottiene proprio un memory transfer.  Nel demo allegato qui sotto viene dimostrato come sia possibile spedire la mappa di bit di un bitmap da client verso server e viceversa.  Per assurdo potrebbe essere addirittura possibile lavorare direttamente con un puntatore alla memoria delle scanlines del bitmap senza passare da uno stream locale ma in questa versione ho utilizzato comunque uno stream su cui poter effettuare delle modifiche.


███╗   ███╗████████╗██████╗     ███████╗███████╗██████╗ ██╗   ██╗███████╗██████╗ 
████╗ ████║╚══██╔══╝██╔══██╗    ██╔════╝██╔════╝██╔══██╗██║   ██║██╔════╝██╔══██╗
██╔████╔██║   ██║   ██████╔╝    ███████╗█████╗  ██████╔╝██║   ██║█████╗  ██████╔╝
██║╚██╔╝██║   ██║   ██╔═══╝     ╚════██║██╔══╝  ██╔══██╗╚██╗ ██╔╝██╔══╝  ██╔══██╗
██║ ╚═╝ ██║   ██║   ██║         ███████║███████╗██║  ██║ ╚████╔╝ ███████╗██║  ██║
╚═╝     ╚═╝   ╚═╝   ╚═╝         ╚══════╝╚══════╝╚═╝  ╚═╝  ╚═══╝  ╚══════╝╚═╝  ╚═╝
                                                                                 



unit iraMtpSrv;

{$B-}           { Enable partial boolean evaluation   }
{$T-}           { Untyped pointers                    }
{$X+}           { Enable extended syntax              }
{$H+}           { Use long strings                    }
{$J+}           { Allow typed constant to be modified }
interface

uses
    Winapi.Windows,
    Winapi.Messages,
    OverbyteIcsWinSock,
    System.SysUtils,
    System.Classes,
{$IFNDEF NOFORMS}
  {$IFDEF FMX}
    FMX.Forms,
  {$ELSE}
    Vcl.Forms,
  {$ENDIF}
{$ENDIF}

    OverbyteIcsTypes,
    OverbyteIcsUtils,
  {$IFDEF FMX}
    Ics.Fmx.OverbyteIcsSocketUtils,
    Ics.Fmx.OverbyteIcsWndControl,
    Ics.Fmx.OverbyteIcsWSocket,
    Ics.Fmx.OverbyteIcsWSocketS,
  {$ELSE}
    OverbyteIcsWndControl,
    OverbyteIcsWSocket,
    OverbyteIcsSocketUtils,
    OverbyteIcsWSocketS,
  {$ENDIF}
    OverbyteIcsFtpSrvT,
    OverbyteIcsWSockBuf,
    StrUtils
    ;



const
    DefaultRcvSize           = 16384;

const
    ftpcPORT      = 0;
    ftpcSTOR      = 1;
    ftpcRETR      = 2;
    ftpcQUIT      = 3;
    ftpcABOR      = 4;


type

    PBoolean = ^Boolean;
    FtpServerException  = class(Exception);
    TFtpString = type String;


type
    EFtpCtrlSocketException = class(Exception);
    TFtpCtrlState = (ftpcInvalid, ftpcWaitingUserCode,
                     ftpcReady, ftpcWaitingAnswer, ftpcFailedAuth);

    TFtpCmdType   = Byte;

type
    TDisplayEvent = procedure (Sender : TObject; Msg : String) of object;
    TCommandEvent = procedure (Sender : TObject; CmdBuf : PAnsiChar; CmdLen : Integer) of object;

    TMtpCtrlSocket = class;

    TClientProcessingThread = class(TThread)
    public
        Client    : TMtpCtrlSocket;
        Keyword   : String;
        Params    : String;
        InData    : String;
        OutData   : String;
        AuxData   : String;
        ClientID  : Integer;
        StartTick : LongWord;
        Sender    : TObject;
    protected
        procedure TriggerEnterSecurityContext;
        procedure TriggerLeaveSecurityContext;
        procedure Execute; override;
    end;

    TMtpServer     = class;

    TMtpCtrlSocket = class(TWSocketClient)
    protected
        FDataSocket        : TWSocket;
        FRcvBuf            : PAnsiChar;
        FRcvCnt            : Integer;
        FRcvSize           : Integer;
        FBusy              : Boolean;
        FLastCommand       : TDateTime;
        FCommandCount      : LongInt;
        FCloseRequest      : Boolean;
        FFtpState          : TFtpCtrlState;
        FAbortingTransfer  : Boolean;
        FUserData          : LongInt;
        FPeerAddr          : String;
        FPeerSAddr         : TSockAddr;
        FHost              : String;
        FOnDisplay         : TDisplayEvent;
        FOnCommand         : TCommandEvent;
        FEpsvAllArgReceived: Boolean;
        FSndBufSize        : Integer;
        FRcvBufSize        : Integer;
        FStreamSize        : LongInt;
        procedure TriggerSessionConnected(Error : Word); override;
        function  TriggerDataAvailable(Error : Word) : boolean; override;
        procedure TriggerCommand(CmdBuf : PAnsiChar; CmdLen : Integer); virtual;
        procedure SetRcvSize(newValue : Integer);
        procedure SetOnBgException(const Value: TIcsBgExceptionEvent); override;
        procedure SetRcvBufSize(newValue : Integer);
        procedure SetSndBufSize(newValue : Integer);
        procedure SetStreamSize(newValue : Integer);
    public
        FtpServer         : TMtpServer;
        BinaryMode        : Boolean;
        DataAddr          : String;
        DataPort          : String;
        MemoryName        : String;
        DataSessionActive : Boolean;
        DataStream        : TMemoryStream;
        HasOpenedFile     : Boolean;
        TransferError     : String;
        DataSent          : Boolean;
        CurCmdType        : TFtpCmdType;
        ProcessingThread  : TClientProcessingThread;
        AnswerDelayed     : Boolean;
        ByteCount         : Int64;
        RestartPos        : Int64;
        LastTick          : Longword;
        SessStartTick     : Longword;
        ReqStartTick      : Longword;
        XferStartTick     : Longword;
        ReqDurMilliSecs   : Integer;
        TotGetBytes       : Int64;
        TotPutBytes       : Int64;
        SessIdInfo        : String;
        FailedAttempts    : Integer;
        DelayAnswerTick   : Longword;
        constructor Create(AOwner: TComponent); override;
        destructor  Destroy; override;
        procedure   SendAnswer(const Answer : RawByteString);
        procedure   SetAbortingTransfer(newValue : Boolean);
        procedure   TriggerSessionClosed(Error : Word); override;
        procedure   DataStreamWriteString(const Str: AnsiString);  overload;
        procedure   DataStreamWriteString(const Str: AnsiString; DstCodePage: LongWord);  overload;

        procedure   DataStreamWriteString(const Str: UnicodeString; DstCodePage: LongWord); overload;
        procedure   DataStreamWriteString(const Str: UnicodeString); overload;

        procedure   DataStreamReadString(var Str: AnsiString; Len: TFtpBigInt); overload;
        procedure   DataStreamReadString(var Str: AnsiString; Len: TFtpBigInt; SrcCodePage: LongWord); overload;

        procedure   DataStreamReadString(var Str: UnicodeString; Len: TFtpBigInt); overload;

        property    DataSocket     : TWSocket    read  FDataSocket;
        property    LastCommand    : TDateTime   read  FLastCommand;
        property    CommandCount   : LongInt     read  FCommandCount;
        property    RcvBuf         : PAnsiChar   read  FRcvBuf;
        property    RcvdCount;
        property    CloseRequest   : Boolean     read  FCloseRequest write FCloseRequest;
        property    AbortingTransfer : Boolean   read  FAbortingTransfer write SetAbortingTransfer;
        property    ID             : LongInt     read  FCliId write FCliId;
        property    PeerSAddr      : TSockAddr   read  FPeerSAddr;
        property    ReadCount      : Int64       read  FReadCount;

    published
        property    FtpState       : TFtpCtrlState  read  FFtpState write FFtpState;
        property    RcvSize        : Integer     read  FRcvSize write SetRcvSize;
        property    Busy           : Boolean     read  FBusy write FBusy;
        property    UserData       : LongInt     read  FUserData write FUserData;
        property    Host           : String      read  FHost write FHost;
        property    SndBufSize     : Integer     read FSndBufSize write SetSndBufSize;
        property    RcvBufSize     : Integer     read FRcvBufSize write SetRcvBufSize;
        property    StreamSize     : Integer     read FStreamSize write SetStreamSize;
        property    OnDisplay      : TDisplayEvent read  FOnDisplay write FOnDisplay;
        property    OnCommand      : TCommandEvent read  FOnCommand write FOnCommand;
        property    OnSessionClosed;
        property    OnDataSent;
        property    HSocket;
        property    AllSent;
        property    State;
    end;

    TMtpCtrlSocketClass = class of TMtpCtrlSocket;
    TFtpSrvClientConnectEvent = procedure (Sender: TObject; Client: TMtpCtrlSocket; AError: Word) of object;
    TFtpSrvDataSessionConnectedEvent = procedure (Sender: TObject; Client: TMtpCtrlSocket; Data  : TWSocket; AError: Word) of object;
    TFtpSrvClientCommandEvent = procedure (Sender: TObject; Client: TMtpCtrlSocket; var Keyword : TFtpString; var Params: TFtpString;
                                           var Answer: TFtpString) of object;
    TFtpSrvAnswerToClientEvent = procedure (Sender: TObject; Client: TMtpCtrlSocket;  var Answer  : TFtpString) of object;
    TFtpSrvDataAvailableEvent = procedure (Sender: TObject; Client: TMtpCtrlSocket; Data : TWSocket; Buf: PAnsiChar; Len: LongInt;
                                          AError: Word) of object;
    TFtpSrvRetrDataSentEvent  = procedure (Sender: TObject; Client: TMtpCtrlSocket; Data : TWSocket; AError: Word) of object;
    TFtpSrvGetProcessingEvent = procedure (Sender  : TObject; Client: TMtpCtrlSocket; var DelayedSend: Boolean) of object;
    TFtpSrvCommandProc = procedure (Client: TMtpCtrlSocket; var Keyword: TFtpString; var Params  : TFtpString;                              var Answer  : TFtpString) of object;

    TFtpSrvCommandTableItem   = record
     KeyWord : String;
     Proc    : TFtpSrvCommandProc;
    end;

    TFtpSrvCommandTable = array of TFtpSrvCommandTableItem;

    TFtpSecurityContextEvent  = procedure (Sender: TObject;Client: TMtpCtrlSocket) of object;
    TFtpSrvGeneralEvent = procedure (Sender: TObject; Client: TMtpCtrlSocket; var Params: TFtpString; var Answer: TFtpString) of object;
    TFtpSrvTimeoutEvent =  procedure (Sender: TObject; Client: TMtpCtrlSocket; Duration: Integer; var Abort: Boolean) of object;
    TFtpSrvDisplayEvent = procedure (Sender: TObject; Client: TMtpCtrlSocket; Msg: TFtpString) of object;

    TMtpServer = class(TIcsWndControl)
    protected
        FAddr                   : String;
        FSocketFamily           : TSocketFamily;
        FPort                   : String;
        FListenBackLog          : Integer;
        FSocketServer           : TWSocketServer ;
        FClientClass            : TMtpCtrlSocketClass;
        FMaxClients             : LongInt;
        FCmdTable               : TFtpSrvCommandTable;
        FLastCmd                : Integer;
        FUserData               : LongInt;
        FTimeoutSecsLogin       : Integer;
        FTimeoutSecsIdle        : Integer;
        FTimeoutSecsXfer        : Integer;
        FEventTimer             : TIcsTimer;
        FAlloExtraSpace         : Integer;
        FMaxAttempts            : Integer;
        FBindFtpData            : Boolean;

        FMsg_WM_FTPSRV_CLOSE_REQUEST  : UINT;
        FMsg_WM_FTPSRV_ABORT_TRANSFER : UINT;
        FMsg_WM_FTPSRV_CLOSE_DATA     : UINT;
        FMsg_WM_FTPSRV_START_SEND     : UINT;
        FOnStart                : TNotifyEvent;
        FOnStop                 : TNotifyEvent;
        FOnClientConnect        : TFtpSrvClientConnectEvent;
        FOnClientDisconnect     : TFtpSrvClientConnectEvent;
        FOnClientCommand        : TFtpSrvClientCommandEvent;
        FOnAnswerToClient       : TFtpSrvAnswerToClientEvent;
        FOnStorSessionConnected : TFtpSrvDataSessionConnectedEvent;
        FOnStorSessionClosed    : TFtpSrvDataSessionConnectedEvent;
        FOnStorDataAvailable    : TFtpSrvDataAvailableEvent;
        FOnRetrSessionConnected : TFtpSrvDataSessionConnectedEvent;
        FOnRetrSessionClosed    : TFtpSrvDataSessionConnectedEvent;
        FOnRetrDataSent         : TFtpSrvRetrDataSentEvent;
        FOnGetProcessing        : TFtpSrvGetProcessingEvent;
        FOnEnterSecurityContext : TFtpSecurityContextEvent;
        FOnLeaveSecurityContext : TFtpSecurityContextEvent;
        FOnTimeout              : TFtpSrvTimeoutEvent;
        FOnDisplay              : TFtpSrvDisplayEvent;
        procedure CreateSocket; virtual;
        function  GetMultiListenIndex: Integer;
        function  GetMultiListenSockets: TWSocketMultiListenCollection;
        procedure SetMultiListenSockets(const Value: TWSocketMultiListenCollection);
        procedure SetOnBgException(const Value: TIcsBgExceptionEvent); override;

        procedure ClientProcessingThreadTerminate(Sender : TObject);
        procedure Notification(AComponent: TComponent; operation: TOperation); override;
        procedure ServSocketStateChange(Sender: TObject; OldState, NewState: TSocketState);
        procedure ClientDataSent(Sender: TObject; AError : Word); virtual;
        procedure ClientCommand(Sender: TObject; CmdBuf: PAnsiChar; CmdLen: Integer);
        procedure ClientStorSessionConnected(Sender: TObject; AError : Word);
        procedure ClientStorSessionClosed(Sender: TObject; AError : Word);
        procedure ClientStorDataAvailable(Sender: TObject; AError : word); virtual;
        procedure ClientRetrSessionConnected(Sender: TObject; AError : Word); virtual;
        procedure ClientRetrSessionClosed(Sender: TObject; AError : Word);
        procedure ClientRetrDataSent(Sender: TObject; AError : Word);
        procedure SendAnswer(Client: TMtpCtrlSocket; Answer: TFtpString);  virtual;
        procedure SendNextDataChunk(Client: TMtpCtrlSocket; Data: TWSocket); virtual;
        procedure StartSendData(Client: TMtpCtrlSocket);
        procedure PrepareStorDataSocket(Client: TMtpCtrlSocket);
        procedure EventTimerOnTimer(Sender: TObject);
        procedure ServerClientConnect(Sender: TObject; Client: TWSocketClient; Error: Word);
        procedure ServerClientDisconnect(Sender: TObject; Client: TWSocketClient; Error: Word);

        procedure TriggerServerStart; virtual;
        procedure TriggerServerStop; virtual;
        procedure TriggerSendAnswer(Client: TMtpCtrlSocket; var Answer: TFtpString); virtual;
        procedure TriggerClientConnect(Client: TMtpCtrlSocket; AError: Word); virtual;
        procedure TriggerClientDisconnect(Client: TMtpCtrlSocket; AError: Word); virtual;
        procedure TriggerClientCommand(Client: TMtpCtrlSocket; var Keyword: TFtpString; var Params: TFtpString; var Answer: TFtpString); virtual;
        procedure TriggerStorSessionConnected(Client: TMtpCtrlSocket; Data: TWSocket; AError: Word); virtual;
        procedure TriggerStorSessionClosed(Client: TMtpCtrlSocket; Data: TWSocket; AError: Word); virtual;
        procedure TriggerRetrSessionConnected(Client: TMtpCtrlSocket; Data: TWSocket; AError: Word); virtual;
        procedure TriggerRetrSessionClosed(Client: TMtpCtrlSocket; Data: TWSocket; AError: Word); virtual;
        procedure TriggerStorDataAvailable(Client: TMtpCtrlSocket; Data: TWSocket; Buf: PAnsiChar; Len: LongInt; AError: Word); virtual;
        procedure TriggerRetrDataSent(Client: TMtpCtrlSocket;Data: TWSocket; AError: Word); virtual;
        procedure TriggerEnterSecurityContext(Client: TMtpCtrlSocket); virtual;
        procedure TriggerLeaveSecurityContext(Client: TMtpCtrlSocket); virtual;
        procedure TriggerTimeout (Client: TMtpCtrlSocket; Duration: Integer; var Abort : Boolean); virtual;
        procedure TriggerDisplay (Client    : TMtpCtrlSocket; Msg: TFtpString); virtual;
        function  GetClientCount: Integer; virtual;
        function  GetClient(nIndex: Integer): TMtpCtrlSocket; virtual;
        function  GetActive: Boolean;
        procedure SetActive(newValue: Boolean);
        procedure SetClientClass(const NewValue: TMtpCtrlSocketClass);
        procedure AddCommand(const Keyword: String; const Proc: TFtpSrvCommandProc); virtual;
        procedure WMFtpSrvCloseRequest(var msg: TMessage); virtual;
        procedure WMFtpSrvAbortTransfer(var msg: TMessage); virtual;
        procedure WMFtpSrvCloseData(var msg: TMessage); virtual;
        procedure WMFtpSrvStartSend(var msg: TMessage); virtual;
        procedure CommandQUIT(Client: TMtpCtrlSocket; var Keyword: TFtpString; var Params: TFtpString; var Answer: TFtpString); virtual;
        procedure CommandPORT(Client: TMtpCtrlSocket; var Keyword: TFtpString; var Params: TFtpString; var Answer: TFtpString); virtual;
        procedure CommandSTOR(Client: TMtpCtrlSocket; var Keyword: TFtpString; var Params: TFtpString; var Answer: TFtpString); virtual;
        procedure CommandRETR(Client: TMtpCtrlSocket; var Keyword: TFtpString; var Params: TFtpString; var Answer: TFtpString); virtual;
        procedure CommandABOR(Client: TMtpCtrlSocket; var Keyword: TFtpString; var Params: TFtpString; var Answer: TFtpString); virtual;

    public
        constructor Create(AOwner: TComponent); override;
        destructor  Destroy; override;
        procedure   Start;
        procedure   Stop;
        procedure   Disconnect(Client : TMtpCtrlSocket);
        procedure   DisconnectAll;
        procedure   DoStartSendData(Client: TMtpCtrlSocket; var Answer : TFtpString); virtual;
        procedure   AllocateMsgHandlers; override;
        procedure   FreeMsgHandlers; override;
        function    MsgHandlersCount: Integer; override;
        procedure   WndProc(var MsgRec: TMessage); override;

        function    IsClient(SomeThing : TObject) : Boolean;
        function    OpenMemoryStream( ): TMemoryStream;
        procedure   CloseMemoryStreams(Client : TMtpCtrlSocket);
        property  ServSocket    : TWSocketServer      read  FSocketServer;
        property  ClientCount   : Integer             read  GetClientCount;
        property  Active        : Boolean             read  GetActive write SetActive;
        property  ClientClass   : TMtpCtrlSocketClass read  FClientClass write SetClientClass;

        property  Client[nIndex : Integer] : TMtpCtrlSocket read  GetClient;
        property  MultiListenIndex       : Integer    read  GetMultiListenIndex;
    published
        property  Addr                   : String     read  FAddr write FAddr;
        property  BindFtpData            : Boolean    read  FBindFtpData write FBindFtpData default True;
        property  SocketFamily           : TSocketFamily   read  FSocketFamily write FSocketFamily;
        property  Port                   : String     read  FPort write FPort;
        property  ListenBackLog          : Integer    read  FListenBackLog write FListenBackLog;
        property MultiListenSockets      : TWSocketMultiListenCollection read  GetMultiListenSockets write SetMultiListenSockets;
        property  UserData               : LongInt    read  FUserData write FUserData;
        property  MaxClients             : LongInt    read  FMaxClients write FMaxClients;
        property  TimeoutSecsLogin       : Integer    read FTimeoutSecsLogin write FTimeoutSecsLogin;
        property  TimeoutSecsIdle        : Integer    read FTimeoutSecsIdle write FTimeoutSecsIdle;
        property  TimeoutSecsXfer        : Integer    read FTimeoutSecsXfer write FTimeoutSecsXfer;
        property  AlloExtraSpace         : Integer    read FAlloExtraSpace write FAlloExtraSpace;
        property  MaxAttempts            : Integer    read  FMaxAttempts write FMaxAttempts ;
        property  OnStart                : TNotifyEvent read  FOnStart write FOnStart;
        property  OnStop                 : TNotifyEvent read  FOnStop write FOnStop;
        property  OnClientDisconnect     : TFtpSrvClientConnectEvent read  FOnClientDisconnect write FOnClientDisconnect;
        property  OnClientConnect        : TFtpSrvClientConnectEvent read  FOnClientConnect write FOnClientConnect;
        property  OnClientCommand        : TFtpSrvClientCommandEvent read  FOnClientCommand write FOnClientCommand;
        property  OnAnswerToClient       : TFtpSrvAnswerToClientEvent read  FOnAnswerToClient write FOnAnswerToClient;
        property  OnStorSessionConnected : TFtpSrvDataSessionConnectedEvent read  FOnStorSessionConnected write FOnStorSessionConnected;
        property  OnRetrSessionConnected : TFtpSrvDataSessionConnectedEvent read  FOnRetrSessionConnected  write FOnRetrSessionConnected;
        property  OnStorSessionClosed    : TFtpSrvDataSessionConnectedEvent read  FOnStorSessionClosed write FOnStorSessionClosed;
        property  OnRetrSessionClosed    : TFtpSrvDataSessionConnectedEvent read  FOnRetrSessionClosed write FOnRetrSessionClosed;
        property  OnRetrDataSent         : TFtpSrvRetrDataSentEvent read  FOnRetrDataSent write FOnRetrDataSent;
        property  OnStorDataAvailable    : TFtpSrvDataAvailableEvent read  FOnStorDataAvailable write FOnStorDataAvailable;
        property  OnGetProcessing        : TFtpSrvGetProcessingEvent read  FOnGetProcessing write FOnGetProcessing;
        property  OnEnterSecurityContext : TFtpSecurityContextEvent read  FOnEnterSecurityContext  write FOnEnterSecurityContext;
        property  OnLeaveSecurityContext : TFtpSecurityContextEvent read  FOnLeaveSecurityContext write FOnLeaveSecurityContext;
        property  OnTimeout              : TFtpSrvTimeoutEvent read  FOnTimeout write FOnTimeout;
        property  OnDisplay              : TFtpSrvDisplayEvent read  FOnDisplay write FOnDisplay;
        property  OnBgException;
    end;


procedure UpdateThreadOnProgress(Obj: TObject; Count: Int64; var Cancel: Boolean);

procedure register;
implementation


const
    msgCmdUnknown     = '500 ''%s'': command not understood.';
    msgOptRespRequired = '331 Response to %s required for %s.';
    msgQuit           = '221 Goodbye.';
    msgPortSuccess    = '200 Port command successful.';
    msgPortFailed     = '501 Invalid PORT command.';
    msgStorDisabled   = '501 Permission Denied'; {'500 Cannot STOR.';}
    msgStorSuccess    = '150 Opening data connection for %s.';
    msgStorFailed     = '501 Cannot STOR. %s';
    msgStorAborted    = '426 Connection closed; %s.';
    msgStorOk         = '226 File received ok';
{   msgStorOk         = '226-Multiple lines answer'#13#10'  Test'#13#10#13#10'226 File received OK'; }
    msgStorError      = '426 Connection closed; transfer aborted. Error %s';
    msgRetrDisabled   = '500 Cannot RETR.';
    msgRetrSuccess    = '150 Opening retrieve data connection for ';
    msgRetrFailed     = '501 Cannot RETR. %s';
    msgRetrAborted    = '426 Connection closed; %s.';
    msgRetrOk         = '226 File sent ok';
    msgRetrError      = '426 Connection closed; transfer aborted. Error %s';
    msgRetrNotExists  = '550 ''%s'': no such file or directory.';
    msgRetrFileErr    = '451 Cannot open file: %s.';
    msgAborOk         = '225 ABOR command successful.';
    msgTimeout        = '421 Connection closed, timed out after %d secs.';

    msgNotAllowed     = '421 Connection not allowed.';

procedure register;
begin
RegisterComponents('ira Mtp', [TMtpServer]);
end;



function atosi(const value : String) : Integer;
var
    i, j : Integer;
begin
    Result := 0;
    i := 1;
    while (i <= Length(Value)) and (Value[i] = ' ') do
        i := i + 1;
    j := i;
    while (i <= Length(Value)) and ((Value[i] = '+') or (Value[i] = '-')) do
       i := i + 1;
    while (i <= Length(Value)) and (Value[i] >= '0') and (Value[i] <= '9')do begin
        Result := Result * 10 + ord(Value[i]) - ord('0');
        i := i + 1;
    end;
    if j < Length(Value) then begin
        if value[j] = '-' then
            Result := -Result;
    end;
end;


procedure TMtpServer.CreateSocket;
begin
    FSocketServer := TWSocketServer.Create(Self);
end;



constructor TMtpServer.Create(AOwner: TComponent);
var
    Len : Cardinal;
begin
    inherited Create(AOwner);
    AllocateHWnd;

    FClientClass          := TMtpCtrlSocket;
    //FSocketServer         := TWSocketServer.Create(Self);
    CreateSocket;
    FSocketServer.Name    := 'WSocketServer';
    FSocketServer.ClientClass         := FClientClass;
    FSocketServer.OnClientConnect     := ServerClientConnect;
    FSocketServer.OnClientDisconnect  := ServerClientDisconnect;

    FPort               := 'ftp';
    FSocketFamily       := DefaultSocketFamily;
    FAddr               := ICS_ANY_HOST_V4;
    FListenBackLog      := 5;
    FTimeoutSecsLogin   := 60;
    FTimeoutSecsIdle    := 300;
    FTimeoutSecsXfer    := 60;
    FAlloExtraSpace     := 1000000;
    FEventTimer         := TIcsTimer.Create(Self);
    FEventTimer.Enabled := false;
    FEventTimer.OnTimer := EventTimerOnTimer;
    FEventTimer.Interval := 5000;
    FMaxAttempts        := 12 ;
    FBindFtpData        := True;

    SetLength(FCmdTable, 5 + 1 + 5);
    AddCommand('PORT', CommandPORT);
    AddCommand('STOR', CommandSTOR);
    AddCommand('RETR', CommandRETR);
    AddCommand('QUIT', CommandQUIT);
    AddCommand('ABOR', CommandABOR);
end;



destructor TMtpServer.Destroy;
begin
    if Assigned(FEventTimer) then begin
        FEventTimer.Destroy;
        FEventTimer := nil;
    end;
    if Assigned(FSocketServer) then begin
        FSocketServer.Destroy;
        FSocketServer := nil;
    end;
    SetLength(FCmdTable, 0);
    inherited Destroy;
end;



function TMtpServer.MsgHandlersCount : Integer;
begin
    Result := 5 + inherited MsgHandlersCount;
end;



procedure TMtpServer.AllocateMsgHandlers;
begin
    inherited AllocateMsgHandlers;
    FMsg_WM_FTPSRV_CLOSE_REQUEST  := FWndHandler.AllocateMsgHandler(Self);
    FMsg_WM_FTPSRV_ABORT_TRANSFER := FWndHandler.AllocateMsgHandler(Self);
    FMsg_WM_FTPSRV_CLOSE_DATA     := FWndHandler.AllocateMsgHandler(Self);
    FMsg_WM_FTPSRV_START_SEND     := FWndHandler.AllocateMsgHandler(Self);
end;



procedure TMtpServer.FreeMsgHandlers;
begin
    if Assigned(FWndHandler) then begin
        FWndHandler.UnregisterMessage(FMsg_WM_FTPSRV_CLOSE_REQUEST);
        FWndHandler.UnregisterMessage(FMsg_WM_FTPSRV_ABORT_TRANSFER);
        FWndHandler.UnregisterMessage(FMsg_WM_FTPSRV_CLOSE_DATA);
        FWndHandler.UnregisterMessage(FMsg_WM_FTPSRV_START_SEND);
    end;
    inherited FreeMsgHandlers;
end;



procedure TMtpServer.WndProc(var MsgRec: TMessage);
begin
    try
        with MsgRec do begin
            if  Msg = FMsg_WM_FTPSRV_CLOSE_REQUEST  then
                WMFtpSrvCloseRequest(MsgRec)
            else if Msg = FMsg_WM_FTPSRV_ABORT_TRANSFER then
                WMFtpSrvAbortTransfer(MsgRec)
            else if Msg = FMsg_WM_FTPSRV_CLOSE_DATA then
                WMFtpSrvCloseData(MsgRec)
            else if Msg = FMsg_WM_FTPSRV_START_SEND then
                WMFtpSrvStartSend(MsgRec)
            else
                inherited WndProc(MsgRec);
        end;
    except
        on E:Exception do
            HandleBackGroundException(E);
    end;
end;



procedure TMtpServer.WMFtpSrvCloseRequest(var msg: TMessage);
var
    Client : TMtpCtrlSocket;
begin
    Client := TMtpCtrlSocket(msg.LParam);
    if FSocketServer.IsClient(Client) then begin
        { Check if client.ID is still the same as when message where posted }
        if WPARAM(Client.ID) = Msg.WParam then begin
            if Client.AllSent then
                Client.Close
            else
                Client.CloseRequest := TRUE;
        end;
    end;
end;



procedure TMtpServer.Notification(AComponent: TComponent; operation: TOperation);
begin
    inherited Notification(AComponent, operation);
    if operation = opRemove then begin
        if AComponent = FSocketServer then
            FSocketServer := nil;
    end;
end;



function TMtpServer.OpenMemoryStream ( ): TMemoryStream;
begin
    Result := TMemoryStream.Create ; //( MAX_BUFSIZE);
end ;


procedure TMtpServer.CloseMemoryStreams(Client : TMtpCtrlSocket);
begin
    if Client.HasOpenedFile then begin
        if Assigned(Client.DataStream) then Client.DataStream.Destroy;
        Client.DataStream    := nil;
        Client.HasOpenedFile := FALSE;
    end;
end;


procedure TMtpServer.AddCommand(
    const Keyword : String;
    const Proc    : TFtpSrvCommandProc);
begin
    if FLastCmd > High(FCmdTable) then
        raise FtpServerException.Create('Too many command');
    FCmdTable[FLastCmd].KeyWord := KeyWord;
    FCmdTable[FLastCmd].Proc    := Proc;
    Inc(FLastCmd);
end;



procedure TMtpServer.Start;
begin
    if FSocketServer.State = wsListening then
        Exit;
    FSocketServer.Port              := Port;
    FSocketServer.Proto             := 'tcp';
    FSocketServer.SocketFamily      := FSocketFamily;
    FSocketServer.Addr              := FAddr;
    FSocketServer.ListenBacklog     := FListenBackLog;

    FSocketServer.banner := '';

    FSocketServer.MaxClients        := FMaxClients;
    FSocketServer.OnChangeState     := ServSocketStateChange;
    FSocketServer.ComponentOptions  := [wsoNoReceiveLoop];
    FSocketServer.MultiListen;
    FEventTimer.Enabled := true;
end;



procedure TMtpServer.Stop;
begin
    FEventTimer.Enabled := false;
    FSocketServer.Close;
end;



procedure TMtpServer.DisconnectAll;
begin
    FSocketServer.DisconnectAll;
end;



procedure TMtpServer.Disconnect(Client : TMtpCtrlSocket);
begin
    if NOT FSocketServer.IsClient(Client) then
        raise FtpServerException.Create('Disconnect: Not one of our clients');
    FSocketServer.Disconnect(Client);
end;



function TMtpServer.GetActive : Boolean;
begin
    Result := (FSocketServer.State = wsListening);
end;



procedure TMtpServer.SetActive(newValue : Boolean);
begin
    if newValue then
        Start
    else
        Stop;
end;


procedure TMtpServer.SetClientClass(const NewValue: TMtpCtrlSocketClass);
begin
    if NewValue <> FSocketServer.ClientClass then begin
        FClientClass := NewValue;
        FSocketServer.ClientClass := NewValue;
    end;
end;


procedure TMtpServer.ServSocketStateChange(Sender : TObject; OldState, NewState : TSocketState);
begin
    if csDestroying in ComponentState then
        Exit;
    if NewState = wsListening then
        TriggerServerStart
    else if NewState = wsClosed then
        TriggerServerStop;
end;



procedure TMtpServer.ServerClientConnect(Sender: TObject;
                                Client: TWSocketClient; Error: Word);
var
    MyClient: TMtpCtrlSocket;
begin
    if Error <> 0 then
        raise FtpServerException.Create('Session Available Error - ' +
                                                    GetWinsockErr(Error));
    MyClient := Client as TMtpCtrlSocket;
    MyClient.DataSocket.Name := Name + '_DataWSocket' + IntToStr(MyClient.ID);
    MyClient.OnCommand       := ClientCommand;
    MyClient.OnDataSent      := ClientDataSent;
    MyClient.FtpServer       := Self;

    MyClient.SessIdInfo      := Client.GetPeerAddr + ' [' + IntToStr (Client.CliId) + ']' ;
    MyClient.FLastCommand    := 0;
    MyClient.FCommandCount   := 0;


    MyClient.FFtpState       := ftpcWaitingUserCode; // ftpcReady;
end;



procedure TMtpServer.SendAnswer(Client : TMtpCtrlSocket; Answer : TFtpString);
begin
    try
         Client.ReqDurMilliSecs := IcsElapsedMsecs (Client.ReqStartTick);
         TriggerSendAnswer(Client, Answer);

        Client.SendAnswer(Answer);
    except
    end;
end;



procedure TMtpServer.ClientCommand(Sender : TObject; CmdBuf : PAnsiChar; CmdLen : Integer);
const
    TELNET_IAC       = #255;
    TELNET_IP        = #244;
    TELNET_DATA_MARK = #242;
var
    Client  : TMtpCtrlSocket;
    Answer  : TFtpString;
    Params  : TFtpString;
    KeyWord : TFtpString;
    I       : Integer;
    RawParams: RawByteString;
begin
    Client := Sender as TMtpCtrlSocket;
    Answer := '';

    try
        Client.ReqStartTick := IcsGetTickCountX;
        Client.ReqDurMilliSecs := 0;
        RawParams := '';
        I      := 0;
        while I < CmdLen do begin
            if CmdBuf[I] <> TELNET_IAC then begin
                RawParams := RawParams + CmdBuf[I];
                Inc(I);
            end
            else begin
                Inc(I);
                if CmdBuf[I] = TELNET_IAC then
                    RawParams := RawParams + CmdBuf[I];
                Inc(I);
            end;
        end;
            Params := RawParams;

        I := 1;
        KeyWord := UpperCase(ScanGetAsciiArg (Params, I));
        ScanFindArg (Params, I);

        Params := Copy(Params, I, Length(Params));

        TriggerClientCommand(Client, Keyword, Params, Answer);
        if Answer <> '' then begin
            SendAnswer(Client, Answer);
            Exit;
        end;

        if Keyword = '' then begin
            SendAnswer(Client, Format(msgCmdUnknown, [Params]));
            Exit;
        end;

        I := 0;
        while I <= High(FCmdTable) do begin
            if FCmdTable[I].KeyWord = KeyWord then begin
                if I <> ftpcABOR then   { AG V8.02 }
                    Client.CurCmdType := I;
                Client.AnswerDelayed := FALSE;
                FCmdTable[I].Proc(Client, KeyWord, Params, Answer);
                if not Client.AnswerDelayed then
                            SendAnswer(Client, Answer);
                Exit;
            end;
            Inc(I);
        end;
        SendAnswer(Client, Format(msgCmdUnknown, [KeyWord]));
    except
        on E:Exception do begin
            SendAnswer(Client, '501 ' + E.Message);
        end;
    end;
end;



procedure TMtpServer.ClientDataSent(Sender : TObject; AError  : Word);
var
    Client  : TMtpCtrlSocket;
begin
    Client := Sender as TMtpCtrlSocket;
    if Client.CloseRequest then begin
        PostMessage(Handle, FMsg_WM_FTPSRV_CLOSE_REQUEST,
                    WPARAM(Client.ID), LPARAM(Client));
    end;
end;



procedure TMtpServer.ServerClientDisconnect(Sender: TObject; Client: TWSocketClient; Error: Word);
var
    MyClient: TMtpCtrlSocket;
begin
    try
        MyClient := Client as TMtpCtrlSocket;
        if MyClient.DataSocket.State = wsConnected then begin
            MyClient.TransferError    := 'ABORT on Disconnect';
            MyClient.AbortingTransfer := TRUE;
            MyClient.DataSocket.Close;
        end;
        CloseMemoryStreams(MyClient);
        TriggerClientDisconnect(MyClient, Error);
    except
    end;
end;


procedure TMtpServer.WMFtpSrvAbortTransfer(var msg: TMessage);
var
    Client : TMtpCtrlSocket;
    Data   : TWSocket;
begin
    Client := TMtpCtrlSocket(Msg.LParam);
    if FSocketServer.IsClient(Client) then begin
        if WPARAM(Client.ID) = Msg.WParam then begin
            Data := Client.DataSocket;

            if Assigned(Data) then begin

                Data.ShutDown(2);
                Data.Close;
            end;
            Client.DataStream.position:=0;
        end;
    end;
end;



procedure TMtpServer.WMFtpSrvCloseData(var msg: TMessage);
var
    Client : TMtpCtrlSocket;
    Data   : TWSocket;
begin
    Client := TMtpCtrlSocket(Msg.LParam);
    if FSocketServer.IsClient(Client) then begin
        { Check if client.ID is still the same as when message where posted }
        if WPARAM(Client.ID) = Msg.WParam then begin
            Data := Client.DataSocket;
            if Assigned(Data) then begin
                Data.ShutDown(1);    {  Wilfried 24/02/04 }
            end;
           Client.DataStream.position:=0;
        end;
    end;
end;



function TMtpServer.GetClient(nIndex : Integer) : TMtpCtrlSocket;
begin
    Result := FSocketServer.Client [nIndex] as TMtpCtrlSocket;
end;



function TMtpServer.IsClient(SomeThing : TObject) : Boolean;
begin
    Result := FSocketServer.IsClient(Something);
end;



function TMtpServer.GetClientCount : Integer;
begin
    Result := FSocketServer.ClientCount;
end;



procedure TMtpServer.TriggerServerStart;
begin
    if Assigned(FOnStart) then
        FOnStart(Self);
end;



procedure TMtpServer.TriggerServerStop;
begin
    if Assigned(FOnStop) then
        FOnStop(Self);
end;



procedure TMtpServer.TriggerSendAnswer(Client : TMtpCtrlSocket; var Answer : TFtpString);
begin
    if Assigned(FOnAnswerToClient) then
        FOnAnswerToClient(Self, Client, Answer);
end;



procedure TMtpServer.TriggerClientDisconnect(Client : TMtpCtrlSocket; AError  : Word);
begin
    if Assigned(FOnClientDisconnect) then
        FOnClientDisconnect(Self, Client, AError);
end;



procedure TMtpServer.TriggerClientConnect(Client : TMtpCtrlSocket; AError  : Word);
begin
    if Assigned(FOnClientConnect) then
        FOnClientConnect(Self, Client, AError);
end;



procedure TMtpServer.TriggerStorSessionConnected(Client : TMtpCtrlSocket; Data : TWSocket; AError  : Word);
begin
    if Assigned(FOnStorSessionConnected) then
        FOnStorSessionConnected(Self, Client, Data, AError);
end;



procedure TMtpServer.TriggerRetrSessionConnected(Client : TMtpCtrlSocket; Data : TWSocket; AError  : Word);
begin
    if Assigned(FOnRetrSessionConnected) then
        FOnRetrSessionConnected(Self, Client, Data, AError);
end;



procedure TMtpServer.TriggerStorSessionClosed( Client : TMtpCtrlSocket; Data : TWSocket; AError  : Word);
begin
    if Assigned(FOnStorSessionClosed) then
        FOnStorSessionClosed(Self, Client, Data, AError);
end;



procedure TMtpServer.TriggerRetrSessionClosed(Client : TMtpCtrlSocket; Data : TWSocket; AError  : Word);
begin
    if Assigned(FOnRetrSessionClosed) then
        FOnRetrSessionClosed(Self, Client, Data, AError);
end;



procedure TMtpServer.TriggerClientCommand(
    Client      : TMtpCtrlSocket;
    var Keyword : TFtpString;
    var Params  : TFtpString;
    var Answer  : TFtpString);
begin
    if Assigned(FOnClientCommand) then
        FOnClientCommand(Self, Client, KeyWord, Params, Answer);
end;




procedure TMtpServer.TriggerStorDataAvailable(
    Client : TMtpCtrlSocket;
    Data   : TWSocket;
    Buf    : PAnsiChar;
    Len    : LongInt;
    AError : Word);
begin
    if Assigned(FOnStorDataAvailable) then
        FOnStorDataAvailable(Self, Client, Data, Buf, Len, AError);
end;



procedure TMtpServer.TriggerRetrDataSent(
    Client : TMtpCtrlSocket;
    Data   : TWSocket;
    AError : Word);
begin
    if Assigned(FOnRetrDataSent) then
        FOnRetrDataSent(Self, Client, Data, AError);
end;





procedure TMtpServer.TriggerEnterSecurityContext(Client : TMtpCtrlSocket);
begin
    if Assigned(FOnEnterSecurityContext) then
        FOnEnterSecurityContext(Self, Client);
end;



procedure TMtpServer.TriggerLeaveSecurityContext( Client : TMtpCtrlSocket);
begin
    if Assigned(FOnLeaveSecurityContext) then
        FOnLeaveSecurityContext(Self, Client);
end;



procedure TMtpServer.TriggerTimeout( Client: TMtpCtrlSocket; Duration: Integer; var Abort   : Boolean);
begin
    if Assigned(FOnTimeout) then
        FOnTimeout(Self, Client, Duration, Abort);
end;



procedure TMtpServer.TriggerDisplay(Client : TMtpCtrlSocket; Msg: TFtpString);
begin
    if Assigned(FOnDisplay) then
        FOnDisplay(Self, Client, Msg);
end;







procedure TMtpServer.CommandQUIT(Client: TMtpCtrlSocket; var Keyword : TFtpString; var Params  : TFtpString; var Answer  : TFtpString);
begin
    Client.CurCmdType := ftpcQUIT;
    Answer            := msgQuit;
    PostMessage(Handle, FMsg_WM_FTPSRV_CLOSE_REQUEST,
                WPARAM(Client.ID), LPARAM(Client));
end;



function GetInteger(var I : Integer; const Src : String) : LongInt;
begin
    while (I <= Length(Src)) and IsSpace(Src[I]) do
        Inc(I);
    Result := 0;
    while (I <= Length(Src)) and IsDigit(Src[I]) do begin
        Result := Result * 10 + Ord(Src[I]) - Ord('0');
        Inc(I);
    end;
    { Skip trailing white spaces }
    while (I <= Length(Src)) and IsSpace(Src[I]) do
        Inc(I);

    if I <= Length(Src) then begin
        if Src[I] = ',' then
            Inc(I)
        else
            raise FtpServerException.Create('GetInteger: unexpected char');
    end;
end;



procedure TMtpServer.CommandPORT(Client: TMtpCtrlSocket; var Keyword : TFtpString; var Params  : TFtpString; var Answer  : TFtpString);
var
    I : Integer;
    N : LongInt;
begin
    try
        Client.CurCmdType := ftpcPORT;
        I                 := 1;
        Client.DataAddr   := IntToStr(GetInteger(I, Params));
        Client.DataAddr   := Client.DataAddr + '.' + IntToStr(GetInteger(I, Params));
        Client.DataAddr   := Client.DataAddr + '.' + IntToStr(GetInteger(I, Params));
        Client.DataAddr   := Client.DataAddr + '.' + IntToStr(GetInteger(I, Params));
        N := GetInteger(I, Params);
        N := (N shl 8) + GetInteger(I, Params);
        Client.DataPort := IcsIntToStrA(N);
        Answer := msgPortSuccess;
    except
        Answer := msgPortFailed;
    end;
end;



procedure TMtpServer.CommandSTOR(Client: TMtpCtrlSocket; var Keyword : TFtpString; var Params  : TFtpString; var Answer  : TFtpString);
var
    Allowed  : Boolean;
    FilePath : TFtpString;
    n: Integer;
begin
    try
        if Client.FtpState <> ftpcWaitingUserCode then begin
            Answer := 'not ftpcWaitingUserCode';
            Exit;
        end;
        if Params = '' then begin
            Answer := Format(msgStorFailed, ['Size not specified']);
            Exit;
        end
        else


          n:= Pos ( ' ', Params , 1);
          if n = 0 then begin
              Answer := 'MemoryName not specified';
              Exit;
          end;


          Client.StreamSize := StrToIntDef( LeftStr ( Params,n-1)  , 0);
          Client.MemoryName := rightStr ( Params , length(params) - n ) ;
          if Assigned(Client.DataStream ) then  begin

            Client.DataStream.SetSize( Client.StreamSize) ;
            Client.DataStream.Position :=0;
          end;

        try
            Client.CurCmdType       := ftpcSTOR;
            //Client.MemoryName         := Client.PeerAddr ;
            Client.HasOpenedFile    := FALSE;
            Allowed := True;
            PrepareStorDataSocket(Client);     // <--- connect al client
            Answer := Format(msgStorSuccess, [Params]);
        except
            on E:Exception do begin
                Answer := Format(msgStorFailed, [E.Message]);
            end;
        end;
    finally
    end;
end;



procedure TMtpServer.PrepareStorDataSocket(Client : TMtpCtrlSocket);
begin
    Client.AbortingTransfer := FALSE;
    Client.TransferError    := 'Transfer Ok';

        Client.DataSocket.Proto               := 'tcp';
        Client.DataSocket.Addr                := Client.DataAddr;
        Client.DataSocket.Port                := Client.DataPort;
        Client.DataSocket.OnSessionConnected  := ClientStorSessionConnected;
        Client.DataSocket.OnSessionClosed     := ClientStorSessionClosed;
        Client.DataSocket.OnDataAvailable     := ClientStorDataAvailable;
        Client.DataSocket.OnDataSent          := nil;
        Client.DataSocket.LingerOnOff         := wsLingerOff;
        Client.DataSocket.LingerTimeout       := 0;
        if FBindFtpData then begin
            Client.DataSocket.LocalAddr           := Client.GetXAddr;
            Client.DataSocket.LocalPort           := 'ftp-data'; {20}
        end;
        Client.DataSocket.ComponentOptions    := [wsoNoReceiveLoop];
        Client.DataSocket.Connect;
        if Client.DataSocket.SocketRcvBufSize <> Client.FRcvBufSize then
           Client.DataSocket.SocketRcvBufSize := Client.FRcvBufSize;
end;




procedure TMtpServer.ClientStorSessionConnected(Sender : TObject; AError  : Word);
var
    Client      : TMtpCtrlSocket;
    Data        : TWSocket;
begin

    Data                     := TWSocket(Sender);
    Client                   := TMtpCtrlSocket(Data.Owner);
    Client.DataSessionActive := TRUE;
    Client.ByteCount := 0;
    Client.TotPutBytes :=0; // ogni volta inizia da 0.
    if Assigned (Client.DataStream) then begin
      Client.DataStream.SetSize(Client.StreamSize );
      Client.DataStream.Position :=0;
    end;

    Client.XferStartTick := IcsGetTickCountX;
    Client.LastTick := IcsGetTickCountX;

    if Client.AbortingTransfer then
        Exit;
    TriggerStorSessionConnected(Client, Data, AError);
end;



procedure TMtpServer.ClientStorSessionClosed(Sender : TObject; AError  : Word);
var
    Client      : TMtpCtrlSocket;
    Data        : TWSocket;
    Duration    : Integer;
    S           : String;
    BytesSec    : Int64;
    Answer      : String;
begin
    Data                     := TWSocket(Sender);
    Client                   := TMtpCtrlSocket(Data.Owner);

    Client.DataSessionActive := FALSE;
    Client.RestartPos        := 0;
    Client.DataPort          := 'ftp-data';

    if Assigned(FOnDisplay) then begin
        Duration := IcsElapsedMsecs (Client.XferStartTick);
        S := Client.MemoryName + ' ' +
                IntToKbyte(Client.ByteCount) + 'bytes received in ';
        if Duration < 2000 then
            S := S + IntToStr(Duration) + ' milliseconds'
        else begin
            S := S + IntToStr(Duration div 1000) + ' seconds';
            if Client.ByteCount > 32767 then
                BytesSec := 1000 * (Client.ByteCount div Duration)
            else
                BytesSec := (1000 * Client.ByteCount) div Duration;
            S := S + ' (' + IntToKbyte(BytesSec) + 'bytes/sec)';
        end;
        TriggerDisplay (Client, S);
    end;

    if Client.AbortingTransfer and (Client.TransferError = '') then
        Exit;

    Answer := '';
    case Client.CurCmdType of
    ftpcSTOR :
        begin
            if Client.AbortingTransfer then
                Answer := Format(msgStorAborted, [Client.TransferError])
            else if AError = 0 then
                Answer := msgStorOk + ':' + Client.memoryName
            else
                Answer := Format(msgStorError, [GetWinsockErr(AError)]);
        end;
    else
        raise Exception.Create('Program error in ClientStorSessionClosed');
        exit;
    end;

    Client.DataStream.position:=0;
    TriggerStorSessionClosed(Client, Data, AError);

    SendAnswer(Client, Answer);
end;



procedure TMtpServer.ClientStorDataAvailable(Sender: TObject; AError  : word);
var
    Len    : Integer;
    Client : TMtpCtrlSocket;
    Data   : TWSocket;
    NewPos : TFtpBigInt;
begin
    Data   := TWSocket(Sender);
    Client := TMtpCtrlSocket(Data.Owner);
    Len    := Data.Receive(Client.RcvBuf, Client.RcvSize);
    if Len <= 0 then
        Exit;

    if Client.AbortingTransfer then
        Exit;
    Client.LastTick := IcsGetTickCountX;

    try
        TriggerStorDataAvailable(Client, Data, Client.RcvBuf, Len, AError);

        if (not Client.HasOpenedFile) and  (not Assigned(Client.DataStream)) then begin
            TriggerEnterSecurityContext(Client);
            try
                Client.DataStream := OpenMemoryStream(  );
            finally
                TriggerLeaveSecurityContext(Client);
            end;
            NewPos := 0;
        Client.HasOpenedFile := TRUE;
        end;

        if Assigned(Client.DataStream) then begin
            Client.ByteCount := Client.ByteCount + Len;
            Client.TotPutBytes := Client.TotPutBytes + Len;
            TriggerEnterSecurityContext(Client);
            try
//                CopyMemory ( DirectMemoryPtr^, Client.RcvBuf^, Len);
                Client.DataStream.WriteBuffer(Client.RcvBuf^, Len);
            finally
                TriggerLeaveSecurityContext(Client);
            end;
        end;
    except
        on E:Exception do begin
            Client.TransferError    := E.Message;
            Client.AbortingTransfer := TRUE;
            PostMessage(Handle, FMsg_WM_FTPSRV_ABORT_TRANSFER,
                        WPARAM(Client.ID), LPARAM(Client));
        end;
    end;
end;




procedure TMtpServer.CommandRETR( Client: TMtpCtrlSocket; var Keyword : TFtpString; var Params  : TFtpString; var Answer  : TFtpString);
var
    Allowed     : Boolean;
    FilePath    : TFtpString;
    DelayedSend : Boolean;
begin
    try
        if Client.FtpState <> ftpcWaitingUserCode then begin
            Answer := 'ftpcWaitingUserCode';
            Exit;
        end;



        try
            Client.CurCmdType    := ftpcRETR;
            Client.HasOpenedFile := FALSE;
            Client.MemoryName    := Params;
            Allowed := True;

            Client.MemoryName := Client.peeraddr;  ;

            Answer := msgRetrSuccess + IntToStr(Client.DataStream.Size  ) ;
            DelayedSend     := FALSE;
            if Assigned(FOnGetProcessing) then
                FOnGetProcessing(Self, Client, DelayedSend);
            if not DelayedSend then
                DoStartSendData(Client, Answer);
        except
            on E:Exception do begin
                Answer := Format(msgRetrFailed, [E.Message]);
            end;
        end;
    finally
    end;
end;



procedure TMtpServer.DoStartSendData(Client : TMtpCtrlSocket; var Answer : TFtpString);
var
    NewPos  : TFtpBigInt;
    FileExt : String;
    Done    : Boolean;
    FreeSpace: Int64;
begin
    try
        if (not Assigned(Client.DataStream)) then begin
            Answer := Format(msgRetrFailed, ['Failed to open local stream']);
            Exit;
        end;
        Client.LastTick := IcsGetTickCountX;

        PostMessage(Handle, FMsg_WM_FTPSRV_START_SEND, 0, LPARAM(Client));
    except
        on E: Exception do begin
            Answer := Format(msgRetrFailed, [E.Message]);
            ClosememoryStreams(Client);
            Exit;
        end;
    end;
end;



procedure TMtpServer.WMFtpSrvStartSend(var msg: TMessage);
var
    Client      : TMtpCtrlSocket;
begin
    Client := TObject(Msg.LParam) as TMtpCtrlSocket;
    StartSendData(Client);
end;




procedure TMtpServer.StartSendData(Client : TMtpCtrlSocket);
begin
    Client.AbortingTransfer              := FALSE;
    Client.DataSent                      := FALSE;
    Client.TransferError                 := 'Transfer Ok';
        Client.DataSocket.Close;
        Client.DataSocket.Proto              := 'tcp';
        Client.DataSocket.Addr               := Client.DataAddr;
        Client.DataSocket.Port               := Client.DataPort;
        Client.DataSocket.OnSessionConnected := ClientRetrSessionConnected;
        Client.DataSocket.OnSessionClosed    := ClientRetrSessionClosed;
        Client.DataSocket.OnDataAvailable    := nil;
        Client.DataSocket.OnDataSent         := ClientRetrDataSent;
        Client.DataSocket.LingerOnOff        := wsLingerOff;
        Client.DataSocket.LingerTimeout      := 0;
        if FBindFtpData then begin
            Client.DataSocket.LocalAddr           := Client.GetXAddr;
            Client.DataSocket.LocalPort           := 'ftp-data'; {20}
        end;
        Client.DataSocket.ComponentOptions    := [wsoNoReceiveLoop];

        Client.DataSocket.Connect;
        if Client.DataSocket.SocketSndBufSize <> Client.FSndBufSize then
            Client.DataSocket.SocketSndBufSize := Client.FSndBufSize;
end;





procedure TMtpServer.ClientRetrSessionConnected(Sender : TObject; AError  : Word);
var
    Client      : TMtpCtrlSocket;
    Data        : TWSocket;
begin
    Data                     := TWSocket(Sender);
    Client                   := TMtpCtrlSocket(Data.Owner);
    Client.DataSessionActive := (AError = 0);

    if Client.AbortingTransfer then
        Exit;

    try
        TriggerRetrSessionConnected(Client, Data, AError);
        if AError <> 0 then
        begin
            raise FtpServerException.Create('Client data socket connection Error - ' +
               GetWinsockErr(AError) + ' - ' + Client.DataAddr + ':' + Client.DataPort);
        end;
    except
        on E: Exception do begin
            Client.AbortingTransfer := TRUE;
            Client.TransferError    := E.Message;
            PostMessage(Handle, FMsg_WM_FTPSRV_ABORT_TRANSFER,
                        WPARAM(Client.ID), LPARAM(Client));
            Exit;
        end;
    end;

    Client.ByteCount := 0;
    Client.TotGetBytes :=0; // ogni volta inizia da 0.
    Client.XferStartTick := IcsGetTickCountX;
    Client.LastTick := IcsGetTickCountX;
    SendNextDataChunk(Client, Data);
end;



procedure TMtpServer.ClientRetrSessionClosed(Sender : TObject; AError  : Word);
var
    Client      : TMtpCtrlSocket;
    Data        : TWSocket;
    Duration    : Integer;
    S           : String;
    BytesSec    : Int64;
begin
    Data                     := TWSocket(Sender);
    Client                   := TMtpCtrlSocket(Data.Owner);


    Client.DataSessionActive := FALSE;
    Client.RestartPos        := 0;

    Client.DataPort          := 'ftp-data';

    // qui non dobbiamo chiudere lo stream. semplicemente proseguire.
    if Assigned(FOnDisplay) then begin
        Duration := IcsElapsedMsecs (Client.XferStartTick);
        S := Client.MemoryName;
        if S = '' then S := 'Directory';
        S := S + ' ' + IntToKbyte(Client.ByteCount) + 'bytes sent in ';
        if Duration < 2000 then
            S := S + IntToStr(Duration) + ' milliseconds'
        else begin
            S := S + IntToStr(Duration div 1000) + ' seconds';
            if Client.ByteCount > 32767 then
                BytesSec := 1000 * (Client.ByteCount div Duration)
            else
                BytesSec := (1000 * Client.ByteCount) div Duration;
            S := S + ' (' + IntToKbyte(BytesSec) + 'bytes/sec)';
        end;
        TriggerDisplay (Client, S);
    end;

    if Client.AbortingTransfer and (Client.TransferError = '') then
        Exit;

    if Client.AbortingTransfer then
        SendAnswer(Client, Format(msgRetrFailed, [Client.TransferError]))
    else if AError <> 0 then
        SendAnswer(Client, Format(msgRetrFailed, ['Error - ' + GetWinsockErr(AError)]))
    else
        SendAnswer(Client, msgRetrOk );

    TriggerRetrSessionClosed(Client, Data, AError);
end;



procedure TMtpServer.SendNextDataChunk(Client : TMtpCtrlSocket; Data: TWSocket);
var
    Count : LongInt;
begin
    try
        Count := 0;
        TriggerEnterSecurityContext(Client);
        try

          begin
                if Assigned(Client.DataStream) then
                    Count := Client.DataStream.Read(Client.RcvBuf^, Client.RcvSize);
            end;
        finally
            TriggerLeaveSecurityContext(Client);
        end;
        Client.LastTick := IcsGetTickCountX;

        if Count > 0 then begin
            Client.ByteCount := Client.ByteCount + Count;
            Client.TotGetBytes := Client.TotGetBytes + Count;
            Data.Send(Client.RcvBuf, Count);
        end
        else begin
            if not Client.DataSent then begin
                Client.DataSent := TRUE;
                PostMessage(Handle, FMsg_WM_FTPSRV_CLOSE_DATA,
                            WPARAM(Client.ID), LPARAM(Client));
            end;
        end;
    except
        on E:Exception do begin
            Client.TransferError    := E.Message;
            Client.AbortingTransfer := TRUE;
            PostMessage(Handle, FMsg_WM_FTPSRV_ABORT_TRANSFER,
                        WPARAM(Client.ID), LPARAM(Client));
        end;
    end;
end;



procedure TMtpServer.ClientRetrDataSent(Sender : TObject; AError : Word);
var
    Client : TMtpCtrlSocket;
    Data   : TWSocket;
begin
    Data   := TWSocket(Sender);
    Client := TMtpCtrlSocket(Data.Owner);

    if Client.AbortingTransfer then
        Exit;

    try
        TriggerRetrDataSent(Client, Data, AError);
        if AError <> 0 then
            raise FtpServerException.Create('Send Error - ' + GetWinsockErr(AError));
        SendNextDataChunk(Client, Data);
    except
        on E:Exception do begin
            Client.TransferError    := E.Message;
            Client.AbortingTransfer := TRUE;
            SendAnswer(Client, Format(msgRetrAborted, [Client.TransferError]));
            PostMessage(Handle, FMsg_WM_FTPSRV_ABORT_TRANSFER,
                        WPARAM(Client.ID), LPARAM(Client));
        end;
    end;
end;





procedure TMtpServer.CommandABOR(
    Client      : TMtpCtrlSocket;
    var Keyword : TFtpString;
    var Params  : TFtpString;
    var Answer  : TFtpString);
begin
    if Client.DataSocket.State = wsConnected then begin
        Client.TransferError    := 'ABORT requested by client';
        Client.AbortingTransfer := TRUE;
        Client.DataSocket.Close;
    end;
    Answer := msgAborOk;
end;





{$IFDEF NOFORMS}
function FtpSrvWindowProc(
    ahWnd   : HWND;
    auMsg   : Integer;
    awParam : WPARAM;
    alParam : LPARAM): Integer; stdcall;
var
    Obj    : TObject;
    MsgRec : TMessage;
begin
    Obj := TObject(GetWindowLong(ahWnd, 0));

    if not (Obj is Tftpserver) then
        Result := DefWindowProc(ahWnd, auMsg, awParam, alParam)
    else begin
        MsgRec.Msg    := auMsg;
        MsgRec.wParam := awParam;
        MsgRec.lParam := alParam;

        TFtpServer(Obj).WndProc(MsgRec);
        Result := MsgRec.Result;
    end;
end;
{$ENDIF}



function TMtpServer.GetMultiListenIndex: Integer;
begin
  if Assigned(FSocketServer) then
        Result := FSocketServer.MultiListenIndex
    else
        Result := -1;
end;



function TMtpServer.GetMultiListenSockets: TWSocketMultiListenCollection;
begin
    if Assigned(FSocketServer) then
        Result := FSocketServer.MultiListenSockets
    else
        Result := nil;
end;



procedure TMtpServer.SetMultiListenSockets(  const Value: TWSocketMultiListenCollection);
begin
    if Assigned(FSocketServer) then
        FSocketServer.MultiListenSockets := Value;
end;



procedure TMtpServer.SetOnBgException(const Value: TIcsBgExceptionEvent);
begin
    if Assigned(FSocketServer) then
        FSocketServer.OnBgException := Value;
    inherited;
end;



procedure TMtpServer.ClientProcessingThreadTerminate(Sender: TObject);
var
    Answer    : TFtpString;
    AThread   : TClientProcessingThread;
    Params    : TFtpString;
    Data      : TWSocket;
begin
    AThread := TClientProcessingThread(Sender);
    if IsClient(AThread.Client) and
       (AThread.Client.ID = AThread.ClientID) then begin
        AThread.Client.ProcessingThread := nil;
        if AThread.Client.State <> wsConnected then
            Exit;

        AThread.Client.LastTick := IcsGetTickCountX;
            Answer := Format('500 Executing command %s failed', [AThread.Keyword]);
        AThread.Client.AnswerDelayed := FALSE;
        SendAnswer(AThread.Client, Answer);
    end;
end;


procedure TMtpServer.EventTimerOnTimer (Sender : TObject);
var
    Client   : TMtpCtrlSocket;
    I        : integer;
    Timeout  : integer;
    Duration : integer;
    Abort    : boolean ;
    CurTicks : LongWord;
begin
    FEventTimer.Enabled := false;
    try
        if FSocketServer.ClientCount = 0 then exit;
        CurTicks := IcsGetTickCountX;
        for I := 0 to Pred (FSocketServer.ClientCount) do begin
            Client := FSocketServer.Client[I] as TMtpCtrlSocket;
            if Client.FSessionClosedFlag then Continue;



            Timeout := 0;
            case Client.FtpState of
                ftpcWaitingUserCode: Timeout := FTimeoutSecsLogin;
                ftpcReady, ftpcWaitingAnswer: Timeout := FTimeoutSecsIdle;
            end;
            if Client.DataSocket.State = wsConnected then begin
                if FTimeoutSecsXfer < FTimeoutSecsIdle then Timeout := FTimeoutSecsXfer;
            end;
            if Timeout > 0 then begin
                Duration :=  IcsDiffTicks(Client.LastTick, CurTicks) div TicksPerSecond;
                if Duration >= Timeout then begin
                    Abort := true;
                    TriggerTimeout(Client, Duration, Abort);
                    if NOT Abort then
                        Client.LastTick := IcsGetTickCountX
                    else begin

                        if Client.DataSocket.State = wsConnected then begin
                            Client.TransferError    := 'ABORT on Timeout';
                            Client.AbortingTransfer := TRUE;
                            Client.DataSocket.Close;
                        end
                        else begin
                            SendAnswer(Client, WideFormat(msgTimeout, [Duration]));

                            Client.Close;
                        end;
                    end;
                end;
            end;
        end;
    finally
        FEventTimer.Enabled := true;
    end ;
end;



constructor TMtpCtrlSocket.Create(AOwner: TComponent);
begin
    inherited Create(AOwner);
    FDataSocket      := TWSocket.Create(Self);
    FDataSocket.Name := 'DataWSocket';
    FFtpState        := ftpcInvalid;
    SetRcvSize(DefaultRcvSize);
    LastTick         := IcsGetTickCountX;
    SessStartTick    := IcsGetTickCountX;
    ReqStartTick     := 0;
    ReqDurMilliSecs  := 0;
    TotGetBytes      := 0;
    TotPutBytes      := 0;
    FailedAttempts   := 0;
    DelayAnswerTick  := TriggerDisabled;
    FSndBufSize      := DefaultRcvSize;
    FRcvBufSize      := DefaultRcvSize;
end;



destructor TMtpCtrlSocket.Destroy;
begin
    FRcvCnt := 0;
    SetRcvSize(0);
    if Assigned(ProcessingThread) then begin
        ProcessingThread.OnTerminate := nil;
        FreeAndNil(ProcessingThread);
    end;
    if Assigned(FDataSocket) then begin
        FDataSocket.Destroy;
        FDataSocket := nil;
    end;
    inherited Destroy;
end;



procedure TMtpCtrlSocket.SetRcvSize(newValue : Integer);
begin
    if FRcvCnt <> 0 then
        raise EFtpCtrlSocketException.Create('Data in buffer, can''t change size');

    if FRcvSize < 0 then
        FRcvSize := 0;

    if FRcvSize = newValue then
        Exit;

    if FRcvBuf <> nil then begin
        FreeMem(FRcvBuf, FRcvSize);
        FRcvBuf := nil;
    end;

    FRcvSize := newValue;

    if newValue > 0 then
        GetMem(FRcvBuf, FRcvSize);
end;



procedure TMtpCtrlSocket.SetRcvBufSize(newValue : Integer);
begin
    if newValue < 1024 then
        FRcvBufSize := 1024
    else
        FRcvBufSize := newValue;
end;



procedure TMtpCtrlSocket.SetSndBufSize(newValue : Integer);
begin
    if newValue < 1024 then
        FSndBufSize := 1024
    else
        FSndBufSize := newValue;
end;


procedure TMtpCtrlSocket.SetStreamSize(newValue : Integer);
begin
    if newValue < 1024 then
        FStreamSize := 1024
    else
        FStreamSize := newValue;
end;




procedure TMtpCtrlSocket.SetOnBgException(const Value: TIcsBgExceptionEvent);
begin
    if Assigned(FDataSocket) then
        FDataSocket.OnBgException := Value;
    inherited;
end;




procedure TMtpCtrlSocket.TriggerSessionClosed(Error: Word);
begin
    if Assigned(ProcessingThread) then
        ProcessingThread.Terminate;
    inherited TriggerSessionClosed(Error);
end;



procedure TMtpCtrlSocket.TriggerSessionConnected(Error : Word);
begin
    FPeerAddr := inherited GetPeerAddr;
    inherited TriggerSessionConnected(Error);
end;



procedure TMtpCtrlSocket.TriggerCommand(CmdBuf : PAnsiChar; CmdLen : Integer);
begin
    if Assigned(FOnCommand) then
        FOnCommand(Self as TMtpCtrlSocket, CmdBuf, CmdLen);
end;



function TMtpCtrlSocket.TriggerDataAvailable(Error : Word) : Boolean;
var
    Len  : Integer;
    I    : Integer;
begin
    Result := TRUE;

    Len := Receive(@FRcvBuf[FRcvCnt], FRcvSize - FRcvCnt - 1);
    if Len <= 0 then
        Exit;

    FRcvCnt := FRcvCnt + Len;
    FRcvBuf[FRcvCnt] := #0;
    LastTick := IcsGetTickCountX;
    TotPutBytes := TotPutBytes + Len;

    while TRUE do begin
        I := 0;
        while (I < FRcvCnt) and (FRcvBuf[I] <> #10) do
            Inc(I);
        if I >= FRcvCnt then begin
            if FRcvCnt >= (FRcvSize - 1) then begin
                StrPCopy(FRcvBuf, 'OVER' + #13#10);
                FRcvCnt := StrLen(FRcvBuf);
                I       := FRcvCnt - 1;
            end
            else
                Exit;
        end;
        FRcvBuf[I]   := #0;
        FLastCommand := Now;
        Inc(FCommandCount);
        if (I > 1) and (FRcvBuf[I - 1] = #13) then begin
            FRcvBuf[I - 1] := #0;
            TriggerCommand(FRcvBuf, I - 1);
            FRcvBuf[I - 1] := #13;
        end
        else
            TriggerCommand(FRcvBuf, I);

        FRcvBuf[I] := #10;
        if I >= (FRcvCnt - 1) then begin
            FRcvCnt    := 0;
            FRcvBuf[0] := #0;
            break;
        end;
        Move(FRcvBuf[I + 1], FRcvBuf^, FRcvCnt - I);
        FRcvCnt := FRcvCnt - I - 1;
    end;
end;



procedure TMtpCtrlSocket.SendAnswer(const Answer : RawByteString);
begin
    SendStr(Answer + #13#10);
    LastTick := IcsGetTickCountX;
    TotGetBytes := TotGetBytes + Length (Answer) + 2;
end;



procedure TMtpCtrlSocket.DataStreamWriteString(const Str: UnicodeString; DstCodePage: LongWord);
begin
    StreamWriteString(DataStream, Str, DstCodePage);
end;



procedure TMtpCtrlSocket.DataStreamWriteString(const Str: UnicodeString);
begin
    StreamWriteString(DataStream, Str, CP_ACP);
end;



procedure TMtpCtrlSocket.DataStreamWriteString(const Str: AnsiString);
begin
    DataStream.Write(Pointer(Str)^, Length(Str));
end;



procedure TMtpCtrlSocket.DataStreamWriteString( const Str: AnsiString; DstCodePage: LongWord);
var
    S : AnsiString;
begin
    if DstCodePage = CP_ACP then
        DataStream.Write(Pointer(Str)^, Length(Str))
    else begin
        S := ConvertCodePage(Str, CP_ACP, DstCodePage);
        DataStream.Write(Pointer(S)^, Length(S));
    end;
end;



procedure TMtpCtrlSocket.DataStreamReadString(var Str: AnsiString;
  Len: TFtpBigInt);
var
    ReadLen: Cardinal;
begin
    SetLength(Str, Len);
    ReadLen := DataStream.Read(Pointer(Str)^, Len);
    if ReadLen < Len then
        SetLength(Str, ReadLen);
end;


procedure TMtpCtrlSocket.DataStreamReadString( var Str: AnsiString; Len: TFtpBigInt; SrcCodePage: LongWord);
var
    BytesRead : Cardinal;
    Buf       : PAnsiChar;
    BufW      : PWideChar;
    L1, L2    : Integer;
begin
    SetLength(Str, 0);
    if Len < 0 then Exit;
    if (SrcCodePage = CP_ACP) then
    begin
        SetLength(Str, Len);
        BytesRead := DataStream.Read(Pointer(Str)^, Len);
        if BytesRead < Len then
            SetLength(Str, BytesRead);
    end
    else begin
        GetMem(Buf, Len);
        try
            BytesRead := DataStream.Read(Buf^, Len);
            L1 :=  IcsMbToWc{MultiByteToWideChar}(SrcCodePage, 0, Buf, BytesRead, nil, 0);
            GetMem(BufW, L1 * SizeOf(WideChar));
            try
                IcsMbToWc{MultiByteToWideChar}(SrcCodePage, 0, Buf, BytesRead, BufW, L1);
                L2 := IcsWcToMb{WideCharToMultibyte}(CP_ACP, 0, BufW, L1, nil, 0, nil, nil);
                if L2 <> Len then
                    ReallocMem(Buf, L2);
                L1 := IcsWcToMb{WideCharToMultibyte}(CP_ACP, 0, BufW, L1, Buf, L2, nil, nil);
                SetLength(Str, L1);
                Move(Buf[0], Pointer(Str)^, L1);
            finally
                ReallocMem(BufW, 0);
            end;
        finally
            ReallocMem(Buf, 0);
        end;
    end;
end;



procedure TMtpCtrlSocket.DataStreamReadString(var Str: UnicodeString; Len: TFtpBigInt );
var
    SBuf : array [0..2047] of AnsiChar;
    HBuf : PAnsiChar;
    eLen : Cardinal;
begin
        SetLength(Str, Len);
        eLen := DataStream.Read(Pointer(Str)^, Len * SizeOf(WideChar));
        if (eLen div SizeOf(WideChar)) < Len then
            SetLength(Str, (eLen div SizeOf(WideChar)));
end;

procedure TMtpCtrlSocket.SetAbortingTransfer(newValue : Boolean);
begin
    FAbortingTransfer := newValue;
end;



procedure UpdateThreadOnProgress( Obj: TObject; Count: Int64; var Cancel: Boolean);
begin
    if (Obj is TClientProcessingThread) then
    begin
        Cancel := (Obj as TClientProcessingThread).Terminated;
        (Obj as TClientProcessingThread).Client.LastTick := IcsGetTickCountX;
    end
    else if (Obj is TMtpCtrlSocket) then
    begin
        Cancel := (Obj as TMtpCtrlSocket).AbortingTransfer;
        (Obj as TMtpCtrlSocket).LastTick := IcsGetTickCountX;
    end
end;



procedure TClientProcessingThread.TriggerEnterSecurityContext;
var
    f_EnterSecurityContext : TFtpSecurityContextEvent;
begin
    f_EnterSecurityContext := Client.FtpServer.FOnEnterSecurityContext;
    if Assigned(f_EnterSecurityContext) then
        f_EnterSecurityContext(Client.FtpServer, Client);
end;




procedure TClientProcessingThread.TriggerLeaveSecurityContext;
var
    f_LeaveSecurityContext : TFtpSecurityContextEvent;
begin
    f_LeaveSecurityContext := Client.FtpServer.FOnLeaveSecurityContext;
    if Assigned(f_LeaveSecurityContext) then
        f_LeaveSecurityContext(Client.FtpServer, Client);

end;


procedure TClientProcessingThread.Execute;
var
    NewSize: Int64;
    TotalFiles: integer;
begin
    ClientID := Client.ID;
    try
        with Client.ProcessingThread do begin
            StartTick := IcsGetTickCountX;
            OutData := '';
        end;
    except
        OutData := '';
    end;
end;


end.


███╗   ███╗████████╗██████╗      ██████╗██╗     ██╗███████╗███╗   ██╗████████╗    
████╗ ████║╚══██╔══╝██╔══██╗    ██╔════╝██║     ██║██╔════╝████╗  ██║╚══██╔══╝    
██╔████╔██║   ██║   ██████╔╝    ██║     ██║     ██║█████╗  ██╔██╗ ██║   ██║       
██║╚██╔╝██║   ██║   ██╔═══╝     ██║     ██║     ██║██╔══╝  ██║╚██╗██║   ██║       
██║ ╚═╝ ██║   ██║   ██║         ╚██████╗███████╗██║███████╗██║ ╚████║   ██║       
╚═╝     ╚═╝   ╚═╝   ╚═╝          ╚═════╝╚══════╝╚═╝╚══════╝╚═╝  ╚═══╝   ╚═╝  

unit iramtpCli;

{$B-}             { Enable partial boolean evaluation   }
{$T-}             { Untyped pointers                    }
{$X+}             { Enable extended syntax              }

interface

uses
    Winapi.Windows,
    Winapi.Messages,
    OverbyteIcsWinSock,
    System.SysUtils,
    System.Classes,
{$IFNDEF NOFORMS}
  {$IFDEF FMX}
    FMX.Forms,
  {$ELSE}
    Vcl.Forms,
  {$ENDIF}
{$ENDIF}
{$IFDEF FMX}
    Ics.Fmx.OverbyteIcsWndControl,
    Ics.Fmx.OverbyteIcsWSocket,
{$ELSE}
    OverbyteIcsWndControl,
    OverbyteIcsWSocket,
{$ENDIF}
    Strutils,

    OverbyteIcsUtils,
    OverByteIcsFtpSrvT;


const

{$IFDEF FTPCLI_BUFFER_OLD}
  FTP_SND_BUF_SIZE = 1460;
  FTP_RCV_BUF_SIZE = 4096;
{$ELSE}
  {$IFDEF FTPCLI_BUFFER_SMALL}
    FTP_SND_BUF_SIZE = 8192;
    FTP_RCV_BUF_SIZE = 8192;
  {$ELSE}
    {$IFDEF FTPCLI_BUFFER_MEDIUM}
      FTP_SND_BUF_SIZE = 16384;
      FTP_RCV_BUF_SIZE = 16384;
    {$ELSE}
      FTP_SND_BUF_SIZE = 32768;
      FTP_RCV_BUF_SIZE = 32768;
    {$ENDIF}
  {$ENDIF}
{$ENDIF}

type
  TFtpOption      = (ftpAcceptLF, ftpWaitUsingSleep);
  TFtpOptions     = set of TFtpOption;
  TFtpState       = (ftpNotConnected,  ftpReady, ftpInternalReady, ftpDnsLookup, ftpConnected, ftpAbort,
                     ftpInternalAbort, ftpWaitingResponse );
  TFtpRequest     = (ftpNone,  ftpOpenAsync, ftpConnectAsync,  ftpReceiveAsync, ftpPortAsync, ftpGetAsync,
                     ftpQuitAsync,  ftpPutAsync, ftpRqAbort,   ftpTransmitAsync   );
  TFtpFct         = (ftpFctNone, ftpFctOpen, ftpFctGet, ftpFctQuit, ftpFctPut, ftpFctPort);
  TFtpFctSet      = set of TFtpFct;

  TFtpDisplay     = procedure(Sender    : TObject; var Msg   : String) of object;
  TFtpProgress64  = procedure(Sender    : TObject; Count     : Int64; var Abort : Boolean) of object;
  TFtpCommand     = procedure(Sender    : TObject; var Cmd   : String) of object;
  TFtpRequestDone = procedure(Sender    : TObject; RqType    : TFtpRequest; ErrCode   : Word) of object;
  TFtpReadyToTransmit = procedure(Sender      : TObject; var bCancel : Boolean) of object;
  TFtpNextProc    = procedure of object;

  FtpException = class(Exception);

  TCustomMtpCli = class(TIcsWndControl)
  protected
    FHostName           : String;
    FPort               : String;
    FSocketFamily       : TSocketFamily;
    FDataPortRangeStart : LongWord;
    FDataPortRangeEnd   : LongWord;
    FLastDataPort       : LongWord;
    FExternalIPv4       : String;
    FDSocketSndBufSize  : Integer;
    FDSocketRcvBufSize  : Integer;
    FLocalAddr          : String;
    FLocalAddr6         : String;
    FDnsResult          : String;
    FType               : Char;
    FProxyServer        : String;
    FProxyPort          : String;
    FAppendFlag         : Boolean;
    FDisplayFileFlag    : Boolean;
    FControlSocket      : TWSocket;
    FDataSocket         : TWSocket;
    FStartTime          : LongInt;
    FStopTime           : LongInt;
    FStreamSize         : LongInt;
    FMemoryPtr          : Pointer ;
    FMemoryName         : String;
    FState              : TFtpState;
    FStatusCode         : LongInt;
    FRequestResult      : Integer;
    FFctSet             : TFtpFctSet;
    FFctPrv             : TFtpFct;
    FHighLevelResult    : Integer;
    FHighLevelFlag      : Boolean;
    FRestartFlag        : Boolean;
    FMsg_WM_FTP_REQUEST_DONE : UINT;
    FMsg_WM_FTP_SENDDATA     : UINT;
    FMsg_WM_FTP_CLOSEDOWN    : UINT;
    FOptions            : TFtpOptions;
    FOnDisplay          : TFtpDisplay;
    FOnDisplayFile      : TFtpDisplay;
    FOnError            : TFtpDisplay;
    FOnCommand          : TFtpCommand;
    FOnResponse         : TNotifyEvent;
    FOnSessionConnected : TSessionConnected;
    FOnSessionClosed    : TSessionClosed;
    FOnStateChange      : TNotifyEvent;
    FOnRequestDone      : TFtpRequestDone;
    FOnReadyToTransmit  : TFtpReadyToTransmit;
    FLocalStream        : TMemoryStream;
    FRequestType        : TFtpRequest;
    FRequestDoneFlag    : Boolean;
    FReceiveBuffer      : array [0..FTP_RCV_BUF_SIZE - 1] of AnsiChar;
    FReceiveLen         : Integer;
    FLastResponse       : String;
    FLastResponseSave   : String;
    FStatusCodeSave     : LongInt;
    FErrorMessage       : String;
    FError              : Word;
    FGetCommand         : String;
    FConnected          : Boolean;
    FSendBuffer         : array [0..FTP_SND_BUF_SIZE - 1] of AnsiChar;
    FOnProgress64       : TFtpProgress64;
    FByteCount          : TFtpBigInt;
    FSizeResult         : TFtpBigInt;
    FNext               : TFtpNextProc;
    FWhenConnected      : TFtpNextProc;
    FDoneAsync          : TFtpNextProc;
    FOkResponses        : array [0..15] of Integer;
    FNextRequest        : TFtpNextProc;
    FServerSaidDone     : Boolean;
    FFileReceived       : Boolean;
    FFileSent           : Boolean;
    FEofFlag            : Boolean;
    FStorAnswerRcvd     : Boolean;
    FPutSessionOpened   : Boolean;
    FDataSocketSentFlag : Boolean;
    FLastMultiResponse  : String;
    FCloseEndTick       : LongWord;
    FCloseEndSecs       : LongWord;
    FKeepAliveSecs      : integer;
    FClientIdStr        : String;
    FPosStart           : TFtpBigInt;
    FPosEnd             : TFtpBigInt;
    FDurationMsecs      : Integer;
    FSocksPassword      : String;
    FSocksPort          : String;
    FSocksServer        : String;
    FSocksUserCode      : String;

    procedure SetKeepAliveSecs (secs: integer);
    procedure   AbortComponent; override;
    procedure   SetMultiThreaded(const Value : Boolean); override;
    procedure   SetOnBgException(const Value: TIcsBgExceptionEvent); override;
    procedure   SetTerminated(const Value: Boolean); override;
    procedure   SetOnMessagePump(const Value: TNotifyEvent); override;
    procedure   SetErrorMessage;
    procedure   LocalStreamWrite(const Buffer; Count : Integer); virtual;
    procedure   LocalStreamWriteString(Str: PAnsiChar; Count: Integer);  overload;
    procedure   LocalStreamWriteString(Str: PWideChar; Count: Integer; ACodePage: LongWord); overload;
    procedure   LocalStreamWriteString(Str: PWideChar; Count: Integer); overload;

    procedure   DataSocketGetDataAvailable(Sender: TObject; ErrCode : word);     //retr
    procedure   DataSocketGetSessionConnected(Sender: TObject; ErrCode : word);  //retr
    procedure   DataSocketGetSessionAvailable(Sender: TObject; ErrCode : word);  //retr
    procedure   DataSocketGetSessionClosed(Sender: TObject; ErrCode : word);     //retr

    procedure   DataSocketPutSessionConnected(Sender: TObject; ErrCode : word);  // stor
    procedure   DataSocketPutDataAvailable(Sender: TObject; ErrCode : word);     // stor
    procedure   DataSocketPutDataSent(Sender: TObject; ErrCode : word);          // stor
    procedure   DataSocketPutSessionAvailable(Sender: TObject; ErrCode : word);  // stor
    procedure   DataSocketPutSessionClosed(Sender: TObject; ErrCode : word);     // stor

    procedure   SendCommand(Cmd : String); virtual;
    procedure   TriggerDisplay(Msg : String); virtual;
    procedure   TriggerReadyToTransmit(var bCancel : Boolean); virtual;
    procedure   TriggerDisplayFile(Msg : String); virtual;
    procedure   TriggerError(Msg: String); virtual;
    procedure   TriggerResponse; virtual;
    procedure   DisplayLastResponse;
    procedure   Notification(AComponent: TComponent; Operation: TOperation); override;
    function    Progress : Boolean; virtual;
    procedure   ControlSocketDnsLookupDone(Sender: TObject; ErrCode: Word);
    procedure   ControlSocketSessionConnected(Sender: TObject; ErrCode: Word); virtual;
    procedure   ControlSocketDataAvailable(Sender: TObject; ErrCode: Word);
    procedure   ControlSocketSessionClosed(Sender: TObject; ErrCode: Word);
    procedure   DataSocketPutAppendInit(const TargetPort, TargetIP : String); virtual;
    procedure   DataSocketGetInit(const TargetPort, TargetIP : String); virtual;
    procedure   TriggerRequestDone(ErrCode: Word);
    procedure   TriggerStateChange;
    procedure   StateChange(NewState : TFtpState);
    procedure   PortAsync; virtual;
    procedure   DoneQuitAsync;
    procedure   ExecAsync(RqType: TFtpRequest; Cmd: String; OkResponses : array of Word; DoneAsync   : TFtpNextProc);
    procedure   NextExecAsync;
    procedure   DoGetAsync(RqType : TFtpRequest);
    procedure   Next1GetAsync;
    procedure   Next2GetAsync;
    procedure   Next3GetAsync;
    procedure   Next1PutAsync;
    procedure   Next2PutAsync;
    procedure   Next3PutAsync;
    procedure   DoPutAppendAsync;
    procedure   DoHighLevelAsync;
    procedure   HighLevelAsync(RqType : TFtpRequest; Fcts : TFtpFctSet);
    procedure   HandleError(const Msg : String);
    function    CheckReady : Boolean;
    procedure   TransfertStats; virtual;
    procedure   SetBinary(Value: Boolean);
    function    GetBinary: Boolean;
    function    GetConnected: Boolean;
    procedure   AllocateMsgHandlers; override;
    procedure   FreeMsgHandlers; override;
    function    MsgHandlersCount: Integer; override;
    procedure   WndProc(var MsgRec: TMessage); override;
    procedure   WMFtpRequestDone(var msg: TMessage); virtual;
    procedure   WMFtpSendData(var msg: TMessage); virtual;
    procedure   WMFtpCloseDown(var msg: TMessage); virtual;
    procedure   DestroyLocalStream;
    procedure   SetLocalStream (Stream:TmemoryStream);
    procedure   SetDataPortRangeStart (NewValue: LongWord);
    procedure   SetDataPortRangeEnd (NewValue: LongWord);
    function    OpenMemoryStream (Buffersize: integer): TmemoryStream;
      {$IFDEF USE_INILE} inline; {$ENDIF}
    procedure   CreateLocalFileStream;
    function    CreateSocket: TWSocket; virtual;
    property    SocketFamily: TSocketFamily read FSocketFamily write FSocketFamily;
    procedure   HandleHttpTunnelError(Sender: TObject; ErrCode: Word;
        TunnelServerAuthTypes: THttpTunnelServerAuthTypes; const Msg: String);
    procedure   HandleSocksError(Sender: TObject; ErrCode: Integer; Msg: String);
    procedure   SetDSocketSndBufSize(const Value: Integer);
    procedure   SetDSocketRcvBufSize(const Value: Integer);
  public
    constructor Create(AOwner: TComponent); override;
    destructor  Destroy; override;

    procedure   OpenAsync;       virtual;
    procedure   ConnectAsync;    virtual;
    procedure   QuitAsync;       virtual;
    procedure   AbortAsync;      virtual;
    procedure   GetAsync;        virtual;
    procedure   ExecGetAsync;    virtual;
    procedure   ReceiveAsync;    virtual;
    procedure   PutAsync;        virtual;
    procedure   ExecPutAsync;    virtual;
    procedure   TransmitAsync;   virtual;

    property    LastResponse      : String               read  FLastResponse;
    property    LastMultiResponse : String               read  FLastMultiResponse;
    property    ErrorMessage      : String               read  FErrorMessage;
    property    DnsResult         : String               read  FDnsResult;
    property    ControlSocket     : TWSocket             read  FControlSocket;
    property    DataSocket        : TWSocket             read  FDataSocket;
    property    Connected         : Boolean              read  GetConnected;
    property    StatusCode        : LongInt              read  FStatusCode;
    property    State             : TFtpState            read  FState;
    property    RequestType       : TFtpRequest          read  FRequestType;
    property    KeepAliveSecs     : Integer              read  FKeepAliveSecs write SetKeepAliveSecs;
    property    LocalStream       : TMemoryStream        read  FLocalStream write SetLocalStream;
    property    OnProgress64      : TFtpProgress64       read  FOnProgress64 write FOnProgress64;
    property    ByteCount         : TFtpBigInt           read  FByteCount;
    property    SizeResult        : TFtpBigInt           read  FSizeResult;
    property    ClientIdStr       : String               read  FClientIdStr write FClientIdStr;
    property    PosStart          : TFtpBigInt           read  FPosStart write FPosStart;
    property    PosEnd            : TFtpBigInt           read  FPosEnd write FPosEnd;
    property    DurationMsecs     : Integer              read  FDurationMsecs;
    property    StartTick         : Integer              read  FStartTime;

    property    StreamSize        : LongInt              read  FStreamSize write FStreamSize;
    property    MemoryPtr         : Pointer              read  FMemoryPtr write FMemoryPtr;
    property    MemoryName        : string               read  FMemoryName write FMemoryName;

    property HostName             : String               read  FHostName write FHostName;
    property Port                 : String               read  FPort write FPort;
    property DataPortRangeStart   : LongWord             read  FDataPortRangeStart write SetDataPortRangeStart;
    property DataPortRangeEnd     : LongWord             read  FDataPortRangeEnd write SetDataPortRangeEnd;
    property ExternalIPv4         : String               read  FExternalIPv4 write FExternalIPv4;
    property LocalAddr            : String               read  FLocalAddr write FLocalAddr;
    property LocalAddr6           : String               read  FLocalAddr6 write FLocalAddr6;
    property DisplayFileFlag      : Boolean              read  FDisplayFileFlag write FDisplayFileFlag;
    property SocksPassword        : String               read  FSocksPassword write FSocksPassword;
    property SocksPort            : String               read  FSocksPort write FSocksPort;
    property SocksServer          : String               read  FSocksServer write FSocksServer;
    property SocksUserCode        : String               read  FSocksUserCode write FSocksUserCode;
    property CloseEndSecs         : LongWord             read  FCloseEndSecs write FCloseEndSecs;
    property DataSocketSndBufSize : Integer              read  FDSocketSndBufSize write SetDSocketSndBufSize default 8192;
    property DataSocketRcvBufSize : Integer              read  FDSocketRcvBufSize write SetDSocketRcvBufSize default 8192;
    property OnDisplay            : TFtpDisplay          read  FOnDisplay write FOnDisplay;
    property OnDisplayFile        : TFtpDisplay          read  FOnDisplayFile write FOnDisplayFile;
    property OnError              : TFTPDisplay          read  FOnError write FOnError;
    property OnCommand            : TFtpCommand          read  FOnCommand write FOnCommand;
    property OnResponse           : TNotifyEvent         read  FOnResponse write FOnResponse;
    property OnSessionConnected   : TSessionConnected    read  FOnSessionConnected write FOnSessionConnected;
    property OnSessionClosed      : TSessionClosed       read  FOnSessionClosed write FOnSessionClosed;
    property OnRequestDone        : TFtpRequestDone      read  FOnRequestDone write FOnRequestDone;
    property OnStateChange        : TNotifyEvent         read  FOnStateChange write FOnStateChange;
    property OnReadyToTransmit    : TFtpReadyToTransmit  read  FOnReadyToTransmit write FOnReadyToTransmit;
    property OnBgException;
  end;

  TMtpClient = class(TCustomMtpCli)
  protected
    FTimeout       : Integer;
    FTimeStop      : LongInt;
    function    Progress : Boolean; override;
    function    Synchronize(Proc : TFtpNextProc) : Boolean; virtual;
    function    WaitUntilReady : Boolean; virtual;
  public
    property    MemoryPtr ;
    constructor Create(AOwner: TComponent); override;
    function    Open       : Boolean;
    function    Connect    : Boolean;
    function    Get        : Boolean;
    function    Put        : Boolean;
    function    MtpPort    : Boolean;
    function    Quit       : Boolean;
    function    Abort      : Boolean;
    function    Receive    : Boolean;
    function    Transmit   : Boolean;
  published
    property    MemoryName ;
    property    StreamSize ;
    property Timeout       : Integer read FTimeout       write FTimeout;
    property MultiThreaded;
    property HostName;
    property Port;
    property DataPortRangeStart;
    property DataPortRangeEnd;
    property ExternalIPv4;
    property LocalAddr;
    property LocalAddr6;
    property DisplayFileFlag;
    property ErrorMessage;
    property SocksPassword;
    property SocksPort;
    property SocksServer;
    property SocksUserCode;
    property DataSocketSndBufSize;
    property OnDisplay;
    property OnDisplayFile;
    property OnCommand;
    property OnError;
    property OnResponse;
    property OnProgress64;
    property OnSessionConnected;
    property OnSessionClosed;
    property OnRequestDone;
    property OnStateChange;
    property OnReadyToTransmit;
    property OnBgException;
    property SocketFamily;
  end;

{$B-}                                 { Enable partial boolean evaluation   }
{$T-}                                 { Untyped pointers                    }
{$X+}                                 { Enable extended syntax              }
{$H+}                                 { Use long strings                    }
{$J+}                                 { Allow typed constant to be modified }


function LookupFTPReq (const RqType: TFtpRequest): String;
function LookupFtpState (const FtpState: TFtpState): String;

procedure register;
implementation
procedure register;
begin
RegisterComponents('ira Mtp', [TMtpClient]);
end;

{$B-}  { Do not evaluate boolean expressions more than necessary }


function LookupFTPReq (const RqType: TFtpRequest): String;
begin
   case RqType of
      ftpNone: result:='none';
      ftpOpenAsync: result:='OpenAsync';
      ftpConnectAsync: result:='ConnectAsync';
      ftpReceiveAsync: result:='ReceiveAsync';
      ftpPortAsync: result:='PortAsync';
      ftpGetAsync: result:='GetAsync';
      ftpQuitAsync: result:='QuitAsync';
      ftpPutAsync: result:='PutAsync';
      ftpRqAbort: result:='RqAbort';
      ftpTransmitAsync: result:='TransmitAsync';
   else
      result:='unknown';
   end;
end;


function LookupFtpState (const FtpState: TFtpState): String;
begin
   case FtpState of
      ftpNotConnected: result := 'Not Connected';
      ftpReady: result := 'Ready';
      ftpInternalReady: result := 'Internal Ready';
      ftpDnsLookup: result := 'DNS Lookup';
      ftpConnected: result := 'Connected';
      ftpAbort: result := 'Abort';
      ftpInternalAbort: result := 'Internal Abort';
      ftpWaitingResponse: result := 'Waiting Response';
   else
      result:='unknown';
   end;
end ;



function GetInteger(Data : PChar; var Number : LongInt) : PChar;
var
    bSign : Boolean;
begin
    Number := 0;
    Result := StpBlk(Data);

    if Result = nil then
        Exit;

    if (Result^ = '-') or (Result^ = '+') then begin
        bSign := (Result^ = '-');
        Inc(Result);
    end
    else
        bSign  := FALSE;

    while (Result^ <> #0) and IsDigit(Result^) do begin
        Number := Number * 10 + ord(Result^) - ord('0');
        Inc(Result);
    end;

    if bSign then
        Number := -Number;
end;



function GetInt64(Data : PChar; var Number : Int64) : PChar;
var
    bSign : Boolean;
begin
    Number := 0;
    Result := StpBlk(Data);

    if Result = nil then
        Exit;

    if (Result^ = '-') or (Result^ = '+') then begin
        bSign := (Result^ = '-');
        Inc(Result);
    end
    else
        bSign  := FALSE;

    while (Result^ <> #0) and IsDigit(Result^) do begin
        Number := Number * 10 + ord(Result^) - ord('0');
        Inc(Result);
    end;

    if bSign then
        Number := -Number;
end;



function GetQuotedString(Data : PChar; var Dst : String) : PChar;
begin
    Dst := '';
    Result := StpBlk(Data);

    if (Result = nil) then
        Exit;

    if Result^ <> '"' then
        Exit;
    Inc(Result);

    while Result^ <> #0 do begin
        if Result^ <> '"' then
            Dst := Dst + Result^
        else begin
            Inc(Result);
            if Result^ <> '"' then
                Break;
            Dst := Dst + Result^;
        end;
        Inc(Result);
    end;
end;


function GetNextString(Data : PChar; var Dst : String) : PChar;
begin
    Dst := '';
    Result := StpBlk(Data);

    if Result = nil then
        Exit;

    while (Result^ <> #0) and (Result^ = #32) do
        Inc(Result);  { skip leading spaces }

    while (Result^ <> #0) and (Result^ <> #32) do begin
        Dst := Dst + Result^;
        Inc(Result);
    end;
end;




{* *                                                                     * *}
{* *                            TCustomMtpCli                            * *}
{* *                                                                     * *}

constructor TCustomMtpCli.Create(AOwner: TComponent);
{$IFDEF MSWINDOWS}
var
    Len : Cardinal;
{$ENDIF}
begin
    inherited Create(AOwner);
    AllocateHWnd;
    FOnDisplay          := nil;
    FOnDisplayFile      := nil;
    FPort               := 'ftp';
    FDataPortRangeStart := 0;
    FDataPortRangeEnd   := 0;
    FCloseEndSecs       := 5;
    FProxyPort          := 'ftp';
    FState              := ftpReady;
    FProxyServer        := '';
    FSocksServer        := '';
    FLocalAddr          := ICS_ANY_HOST_V4;
    FLocalAddr6         := ICS_ANY_HOST_V6;
    FKeepAliveSecs      := 0;
    FSocketFamily       := DefaultSocketFamily;
    FControlSocket      := CreateSocket;
    FControlSocket.ExceptAbortProc    := AbortComponent;
    FControlSocket.OnSessionConnected := ControlSocketSessionConnected;
    FControlSocket.OnDataAvailable    := ControlSocketDataAvailable;
    FControlSocket.OnSessionClosed    := ControlSocketSessionClosed;
    FControlSocket.OnDnsLookupDone    := ControlSocketDnsLookupDone;
    FDataSocket         := CreateSocket;
    FDataSocket.ExceptAbortProc       := AbortComponent;

    FDSocketSndBufSize := 8192;
    FDSocketRcvBufSize := 8192;
end;



destructor TCustomMtpCli.Destroy;
begin
    DestroyLocalStream;
    FDataSocket.Free;
    FControlSocket.Free;
    inherited Destroy;
end;



function TCustomMtpCli.MsgHandlersCount : Integer;
begin
    Result := 3 + inherited MsgHandlersCount;
end;



procedure TCustomMtpCli.AllocateMsgHandlers;
begin
    inherited AllocateMsgHandlers;
    FMsg_WM_FTP_REQUEST_DONE := FWndHandler.AllocateMsgHandler(Self);
    FMsg_WM_FTP_SENDDATA     := FWndHandler.AllocateMsgHandler(Self);
    FMsg_WM_FTP_CLOSEDOWN    := FWndHandler.AllocateMsgHandler(Self);
end;



procedure TCustomMtpCli.FreeMsgHandlers;
begin
    if Assigned(FWndHandler) then begin
        FWndHandler.UnregisterMessage(FMsg_WM_FTP_REQUEST_DONE);
        FWndHandler.UnregisterMessage(FMsg_WM_FTP_SENDDATA);
        FWndHandler.UnregisterMessage(FMsg_WM_FTP_CLOSEDOWN);
    end;
    inherited FreeMsgHandlers;
end;



procedure TCustomMtpCli.WndProc(var MsgRec: TMessage);
begin
    try
         with MsgRec do begin
             if Msg = FMsg_WM_FTP_REQUEST_DONE then
                 WMFtpRequestDone(MsgRec)
             else if Msg = FMsg_WM_FTP_SENDDATA then
                 WMFtpSendData(MsgRec)
             else if Msg = FMsg_WM_FTP_CLOSEDOWN then
                 WMFtpCloseDown(MsgRec)
             else
                 inherited WndProc(MsgRec);
        end;
    except
        on E: Exception do
            HandleBackGroundException(E);
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomMtpCli.AbortComponent;
begin
    try
        AbortAsync;
    except
    end;
    inherited;
end;



procedure TCustomMtpCli.WMFtpRequestDone(var msg: TMessage);
begin
    if Assigned(FOnRequestDone) then
        FOnRequestDone(Self, FRequestType, Msg.LParam);
end;



procedure TCustomMtpCli.Notification(AComponent: TComponent; Operation: TOperation);
begin
    inherited Notification(AComponent, Operation);
    if Operation = opRemove then begin
        if AComponent = FControlSocket then
            FControlSocket := nil
        else if AComponent = FDataSocket then
            FDataSocket := nil;
    end;
end;



procedure TCustomMtpCli.SetDSocketSndBufSize(const Value: Integer);
begin
    if Value < 1024 then
        FDSocketSndBufSize := 1024
    else
        FDSocketSndBufSize := Value;
end;



procedure TCustomMtpCli.SetDSocketRcvBufSize(const Value: Integer);
begin
    if Value < 1024 then
        FDSocketRcvBufSize := 1024
    else
        FDSocketRcvBufSize := Value;
end;



procedure TCustomMtpCli.SetErrorMessage;
begin
    if FErrorMessage = '' then
        FErrorMessage := FLastResponse;
end;


function TCustomMtpCli.CreateSocket: TWSocket;
begin
  Result := TWSocket.Create(Self);
end;



procedure TCustomMtpCli.DestroyLocalStream;
var
    NewSize: Int64;
begin
    if Assigned(FLocalStream) then begin
        FLocalStream.Free;
        FLocalStream := nil;
    end;
end;


function TCustomMtpCli.OpenMemoryStream (Buffersize: integer): TmemoryStream;
begin
    Result := TmemoryStream.Create ;
    Result.SetSize(BufferSize);
end ;


procedure TCustomMtpCli.CreateLocalFileStream;
begin
    try
            FreeAndNil(FLocalStream);
            FLocalStream := OpenMemoryStream(FStreamSize);//960*540*3);
    except
        on E:Exception do begin
            FLastResponse := 'Unable to open local stream ';
            FStatusCode   := 550;
            SetErrorMessage;
            FRequestResult := FStatusCode;
            TriggerRequestDone(FRequestResult);
            exit;
        end;
    end;
end;


procedure TCustomMtpCli.LocalStreamWriteString(Str: PWideChar; Count: Integer;
    ACodePage: LongWord);
begin
    StreamWriteString(FLocalStream, Str, Count, ACodePage);
end;



procedure TCustomMtpCli.LocalStreamWriteString(Str: PWideChar; Count: Integer);
begin
    StreamWriteString(FLocalStream, Str, Count, CP_ACP);
end;



procedure TCustomMtpCli.LocalStreamWriteString(Str: PAnsiChar; Count : Integer);
begin
    FLocalStream.WriteBuffer(Str^, Count);
end;



procedure TCustomMtpCli.LocalStreamWrite(const Buffer; Count : Integer);
begin
    FLocalStream.WriteBuffer(Buffer, Count);
end;



procedure TCustomMtpCli.SetKeepAliveSecs (secs: integer);
begin
    if FKeepAliveSecs <> secs then begin
        if secs = 0 then
            FControlSocket.KeepAliveOnOff := wsKeepAliveOnSystem
        else begin
            FControlSocket.KeepAliveOnOff := wsKeepAliveOnCustom ;
            FControlSocket.KeepAliveTime := LongWord (secs) * 1000;
            if secs < 10 then
                FControlSocket.KeepAliveInterval := 1000
            else
                FControlSocket.KeepAliveInterval := LongWord (secs div 5) * 1000;
        end ;
    end;
    FKeepAliveSecs := secs;
end;




procedure TCustomMtpCli.SetLocalStream(Stream: TmemoryStream);
begin
    FLocalStream := Stream;
end;



procedure TCustomMtpCli.SetDataPortRangeStart(NewValue: LongWord);
begin
    if NewValue > 65535 then
        HandleError('DataPortRangeStart must be in the range 0..65535')
    else
        FDataPortRangeStart := NewValue;
end;



procedure TCustomMtpCli.SetDataPortRangeEnd(NewValue: LongWord);
begin
    if NewValue > 65535 then
        HandleError('DataPortRangeEnd must be in the range 0..65535')
    else
        FDataPortRangeEnd := NewValue
end;



procedure TCustomMtpCli.TriggerDisplay(Msg : String);
begin
    if Assigned(FOnDisplay) then
        FOnDisplay(Self, Msg);
end;



procedure TCustomMtpCli.TriggerDisplayFile(Msg : String);
begin
    if Assigned(FOnDisplayFile) then
        FOnDisplayFile(Self, Msg);
end;



procedure TCustomMtpCli.TriggerError(Msg : String);
begin
    if Assigned(FOnError) then
        FOnError(Self, Msg);
end;



procedure TCustomMtpCli.DisplayLastResponse;
begin
    if Pos('Will attempt to restart', FLastResponse) > 0 then
        TriggerDisplay('< DEBUG !');

    TriggerDisplay('< ' + FLastResponse);
end;



procedure TCustomMtpCli.SetMultiThreaded(const Value : Boolean);
begin
    if Assigned(FDataSocket) then
        FDataSocket.MultiThreaded := Value;
    if Assigned(FControlSocket) then
        FControlSocket.MultiThreaded := Value;
    inherited SetMultiThreaded(Value);
end;



procedure TCustomMtpCli.SetTerminated(const Value: Boolean);
begin
    if Assigned(FDataSocket) then
        FDataSocket.Terminated := Value;
    if Assigned(FControlSocket) then
        FControlSocket.Terminated := Value;
    inherited SetTerminated(Value);
end;



procedure TCustomMtpCli.SetOnBgException(const Value: TIcsBgExceptionEvent);
begin
    if Assigned(FDataSocket) then
        FDataSocket.OnBgException := Value;
    if Assigned(FControlSocket) then
        FControlSocket.OnBgException := Value;
    inherited SetOnBgException(Value);
end;



procedure TCustomMtpCli.SetOnMessagePump(const Value: TNotifyEvent);
begin
    if Assigned(FDataSocket) then
        FDataSocket.OnMessagePump := Value;
    if Assigned(FControlSocket) then
        FControlSocket.OnMessagePump := Value;
    inherited SetOnMessagePump(Value);
end;



procedure TCustomMtpCli.StateChange(NewState : TFtpState);
begin
    if FState <> NewState then begin
        FState := NewState;
        TriggerStateChange;
    end;
end;



function TCustomMtpCli.GetBinary : Boolean;
begin
     Result := (FType = 'I');
end;



procedure TCustomMtpCli.SetBinary(Value : Boolean);
begin
     if Value then
         FType := 'I'
     else
         FType := 'A';
end;



function TCustomMtpCli.Progress : Boolean;
var
    Abort : Boolean;
begin
    Abort := FALSE;
    if Assigned(FOnProgress64) then
        FOnProgress64(Self, FByteCount , Abort);
    if Abort then begin
     //   TriggerDisplay('! Abort requested');
     //   FDataSocket.Close;
        AbortAsync ;
    end;

    Result := not Abort;
end;



procedure TCustomMtpCli.SendCommand(Cmd : String);
var
    RawCmd: AnsiString;
begin
    if Assigned(FOnCommand) then
        FOnCommand(Self, Cmd);
    TriggerDisplay('> ' + Cmd);
    RawCmd := Cmd;
    if FControlSocket.State = wsConnected then
        FControlSocket.SendStr(RawCmd + #13#10)

    else begin
        if cmd = 'QUIT' then
            FStatusCode := 200
        else
            FStatusCode := 550;

         FNextRequest   := nil;
         FDoneAsync     := nil;
         FConnected     := FALSE;
         FRequestResult := FStatusCode;
         FLastResponse  := IntToStr(FStatusCode) + ' not connected';
         if FStatusCode = 550 then begin
            SetErrorMessage;
            TriggerRequestDone(550);
         end
         else
            TriggerRequestDone(0);
    end;
end;



procedure TCustomMtpCli.HandleError(const Msg : String);
begin
    FFctSet           := [];
    FFctPrv           := ftpFctNone;
    FLastResponse     := '';
    FErrorMessage     := '';
    FNextRequest      := nil;
    if Assigned(FOnError) then
        TriggerError(Msg)
    else
        raise FtpException.Create(Msg);
end;



function TCustomMtpCli.CheckReady : Boolean;
begin
    Result := (FState in [ftpReady, ftpInternalReady, ftpConnected]);
    if not Result then
        HandleError('FTP component not ready, state ' + LookupFtpState (FState));
end;



procedure TCustomMtpCli.OpenAsync;
begin
    if not CheckReady then begin
        TriggerDisplay('Not ready for Open');
        Exit;
    end;
    if FConnected then begin
        HandleError('FTP component already connected');
        Exit;
    end;

    if not FHighLevelFlag then
        FRequestType := ftpOpenAsync;

    FRequestDoneFlag     := FALSE;
    FReceiveLen          := 0;
    FRequestResult       := 0;
    FDnsResult           := '';
    FControlSocket.SocketFamily := FSocketFamily;
    FLastResponse        := '';
    FErrorMessage        := '';
    FStatusCode          := 0;

    FControlSocket.SocksAuthentication := socksNoAuthentication;

    FControlSocket.HttpTunnelServer := '';
    FDataSocket.HttpTunnelServer    := '';

    StateChange(ftpDnsLookup);
    FControlSocket.Addr := FHostName;
    FControlSocket.DnsLookup(FHostName);
end;



procedure TCustomMtpCli.ExecAsync(
    RqType      : TFtpRequest;
    Cmd         : String;
    OkResponses : array of Word;
    DoneAsync   : TFtpNextProc);
var
    I : Integer;
begin

    if not((Cmd = 'ABOR') or (Cmd = 'STAT') or (Cmd = 'QUIT')) then begin
        if not CheckReady then begin
            TriggerDisplay('Not ready for next command, Req=' + LookupFTPReq (RqType) + ' - '  + Cmd);
            Exit;
        end;
        if not FConnected then begin
            HandleError('MTP component not connected');
            Exit;
        end;
    end;

    if not FHighLevelFlag then
        FRequestType := RqType;

    for I := 0 to High(OkResponses) do
        FOkResponses[I] := OkResponses[I];
    FOkResponses[High(OkResponses) + 1] := 0;

    FLastMultiResponse := '';
    FRequestDoneFlag   := FALSE;
    FNext              := NextExecAsync;
    FDoneAsync         := DoneAsync;
    FErrormessage      := '';
    StateChange(ftpWaitingResponse);
    SendCommand(Cmd);
end;



procedure TCustomMtpCli.NextExecAsync;
var
    I : Integer;
    p : PChar;
begin
    DisplayLastResponse;

    if not IsDigit(FLastResponse[1]) then
        Exit;
    p := GetInteger(@FLastResponse[1], FStatusCode);
    if p^ = '-' then
        Exit;

    if FOkResponses[0] = 0 then begin

        if FStatusCode >= 500 then begin
            { Not a good response }
            FRequestResult := FStatusCode;
            SetErrorMessage;
        end
        else
            FRequestResult := 0;
    end
    else begin

        for I := 0 to High(FOkResponses) do begin
            if FOkResponses[I] = 0 then begin
                FRequestResult := FStatusCode;
                SetErrorMessage;
                break;
            end;
            if FOkResponses[I] = FStatusCode then begin
                FRequestResult := 0;
                Break;
            end;
        end;
    end;


    if Assigned(FDoneAsync) then
        FDoneAsync
    else
        TriggerRequestDone(FRequestResult);
end;



procedure TCustomMtpCli.QuitAsync;
begin
    DestroyLocalStream;
    FFctPrv := ftpFctQuit;
    ExecAsync(ftpQuitAsync, 'QUIT', [221], DoneQuitAsync);
end;



procedure TCustomMtpCli.DoneQuitAsync;
begin
   StateChange(ftpInternalReady);
   FControlSocket.Close;
end;




procedure TCustomMtpCli.AbortAsync;
begin
    StateChange(ftpAbort);

    FLocalStream.Position :=0;


    //FControlSocket.Abort; // il datasocket viene ricreato con PORT ma il controlsocket no. non c'è QUIT, rimane connesso
    FDataSocket.Abort;
    //FConnected := FALSE;
    StateChange(ftpReady);
end;



procedure TCustomMtpCli.DoHighLevelAsync;
begin
    if FState = ftpAbort then begin
        FFctSet := [];
        FHighLevelResult := 426;
        FErrorMessage    := '426 Operation aborted.';
    end;

    FNextRequest := DoHighLevelAsync;

    if FRequestResult <> 0 then begin
        { Previous command had errors }
        FHighLevelResult := FRequestResult;
        if (FFctPrv = ftpFctQuit) or (not (ftpFctQuit in FFctSet)) then
            FFctSet := []
        else
            FFctSet := [ftpFctQuit];
    end;


    if ftpFctOpen in FFctSet then begin
        FFctPrv := ftpFctOpen;
        FFctSet := FFctSet - [FFctPrv];
        OpenAsync;
        Exit;
    end;




    if ftpFctPort in FFctSet then begin
        FFctPrv := ftpFctPort;
        FFctSet := FFctSet - [FFctPrv];
        PortAsync;
        Exit;
    end;


    if ftpFctGet in FFctSet then begin

        FFctPrv   := ftpFctGet;
        FFctSet   := FFctSet - [FFctPrv];
        ExecGetAsync;
        Exit;
    end;

    if ftpFctPut in FFctSet then begin
        FFctPrv := ftpFctPut;
        FFctSet := FFctSet - [FFctPrv];
        ExecPutAsync;
        Exit;
    end;



    if ftpFctQuit in FFctSet then begin
        FFctPrv := ftpFctQuit;
        FFctSet := FFctSet - [FFctPrv];
        FLastResponseSave := FLastResponse;
        FStatusCodeSave   := FStatusCode;
        QuitAsync;
        Exit;
    end;


    FFctSet          := [];
    FNextRequest     := nil;
    FRequestDoneFlag := FALSE;
    TriggerRequestDone(FHighLevelResult);
end;



procedure TCustomMtpCli.HighLevelAsync(RqType : TFtpRequest; Fcts : TFtpFctSet);
begin
    if FConnected and (ftpFctOpen in Fcts) then begin
        HandleError('MTP component already connected');
        Exit;
    end;
    if not CheckReady then begin
        TriggerDisplay('Not ready for Request, Req=' + LookupFTPReq (RqType));
        Exit;
    end;
    FLastResponseSave := FLastResponse;
    FStatusCodeSave   := -1;
    FRequestType      := RqType;
    FRequestResult    := 0;
    FFctSet           := Fcts;
    FFctPrv           := ftpFctNone;
    FHighLevelResult  := 0;
    FHighLevelFlag    := TRUE;
    FLastResponse     := '';
    FErrorMessage     := '';
    FRestartFlag      := FALSE;
    FNextRequest      := nil;
    DoHighLevelAsync;
end;



procedure TCustomMtpCli.ConnectAsync;
begin
    HighLevelAsync(ftpConnectAsync,
                   [ftpFctOpen]);
end;



procedure TCustomMtpCli.ReceiveAsync;
begin
    HighLevelAsync(ftpReceiveAsync,
                   [ftpFctOpen, ftpFctPort, ftpFctGet,  ftpFctQuit]);
end;



procedure TCustomMtpCli.PutAsync;
begin
DataSocket.LastError := 0;
HighLevelAsync(ftpPutAsync, //
                 [ftpFctPort, ftpFctPut]);
    if DataSocket.LastError <> 0 then
       raise FtpException.Create('Socket Error - ' +
                              GetWinsockErr(DataSocket.LastError));
end;




procedure TCustomMtpCli.TransmitAsync;
begin
    HighLevelAsync(ftpTransmitAsync,
                   [ftpFctOpen,  ftpFctPort,  ftpFctPut,  ftpFctQuit]);
end;



procedure TCustomMtpCli.GetAsync;
begin
    HighLevelAsync(ftpGetAsync, [ftpFctPort, ftpFctGet]);
end;




procedure TCustomMtpCli.DataSocketGetDataAvailable( Sender  : TObject; ErrCode : word);
var
    Len     : Integer;
    Buffer  : array [1..FTP_RCV_BUF_SIZE] of AnsiChar;
    aSocket : TWSocket;
    I, J    : Integer;
    Line    : AnsiString;
    ACodePage : LongWord;
begin
    if not Progress then
        Exit;

    aSocket := Sender as TWSocket;

    Len := aSocket.Receive(@Buffer[1], High(Buffer));
{TriggerDisplay('! Data received ' + IntToStr(Len));}
    if Len = 0 then
    else if Len < 0 then begin
        if (aSocket.State = wsConnected) and
           (aSocket.LastError <> WSAEWOULDBLOCK) then begin
            TriggerDisplay('! Data: Receive Error - ' +
                                     GetWinsockErr(aSocket.LastError));
            aSocket.Shutdown(2);
            Exit;
        end;
    end
    else begin

        if FState in [ftpAbort, ftpInternalAbort] then begin
            TriggerDisplay('! Data ignored while aborting');
            exit;
        end ;
        if FLocalStream <> nil then begin
            try
                LocalStreamWrite(Buffer, Len);
            except
                TriggerDisplay('! Error writing local file');
                aSocket.Shutdown(2);
                Exit;
            end;
        end;

        FByteCount := FByteCount + Len;


        SetLength(Line, Len);
        Move(Buffer[1], Line[1], Length(Line));
        TriggerDisplayFile(AnsiToUnicode(Line, ACodePage));
    end;

end;



procedure TCustomMtpCli.DataSocketGetSessionConnected(Sender  : TObject; ErrCode : word);
begin
    FDataSocket.OnSessionClosed := DataSocketGetSessionClosed;
    FDataSocket.OnDataAvailable := DataSocketGetDataAvailable;
    FDataSocket.OnDataSent      := nil;

    FStartTime := LongInt(IcsGetTickCount);
    FDurationMsecs := 0;

    if ErrCode <> 0 then begin
        FLastResponse := 'Unable to establish data connection - ' +
                         WSocketGetErrorMsgFromErrorCode(ErrCode);
        FStatusCode   := 550;
        SetErrorMessage;
        FDataSocket.Close;
        FRequestResult := FStatusCode;
        TriggerRequestDone(FRequestResult);
    end
    else begin
        if FDataSocket.SocketRcvBufSize <> FDSocketRcvBufSize then
            FDataSocket.SocketRcvBufSize := FDSocketRcvBufSize;
    end;
end;



procedure TCustomMtpCli.DataSocketPutSessionConnected(Sender  : TObject; ErrCode : word);
begin
    FDataSocket.OnSessionClosed := DataSocketPutSessionClosed;
    FDataSocket.OnDataAvailable := nil;
    FDataSocket.OnDataSent      := nil;

    FPutSessionOpened := TRUE;

    FStartTime := LongInt(IcsGetTickCount);
    FDurationMsecs := 0;

    if ErrCode <> 0 then begin
        FLastResponse := 'Unable to establish data connection - ' +
                         WSocketGetErrorMsgFromErrorCode(ErrCode);
        FStatusCode   := 550;
        SetErrorMessage;
        FDataSocket.Close;
        FRequestResult := FStatusCode;
        TriggerRequestDone(FRequestResult);
        Exit;
    end;
    if FDataSocket.SocketSndBufSize <> FDSocketSndBufSize then
        FDataSocket.SocketSndBufSize := FDSocketSndBufSize;
    StateChange(ftpWaitingResponse);
    FNext := Next1PutAsync;

    SendCommand('STOR ' + IntToStr(FStreamSize ) + ' ' + MemoryName );
end;



procedure TCustomMtpCli.DataSocketGetSessionAvailable(Sender  : TObject; ErrCode : word);
var
    aSocket : TSocket;
begin
    aSocket := FDataSocket.Accept;

    FDataSocket.Close;


    FDataSocket.OnSessionClosed  := DataSocketGetSessionClosed;
    FDataSocket.OnDataAvailable  := DataSocketGetDataAvailable;
    FDataSocket.OnDataSent       := nil;
    FDataSocket.HSocket          := aSocket;
    if FDataSocket.SocketRcvBufSize <> FDSocketRcvBufSize then
        FDataSocket.SocketRcvBufSize := FDSocketRcvBufSize;
    FDataSocket.ComponentOptions := [wsoNoReceiveLoop];

    FStartTime := LongInt(IcsGetTickCount);
    FDurationMsecs := 0;
end;



procedure TCustomMtpCli.DataSocketGetSessionClosed(Sender  : TObject; ErrCode : word);
begin
    FLocalStream.Position :=0;
    FFileReceived := TRUE;
    FError        := ErrCode;
    Next3GetAsync;
end;



procedure TCustomMtpCli.DataSocketPutSessionAvailable(Sender  : TObject; ErrCode : word);
var
    aSocket : TSocket;
begin
    aSocket := FDataSocket.Accept;

    FDataSocket.Close;

    FDataSocket.OnSessionClosed  := DataSocketPutSessionClosed;
    FDataSocket.OnDataAvailable  := DataSocketPutDataAvailable;
    FDataSocket.OnDataSent       := DataSocketPutDataSent;
{   FDataSocket.OnDisplay        := FOnDisplay; } { Debugging only }
    FDataSocket.HSocket          := aSocket;
    if FDataSocket.SocketSndBufSize <> FDSocketSndBufSize then
        FDataSocket.SocketSndBufSize := FDSocketSndBufSize;
    FDataSocket.ComponentOptions := [wsoNoReceiveLoop];

    // Chiusura sicura del socket
    FDataSocket.LingerOnOff   := wsLingerOn; //wsLingerOff;// wsLingerOn;
    FDataSocket.LingerTimeout := 10;//10;   0 e off
    FDataSocket.SetLingerOption;
    FPutSessionOpened := TRUE;
    if FStorAnswerRcvd and (FStartTime = 0) then
        PostMessage(Handle, FMsg_WM_FTP_SENDDATA, 0, 0);

end;



procedure TCustomMtpCli.WMFtpSendData(var msg: TMessage);
begin
    FStartTime := LongInt(IcsGetTickCount);
    FDurationMsecs := 0;

    if not FDataSocketSentFlag then
        DataSocketPutDataSent(FDataSocket, 0);
end;



procedure  TCustomMtpCli.WMFtpCloseDown(var msg: TMessage);
begin
    if (FDataSocket.BufferedByteCount = 0) or
       (FCloseEndTick < IcsGetTickCount) then begin
        FDataSocket.ShutDown(1);
        FEofFlag := TRUE;
    end
    else if ((FControlSocket.State = wsConnected) and
             (FDataSocket.State    = wsConnected)) then
        PostMessage(Handle, FMSG_WM_FTP_CLOSEDOWN, 0, 0);
end;



procedure TCustomMtpCli.DataSocketPutDataSent( Sender  : TObject; ErrCode : word);
var
    Count : Integer;
begin
    if (FLocalStream = nil) or (not Progress) then
        Exit;
    if FLocalStream = nil then
        Exit;

    if ErrCode <> 0 then begin
        TriggerDisplay('! Error sending data - ' + GetWinsockErr(ErrCode));
        FDataSocket.Close;
        Exit;
    end;

    if FEofFlag or (not FStorAnswerRcvd) or (not FPutSessionOpened) then begin
        Exit;
    end;

    if not FDataSocketSentFlag then
        FDataSocketSentFlag := TRUE;

    try
            Count := FLocalStream.Read(FSendBuffer, SizeOf(FSendBuffer));
        if Count > 0 then begin
            FByteCount := FByteCount + Count;
            FDataSocket.Send(@FSendBuffer, Count);
        end
        else begin { EOF }
            {$IFNDEF VER80}
            FCloseEndTick := IcsGetTickCount + (FCloseEndSecs * 1000);
            PostMessage(Handle, FMsg_WM_FTP_CLOSEDOWN, 0, 0);
            exit;
            {$ENDIF}
            FDataSocket.ShutDown(1);
            FEofFlag := TRUE;
        end;
    except
        on E:Exception do begin
            TriggerDisplay('! Error reading file ' + E.ClassName + ': ' + E.Message);
            FDataSocket.Close;
        end;
    end;
end;



procedure TCustomMtpCli.DataSocketPutSessionClosed(Sender  : TObject; ErrCode : word);
begin
    FLocalStream.Position :=0;
    FFileSent := TRUE;
    FError    := ErrCode;
    Next3PutAsync;
end;



procedure TCustomMtpCli.DataSocketPutDataAvailable(Sender  : TObject; ErrCode : word);
var
    Buffer  : array [1..2048] of Byte;
    aSocket : TWSocket;
begin
    aSocket := Sender as TWSocket;
    aSocket.Receive(@Buffer[1], High(Buffer));
end;



procedure TCustomMtpCli.TransfertStats;
var
    Buffer   : String;
    BytesSec : Int64 ;
    Duration : Int64 ;
begin
    FStopTime := LongInt(IcsGetTickCount);
    Buffer    := IntToKByte(FByteCount) + 'bytes received/sent in ';
    if LongWord (FStopTime) >= LongWord (FStartTime) then
        Duration := LongWord (FStopTime) - LongWord (FStartTime)
    else
        Duration := ($FFFFFFFF - LongWord (FStartTime)) + LongWord (FStopTime);
    if Duration < 5000 then
        Buffer := Buffer + IntToStr(Duration) + ' milliseconds'
    else begin
        Buffer := Buffer + IntToStr(Duration div 1000) + ' seconds';
    if FStopTime <> FStartTime then begin
        if FByteCount > 32767 then
                BytesSec := 1000 * (FByteCount div Duration)
        else
                BytesSec := (1000 * FByteCount) div Duration;
            Buffer := Buffer + ' (' + IntToKByte(BytesSec) + 'bytes/sec)';
    end;
    end;
    FDurationMsecs := Integer (Duration);
    TriggerDisplay('! ' + Buffer);
end;



procedure TCustomMtpCli.ExecGetAsync;
begin
    DoGetAsync(ftpGetAsync);
end;





procedure TCustomMtpCli.DataSocketGetInit(const TargetPort, TargetIP : String);
begin
    FDataSocket.Port               := TargetPort;
    FDataSocket.Addr               := TargetIP;
    FDataSocket.LocalAddr          := FLocalAddr;
    FDataSocket.LocalAddr6         := FLocalAddr6;
    FDataSocket.OnSessionConnected := DataSocketGetSessionConnected;
    FDataSocket.LingerOnOff        := wsLingerOff;
    FDataSocket.LingerTimeout      := 0;
    FDataSocket.ComponentOptions   := [wsoNoReceiveLoop];

    FDataSocket.SocksAuthentication := socksNoAuthentication;
end;



function GetZlibCacheFileName(const S : String) : String;
var
    I : Integer;
    Ticks: String;
begin
    Result := AnsiLowercase (S);
    if Length(Result) = 0 then Result := 'temp';
    for I := 1 to Length(Result) do begin
        if (Result [I] = '\') or (Result [I] = '.') or
                           (Result [I] = ':') then Result[I] := '_';
    end;
    Ticks := IntToStr(IcsGetTickCountX);
    I := Length(Ticks);
    if I < 6 then Ticks := '123' + Ticks;
    Result := Result + '_' + Copy (Ticks, I-6, 6) + '.zlib';
end;



(* Riceve un file *)
procedure TCustomMtpCli.DoGetAsync(RqType : TFtpRequest);
var
    Temp       : String;
    I {, MaxWbits} : Integer;
    TargetPort : WORD;    { 10/30/99 }
    TargetIP   : String;
    NewPos     : TFtpBigInt;
    Delim      : Char;
    DelimCnt, N: Integer;
begin
    if not FConnected then begin
        HandleError(FGetCommand + ': not connected');
        Exit;
    end;

    if not FHighLevelFlag then
        FRequestType := RqType;

    FGetCommand := 'RETR';


    FServerSaidDone    := FALSE;
    FFileReceived      := FALSE;
    FRequestDoneFlag   := FALSE;
    FStartTime         := 0;
    FByteCount         := 0;
    FDurationMsecs     := 0;
    FError             := 0;

    FDataSocket.OnSessionAvailable := DataSocketGetSessionAvailable;

    { open the destination file }
    { Don't open a file if we're on FDisplayFileFlag }
    if not FDisplayFileFlag then
    try
        DestroyLocalStream;
            if not Assigned(FLocalStream)then begin
                FLocalStream := OpenMemoryStream(FStreamSize);
            end;
    except
        on E:Exception do begin
            FLastResponse := 'Unable to open local stream ' + ': ' + E.Message;
            FStatusCode   := 550;
            SetErrorMessage;
            FDataSocket.Close;
            FRequestResult := FStatusCode;
            TriggerRequestDone(FRequestResult);
            exit;
        end;
    end;


    StateChange(ftpWaitingResponse);
    FNext := Next1GetAsync;
    SendCommand(FGetCommand);
end;



(*Qui arriviamo quando abbiamo ricevuto il response per il comando RETR che abbiamo invito prima *)
procedure TCustomMtpCli.Next1GetAsync;
begin
    DisplayLastResponse;
    GetInteger(@FLastResponse[1], FStatusCode);
    if not (((FStatusCode div 10) = 15) or
            (FStatusCode = 125)) then begin
        SetErrorMessage;
        FNext := nil;
        FDataSocket.Close;
        DestroyLocalStream;

        FRequestResult := FStatusCode;
        TriggerRequestDone(FRequestResult);
        Exit;
    end;
    FNext := Next2GetAsync;
end;



(*Qui arriviamo quando MtpServer ha spedito il file che abbiamo chiesot con GET *)
procedure TCustomMtpCli.Next2GetAsync;
begin
    DisplayLastResponse;
    GetInteger(@FLastResponse[1], FStatusCode);
    if not ((FStatusCode = 125) or (FStatusCode = 226) or
            (FStatusCode = 250)) then begin
        SetErrorMessage;
        DestroyLocalStream;
        FDataSocket.Close;
        TriggerDisplay('! RETR/LIST/NLST Failed');
        FRequestResult := FStatusCode;
        TriggerRequestDone(FRequestResult);
        Exit;
    end;
    FServerSaidDone := TRUE;
    Next3GetAsync;
end;



(*Qui arriviamo quando abbiamo ricevuto il file dal MtpServer o quando abbiamo una risposta *)
procedure TCustomMtpCli.Next3GetAsync;
begin
    if (not FServerSaidDone) or (not FFileReceived) then
        Exit;

    { Display statistics }
    TransfertStats;

    FRequestResult := FError;
    TriggerRequestDone(FRequestResult);
end;


procedure TCustomMtpCli.ExecPutAsync;
begin
    FAppendFlag  := FALSE;
    FRequestType := ftpPutAsync;
    DoPutAppendAsync;
end;


procedure TCustomMtpCli.DataSocketPutAppendInit(const TargetPort, TargetIP : String);
begin
    FDataSocket.Port               := TargetPort;
    FDataSocket.Addr               := TargetIP;
    FDataSocket.LocalAddr          := FLocalAddr;
    FDataSocket.LocalAddr6         := FLocalAddr6;
    FDataSocket.OnSessionConnected := DataSocketPutSessionConnected;
    FDataSocket.LingerOnOff        := wsLingerOff;
    FDataSocket.LingerTimeout      := 0;
    FDataSocket.ComponentOptions   := [wsoNoReceiveLoop];
    FDataSocketSentFlag            := FALSE;
    FDataSocket.SocksAuthentication := socksNoAuthentication;
end;



procedure TCustomMtpCli.DoPutAppendAsync;
var
    Temp        : String;
    I           : Integer;
    TargetPort  : WORD;
    TargetIP    : String;
    bCancel     : Boolean;
    NewPos      : TFtpBigInt;
    Uploadsize  : TFtpBigInt;
    Count : Integer;

begin
    if not FConnected then begin
        HandleError('STOR/APPE: not connected');
        Exit;
    end;

    FServerSaidDone    := FALSE;
    FFileSent          := FALSE;
    FRequestDoneFlag   := FALSE;
    FPutSessionOpened  := FALSE;
    FStorAnswerRcvd    := FALSE;
    FStartTime         := 0;
    FDurationMsecs     := 0;
    FByteCount         := 0;
    FError             := 0;

    bCancel := FALSE;
    TriggerReadyToTransmit(bCancel);
    if bCancel then begin
        FErrorMessage := '426 Transmit cancelled by application';
        FStatusCode   := 426;
        TriggerDisplay('! ' + FErrorMessage);
        FRequestResult := FStatusCode;
        TriggerRequestDone(FRequestResult);
        Exit;
    end;

    FDataSocket.OnSessionAvailable := DataSocketPutSessionAvailable;

    try
        DestroyLocalStream;
        FEofFlag     := FALSE;
        if not Assigned(FLocalStream) then begin
            FLocalStream := OpenMemoryStream(FStreamSize);
        end;
    except
        on E:Exception do begin
            FLastResponse := 'Unable to open local file ' + ': ' + E.Message;
            FStatusCode   := 426;
            SetErrorMessage;
            TriggerDisplay('! ' + FErrorMessage);
            FDataSocket.Close;
            FRequestResult := FStatusCode;
            TriggerRequestDone(FRequestResult);
            Exit;
        end;
    end;

    Uploadsize := FStreamSize - FLocalStream.Position;
    Count := FLocalStream.WriteData  ( MemoryPtr , FStreamSize );
    FLocalStream.Position:=0;
    TriggerDisplay('! Upload Size ' + IntToKByte (Uploadsize)) ;


    StateChange(ftpWaitingResponse);
    FNext := Next1PutAsync;

    SendCommand('STOR ' + IntToStr(fStreamSize) + ' ' + MemoryName );
end;



(*Qui arriviamo quando abbiamo ricevuto la risposta per il comando STOR che abbiamo inviato prima *)
procedure TCustomMtpCli.Next1PutAsync;
var
    p : PChar;
begin
    DisplayLastResponse;
    if not IsDigit(FLastResponse[1]) then
        Exit;
    p := GetInteger(@FLastResponse[1], FStatusCode);
    if p^ = '-' then
        Exit;

    if not ((FStatusCode = 150) or (FStatusCode = 125)) then begin
        SetErrorMessage;
        FNext := nil;
        FDataSocket.Close;
        DestroyLocalStream;

        FRequestResult := FStatusCode;
        TriggerRequestDone(FRequestResult);
        Exit;
    end;

        FStorAnswerRcvd := TRUE;
        if FPutSessionOpened and (FStartTime = 0) then
            PostMessage(Handle, FMsg_WM_FTP_SENDDATA, 0, 0);


    FNext := Next2PutAsync;
end;



(*Qui arriviamo quando MtpServer ha ricevuto il file che abbiamo spedito con STOR *)
procedure TCustomMtpCli.Next2PutAsync;
var
    p : PChar;
begin
    DisplayLastResponse;
    if not IsDigit(FLastResponse[1]) then
        Exit;
    p := GetInteger(@FLastResponse[1], FStatusCode);
    if p^ = '-' then
        Exit;
    if not ((FStatusCode = 226) or (FStatusCode = 250)) then begin
        SetErrorMessage;
        DestroyLocalStream;
        FDataSocket.Close;
        TriggerDisplay('! STOR Failed');
        FRequestResult := FStatusCode;
        TriggerRequestDone(FRequestResult);
        Exit;
    end;
    FServerSaidDone := TRUE;
    Next3PutAsync;
end;



(*Qui arriviamo quando abbiamo trasferito il file o MtpServer ci dice che l'ha ricevuto*)
procedure TCustomMtpCli.Next3PutAsync;
begin
    if (not FServerSaidDone) or (not FFileSent) then
        Exit;

    TransfertStats;

    // Resetta il puntatore dello stream
    FLocalStream.Position :=0;
    FRequestResult := FError;
    TriggerRequestDone(FRequestResult);
end;



procedure TCustomMtpCli.PortAsync;
type
    T4Bytes = array[0..3] of Byte;
    P4Bytes = ^T4Bytes;
var
    Msg          : String;
    saddr        : TSockAddrIn6;
    saddrlen     : Integer;
    DataPort     : LongWord;
    IPAddr       : TInAddr;
    StartDataPort: LongWord;
begin
    if not FConnected then begin
        HandleError('FTP component not connected');
        Exit;
    end;
    FDataSocket.Proto              := 'tcp';
    if FControlSocket.CurrentSocketFamily = sfIPv6 then
        FDataSocket.Addr := ICS_ANY_HOST_V6
    else
        FDataSocket.Addr := ICS_ANY_HOST_V4;
    FDataSocket.Port               := AnsiChar('0');
    FDataSocket.OnSessionAvailable := nil;
    FDataSocket.OnSessionClosed    := nil;
    FDataSocket.OnDataAvailable    := nil;
    FDataSocketSentFlag            := FALSE;

        if (ftpFctGet in FFctSet) then
            FDataSocket.OnSessionAvailable := DataSocketGetSessionAvailable
        else if ftpFctPut in FFctSet then
            FDataSocket.OnSessionAvailable := DataSocketPutSessionAvailable;
        FDataSocket.LingerOnOff        := wsLingerOff;// wsLingerOn;
        FDataSocket.LingerTimeout      := 0;//10

        if (FDataPortRangeStart = 0) and (FDataPortRangeEnd = 0) then begin
            FDataSocket.Listen;
            saddrLen  := SizeOf(saddr);
            FDataSocket.GetSockName(PSockAddrIn(@saddr)^, saddrLen);
            DataPort  := WSocket_ntohs(saddr.sin6_port);
        end
        else begin
            if FDataPortRangeStart > FDataPortRangeEnd then begin
                HandleError('DataPortRangeEnd must be greater than DataPortRangeStart');
                Exit;
            end;
            if (FLastDataPort < FDataPortRangeStart) or
               (FLastDataPort > FDataPortRangeEnd) then
                FLastDataPort := FDataPortRangeStart;
            DataPort      := FLastDataPort;
            StartDataPort := DataPort;
            while TRUE do begin
                FDataSocket.Port := IntToStr(DataPort);
                try
                    FDataSocket.Listen;
                    break;
                except
                    if FDataSocket.LastError = WSAEADDRINUSE then begin
                        DataPort := DataPort + 1;
                        if DataPort > FDataPortRangeEnd then
                            DataPort := FDataPortRangeStart;
                        if DataPort = StartDataPort then begin
                            HandleError('All ports in DataPortRange are in use');
                            Exit;
                        end;
                    end
                    else begin
                        HandleError('Data connection winsock bind failed - ' +
                                    GetWinsockErr(FDataSocket.LastError));
                        Exit;
                    end;
                end;
            end;
            FLastDataPort := DataPort + 1;
            if FLastDataPort > FDataPortRangeEnd then
                FLastDataPort := FDataPortRangeStart;
        end;

    saddrlen := SizeOf(saddr);
    FControlSocket.GetSockName(PSockAddrIn(@saddr)^, saddrlen);
    IPAddr   := PSockAddrIn(@saddr).sin_addr;

        if saddr.sin6_family = AF_INET6 then
        begin
            Msg := 'EPRT |2|' +
                   WSocketIPv6ToStr(PIcsIPv6Address(@saddr.sin6_addr)^) +
                   '|' + IntToStr(DataPort) + '|';
        end
        else
        if WSocketIsIPv4(FExternalIPv4) then
            Msg := Format('PORT %s,%d,%d',
                          [StringReplace(FExternalIPv4, '.', ',', [rfReplaceAll]),
                           IcsHiByte(DataPort),
                           IcsLoByte(DataPort)])
        else
        if FControlSocket.sin.sin_addr.s_addr = WSocket_htonl($7F000001) then
            Msg := Format('PORT 127,0,0,1,%d,%d',
                          [IcsHiByte(DataPort),
                           IcsLoByte(DataPort)])
        else
          {$IFDEF MSWINDOWS}
            Msg := Format('PORT %d,%d,%d,%d,%d,%d',
                          [ord(IPAddr. S_un_b.s_b1),
                           ord(IPAddr.S_un_b.s_b2),
                           ord(IPAddr.S_un_b.s_b3),
                           ord(IPAddr.S_un_b.s_b4),
                           IcsHiByte(DataPort),
                           IcsLoByte(DataPort)]);
          {$ENDIF}
          {$IFDEF POSIX}
            Msg := Format('PORT %d,%d,%d,%d,%d,%d',
                          [P4Bytes(@IPAddr.s_addr)^[0],
                           P4Bytes(@IPAddr.s_addr)^[1],
                           P4Bytes(@IPAddr.s_addr)^[2],
                           P4Bytes(@IPAddr.s_addr)^[3],
                           IcsHiByte(DataPort),
                           IcsLoByte(DataPort)]);
          {$ENDIF}

    FByteCount := 0;
    FFctPrv    := ftpFctPort;
    if saddr.sin6_family = AF_INET6 then
        ExecAsync(ftpPortAsync, Msg, [200, 229], nil)
    else
        ExecAsync(ftpPortAsync, Msg, [200, 227], nil);
end;



procedure TCustomMtpCli.ControlSocketDnsLookupDone(Sender  : TObject; ErrCode : Word);
begin
    if ErrCode <> 0 then begin
        FLastResponse  := '500 DNS lookup error - ' + GetWinsockErr(ErrCode) ;
        FStatusCode    := 500;
        FRequestResult :=  FStatusCode;
        SetErrorMessage;
        TriggerRequestDone(ErrCode);
    end
    else begin
        FDnsResult               := FControlSocket.DnsResult;
        FControlSocket.Addr      := FDnsResult;
        FControlSocket.LocalAddr := FLocalAddr;
        FControlSocket.LocalAddr6 := FLocalAddr6;
        FControlSocket.Proto     := 'tcp';

        FControlSocket.Port  := FPort;
{       FControlSocket.OnDisplay := FOnDisplay; } { Debugging only }


        StateChange(ftpReady);
        try
            FControlSocket.Connect;
        except
            on E:Exception do begin
                FLastResponse := '500 ' + E.ClassName + ': ' + E.Message;
                FStatusCode   := 500;
                FRequestResult :=  FStatusCode;
                SetErrorMessage;
                TriggerRequestDone(FStatusCode);
            end;
        end;
    end;
end;



procedure TCustomMtpCli.HandleHttpTunnelError(
    Sender                : TObject;
    ErrCode               : Word;
    TunnelServerAuthTypes : THttpTunnelServerAuthTypes;
    const Msg             : String);
begin
    FLastResponse := Msg;
end;



procedure TCustomMtpCli.HandleSocksError(
    Sender  : TObject;
    ErrCode : Integer;
    Msg     : String);
begin
    FLastResponse := Msg;
end;



procedure TCustomMtpCli.ControlSocketSessionConnected(Sender: TObject; ErrCode: Word);
begin
    if ErrCode <> 0 then begin
      {$IFDEF POSIX}
        if (ErrCode <= WSAELAST) then
      {$ELSE}
        if (ErrCode >= WSABASEERR) and (ErrCode < ICS_SOCKS_BASEERR) then
      {$ENDIF}
            FLastResponse  := '500 Connect error - ' + GetWinsockErr(ErrCode)
        else if WSocketIsProxyErrorCode(ErrCode) then
            FLastResponse  := '500 Connect error - ' + FLastResponse + ' (#' + IntToStr(ErrCode) + ')'
        else
            FLastResponse  := '500 Connect Unknown Error (#' + IntToStr(ErrCode) + ')';
        FStatusCode    := 500;
        FRequestResult := FStatusCode;
        SetErrorMessage;
        FNextRequest   := nil;
        TriggerRequestDone(ErrCode);
        FControlSocket.Close;
        StateChange(ftpReady);
    end
    else begin
        FConnected := TRUE;

            StateChange(ftpConnected);
            if Assigned(FOnSessionConnected) then
                FOnSessionConnected(Self, ErrCode);

            if Assigned(FWhenConnected) then
                FWhenConnected
            else begin
                TriggerRequestDone(0);
            end;
    end;
end;



procedure TCustomMtpCli.ControlSocketDataAvailable(Sender: TObject; ErrCode: Word);
var
    Len  : Integer;
    I, J : Integer;
    p    : PChar;
    Feat : String;
    ACodePage : LongWord;
    RawResponse: AnsiString;
    x     : integer;
const
    NewLine =  #13#10 ;
begin
    Len := FControlSocket.Receive(@FReceiveBuffer[FReceiveLen],
                                  SizeOf(FReceiveBuffer) - FReceiveLen - 1);

    if FRequestType = ftpRqAbort then
        Exit;

    if Len = 0 then begin
        Exit;
    end;
    if Len < 0 then
        Exit;

    FReceiveBuffer[FReceiveLen + Len] := #0;
    FReceiveLen := FReceiveLen + Len;

    while FReceiveLen > 0 do begin
        if ftpAcceptLF in FOptions then begin
            I := Pos(AnsiChar(10), FReceiveBuffer);
            J := I;
        end
        else begin
            I := Pos(AnsiString(#13#10), FReceiveBuffer);
            J := I + 1;
        end;
        if I <= 0 then
            break;
        if I > FReceiveLen then
            break;
        RawResponse := Copy(FReceiveBuffer, 1, I);

        while (Length(RawResponse) > 0) and
              IsCRLF(RawResponse[Length(RawResponse)]) do
             SetLength(RawResponse, Length(RawResponse) - 1);
        FLastResponse := RawResponse ;

            if LongInt(Length(FLastMultiResponse)) < 65536 then
                FLastMultiResponse := FLastMultiResponse + FLastResponse + #13#10;
        TriggerResponse;

        FReceiveLen := FReceiveLen - J;
        if FReceiveLen > 0 then
            Move(FReceiveBuffer[J], FReceiveBuffer[0], FReceiveLen + 1)
        else if FReceiveLen < 0 then
            FReceiveLen := 0;


         if FState = ftpWaitingResponse then begin
            if (FLastResponse = '') or
               (not IsDigit(FLastResponse[1])) then begin
                DisplayLastResponse;
                Continue;
            end;
            p := GetInteger(@FLastResponse[1], FStatusCode);

                x:= pos ('150 Opening retrieve data connection for ',FLastResponse,1);
                if x <> 0  then begin
                  LocalStream.SetSize(  StrToInt(RightStr(FLastResponse, length(FLastResponse) - 41 )  ));
                  LocalStream.Position :=0;
                end;
            if p^ = '-' then begin

                DisplayLastResponse;
                Continue;
            end;
            if Assigned(FNext) then
                FNext
            else begin
                HandleError('Program error: FNext is nil');
                Exit;
            end;
        end
        else
            DisplayLastResponse;
    end;
end;



procedure TCustomMtpCli.ControlSocketSessionClosed(
    Sender  : TObject;
    ErrCode : Word);
var
    LClosedState : TFtpState;
begin
    if (ErrCode <> 0) and (FState = ftpInternalReady) and
       ((FRequestType = ftpQuitAsync) or (FFctPrv = ftpFctQuit)) then
        ErrCode := 0;

    LClosedState := FState;
    if FConnected then begin
        FConnected := FALSE;
        if FState <> ftpAbort then
            StateChange(ftpNotConnected);
        if Assigned(FOnSessionClosed) then
            FOnSessionClosed(Self, ErrCode);
    end;
    if FState <> ftpAbort then
        StateChange(ftpInternalReady);
    if FRequestType <> ftpRqAbort then begin
        if (ErrCode <> 0) or ((FRequestType <> ftpQuitAsync) and
           (LClosedState in [ ftpWaitingResponse])) then begin
            FLastResponse  := '500 Control connection closed - ' +
                               WSocketGetErrorMsgFromErrorCode(ErrCode);
            FStatusCode    := 500;
            FRequestResult := FStatusCode;
            SetErrorMessage;
        end;
        TriggerRequestDone(FRequestResult);
    end;
end;



procedure TCustomMtpCli.TriggerStateChange;
begin
    if Assigned(FOnStateChange) then
        FOnStateChange(Self);
end;



procedure TCustomMtpCli.TriggerRequestDone(ErrCode: Word);
begin
    if not FRequestDoneFlag then begin
        FRequestDoneFlag := TRUE;
        if (ErrCode = 0) and Assigned(FNextRequest) then begin
            if (FState <> ftpAbort) then  StateChange(ftpInternalReady);
            FNextRequest;
        end
        else begin
            StateChange(ftpReady);
            if FDataSocket.State <> wsClosed then
                FDataSocket.Close;
            if FHighLevelFlag and (FStatusCodeSave >= 0) then begin
                 FLastResponse := FLastResponseSave;
                 FStatusCode   := FStatusCodeSave;
            end;
            FHighLevelFlag := FALSE;
            FNextRequest   := nil;
            PostMessage(Handle, FMsg_WM_FTP_REQUEST_DONE, 0, ErrCode);
            { if Assigned(FOnRequestDone) then
                FOnRequestDone(Self, FRequestType, ErrCode); }
        end;
    end;
end;


procedure TCustomMtpCli.TriggerResponse;
begin
    if Assigned(FOnResponse) then
        FOnResponse(Self);
end;



procedure TCustomMtpCli.TriggerReadyToTransmit(var bCancel : Boolean);
begin
    if Assigned(FOnReadyToTransmit) then
        FOnReadyToTransmit(Self, bCancel);
end;



function TCustomMtpCli.GetConnected : Boolean;
begin
    Result := FControlSocket.State <> wsClosed;
end;




{* *                                                                     * *}
{* *                              TFtpClient                             * *}
{* *                                                                     * *}


constructor TMtpClient.Create(AOwner: TComponent);
begin
    inherited Create(AOwner);
    FTimeout := 15;
end;



function TMtpClient.Open : Boolean;
begin
    Result := Synchronize(OpenAsync);
end;




function TMtpClient.Connect : Boolean;
begin
    Result := Synchronize(ConnectASync);
end;




function TMtpClient.Get : Boolean;
begin
    Result := Synchronize(GetASync);
end;


function TMtpClient.MtpPort : Boolean;
begin
    Result := Synchronize(PortASync);
end;



function TMtpClient.Put : Boolean;
begin
    Result := Synchronize(PutASync);
end;



function TMtpClient.Quit : Boolean;
begin
    Result := Synchronize(QuitASync);
end;



function TMtpClient.Abort : Boolean;
begin
    Result := Synchronize(AbortASync);
end;



function TMtpClient.Receive : Boolean;
begin
    Result := Synchronize(ReceiveASync);
end;



function TMtpClient.Transmit : Boolean;
begin
    Result := Synchronize(TransmitASync);
end;




function TMtpClient.Progress : Boolean;
begin
    Result := inherited Progress;
    if FTimeout > 0 then
        FTimeStop := LongInt(IcsGetTickCount) + LongInt(FTimeout) * 1000;
end;



function TMtpClient.WaitUntilReady : Boolean;
{$IFDEF MSWINDOWS}
var
    DummyHandle     : THandle;
{$ENDIF}
begin
    FTimeStop := LongInt(IcsGetTickCount) + LongInt(FTimeout) * 1000;
  {$IFDEF MSWINDOWS}
    DummyHandle := INVALID_HANDLE_VALUE;
  {$ENDIF}
    while TRUE do begin
        if FState in [ftpReady {, ftpInternalReady}] then begin
            Result := (FRequestResult = 0);
            break;
        end;

        if Terminated or
           ((FTimeout > 0) and (LongInt(IcsGetTickCount) > FTimeStop)) then begin
            AbortAsync;
            FErrorMessage := '426 Timeout';
            FStatusCode   := 426;
            Result        := FALSE;
            break;
        end;
      {$IFDEF MSWINDOWS}
        if ftpWaitUsingSleep in FOptions then
            Sleep(0)
        else
            MsgWaitForMultipleObjects(0, DummyHandle, FALSE, 1000, QS_ALLINPUT);
      {$ENDIF}
        MessagePump;
    end;
end;



function TMtpClient.Synchronize(Proc : TFtpNextProc) : Boolean;
begin
    try
        Proc;
        Result := WaitUntilReady;
    except
        Result := FALSE;
    end;
end;



end.


███╗   ███╗████████╗██████╗     ██████╗ ███████╗███╗   ███╗ ██████╗ 
████╗ ████║╚══██╔══╝██╔══██╗    ██╔══██╗██╔════╝████╗ ████║██╔═══██╗
██╔████╔██║   ██║   ██████╔╝    ██║  ██║█████╗  ██╔████╔██║██║   ██║
██║╚██╔╝██║   ██║   ██╔═══╝     ██║  ██║██╔══╝  ██║╚██╔╝██║██║   ██║
██║ ╚═╝ ██║   ██║   ██║         ██████╔╝███████╗██║ ╚═╝ ██║╚██████╔╝
╚═╝     ╚═╝   ╚═╝   ╚═╝         ╚═════╝ ╚══════╝╚═╝     ╚═╝ ╚═════╝ 
                                                                    
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, OverbyteIcsWndControl, OverbyteIcsWSocket, iramtpCli,
  Vcl.StdCtrls, iraMtpSrv, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    MtpClient: TMtpClient;
    Button1: TButton;
    MtpServer: TMtpServer;
    Memo1: TMemo;
    Memo2: TMemo;
    Image1: TImage;
    lbl5: TLabel;
    Image2: TImage;
    Button2: TButton;
    procedure MtpClientBgException(Sender: TObject; E: Exception; var CanClose: Boolean);
    procedure MtpClientDisplay(Sender: TObject; var Msg: string);
    procedure MtpClientError(Sender: TObject; var Msg: string);
    procedure MtpClientResponse(Sender: TObject);
    procedure MtpClientSessionClosed(Sender: TObject; ErrCode: Word);
    procedure MtpClientSessionConnected(Sender: TObject; ErrCode: Word);
    procedure MtpClientStateChange(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure MtpServerBgException(Sender: TObject; E: Exception; var CanClose: Boolean);
    procedure MtpServerClientCommand(Sender: TObject; Client: TMtpCtrlSocket;  var Keyword, Params, Answer: TFtpString);
    procedure MtpServerClientConnect(Sender: TObject; Client: TMtpCtrlSocket;   AError: Word);
    procedure MtpServerStorSessionClosed(Sender: TObject; Client: TMtpCtrlSocket; Data: TWSocket; AError: Word);
    procedure FormCreate(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure MtpServerDisplay(Sender: TObject; Client: TMtpCtrlSocket;
      Msg: TFtpString);
  private
    { Private declarations }
    procedure DisplayC ( s: string);
    procedure DisplayS ( s: string);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  DIR_APP: string;
implementation

{$R *.dfm}
function LookupFtpState (const FtpState: TFtpState): String;
begin
   case FtpState of
      ftpNotConnected: result := 'Not Connected';
      ftpReady: result := 'Ready';
      ftpInternalReady: result := 'Internal Ready';
      ftpDnsLookup: result := 'DNS Lookup';
      ftpConnected: result := 'Connected';
      ftpAbort: result := 'Abort';
      ftpInternalAbort: result := 'Internal Abort';
      ftpWaitingResponse: result := 'Waiting Response';
   else
      result:='unknown';
   end;
end ;


procedure TForm1.Button1Click(Sender: TObject);
begin

  MtpClient.StreamSize := image1.Picture.Bitmap.Width * image1.Picture.Bitmap.height * 3  ;
  MtpClient.MemoryName := 'image1';
  MtpClient.MemoryPtr := image1.Picture.Bitmap.ScanLine [image1.Picture.Bitmap.Height -1] ;
  MtpClient.Put ;

end;

procedure TForm1.MtpClientBgException(Sender: TObject; E: Exception;  var CanClose: Boolean);
begin
  displayC ('exc ' + e.Message );

end;

procedure TForm1.MtpClientDisplay(Sender: TObject; var Msg: string);
var
Ptr: pointer;
BMP: TBitmap;
begin
  displayC(msg);

  if Msg = '< 226 File sent ok' then begin

    Ptr:=  Image1.Picture.Bitmap.scanline [Image1.Picture.Bitmap.Height -1] ;
    MtpClient.LocalStream.Read  ( Ptr^ , MtpClient.LocalStream.Size  );
    image1.Invalidate ;
  end;

end;

procedure TForm1.MtpClientError(Sender: TObject; var Msg: string);
begin
  DisplayC('error ' + Msg);

end;

procedure TForm1.MtpClientResponse(Sender: TObject);
begin
  DisplayC(MtpClient.LastResponse);

end;

procedure TForm1.MtpClientSessionClosed(Sender: TObject; ErrCode: Word);
begin
   lbl5.Font.Color:= clRed;
   lbl5.Caption := 'Mtp Closed';


end;

procedure TForm1.MtpClientSessionConnected(Sender: TObject; ErrCode: Word);
begin
If ErrCode = 0 then begin
  DisplayC ( 'Connessione a Mtp :OK') ;
  lbl5.Font.Color:= clgreen;
  lbl5.Caption := 'Mtp Connected';
end
 else
    DisplayC ( 'Connessione a Mtp Error:' + IntToStr(Errcode));

end;

procedure TForm1.MtpClientStateChange(Sender: TObject);
begin
  DisplayC(LookupFtpState (MtpClient.State ) );

end;

procedure TForm1.MtpServerBgException(Sender: TObject; E: Exception;   var CanClose: Boolean);
begin
  ShowMessage('except:' + e.Message );

end;

procedure TForm1.MtpServerClientCommand(Sender: TObject; Client: TMtpCtrlSocket;     var Keyword, Params, Answer: TFtpString);
begin
  DisplayS(Keyword + ' ' + params);
  if Keyword = 'RETR' then begin
    Client.DataStream.Position := 0;
    client.DataStream.Size := image2.Picture.Bitmap.Width * image2.Picture.Bitmap.height * 3;
  end;



end;

procedure TForm1.MtpServerClientConnect(Sender: TObject; Client: TMtpCtrlSocket;  AError: Word);
begin
    if MtpServer.ClientCount  >= MtpServer.MaxClients then begin
     Client.CloseDelayed ;
     Exit;
    end;


  DisplayS( 'Mtp: client connesso: ' + Client.peerAddr  );

end;

procedure TForm1.MtpServerDisplay(Sender: TObject; Client: TMtpCtrlSocket;  Msg: TFtpString);
begin
  displayS(Msg);
end;

procedure TForm1.MtpServerStorSessionClosed(Sender: TObject;   Client: TMtpCtrlSocket; Data: TWSocket; AError: Word);
var
Ptr: pointer;
BMP: TBitmap;
begin
  BMP:= Tbitmap.Create ;
  BMP.PixelFormat := pf24bit;
  BMP.Width := image1.picture.Bitmap.Width   ;
  BMP.Height := image1.picture.Bitmap.Height ;
  BMP.Canvas.Brush.Color:= clyellow;
  BMP.Canvas.FillRect(rect(0,0,BMP.Width,BMP.Height ) );
  image2.Picture.Assign(BMP);

  Client.DataStream.Position :=0;
  Ptr:=  Image2.Picture.Bitmap.scanline [Image2.Picture.Bitmap.Height -1] ;
  Client.DataStream.Read  ( Ptr^ , Client.DataStream.size );

  // il server modifica image2 e riscriva il client.datastream
  image2.Picture.Bitmap.Canvas.Font.Size := 24;
  image2.Picture.Bitmap.Canvas.TextOut(150,300 ,'MODIFIED');
  image2.Invalidate ;

  Client.DataStream.Position :=0;
  Client.DataStream.Write  ( Ptr^ , Client.DataStream.size );  // il size del Bitmap è sempre quello


end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  mtpclient.LocalStream.Position :=0;
  mtpClient.Get ;
end;

procedure Tform1.DisplayC ( s: string);
begin
  memo1.lines.add(S);
end;
procedure Tform1.DisplayS ( s: string);
begin
  memo2.lines.add(S);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  DIR_APP:= extractfilepath (application.ExeName);
  MtpServer.Start ;


  MtpClient.HostName  := '127.0.0.1';
  MtpClient.Port := 'ftp'; // o qualsiasi numero
  MtpClient.Open;

  memo1.Lines.Clear;
  memo2.Lines.Clear;
end;

end.


Commenti

Post più popolari