FTPSRV.PAS
上传用户:xdgkgcw
上传日期:2007-01-04
资源大小:92k
文件大小:95k
源码类别:

Delphi控件源码

开发平台:

WINDOWS

  1. {*_* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2. Author:       Fran鏾is PIETTE
  3. Description:  TFtpServer class encapsulate the FTP protocol (server side)
  4.               See RFC-959 for a complete protocol description.
  5. EMail:        francois.piette@pophost.eunet.be
  6.               francois.piette@rtfm.be             http://www.rtfm.be/fpiette
  7. Creation:     April 21, 1998
  8. Version:      1.04
  9. Support:      Use the mailing list twsocket@rtfm.be See website for details.
  10. Legal issues: Copyright (C) 1997, 1998 by Fran鏾is PIETTE
  11.               Rue de Grady 24, 4053 Embourg, Belgium. Fax: +32-4-365.74.56
  12.               <francois.piette@pophost.eunet.be>
  13.               This software is provided 'as-is', without any express or
  14.               implied warranty.  In no event will the author be held liable
  15.               for any  damages arising from the use of this software.
  16.               Permission is granted to anyone to use this software for any
  17.               purpose, including commercial applications, and to alter it
  18.               and redistribute it freely, subject to the following
  19.               restrictions:
  20.               1. The origin of this software must not be misrepresented,
  21.                  you must not claim that you wrote the original software.
  22.                  If you use this software in a product, an acknowledgment
  23.                  in the product documentation would be appreciated but is
  24.                  not required.
  25.               2. Altered source versions must be plainly marked as such, and
  26.                  must not be misrepresented as being the original software.
  27.               3. This notice may not be removed or altered from any source
  28.                  distribution.
  29.               4. You must register this software by sending a picture postcard
  30.                  to the author. Use a nice stamp and mention your name, street
  31.                  address, EMail address and any comment you like to say.
  32. History:
  33. Apr 29, 1998  V0.90 released for beta testing.
  34. May 01, 1998  V0.92 Adapted for Delphi 1.0
  35. May 03, 1998  V0.93 Adapted for Delphi 2.0 and C++Builder
  36. May 04, 1998  V0.94 Use '/' or '' as path delimiter. Expose only '/' to the
  37.               outside. Stripped any telnet options (IE send two !). Handled
  38.               absolute path. Implemented SIZE and REST commands.
  39.               Added support for UNC (not finished !)
  40. May 06, 1998  V0.95 Corrected spurious 226 message on PASV mode STOR.
  41.               Made GetInteger retunrs a LongInt.
  42.               Use a LongInt for N in CommandPORT (needed for 16 bits)
  43.               Added slash substitution in BuildFilePath command.
  44. Jul 09, 1998  V1.00 Adapted for Delphi 4, removed beta status.
  45. Jul 21, 1998  V1.01 Added OnValidateDele event
  46.               Changed function to get file size (do not open the file)
  47. Feb 14, 1999  V1.02 Replaced straight winsock call by indirect calls thru
  48.               wsocket (this provide runtime link to winsock DLL).
  49. Mar 06, 1999  V1.03 Added code from  Plegge, Steve <jsp@nciinc.com> to add
  50.               APPE, XMKD, KRMD and STRU commands support.
  51. Jul 24, 1999  V1.04 Replaced msgStorDisabled value from '500 Cannot STOR.' to
  52.               '501 Permission Denied' because CuteFTP doesn't like error 500.
  53.               Suggested by Cedric Veilleux <webmaster@smashweb.com>.
  54.  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  55. unit FtpSrv;
  56. {$B-}           { Enable partial boolean evaluation   }
  57. {$T-}           { Untyped pointers                    }
  58. {$IFNDEF VER80}
  59.     {$J+}       { Allow typed constant to be modified }
  60. {$ENDIF}
  61. {$IFDEF VER110} { C++ Builder V3.0                    }
  62.     {$ObjExportAll On}
  63. {$ENDIF}
  64. interface
  65. uses
  66.     WinTypes, WinProcs, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  67.     Dialogs, Winsock, WSocket, FtpSrvC;
  68. const
  69.     FtpServerVersion         = 104;
  70.     CopyRight : String       = ' TFtpServer (c) 1998 F. Piette V1.04 ';
  71.     WM_FTPSRV_CLOSE_REQUEST  = WM_USER + 1;
  72.     WM_FTPSRV_CLIENT_CLOSED  = WM_USER + 2;
  73.     WM_FTPSRV_ABORT_TRANSFER = WM_USER + 3;
  74.     WM_FTPSRV_CLOSE_DATA     = WM_USER + 4;
  75. type
  76.     FtpServerException  = class(Exception);
  77. { Various Delphi and C++Builder version handle string parameter passed as var }
  78. { differently. To get application code compatible across all versions, we     }
  79. { need to define our own string type. We use the larger we can with the given }
  80. { compiler version. btw: the 255 limit is not a problem because it applies to }
  81. { the command laines sent to the server and 255 should be enough except if    }
  82. { you use incredibly long file names.                                         }
  83. {$IFDEF VER100}                     { Delphi 3   }
  84.     TFtpString = String;
  85. {$ELSE}                             { All others }
  86.     TFtpString = String[255];
  87. {$ENDIF}
  88.     TFtpCtrlSocketClass = class of TFtpCtrlSocket;
  89.     TFtpSrvAuthenticateEvent  =  procedure (Sender   : TObject;
  90.                                             Client   : TFtpCtrlSocket;
  91.                                             UserName : TFtpString;
  92.                                             Password : TFtpString;
  93.                                             var Authenticated : Boolean) of object;
  94.     TFtpSrvChangeDirectoryEvent =  procedure (Sender      : TObject;
  95.                                               Client      : TFtpCtrlSocket;
  96.                                               Directory   : TFtpString;
  97.                                               var Allowed : Boolean) of object;
  98.     TFtpSrvBuildDirectoryEvent =  procedure (Sender        : TObject;
  99.                                              Client        : TFtpCtrlSocket;
  100.                                              var Directory : TFtpString;
  101.                                              Detailed      : Boolean) of object;
  102.     TFtpSrvClientConnectEvent = procedure (Sender : TObject;
  103.                                            Client : TFtpCtrlSocket;
  104.                                            Error  : Word) of object;
  105.     TFtpSrvDataSessionConnectedEvent = procedure (Sender : TObject;
  106.                                                   Client : TFtpCtrlSocket;
  107.                                                   Data   : TWSocket;
  108.                                                   Error  : Word) of object;
  109.     TFtpSrvClientCommandEvent = procedure (Sender        : TObject;
  110.                                            Client        : TFtpCtrlSocket;
  111.                                            var Keyword   : TFtpString;
  112.                                            var Params    : TFtpString;
  113.                                            var Answer    : TFtpString) of object;
  114.     TFtpSrvAnswerToClientEvent = procedure (Sender        : TObject;
  115.                                             Client        : TFtpCtrlSocket;
  116.                                             var Answer    : TFtpString) of object;
  117.     TFtpSrvValidateXferEvent  = procedure (Sender        : TObject;
  118.                                            Client        : TFtpCtrlSocket;
  119.                                            var FilePath  : TFtpString;
  120.                                            var Allowed   : Boolean) of object;
  121.     TFtpSrvDataAvailableEvent = procedure (Sender : TObject;
  122.                                            Client : TFtpCtrlSocket;
  123.                                            Data   : TWSocket;
  124.                                            Buf    : PChar;
  125.                                            Len    : LongInt;
  126.                                            Error  : Word) of object;
  127.     TFtpSrvRetrDataSentEvent  = procedure (Sender : TObject;
  128.                                            Client : TFtpCtrlSocket;
  129.                                            Data   : TWSocket;
  130.                                            Error  : Word) of object;
  131.     TFtpSrvCommandProc        = procedure (Client        : TFtpCtrlSocket;
  132.                                            var Keyword   : TFtpString;
  133.                                            var Params    : TFtpString;
  134.                                            var Answer    : TFtpString) of object;
  135.     TFtpSrvCommandTableItem   = record
  136.                                     KeyWord : String;
  137.                                     Proc    : TFtpSrvCommandProc;
  138.                                 end;
  139.     TFtpServer = class(TComponent)
  140.     protected
  141.         FPort                   : String;
  142.         FBanner                 : String;
  143.         FServSocket             : TWSocket;
  144.         FWindowHandle           : HWND;
  145.         FClientClass            : TFtpCtrlSocketClass;
  146.         FClientList             : TList;
  147.         FClientNum              : LongInt;
  148.         FMaxClients             : LongInt;
  149.         FCmdTable               : array [0..31] of TFtpSrvCommandTableItem;
  150.         FLastCmd                : Integer;
  151.         FUserData               : LongInt;      { Reserved for component user }
  152.         FOnStart                : TNotifyEvent;
  153.         FOnStop                 : TNotifyEvent;
  154.         FOnAuthenticate         : TFtpSrvAuthenticateEvent;
  155.         FOnClientConnect        : TFtpSrvClientConnectEvent;
  156.         FOnClientDisconnect     : TFtpSrvClientConnectEvent;
  157.         FOnClientCommand        : TFtpSrvClientCommandEvent;
  158.         FOnAnswerToClient       : TFtpSrvAnswerToClientEvent;
  159.         FOnChangeDirectory      : TFtpSrvChangeDirectoryEvent;
  160.         FOnMakeDirectory        : TFtpSrvChangeDirectoryEvent;
  161.         FOnBuildDirectory       : TFtpSrvBuildDirectoryEvent;
  162.         FOnAlterDirectory       : TFtpSrvBuildDirectoryEvent;
  163.         FOnValidatePut          : TFtpSrvValidateXferEvent;
  164.         FOnValidateDele         : TFtpSrvValidateXferEvent;
  165.         FOnStorSessionConnected : TFtpSrvDataSessionConnectedEvent;
  166.         FOnStorSessionClosed    : TFtpSrvDataSessionConnectedEvent;
  167.         FOnStorDataAvailable    : TFtpSrvDataAvailableEvent;
  168.         FOnValidateGet          : TFtpSrvValidateXferEvent;
  169.         FOnRetrSessionConnected : TFtpSrvDataSessionConnectedEvent;
  170.         FOnRetrSessionClosed    : TFtpSrvDataSessionConnectedEvent;
  171.         FOnRetrDataSent         : TFtpSrvRetrDataSentEvent;
  172.         procedure Notification(AComponent: TComponent; operation: TOperation); override;
  173.         procedure ServSocketSessionAvailable(Sender : TObject; Error : Word);
  174.         procedure ServSocketStateChange(Sender : TObject; OldState, NewState : TSocketState);
  175.         procedure ClientSessionClosed(Sender : TObject; Error : Word);
  176.         procedure ClientDataSent(Sender : TObject; Error : Word);
  177.         procedure ClientCommand(Sender : TObject; CmdBuf : PChar; CmdLen : Integer);
  178.         procedure ClientPassiveSessionAvailable(Sender : TObject; Error : Word);
  179.         procedure ClientStorSessionConnected(Sender : TObject; Error : Word);
  180.         procedure ClientStorSessionClosed(Sender : TObject; Error : Word);
  181.         procedure ClientStorDataAvailable(Sender: TObject; Error : word);
  182.         procedure ClientRetrSessionConnected(Sender : TObject; Error : Word);
  183.         procedure ClientRetrSessionClosed(Sender : TObject; Error : Word);
  184.         procedure ClientRetrDataSent(Sender : TObject; Error : Word);
  185.         procedure SendAnswer(Client : TFtpCtrlSocket; Answer : TFtpString);
  186.         procedure SendNextDataChunk(Client : TFtpCtrlSocket; Data : TWSocket);
  187.         procedure StartSendData(Client : TFtpCtrlSocket);
  188.         procedure BuildDirectory(Client : TFtpCtrlSocket; var Params : TFtpString; Stream : TStream; Detailed   : Boolean);
  189.         procedure TriggerServerStart; virtual;
  190.         procedure TriggerServerStop; virtual;
  191.         procedure TriggerAuthenticate(Client            : TFtpCtrlSocket;
  192.                                       UserName          : String;
  193.                                       PassWord          : String;
  194.                                       var Authenticated : Boolean); virtual;
  195.         procedure TriggerChangeDirectory(Client         : TFtpCtrlSocket;
  196.                                          Directory      : String;
  197.                                          var Allowed    : Boolean); virtual;
  198.         procedure TriggerMakeDirectory(Client         : TFtpCtrlSocket;
  199.                                        Directory      : String;
  200.                                        var Allowed    : Boolean); virtual;
  201.         procedure TriggerBuildDirectory(Client        : TFtpCtrlSocket;
  202.                                         var Params    : TFtpString;
  203.                                         Detailed      : Boolean);
  204.         procedure TriggerAlterDirectory(Client        : TFtpCtrlSocket;
  205.                                         var Params    : TFtpString;
  206.                                         Detailed      : Boolean);
  207.         procedure TriggerSendAnswer(Client : TFtpCtrlSocket;
  208.                                     var Answer : TFtpString); virtual;
  209.         procedure TriggerClientConnect(Client : TFtpCtrlSocket; Error : Word); virtual;
  210.         procedure TriggerClientDisconnect(Client : TFtpCtrlSocket; Error : Word); virtual;
  211.         procedure TriggerClientCommand(Client      : TFtpCtrlSocket;
  212.                                        var Keyword : TFtpString;
  213.                                        var Params  : TFtpString;
  214.                                        var Answer  : TFtpString); virtual;
  215.         procedure TriggerStorSessionConnected(Client : TFtpCtrlSocket;
  216.                                               Data   : TWSocket;
  217.                                               Error  : Word); virtual;
  218.         procedure TriggerStorSessionClosed(Client : TFtpCtrlSocket;
  219.                                            Data   : TWSocket;
  220.                                            Error  : Word); virtual;
  221.         procedure TriggerValidatePut(Client        : TFtpCtrlSocket;
  222.                                      var FilePath  : TFtpString;
  223.                                      var Allowed   : Boolean); virtual;
  224.         procedure TriggerValidateDele(Client        : TFtpCtrlSocket;
  225.                                       var FilePath  : TFtpString;
  226.                                       var Allowed   : Boolean); virtual;
  227.         procedure TriggerRetrSessionConnected(Client : TFtpCtrlSocket;
  228.                                               Data   : TWSocket;
  229.                                               Error  : Word); virtual;
  230.         procedure TriggerRetrSessionClosed(Client : TFtpCtrlSocket;
  231.                                            Data   : TWSocket;
  232.                                            Error  : Word); virtual;
  233.         procedure TriggerValidateGet(Client        : TFtpCtrlSocket;
  234.                                      var FilePath  : TFtpString;
  235.                                      var Allowed   : Boolean); virtual;
  236.         procedure TriggerStorDataAvailable(Client : TFtpCtrlSocket;
  237.                                        Data   : TWSocket;
  238.                                        Buf    : PChar;
  239.                                        Len    : LongInt;
  240.                                        Error  : Word); virtual;
  241.         procedure TriggerRetrDataSent(Client : TFtpCtrlSocket;
  242.                                        Data   : TWSocket;
  243.                                        Error  : Word); virtual;
  244.         function  GetClientCount : Integer; virtual;
  245.         function  GetActive : Boolean;
  246.         procedure SetActive(newValue : Boolean);
  247.         procedure AddCommand(const Keyword : String;
  248.                              const Proc : TFtpSrvCommandProc); virtual;
  249.         procedure WMFtpSrvCloseRequest(var msg: TMessage);
  250.                                        message WM_FTPSRV_CLOSE_REQUEST;
  251.         procedure WMFtpSrvClientClosed(var msg: TMessage);
  252.                                        message WM_FTPSRV_CLIENT_CLOSED;
  253.         procedure WMFtpSrvAbortTransfer(var msg: TMessage);
  254.                                        message WM_FTPSRV_ABORT_TRANSFER;
  255.         procedure WMFtpSrvCloseData(var msg: TMessage);
  256.                                        message WM_FTPSRV_CLOSE_DATA;
  257.         procedure CommandDirectory(Client      : TFtpCtrlSocket;
  258.                                    var Keyword : TFtpString;
  259.                                    var Params  : TFtpString;
  260.                                    var Answer  : TFtpString;
  261.                                    Detailed    : Boolean);
  262.         procedure CommandUSER(Client      : TFtpCtrlSocket;
  263.                               var Keyword : TFtpString;
  264.                               var Params  : TFtpString;
  265.                               var Answer  : TFtpString); virtual;
  266.         procedure CommandPASS(Client      : TFtpCtrlSocket;
  267.                               var Keyword : TFtpString;
  268.                               var Params  : TFtpString;
  269.                               var Answer  : TFtpString); virtual;
  270.         procedure CommandQUIT(Client      : TFtpCtrlSocket;
  271.                               var Keyword : TFtpString;
  272.                               var Params  : TFtpString;
  273.                               var Answer  : TFtpString); virtual;
  274.         procedure CommandNOOP(Client      : TFtpCtrlSocket;
  275.                               var Keyword : TFtpString;
  276.                               var Params  : TFtpString;
  277.                               var Answer  : TFtpString); virtual;
  278.         procedure CommandLIST(Client      : TFtpCtrlSocket;
  279.                               var Keyword : TFtpString;
  280.                               var Params  : TFtpString;
  281.                               var Answer  : TFtpString); virtual;
  282.         procedure CommandNLST(Client      : TFtpCtrlSocket;
  283.                               var Keyword : TFtpString;
  284.                               var Params  : TFtpString;
  285.                               var Answer  : TFtpString); virtual;
  286.         procedure CommandDELE(Client      : TFtpCtrlSocket;
  287.                               var Keyword : TFtpString;
  288.                               var Params  : TFtpString;
  289.                               var Answer  : TFtpString); virtual;
  290.         procedure CommandSIZE(Client      : TFtpCtrlSocket;
  291.                               var Keyword : TFtpString;
  292.                               var Params  : TFtpString;
  293.                               var Answer  : TFtpString); virtual;
  294.         procedure CommandREST(Client      : TFtpCtrlSocket;
  295.                               var Keyword : TFtpString;
  296.                               var Params  : TFtpString;
  297.                               var Answer  : TFtpString); virtual;
  298.         procedure CommandRNFR(Client      : TFtpCtrlSocket;
  299.                               var Keyword : TFtpString;
  300.                               var Params  : TFtpString;
  301.                               var Answer  : TFtpString); virtual;
  302.         procedure CommandRNTo(Client      : TFtpCtrlSocket;
  303.                               var Keyword : TFtpString;
  304.                               var Params  : TFtpString;
  305.                               var Answer  : TFtpString); virtual;
  306.         procedure CommandPORT(Client      : TFtpCtrlSocket;
  307.                               var Keyword : TFtpString;
  308.                               var Params  : TFtpString;
  309.                               var Answer  : TFtpString); virtual;
  310.         procedure CommandSTOR(Client      : TFtpCtrlSocket;
  311.                               var Keyword : TFtpString;
  312.                               var Params  : TFtpString;
  313.                               var Answer  : TFtpString); virtual;
  314.         procedure CommandRETR(Client      : TFtpCtrlSocket;
  315.                               var Keyword : TFtpString;
  316.                               var Params  : TFtpString;
  317.                               var Answer  : TFtpString); virtual;
  318.         procedure CommandTYPE(Client      : TFtpCtrlSocket;
  319.                               var Keyword : TFtpString;
  320.                               var Params  : TFtpString;
  321.                               var Answer  : TFtpString); virtual;
  322.         procedure CommandCWD (Client      : TFtpCtrlSocket;
  323.                               var Keyword : TFtpString;
  324.                               var Params  : TFtpString;
  325.                               var Answer  : TFtpString); virtual;
  326.         procedure CommandChangeDir(Client : TFtpCtrlSocket;
  327.                               var Keyword : TFtpString;
  328.                               var Params  : TFtpString;
  329.                               var Answer  : TFtpString); virtual;
  330.         procedure CommandMKD (Client      : TFtpCtrlSocket;
  331.                               var Keyword : TFtpString;
  332.                               var Params  : TFtpString;
  333.                               var Answer  : TFtpString); virtual;
  334.         procedure CommandRMD (Client      : TFtpCtrlSocket;
  335.                               var Keyword : TFtpString;
  336.                               var Params  : TFtpString;
  337.                               var Answer  : TFtpString); virtual;
  338.         procedure CommandCDUP(Client      : TFtpCtrlSocket;
  339.                               var Keyword : TFtpString;
  340.                               var Params  : TFtpString;
  341.                               var Answer  : TFtpString); virtual;
  342.         procedure CommandXPWD(Client      : TFtpCtrlSocket;
  343.                               var Keyword : TFtpString;
  344.                               var Params  : TFtpString;
  345.                               var Answer  : TFtpString); virtual;
  346.         procedure CommandPWD (Client      : TFtpCtrlSocket;
  347.                               var Keyword : TFtpString;
  348.                               var Params  : TFtpString;
  349.                               var Answer  : TFtpString); virtual;
  350.         procedure CommandSYST(Client      : TFtpCtrlSocket;
  351.                               var Keyword : TFtpString;
  352.                               var Params  : TFtpString;
  353.                               var Answer  : TFtpString); virtual;
  354.         procedure CommandABOR(Client      : TFtpCtrlSocket;
  355.                               var Keyword : TFtpString;
  356.                               var Params  : TFtpString;
  357.                               var Answer  : TFtpString); virtual;
  358.         procedure CommandPASV(Client      : TFtpCtrlSocket;
  359.                               var Keyword : TFtpString;
  360.                               var Params  : TFtpString;
  361.                               var Answer  : TFtpString); virtual;
  362.         procedure CommandAPPE(Client      : TFtpCtrlSocket;
  363.                               var Keyword : TFtpString;
  364.                               var Params  : TFtpString;
  365.                               var Answer  : TFtpString); virtual;
  366.         procedure CommandSTRU(Client      : TFtpCtrlSocket;
  367.                               var Keyword : TFtpString;
  368.                               var Params  : TFtpString;
  369.                               var Answer  : TFtpString); virtual;
  370.     public
  371.         constructor Create(AOwner: TComponent); override;
  372.         destructor  Destroy; override;
  373.         procedure   Start;
  374.         procedure   Stop;
  375.         procedure   DisconnectAll;
  376.         procedure WndProc(var MsgRec: TMessage);
  377.         property  ServSocket    : TWSocket            read  FServSocket;
  378.         property  Handle        : HWND                read  FWindowHandle;
  379.         property  ClientCount   : Integer             read  GetClientCount;
  380.         property  Active        : Boolean             read  GetActive
  381.                                                       write SetActive;
  382.         property  ClientClass            : TFtpCtrlSocketClass
  383.                                                       read  FClientClass
  384.                                                       write FClientClass;
  385.     published
  386.         property  Port                   : String     read  FPort
  387.                                                       write FPort;
  388.         property  Banner                 : String     read  FBanner
  389.                                                       write FBanner;
  390.         property  UserData               : LongInt    read  FUserData
  391.                                                       write FUserData;
  392.         property  MaxClients             : LongInt    read  FMaxClients
  393.                                                       write FMaxClients;
  394.         property  OnStart                : TNotifyEvent
  395.                                                       read  FOnStart
  396.                                                       write FOnStart;
  397.         property  OnStop                 : TNotifyEvent
  398.                                                       read  FOnStop
  399.                                                       write FOnStop;
  400.         property  OnAuthenticate         : TFtpSrvAuthenticateEvent
  401.                                                       read  FOnAuthenticate
  402.                                                       write FOnAuthenticate;
  403.         property  OnClientDisconnect     : TFtpSrvClientConnectEvent
  404.                                                       read  FOnClientDisconnect
  405.                                                       write FOnClientDisconnect;
  406.         property  OnClientConnect        : TFtpSrvClientConnectEvent
  407.                                                       read  FOnClientConnect
  408.                                                       write FOnClientConnect;
  409.         property  OnClientCommand        : TFtpSrvClientCommandEvent
  410.                                                       read  FOnClientCommand
  411.                                                       write FOnClientCommand;
  412.         property  OnAnswerToClient       : TFtpSrvAnswerToClientEvent
  413.                                                       read  FOnAnswerToClient
  414.                                                       write FOnAnswerToClient;
  415.         property  OnChangeDirectory      : TFtpSrvChangeDirectoryEvent
  416.                                                       read  FOnChangeDirectory
  417.                                                       write FOnChangeDirectory;
  418.         property  OnMakeDirectory        : TFtpSrvChangeDirectoryEvent
  419.                                                       read  FOnMakeDirectory
  420.                                                       write FOnMakeDirectory;
  421.         property  OnBuildDirectory       : TFtpSrvBuildDirectoryEvent
  422.                                                       read  FOnBuildDirectory
  423.                                                       write FOnBuildDirectory;
  424.         property  OnAlterDirectory       : TFtpSrvBuildDirectoryEvent
  425.                                                       read  FOnAlterDirectory
  426.                                                       write FOnAlterDirectory;
  427.         property  OnStorSessionConnected : TFtpSrvDataSessionConnectedEvent
  428.                                                       read  FOnStorSessionConnected
  429.                                                       write FOnStorSessionConnected;
  430.         property  OnRetrSessionConnected : TFtpSrvDataSessionConnectedEvent
  431.                                                       read  FOnRetrSessionConnected
  432.                                                       write FOnRetrSessionConnected;
  433.         property  OnStorSessionClosed    : TFtpSrvDataSessionConnectedEvent
  434.                                                       read  FOnStorSessionClosed
  435.                                                       write FOnStorSessionClosed;
  436.         property  OnRetrSessionClosed    : TFtpSrvDataSessionConnectedEvent
  437.                                                       read  FOnRetrSessionClosed
  438.                                                       write FOnRetrSessionClosed;
  439.         property  OnRetrDataSent         : TFtpSrvRetrDataSentEvent
  440.                                                       read  FOnRetrDataSent
  441.                                                       write FOnRetrDataSent;
  442.         property  OnValidatePut          : TFtpSrvValidateXferEvent
  443.                                                       read  FOnValidatePut
  444.                                                       write FOnValidatePut;
  445.         property  OnValidateDele         : TFtpSrvValidateXferEvent
  446.                                                       read  FOnValidateDele
  447.                                                       write FOnValidateDele;
  448.         property  OnValidateGet          : TFtpSrvValidateXferEvent
  449.                                                       read  FOnValidateGet
  450.                                                       write FOnValidateGet;
  451.         property  OnStorDataAvailable    : TFtpSrvDataAvailableEvent
  452.                                                       read  FOnStorDataAvailable
  453.                                                       write FOnStorDataAvailable;
  454.     end;
  455. procedure Register;
  456. implementation
  457. const
  458.     msgDftBanner      = '220 ICS FTP Server ready.';
  459.     msgTooMuchClients = '421 Too many users connected.';
  460.     msgCmdUnknown     = '500 ''%s'': command not understood.';
  461.     msgLoginFailed    = '530 Login incorrect.';
  462.     msgNotLogged      = '530 Please login with USER and PASS.';
  463.     msgNoUser         = '503 Login with USER first.';
  464.     msgLogged         = '230 User %s logged in.';
  465.     msgPassRequired   = '331 Password required for %s.';
  466.     msgCWDSuccess     = '250 CWD command successful. "%s" is current directory.';
  467.     msgCWDFailed      = '501 CWD failed. %s';
  468.     msgPWDSuccess     = '257 "%s" is current directory.';
  469.     msgQuit           = '221 Goodbye.';
  470.     msgPortSuccess    = '200 Port command successful.';
  471.     msgPortFailed     = '501 Invalid PORT command.';
  472.     msgStorDisabled   = '501 Permission Denied'; {'500 Cannot STOR.';}
  473.     msgStorSuccess    = '150 Opening data connection for %s.';
  474.     msgStorFailed     = '501 Cannot STOR. %s';
  475.     msgStorAborted    = '426 Connection closed; %s.';
  476.     msgStorOk         = '226 File received ok';
  477.     msgStorError      = '426 Connection closed; transfer aborted. Error #%d';
  478.     msgRetrDisabled   = '500 Cannot RETR.';
  479.     msgRetrSuccess    = '150 Opening data connection for %s.';
  480.     msgRetrFailed     = '501 Cannot RETR. %s';
  481.     msgRetrAborted    = '426 Connection closed; %s.';
  482.     msgRetrOk         = '226 File sent ok';
  483.     msgRetrError      = '426 Connection closed; transfer aborted. Error #%d';
  484.     msgSystem         = '215 UNIX Type: L8 Internet Component Suite';
  485.     msgDirOpen        = '150 Opening data connection for directory list.';
  486.     msgDirFailed      = '451 Failed: %s.';
  487.     msgTypeOk         = '200 Type set to %s.';
  488.     msgTypeFailed     = '500 ''TYPE %s'': command not understood.';
  489.     msgDeleNotExists  = '550 ''%s'': no such file or directory.';
  490.     msgDeleOk         = '250 File ''%s'' deleted.';
  491.     msgDeleFailed     = '450 File ''%s'' can''t be deleted.';
  492.     msgDeleSyntax     = '501 Syntax error in parameter.';
  493.     msgDeleDisabled   = '500 Cannot DELE.';
  494.     msgRnfrNotExists  = '550 ''%s'': no such file or directory.';
  495.     msgRnfrSyntax     = '501 Syntax error is parameter.';
  496.     msgRnfrOk         = '350 File exists, ready for destination name.';
  497.     msgRntoNotExists  = '550 ''%s'': no such file or directory.';
  498.     msgRntoAlready    = '553 ''%s'': file already exists.';
  499.     msgRntoOk         = '250 File ''%s'' renamed to ''%s''.';
  500.     msgRntoFailed     = '450 File ''%s'' can''t be renamed.';
  501.     msgRntoSyntax     = '501 Syntax error in parameter.';
  502.     msgMkdOk          = '257 ''%s'': directory created.';
  503.     msgMkdAlready     = '550 ''%s'': file or directory already exists.';
  504.     msgMkdFailed      = '550 ''%s'': can''t create directory.';
  505.     msgMkdSyntax      = '501 Syntax error in parameter.';
  506.     msgRmdOk          = '250 ''%s'': directory removed.';
  507.     msgRmdNotExists   = '550 ''%s'': no such directory.';
  508.     msgRmdFailed      = '550 ''%s'': can''t remove directory.';
  509.     msgRmdSyntax      = '501 Syntax error in parameter.';
  510.     msgNoopOk         = '200 Ok. Parameter was ''%s''.';
  511.     msgAborOk         = '225 ABOR command successful.';
  512.     msgPasvLocal      = '227 Entering Passive Mode (127,0,0,1,%d,%d).';
  513.     msgPasvRemote     = '227 Entering Passive Mode (%d,%d,%d,%d,%d,%d).';
  514.     msgPasvExcept     = '500 PASV exception: ''%s''.';
  515.     msgSizeOk         = '213 %d';
  516.     msgSizeFailed     = '550 Command failed: %s.';
  517.     msgSizeSyntax     = '501 Syntax error in parameter.';
  518.     msgRestOk         = '350 REST supported. Ready to resume at byte offset %d.';
  519.     msgRestZero       = '501 Required byte offset parameter bad or missing.';
  520.     msgRestFailed     = '501 Syntax error in parameter: %s.';
  521.     msgAppeFailed     = '550 APPE failed.';
  522.     msgAppeSuccess    = '150 Opening data connection for %s (append).';
  523.     msgAppeDisabled   = '500 Cannot APPE.';
  524.     msgAppeAborted    = '426 Connection closed; %s.';
  525.     msgAppeOk         = '226 File received ok';
  526.     msgAppeError      = '426 Connection closed; transfer aborted. Error #%d';
  527.     msgAppeReady      = '150 APPE supported.  Ready to append file "%s" at offset %d.';
  528.     msgStruOk         = '200 Ok. STRU parameter ''%s'' ignored.';
  529. function SlashesToBackSlashes(const S : String) : String; forward;
  530. function BackSlashesToSlashes(const S : String) : String; forward;
  531. function BuildFilePath(const Directory : String;
  532.                        FileName        : String) : String; forward;
  533. var
  534.     ThisYear, ThisMonth, ThisDay : Word;
  535. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  536. procedure Register;
  537. begin
  538.     RegisterComponents('FPiette', [TFtpServer]);
  539. end;
  540. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  541. {$IFDEF VER80}
  542. procedure SetLength(var S: string; NewLength: Integer);
  543. begin
  544.     S[0] := chr(NewLength);
  545. end;
  546. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  547. function TrimRight(Str : String) : String;
  548. var
  549.     i : Integer;
  550. begin
  551.     i := Length(Str);
  552.     while (i > 0) and (Str[i] = ' ') do
  553.         i := i - 1;
  554.     Result := Copy(Str, 1, i);
  555. end;
  556. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  557. function TrimLeft(Str : String) : String;
  558. var
  559.     i : Integer;
  560. begin
  561.     if Str[1] <> ' ' then
  562.         Result := Str
  563.     else begin
  564.         i := 1;
  565.         while (i <= Length(Str)) and (Str[i] = ' ') do
  566.             i := i + 1;
  567.         Result := Copy(Str, i, Length(Str) - i + 1);
  568.     end;
  569. end;
  570. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  571. function Trim(Str : String) : String;
  572. begin
  573.     Result := TrimLeft(TrimRight(Str));
  574. end;
  575. {$ENDIF}
  576. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  577. function GetFileSize(FileName : String) : LongInt;
  578. var
  579.     SR : TSearchRec;
  580. begin
  581.     if FindFirst(FileName, faReadOnly or faHidden or
  582.                  faSysFile or faArchive, SR) = 0 then
  583.         Result := SR.Size
  584.     else
  585.         Result := -1;
  586.     FindClose(SR);
  587. end;
  588. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  589. constructor TFtpServer.Create(AOwner: TComponent);
  590. begin
  591.     inherited Create(AOwner);
  592.     FWindowHandle    := AllocateHWnd(WndProc);
  593.     FServSocket      := TWSocket.Create(Self);
  594.     FServSocket.Name := 'ServerWSocket';
  595.     FClientList      := TList.Create;
  596.     FPort            := 'ftp';
  597.     FBanner          := msgDftBanner;
  598.     FClientClass     := TFtpCtrlSocket;
  599.     AddCommand('PORT', CommandPORT);
  600.     AddCommand('STOR', CommandSTOR);
  601.     AddCommand('RETR', CommandRETR);
  602.     AddCommand('CWD',  CommandCWD);
  603.     AddCommand('XPWD', CommandXPWD);
  604.     AddCommand('PWD',  CommandPWD);
  605.     AddCommand('USER', CommandUSER);
  606.     AddCommand('PASS', CommandPASS);
  607.     AddCommand('LIST', CommandLIST);
  608.     AddCommand('NLST', CommandNLST);
  609.     AddCommand('TYPE', CommandTYPE);
  610.     AddCommand('SYST', CommandSYST);
  611.     AddCommand('QUIT', CommandQUIT);
  612.     AddCommand('DELE', CommandDELE);
  613.     AddCommand('SIZE', CommandSIZE);
  614.     AddCommand('REST', CommandREST);
  615.     AddCommand('RNFR', CommandRNFR);
  616.     AddCommand('RNTO', CommandRNTO);
  617.     AddCommand('MKD',  CommandMKD);
  618.     AddCommand('RMD',  CommandRMD);
  619.     AddCommand('ABOR', CommandABOR);
  620.     AddCommand('PASV', CommandPASV);
  621.     AddCommand('NOOP', CommandNOOP);
  622.     AddCommand('CDUP', CommandCDUP);
  623.     AddCommand('APPE', CommandAPPE);
  624.     AddCommand('STRU', CommandSTRU);
  625.     AddCommand('XMKD', CommandMKD);
  626.     AddCommand('XRMD', CommandRMD);
  627. end;
  628. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  629. destructor TFtpServer.Destroy;
  630. begin
  631.     if Assigned(FServSocket) then begin
  632.         FServSocket.Destroy;
  633.         FServSocket := nil;
  634.     end;
  635.     if Assigned(FClientList) then begin
  636.         FClientList.Destroy;
  637.         FClientList := nil;
  638.     end;
  639.     DeallocateHWnd(FWindowHandle);
  640.     inherited Destroy;
  641. end;
  642. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  643. procedure TFtpServer.WndProc(var MsgRec: TMessage);
  644. begin
  645.     with MsgRec do begin
  646.         case Msg of
  647.         WM_FTPSRV_CLOSE_REQUEST  : WMFtpSrvCloseRequest(MsgRec);
  648.         WM_FTPSRV_CLIENT_CLOSED  : WMFtpSrvClientClosed(MsgRec);
  649.         WM_FTPSRV_ABORT_TRANSFER : WMFtpSrvAbortTransfer(MsgRec);
  650.         WM_FTPSRV_CLOSE_DATA     : WMFtpSrvCloseData(MsgRec);
  651.         else
  652.             Result := DefWindowProc(Handle, Msg, wParam, lParam);
  653.         end;
  654.     end;
  655. end;
  656. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  657. procedure TFtpServer.WMFtpSrvCloseRequest(var msg: TMessage);
  658. var
  659.     Client : TFtpCtrlSocket;
  660. begin
  661.     Client := TFtpCtrlSocket(msg.LParam);
  662.     if Client.AllSent then
  663.         Client.Close
  664.     else
  665.         Client.CloseRequest := TRUE;
  666. end;
  667. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  668. procedure TFtpServer.Notification(AComponent: TComponent; operation: TOperation);
  669. begin
  670.     inherited Notification(AComponent, operation);
  671.     if operation = opRemove then begin
  672.         if AComponent = FServSocket then
  673.             FServSocket := nil;
  674.     end;
  675. end;
  676. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  677. procedure TFtpServer.AddCommand(
  678.     const Keyword : String;
  679.     const Proc    : TFtpSrvCommandProc);
  680. begin
  681.     if FLastCmd > High(FCmdTable) then
  682.         raise FtpServerException.Create('Too many command');
  683.     FCmdTable[FLastCmd].KeyWord := KeyWord;
  684.     FCmdTable[FLastCmd].Proc    := Proc;
  685.     Inc(FLastCmd);
  686. end;
  687. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  688. procedure TFtpServer.Start;
  689. begin
  690.     if FServSocket.State = wsListening then
  691.         Exit;             { Server is already running }
  692.     FServSocket.Port  := Port;
  693.     FServSocket.Proto := 'tcp';
  694.     FServSocket.Addr  := '0.0.0.0';
  695.     FServSocket.OnSessionAvailable := ServSocketSessionAvailable;
  696.     FServSocket.OnChangeState      := ServSocketStateChange;
  697.     FServSocket.Listen;
  698. end;
  699. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  700. procedure TFtpServer.Stop;
  701. begin
  702.     FServSocket.Close;
  703. end;
  704. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  705. procedure TFtpServer.DisconnectAll;
  706. var
  707.     Client : TFtpCtrlSocket;
  708. begin
  709.     while FClientList.Count > 0 do begin
  710.         Client := TFtpCtrlSocket(FClientList.Items[0]);
  711.         Client.Close;
  712.         FClientList.Remove(Client);
  713.     end;
  714. end;
  715. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  716. function TFtpServer.GetActive : Boolean;
  717. begin
  718.     Result := (FServSocket.State = wsListening);
  719. end;
  720. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  721. procedure TFtpServer.SetActive(newValue : Boolean);
  722. begin
  723.     if newValue then
  724.         Start
  725.     else
  726.         Stop;
  727. end;
  728. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  729. procedure TFtpServer.ServSocketStateChange(Sender : TObject; OldState, NewState : TSocketState);
  730. begin
  731.     if csDestroying in ComponentState then
  732.         Exit;
  733.     if NewState = wsListening then
  734.         TriggerServerStart
  735.     else if NewState = wsClosed then
  736.         TriggerServerStop;
  737. end;
  738. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  739. procedure TFtpServer.ServSocketSessionAvailable(Sender : TObject; Error : Word);
  740. var
  741.     Client : TFtpCtrlSocket;
  742. begin
  743.     if Error <> 0 then
  744.         raise FtpServerException.Create('Session available error #' + IntToStr(Error));
  745.     Inc(FClientNum);
  746.     Client                 := FClientClass.Create(Self);
  747.     FClientList.Add(Client);
  748.     Client.Name            := 'ClientWSocket' + IntToStr(FClientNum);
  749.     Client.DataSocket.Name := 'DataWSocket' + IntToStr(FClientNum);
  750.     Client.Banner          := FBanner;
  751.     Client.HSocket         := ServSocket.Accept;
  752.     Client.OnCommand       := ClientCommand;
  753.     Client.OnSessionClosed := ClientSessionClosed;
  754.     Client.OnDataSent      := ClientDataSent;
  755.     TriggerClientConnect(Client, Error);
  756.     { The event handler may have destroyed the client ! }
  757.     if FClientList.IndexOf(Client) < 0 then
  758.         Exit;
  759.     { The event handler may have closed the connection }
  760.     if Client.State <> wsConnected then
  761.         Exit;
  762.     { Ok, the client is still there, process with the connection }
  763.     if (FMaxClients > 0) and (FMaxClients < ClientCount) then begin
  764.         { Sorry, toomuch clients }
  765.         Client.Banner := msgTooMuchClients;
  766.         Client.StartConnection;
  767.         Client.Close;
  768.     end
  769.     else
  770.         Client.StartConnection;
  771. end;
  772. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  773. procedure TFtpServer.SendAnswer(Client : TFtpCtrlSocket; Answer : TFtpString);
  774. begin
  775.     try
  776.         TriggerSendAnswer(Client, Answer);
  777.         Client.SendAnswer(Answer);
  778.     except
  779.         { Just ignore any exception here }
  780.     end;
  781. end;
  782. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  783. procedure TFtpServer.ClientCommand(
  784.     Sender : TObject;
  785.     CmdBuf : PChar;
  786.     CmdLen : Integer);
  787. const
  788.     TELNET_IAC       = #255;
  789.     TELNET_IP        = #244;
  790.     TELNET_DATA_MARK = #242;
  791. var
  792.     Client  : TFtpCtrlSocket;
  793.     Answer  : TFtpString;
  794.     Params  : TFtpString;
  795.     KeyWord : TFtpString;
  796.     I, J    : Integer;
  797. begin
  798.     Client := Sender as TFtpCtrlSocket;
  799.     Answer := '';
  800.     { Copy the command received, removing any telnet option }
  801.     try
  802.         Params := '';
  803.         I      := 0;
  804.         while I < CmdLen do begin
  805.             if CmdBuf[I] <> TELNET_IAC then begin
  806.                 Params := Params + CmdBuf[I];
  807.                 Inc(I);
  808.             end
  809.             else begin
  810.                 Inc(I);
  811.                 if CmdBuf[I] = TELNET_IAC then
  812.                     Params := Params + CmdBuf[I];
  813.                 Inc(I);
  814.             end;
  815.         end;
  816.         { Extract keyword, ignoring leading spaces and tabs }
  817.         I := 1;
  818.         while (I <= Length(Params)) and (Params[I] in [' ', #9]) do
  819.             Inc(I);
  820.         J := I;
  821.         while (J <= Length(Params)) and (Params[J] in ['A'..'Z', 'a'..'z', '0'..'9']) do
  822.             Inc(J);
  823.         KeyWord := UpperCase(Copy(Params, I, J - I));
  824.         { Extract parameters, ignoring leading spaces and tabs }
  825.         while (J <= Length(Params)) and (Params[J] in [' ', #9]) do
  826.             Inc(J);
  827.         Params := Copy(Params, J, Length(Params));
  828.         { Pass the command to the component user to let him a chance to }
  829.         { handle it. If it does, he must return the answer.             }
  830.         TriggerClientCommand(Client, Keyword, Params, Answer);
  831.         if Answer <> '' then begin
  832.             { Event handler has processed the client command, send the answer }
  833.             SendAnswer(Client, Answer);
  834.             Exit;
  835.         end;
  836.         { The command has not been processed, we'll process it }
  837.         if Keyword = '' then begin
  838.             { Empty keyword (should never occurs) }
  839.             SendAnswer(Client, Format(msgCmdUnknown, [Params]));
  840.             Exit;
  841.         end;
  842.         { We need to process the client command, search our command table }
  843.         I := 0;
  844.         while I <= High(FCmdTable) do begin
  845.             if FCmdTable[I].KeyWord = KeyWord then begin
  846.                 FCmdTable[I].Proc(Client, KeyWord, Params, Answer);
  847.                 SendAnswer(Client, Answer);
  848.                 Exit;
  849.             end;
  850.             Inc(I);
  851.         end;
  852.         SendAnswer(Client, Format(msgCmdUnknown, [KeyWord]));
  853.     except
  854.         on E:Exception do begin
  855.             SendAnswer(Client, '501 ' + E.Message);
  856.         end;
  857.     end;
  858. end;
  859. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  860. procedure TFtpServer.ClientDataSent(Sender : TObject; Error : Word);
  861. var
  862.     Client  : TFtpCtrlSocket;
  863. begin
  864.     Client := Sender as TFtpCtrlSocket;
  865.     if Client.CloseRequest then begin
  866.         Client.CloseRequest := FALSE;
  867.         PostMessage(FWindowHandle, WM_FTPSRV_CLOSE_REQUEST, 0, LongInt(Client));
  868.     end;
  869. end;
  870. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  871. procedure TFtpServer.ClientSessionClosed(Sender : TObject; Error : Word);
  872. begin
  873.     PostMessage(FWindowHandle, WM_FTPSRV_CLIENT_CLOSED, 0, LongInt(Sender));
  874. end;
  875. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  876. procedure TFtpServer.WMFtpSrvClientClosed(var msg: TMessage);
  877. var
  878.     Client : TFtpCtrlSocket;
  879. begin
  880.     Client := TFtpCtrlSocket(Msg.LParam);
  881.     try
  882.         FClientList.Remove(Client);
  883.         TriggerClientDisconnect(Client, Error);
  884.     finally
  885.         Client.Destroy;
  886.     end;
  887. end;
  888. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  889. procedure TFtpServer.WMFtpSrvAbortTransfer(var msg: TMessage);
  890. var
  891.     Data : TWSocket;
  892. begin
  893.     Data := TWSocket(Msg.LParam);
  894.     Data.ShutDown(2);
  895.     Data.Close;
  896. end;
  897. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  898. procedure TFtpServer.WMFtpSrvCloseData(var msg: TMessage);
  899. var
  900.     Data : TWSocket;
  901. begin
  902.     if msg.WParam > 0 then begin
  903. {$IFNDEF VER80}
  904.         Sleep(0);  { Release time slice }
  905. {$ENDIF}
  906.         PostMessage(FWindowHandle, Msg.Msg, msg.WParam - 1, msg.LParam);
  907.     end
  908.     else begin
  909.         Data := TWSocket(Msg.LParam);
  910.         Data.Close;
  911.     end;
  912. end;
  913. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  914. function TFtpServer.GetClientCount : Integer;
  915. begin
  916.     if Assigned(FClientList) then
  917.         Result := FClientList.Count
  918.     else
  919.         Result := 0;
  920. end;
  921. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  922. procedure TFtpServer.TriggerServerStart;
  923. begin
  924.     if Assigned(FOnStart) then
  925.         FOnStart(Self);
  926. end;
  927. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  928. procedure TFtpServer.TriggerServerStop;
  929. begin
  930.     if Assigned(FOnStop) then
  931.         FOnStop(Self);
  932. end;
  933. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  934. procedure TFtpServer.TriggerAuthenticate(
  935.     Client            : TFtpCtrlSocket;
  936.     UserName          : String;
  937.     PassWord          : String;
  938.     var Authenticated : Boolean);
  939. begin
  940.     if Assigned(FOnAuthenticate) then
  941.         FOnAuthenticate(Self, Client, UserName, Password, Authenticated);
  942. end;
  943. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  944. procedure TFtpServer.TriggerChangeDirectory(
  945.     Client         : TFtpCtrlSocket;
  946.     Directory      : String;
  947.     var Allowed    : Boolean);
  948. begin
  949.     if Assigned(FOnChangeDirectory) then
  950.         FOnChangeDirectory(Self, Client, Directory, Allowed);
  951. end;
  952. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  953. procedure TFtpServer.TriggerMakeDirectory(
  954.     Client         : TFtpCtrlSocket;
  955.     Directory      : String;
  956.     var Allowed    : Boolean);
  957. begin
  958.     if Assigned(FOnMakeDirectory) then
  959.         FOnMakeDirectory(Self, Client, Directory, Allowed);
  960. end;
  961. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  962. procedure TFtpServer.TriggerBuildDirectory(
  963.     Client        : TFtpCtrlSocket;
  964.     var Params    : TFtpString;
  965.     Detailed      : Boolean);
  966. begin
  967.     if Assigned(FOnBuildDirectory) then
  968.         FOnBuildDirectory(Self, Client, Params, Detailed);
  969. end;
  970. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  971. procedure TFtpServer.TriggerAlterDirectory(
  972.     Client        : TFtpCtrlSocket;
  973.     var Params    : TFtpString;
  974.     Detailed      : Boolean);
  975. begin
  976.     if Assigned(FOnAlterDirectory) then
  977.         FOnAlterDirectory(Self, Client, Params, Detailed);
  978. end;
  979. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  980. procedure TFtpServer.TriggerSendAnswer(
  981.     Client     : TFtpCtrlSocket;
  982.     var Answer : TFtpString);
  983. begin
  984.     if Assigned(FOnAnswerToClient) then
  985.         FOnAnswerToClient(Self, Client, Answer);
  986. end;
  987. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  988. procedure TFtpServer.TriggerClientDisconnect(Client : TFtpCtrlSocket; Error : Word);
  989. begin
  990.     if Assigned(FOnClientDisconnect) then
  991.         FOnClientDisconnect(Self, Client, Error);
  992. end;
  993. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  994. procedure TFtpServer.TriggerClientConnect(Client : TFtpCtrlSocket; Error : Word);
  995. begin
  996.     if Assigned(FOnClientConnect) then
  997.         FOnClientConnect(Self, Client, Error);
  998. end;
  999. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1000. procedure TFtpServer.TriggerStorSessionConnected(
  1001.     Client : TFtpCtrlSocket; Data : TWSocket; Error : Word);
  1002. begin
  1003.     if Assigned(FOnStorSessionConnected) then
  1004.         FOnStorSessionConnected(Self, Client, Data, Error);
  1005. end;
  1006. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1007. procedure TFtpServer.TriggerRetrSessionConnected(
  1008.     Client : TFtpCtrlSocket; Data : TWSocket; Error : Word);
  1009. begin
  1010.     if Assigned(FOnRetrSessionConnected) then
  1011.         FOnRetrSessionConnected(Self, Client, Data, Error);
  1012. end;
  1013. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1014. procedure TFtpServer.TriggerStorSessionClosed(
  1015.     Client : TFtpCtrlSocket; Data : TWSocket; Error : Word);
  1016. begin
  1017.     if Assigned(FOnStorSessionClosed) then
  1018.         FOnStorSessionClosed(Self, Client, Data, Error);
  1019. end;
  1020. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1021. procedure TFtpServer.TriggerRetrSessionClosed(
  1022.     Client : TFtpCtrlSocket; Data : TWSocket; Error : Word);
  1023. begin
  1024.     if Assigned(FOnRetrSessionClosed) then
  1025.         FOnRetrSessionClosed(Self, Client, Data, Error);
  1026. end;
  1027. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1028. procedure TFtpServer.TriggerClientCommand(
  1029.     Client      : TFtpCtrlSocket;
  1030.     var Keyword : TFtpString;
  1031.     var Params  : TFtpString;
  1032.     var Answer  : TFtpString);
  1033. begin
  1034.     if Assigned(FOnClientCommand) then
  1035.         FOnClientCommand(Self, Client, KeyWord, Params, Answer);
  1036. end;
  1037. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1038. procedure TFtpServer.TriggerValidatePut(
  1039.     Client        : TFtpCtrlSocket;
  1040.     var FilePath  : TFtpString;
  1041.     var Allowed   : Boolean);
  1042. begin
  1043.     if Assigned(FOnValidatePut) then
  1044.         FOnValidatePut(Self, Client, FilePath, Allowed);
  1045. end;
  1046. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1047. procedure TFtpServer.TriggerValidateDele(
  1048.     Client        : TFtpCtrlSocket;
  1049.     var FilePath  : TFtpString;
  1050.     var Allowed   : Boolean);
  1051. begin
  1052.     if Assigned(FOnValidateDele) then
  1053.         FOnValidateDele(Self, Client, FilePath, Allowed);
  1054. end;
  1055. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1056. procedure TFtpServer.TriggerValidateGet(
  1057.     Client        : TFtpCtrlSocket;
  1058.     var FilePath  : TFtpString;
  1059.     var Allowed   : Boolean);
  1060. begin
  1061.     if Assigned(FOnValidateGet) then
  1062.         FOnValidateGet(Self, Client, FilePath, Allowed);
  1063. end;
  1064. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1065. procedure TFtpServer.TriggerStorDataAvailable(
  1066.     Client : TFtpCtrlSocket;
  1067.     Data   : TWSocket;
  1068.     Buf    : PChar;
  1069.     Len    : LongInt;
  1070.     Error  : Word);
  1071. begin
  1072.     if Assigned(FOnStorDataAvailable) then
  1073.         FOnStorDataAvailable(Self, Client, Data, Buf, Len, Error);
  1074. end;
  1075. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1076. procedure TFtpServer.TriggerRetrDataSent(
  1077.     Client : TFtpCtrlSocket;
  1078.     Data   : TWSocket;
  1079.     Error  : Word);
  1080. begin
  1081.     if Assigned(FOnRetrDataSent) then
  1082.         FOnRetrDataSent(Self, Client, Data, Error);
  1083. end;
  1084. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1085. procedure TFtpServer.CommandUSER(
  1086.     Client      : TFtpCtrlSocket;
  1087.     var Keyword : TFtpString;
  1088.     var Params  : TFtpString;
  1089.     var Answer  : TFtpString);
  1090. begin
  1091.     Client.CurCmdType := ftpcUSER;
  1092.     Client.UserName   := Trim(Params);
  1093.     Client.FtpState   := ftpcWaitingPassword;
  1094.     Answer            := Format(msgPassRequired, [Client.UserName]);
  1095. end;
  1096. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1097. procedure TFtpServer.CommandPASS(
  1098.     Client      : TFtpCtrlSocket;
  1099.     var Keyword : TFtpString;
  1100.     var Params  : TFtpString;
  1101.     var Answer  : TFtpString);
  1102. var
  1103.     Authenticated : Boolean;
  1104. begin
  1105.     if Client.FtpState <> ftpcWaitingPassword then
  1106.         Answer := msgNoUser
  1107.     else begin
  1108.         Client.CurCmdType    := ftpcPASS;
  1109.         Client.PassWord      := Trim(Params);
  1110.         Authenticated        := TRUE;
  1111.         TriggerAuthenticate(Client, Client.UserName, Client.PassWord, Authenticated);
  1112.         if Authenticated then begin
  1113.             Client.FtpState  := ftpcReady;
  1114.             Client.Directory := Client.HomeDir;
  1115.             Answer           := Format(msgLogged, [Client.UserName])
  1116.         end
  1117.         else begin
  1118.             Client.FtpState  := ftpcWaitingUserCode;
  1119.             Answer           := msgLoginFailed;
  1120.         end;
  1121.     end;
  1122. end;
  1123. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1124. procedure TFtpServer.CommandCDUP(
  1125.     Client      : TFtpCtrlSocket;
  1126.     var Keyword : TFtpString;
  1127.     var Params  : TFtpString;
  1128.     var Answer  : TFtpString);
  1129. begin
  1130.     if Client.FtpState <> ftpcReady then begin
  1131.         Answer := msgNotLogged;
  1132.         Exit;
  1133.     end;
  1134.     Client.CurCmdType := ftpcCDUP;
  1135.     Params := '..';
  1136.     CommandChangeDir(Client, Keyword, Params, Answer);
  1137. end;
  1138. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1139. procedure TFtpServer.CommandCWD(
  1140.     Client      : TFtpCtrlSocket;
  1141.     var Keyword : TFtpString;
  1142.     var Params  : TFtpString;
  1143.     var Answer  : TFtpString);
  1144. begin
  1145.     if Client.FtpState <> ftpcReady then begin
  1146.         Answer := msgNotLogged;
  1147.         Exit;
  1148.     end;
  1149.     Client.CurCmdType    := ftpcCWD;
  1150.     CommandChangeDir(Client, Keyword, Params, Answer);
  1151. end;
  1152. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1153. function SlashesToBackSlashes(const S : String) : String;
  1154. var
  1155.     I : Integer;
  1156. begin
  1157.     Result := S;
  1158.     for I := 1 to Length(Result) do begin
  1159.         if Result [I] = '/' then
  1160.             Result[I] := '';
  1161.     end;
  1162. end;
  1163. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1164. function BackSlashesToSlashes(const S : String) : String;
  1165. var
  1166.     I : Integer;
  1167. begin
  1168.     Result := S;
  1169.     for I := 1 to Length(Result) do begin
  1170.         if Result [I] = '' then
  1171.             Result[I] := '/';
  1172.     end;
  1173. end;
  1174. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1175. procedure TFtpServer.CommandChangeDir(
  1176.     Client      : TFtpCtrlSocket;
  1177.     var Keyword : TFtpString;
  1178.     var Params  : TFtpString;
  1179.     var Answer  : TFtpString);
  1180. var
  1181.     Allowed : Boolean;
  1182.     OldDir  : String;
  1183. begin
  1184.     OldDir := Client.Directory;
  1185.     try
  1186.         Params               := SlashesToBackSlashes(Params);
  1187.         Client.Directory     := Trim(Params);
  1188.         Allowed              := TRUE;
  1189.         TriggerChangeDirectory(Client, Client.Directory, Allowed);
  1190.         if Allowed then
  1191.             Answer           := Format(msgCWDSuccess,
  1192.                                        [BackSlashesToSlashes(Client.Directory)])
  1193.         else begin
  1194.             Client.Directory := OldDir;
  1195.             Answer           := Format(msgCWDFailed, ['No permission']);
  1196.         end;
  1197.     except
  1198.         on E:Exception do begin
  1199.             Client.Directory := OldDir;
  1200.             Answer           := Format(msgCWDFailed, [E.Message]);
  1201.         end;
  1202.     end;
  1203. end;
  1204. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1205. procedure TFtpServer.CommandXPWD(
  1206.     Client      : TFtpCtrlSocket;
  1207.     var Keyword : TFtpString;
  1208.     var Params  : TFtpString;
  1209.     var Answer  : TFtpString);
  1210. begin
  1211.     if Client.FtpState <> ftpcReady then begin
  1212.         Answer := msgNotLogged;
  1213.         Exit;
  1214.     end;
  1215.     Client.CurCmdType := ftpcXPWD;
  1216.     Answer            := Format(msgPWDSuccess,
  1217.                                 [BackSlashesToSlashes(Client.Directory)]);
  1218. end;
  1219. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1220. procedure TFtpServer.CommandPWD(
  1221.     Client      : TFtpCtrlSocket;
  1222.     var Keyword : TFtpString;
  1223.     var Params  : TFtpString;
  1224.     var Answer  : TFtpString);
  1225. begin
  1226.     if Client.FtpState <> ftpcReady then begin
  1227.         Answer := msgNotLogged;
  1228.         Exit;
  1229.     end;
  1230.     Client.CurCmdType := ftpcPWD;
  1231.     Answer            := Format(msgPWDSuccess,
  1232.                                 [BackSlashesToSlashes(Client.Directory)]);
  1233. end;
  1234. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1235. procedure TFtpServer.CommandQUIT(
  1236.     Client      : TFtpCtrlSocket;
  1237.     var Keyword : TFtpString;
  1238.     var Params  : TFtpString;
  1239.     var Answer  : TFtpString);
  1240. begin
  1241.     Client.CurCmdType := ftpcQUIT;
  1242.     Answer            := msgQuit;
  1243.     PostMessage(FWindowHandle, WM_FTPSRV_CLOSE_REQUEST, 0, LongInt(Client));
  1244. end;
  1245. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1246. function GetInteger(var I : Integer; const Src : String) : LongInt;
  1247. begin
  1248.     { Skip leading white spaces }
  1249.     while (I <= Length(Src)) and (Src[I] in [' ' , #9]) do
  1250.         Inc(I);
  1251.     Result := 0;
  1252.     while (I <= Length(Src)) and (Src[I] in ['0'..'9']) do begin
  1253.         Result := Result * 10 + Ord(Src[I]) - Ord('0');
  1254.         Inc(I);
  1255.     end;
  1256.     { Skip trailing white spaces }
  1257.     while (I <= Length(Src)) and (Src[I] in [' ' , #9]) do
  1258.         Inc(I);
  1259.     { Check if end of string of comma. If not, error, returns -1 }
  1260.     if I <= Length(Src) then begin
  1261.         if Src[I] = ',' then
  1262.             Inc(I)        { skip comma           }
  1263.         else
  1264.             raise Exception.Create('unexpected char'); { error, must be comma }
  1265.     end;
  1266. end;
  1267. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1268. procedure TFtpServer.CommandPORT(
  1269.     Client      : TFtpCtrlSocket;
  1270.     var Keyword : TFtpString;
  1271.     var Params  : TFtpString;
  1272.     var Answer  : TFtpString);
  1273. var
  1274.     I : Integer;
  1275.     N : LongInt;
  1276. begin
  1277.     if Client.FtpState <> ftpcReady then begin
  1278.         Answer := msgNotLogged;
  1279.         Exit;
  1280.     end;
  1281.     try
  1282.         Client.CurCmdType := ftpcPORT;
  1283.         I                 := 1;
  1284.         Client.DataAddr   := IntToStr(GetInteger(I, Params));
  1285.         Client.DataAddr   := Client.DataAddr + '.' + IntToStr(GetInteger(I, Params));
  1286.         Client.DataAddr   := Client.DataAddr + '.' + IntToStr(GetInteger(I, Params));
  1287.         Client.DataAddr   := Client.DataAddr + '.' + IntToStr(GetInteger(I, Params));
  1288.         N := GetInteger(I, Params);
  1289.         N := (N shl 8) + GetInteger(I, Params);
  1290.         Client.DataPort := IntToStr(N);
  1291.         Answer := msgPortSuccess;
  1292.     except
  1293.         Answer := msgPortFailed;
  1294.     end;
  1295. end;
  1296. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1297. procedure TFtpServer.CommandSTOR(
  1298.     Client      : TFtpCtrlSocket;
  1299.     var Keyword : TFtpString;
  1300.     var Params  : TFtpString;
  1301.     var Answer  : TFtpString);
  1302. var
  1303.     Allowed  : Boolean;
  1304.     FilePath : TFtpString;
  1305. begin
  1306.     if Client.FtpState <> ftpcReady then begin
  1307.         Answer := msgNotLogged;
  1308.         Exit;
  1309.     end;
  1310.     try
  1311.         Client.CurCmdType       := ftpcSTOR;
  1312.         Client.FileName         := SlashesToBackSlashes(Params);
  1313.         Client.HasOpenedFile    := FALSE;
  1314.         Client.AbortingTransfer := FALSE;
  1315.         Client.TransferError    := 'Transfer Ok';
  1316.         Allowed                 := TRUE;
  1317.         FilePath                := BuildFilePath(Client.Directory, Client.FileName);
  1318.         TriggerValidatePut(Client, FilePath, Allowed);
  1319.         if not Allowed then begin
  1320.             Answer := msgStorDisabled;
  1321.             Exit;
  1322.         end;
  1323.         Client.FilePath := FilePath;
  1324.         if Client.PassiveMode then begin
  1325.             Client.DataSocket.OnSessionConnected  := ClientStorSessionConnected;
  1326.             Client.DataSocket.OnSessionClosed     := ClientStorSessionClosed;
  1327.             Client.DataSocket.OnDataAvailable     := ClientStorDataAvailable;
  1328.             Client.DataSocket.OnDataSent          := nil;
  1329.             if Client.PassiveConnected then
  1330.                 Client.DataSocket.OnSessionConnected(Client.DataSocket, 0)
  1331.             else
  1332.                 Client.PassiveStart := TRUE;
  1333.         end
  1334.         else begin
  1335.             Client.DataSocket.Proto               := 'tcp';
  1336.             Client.DataSocket.Addr                := Client.DataAddr;
  1337.             Client.DataSocket.Port                := Client.DataPort;
  1338.             Client.DataSocket.OnSessionConnected  := ClientStorSessionConnected;
  1339.             Client.DataSocket.OnSessionClosed     := ClientStorSessionClosed;
  1340.             Client.DataSocket.OnDataAvailable     := ClientStorDataAvailable;
  1341.             Client.DataSocket.OnDataSent          := nil;
  1342.             Client.DataSocket.LingerOnOff         := wsLingerOff;
  1343.             Client.DataSocket.LingerTimeout       := 0;
  1344.             Client.DataSocket.Connect;
  1345.         end;
  1346.         Answer := Format(msgStorSuccess, [Params]);
  1347.     except
  1348.         on E:Exception do begin
  1349.             Answer := Format(msgStorFailed, [E.Message]);
  1350.         end;
  1351.     end;
  1352. end;
  1353. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1354. procedure TFtpServer.ClientStorSessionConnected(Sender : TObject; Error : Word);
  1355. var
  1356.     Client      : TFtpCtrlSocket;
  1357.     Data        : TWSocket;
  1358. begin
  1359.     Data                     := TWSocket(Sender);
  1360.     Client                   := TFtpCtrlSocket(Data.Owner);
  1361.     Client.DataSessionActive := TRUE;
  1362.     Client.PassiveMode       := FALSE;
  1363.     TriggerStorSessionConnected(Client, Data, Error);
  1364. end;
  1365. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1366. procedure TFtpServer.ClientStorSessionClosed(Sender : TObject; Error : Word);
  1367. var
  1368.     Client      : TFtpCtrlSocket;
  1369.     Data        : TWSocket;
  1370. begin
  1371.     Data                     := TWSocket(Sender);
  1372.     Client                   := TFtpCtrlSocket(Data.Owner);
  1373.     Client.DataSessionActive := FALSE;
  1374.     Client.PassiveStart      := FALSE;
  1375.     Client.PassiveConnected  := FALSE;
  1376.     Client.RestartPos        := 0;
  1377.     { Reset data port to standard value }
  1378.     Client.DataPort          := 'ftp-data';
  1379.     { If we had opened a data stream ourself, then close it }
  1380.     if Client.HasOpenedFile then begin
  1381.         if Assigned(Client.DataStream) then
  1382.             Client.DataStream.Destroy;
  1383.         Client.DataStream    := nil;
  1384.         Client.HasOpenedFile := FALSE;
  1385.     end;
  1386.     TriggerStorSessionClosed(Client, Data, Error);
  1387.     if Client.CurCmdType = ftpcSTOR then begin
  1388.         if Client.AbortingTransfer then
  1389.             SendAnswer(Client, Format(msgStorAborted, [Client.TransferError]))
  1390.         else if Error = 0 then
  1391.             SendAnswer(Client, msgStorOk)
  1392.         else
  1393.             SendAnswer(Client, Format(msgStorError, [Error]));
  1394.     end
  1395.     else if Client.CurCmdType = ftpcAPPE then begin
  1396.         if Client.AbortingTransfer then
  1397.             SendAnswer(Client, Format(msgAppeAborted, [Client.TransferError]))
  1398.         else if Error = 0 then
  1399.             SendAnswer(Client, msgAppeOk)
  1400.         else
  1401.             SendAnswer(Client, Format(msgAppeError, [Error]));
  1402.     end
  1403.     else { Should never comes here }
  1404.         raise Exception.Create('Program error in ClientStorSessionClosed');
  1405. end;
  1406. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1407. procedure TFtpServer.ClientStorDataAvailable(Sender: TObject; Error : word);
  1408. var
  1409.     Len    : Integer;
  1410.     Client : TFtpCtrlSocket;
  1411.     Data   : TWSocket;
  1412. begin
  1413.     Data   := TWSocket(Sender);
  1414.     Client := TFtpCtrlSocket(Data.Owner);
  1415.     Len    := Data.Receive(Client.RcvBuf, Client.RcvSize);
  1416.     if Len <= 0 then
  1417.         Exit;
  1418.     if Client.AbortingTransfer then
  1419.         Exit;
  1420.     try
  1421.         { Trigger the user event for the received data }
  1422.         TriggerStorDataAvailable(Client, Data, Client.RcvBuf, Len, Error);
  1423.         { We need to open a datastream if not already done and a FilePath }
  1424.         { exists (the component user can have nullified the FilePath      }
  1425.         if (not Client.HasOpenedFile) and
  1426.            (Length(Client.FilePath) > 0) and
  1427.            (not Assigned(Client.DataStream)) then begin
  1428.             { Use different file modes for APPE vs STOR }
  1429.             if (Client.CurCmdType = ftpcAPPE) and
  1430.                (GetFileSize(Client.FilePath) > -1) then
  1431.                 Client.DataStream    := TFileStream.Create(Client.FilePath,
  1432.                                         fmOpenReadWrite or fmShareDenyWrite)
  1433.             else
  1434.                 Client.DataStream    := TFileStream.Create(Client.FilePath,
  1435.                                         fmCreate);
  1436.             Client.DataStream.Seek(Client.RestartPos, soFromBeginning);
  1437.             Client.HasOpenedFile := TRUE;
  1438.         end;
  1439.         { If we have a DataStream, then we need to write the data }
  1440.         if Assigned(Client.DataStream) then
  1441.             Client.DataStream.WriteBuffer(Client.RcvBuf^, Len);
  1442.     except
  1443.         { An exception occured, so we abort the transfer }
  1444.         on E:Exception do begin
  1445.             Client.TransferError    := E.Message;
  1446.             Client.AbortingTransfer := TRUE;
  1447.             PostMessage(FWindowHandle, WM_FTPSRV_ABORT_TRANSFER, 0, LongInt(Data));
  1448.         end;
  1449.     end;
  1450. end;
  1451. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1452. function BuildFilePath(
  1453.     const Directory : String;
  1454.     FileName        : String) : String;
  1455. var
  1456.     Drive : String;
  1457.     Path  : String;
  1458. begin
  1459.     FileName := SlashesToBackSlashes(FileName);
  1460.     if IsUNC(FileName) then
  1461.         Result := FileName
  1462.     else if IsUNC(Directory) then begin
  1463.         if (Length(FileName) > 0) and (FileName[1] = '') then
  1464.             Result := ExtractFileDrive(Directory) + FileName
  1465.         else
  1466.             Result := Directory + FileName;
  1467.     end
  1468.     else begin
  1469.         if (Length(FileName) > 1) and (FileName[2] = ':') then begin
  1470.             Drive := UpperCase(Copy(FileName, 1, 2));
  1471.             Path  := Copy(FileName, 3, Length(FileName));
  1472.         end
  1473.         else begin
  1474.             Drive := Copy(Directory, 1, 2);
  1475.             Path  := FileName;
  1476.         end;
  1477.         if (Length(Path) > 0) and (Path[1] = '') then
  1478.             Result := Drive + Path
  1479.         else begin
  1480.             if Drive <> Copy(Directory, 1, 2) then
  1481.                 raise Exception.Create('No current dir for ''' + Drive + '''');
  1482.             Result := Directory + Path;
  1483.         end;
  1484.     end;
  1485. end;
  1486. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1487. procedure TFtpServer.CommandRETR(
  1488.     Client      : TFtpCtrlSocket;
  1489.     var Keyword : TFtpString;
  1490.     var Params  : TFtpString;
  1491.     var Answer  : TFtpString);
  1492. var
  1493.     Allowed  : Boolean;
  1494.     FilePath : TFtpString;
  1495. begin
  1496.     if Client.FtpState <> ftpcReady then begin
  1497.         Answer := msgNotLogged;
  1498.         Exit;
  1499.     end;
  1500.     try
  1501.         Client.CurCmdType    := ftpcRETR;
  1502.         Client.HasOpenedFile := FALSE;
  1503.         Client.FileName      := SlashesToBackSlashes(Params);
  1504.         Allowed              := TRUE;
  1505.         FilePath             := BuildFilePath(Client.Directory, Client.FileName);
  1506.         TriggerValidateGet(Client, FilePath, Allowed);
  1507.         if not Allowed then begin
  1508.             Answer := msgRetrDisabled;
  1509.             Exit;
  1510.         end;
  1511.         Client.FilePath := FilePath;
  1512.         Answer          := Format(msgRetrSuccess, [Params]);
  1513.         StartSendData(Client);
  1514.     except
  1515.         on E:Exception do begin
  1516.             Answer := Format(msgRetrFailed, [E.Message]);
  1517.         end;
  1518.     end;
  1519. end;
  1520. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1521. procedure TFtpServer.ClientPassiveSessionAvailable(Sender : TObject; Error : Word);
  1522. var
  1523.     HSocket : TSocket;
  1524.     Client  : TFtpCtrlSocket;
  1525.     Data    : TWSocket;
  1526. begin
  1527.     Data    := TWSocket(Sender);
  1528.     Client  := TFtpCtrlSocket(Data.Owner);
  1529.     HSocket := Data.Accept;
  1530.     Data.OnSessionClosed := nil;
  1531.     Data.Close;   { We don't need to listen any more }
  1532.     if Client.CurCmdType in [ftpcSTOR, ftpcAPPE] then begin
  1533.         Client.DataSocket.OnSessionConnected  := ClientStorSessionConnected;
  1534.         Client.DataSocket.OnSessionClosed     := ClientStorSessionClosed;
  1535.         Client.DataSocket.OnDataAvailable     := ClientStorDataAvailable;
  1536.         Client.DataSocket.OnDataSent          := nil;
  1537.     end
  1538.     else if Client.CurCmdType in [ftpcRETR, ftpcLIST, ftpcNLST] then begin
  1539.         Client.DataSocket.OnSessionConnected  := ClientRetrSessionConnected;
  1540.         Client.DataSocket.OnSessionClosed     := ClientRetrSessionClosed;
  1541.         Client.DataSocket.OnDataAvailable     := nil;
  1542.         Client.DataSocket.OnDataSent          := ClientRetrDataSent;
  1543.     end
  1544.     else begin
  1545.         Client.DataSocket.OnSessionConnected  := nil;
  1546.         Client.DataSocket.OnSessionClosed     := nil;
  1547.         Client.DataSocket.OnDataAvailable     := nil;
  1548.         Client.DataSocket.OnDataSent          := nil;
  1549.     end;
  1550.     Client.DataSocket.LingerOnOff             := wsLingerOff;
  1551.     Client.DataSocket.LingerTimeout           := 0;
  1552.     Client.DataSocket.HSocket                 := HSocket;
  1553.     Client.PassiveConnected                   := TRUE;
  1554.     if Client.PassiveStart then
  1555.         Client.DataSocket.OnSessionConnected(Client.DataSocket, 0);
  1556. end;
  1557. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1558. procedure TFtpServer.StartSendData(Client : TFtpCtrlSocket);
  1559. begin
  1560.     Client.AbortingTransfer              := FALSE;
  1561.     Client.DataSent                      := FALSE;
  1562.     Client.TransferError                 := 'Transfer Ok';
  1563.     if Client.PassiveMode then begin
  1564.         Client.DataSocket.OnSessionConnected  := ClientRetrSessionConnected;
  1565.         Client.DataSocket.OnSessionClosed     := ClientRetrSessionClosed;
  1566.         Client.DataSocket.OnDataAvailable     := nil;
  1567.         Client.DataSocket.OnDataSent          := ClientRetrDataSent;
  1568.         if Client.PassiveConnected then
  1569.             Client.DataSocket.OnSessionConnected(Client.DataSocket, 0)
  1570.         else
  1571.             Client.PassiveStart := TRUE;
  1572.     end
  1573.     else begin
  1574.         Client.DataSocket.Close;
  1575.         Client.DataSocket.Proto              := 'tcp';
  1576.         Client.DataSocket.Addr               := Client.DataAddr;
  1577.         Client.DataSocket.Port               := Client.DataPort;
  1578.         Client.DataSocket.OnSessionConnected := ClientRetrSessionConnected;
  1579.         Client.DataSocket.OnSessionClosed    := ClientRetrSessionClosed;
  1580.         Client.DataSocket.OnDataAvailable    := nil;
  1581.         Client.DataSocket.OnDataSent         := ClientRetrDataSent;
  1582.         Client.DataSocket.LingerOnOff        := wsLingerOff;
  1583.         Client.DataSocket.LingerTimeout      := 0;
  1584.         Client.DataSocket.Connect;
  1585.     end;
  1586. end;
  1587. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1588. procedure TFtpServer.ClientRetrSessionConnected(Sender : TObject; Error : Word);
  1589. var
  1590.     Client      : TFtpCtrlSocket;
  1591.     Data        : TWSocket;
  1592. begin
  1593.     Data                     := TWSocket(Sender);
  1594.     Client                   := TFtpCtrlSocket(Data.Owner);
  1595.     Client.DataSessionActive := TRUE;
  1596.     Client.PassiveMode       := FALSE;
  1597.     try
  1598.         TriggerRetrSessionConnected(Client, Data, Error);
  1599.         { We need to open a datastream if not already done and a FilePath }
  1600.         { exists the component user can have nullified the FilePath or    }
  1601.         { created his own data stream (virtual file feature)              }
  1602.         if (not Client.HasOpenedFile) and
  1603.            (Length(Client.FilePath) > 0) and
  1604.            (not Assigned(Client.DataStream)) then begin
  1605.             Client.DataStream    := TFileStream.Create(Client.FilePath,
  1606.                                                        fmOpenRead + fmShareDenyNone);
  1607.             Client.DataStream.Seek(Client.RestartPos, soFromBeginning);
  1608.             Client.HasOpenedFile := TRUE;
  1609.         end;
  1610.     except
  1611.         on E:Exception do begin
  1612.             Client.AbortingTransfer := TRUE;
  1613.             Client.TransferError    := E.Message;
  1614.             PostMessage(FWindowHandle, WM_FTPSRV_ABORT_TRANSFER,
  1615.                         0, LongInt(Data));
  1616.             Exit;
  1617.         end;
  1618.     end;
  1619.     SendNextDataChunk(Client, Data);
  1620. end;
  1621. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1622. procedure TFtpServer.ClientRetrSessionClosed(Sender : TObject; Error : Word);
  1623. var
  1624.     Client      : TFtpCtrlSocket;
  1625.     Data        : TWSocket;
  1626. begin
  1627.     Data                     := TWSocket(Sender);
  1628.     Client                   := TFtpCtrlSocket(Data.Owner);
  1629.     Client.DataSessionActive := FALSE;
  1630.     Client.PassiveStart      := FALSE;
  1631.     Client.PassiveConnected  := FALSE;
  1632.     Client.RestartPos        := 0;
  1633.     { Reset data port to standard value }
  1634.     Client.DataPort          := 'ftp-data';
  1635.     { If we had opened a data stream ourself, then close it }
  1636.     if Client.HasOpenedFile then begin
  1637.         if Assigned(Client.DataStream) then begin
  1638.             Client.DataStream.Destroy;
  1639.         end;
  1640.         Client.DataStream    := nil;
  1641.         Client.HasOpenedFile := FALSE;
  1642.     end;
  1643.     if Client.AbortingTransfer then
  1644.         SendAnswer(Client, Format(msgRetrFailed, [Client.TransferError]))
  1645.     else if Error <> 0 then
  1646.         SendAnswer(Client, Format(msgRetrFailed, ['Error #' + IntToStr(Error)]))
  1647.     else
  1648.         SendAnswer(Client, msgRetrOk);
  1649.     TriggerRetrSessionClosed(Client, Data, Error);
  1650. end;
  1651. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1652. procedure TFtpServer.SendNextDataChunk(
  1653.     Client : TFtpCtrlSocket;
  1654.     Data   : TWSocket);
  1655. var
  1656.     Count : LongInt;
  1657. begin
  1658.     try
  1659.         if Assigned(Client.DataStream) then
  1660.             Count := Client.DataStream.Read(Client.RcvBuf^, Client.RcvSize)
  1661.         else
  1662.             Count := 0;
  1663.         if Count > 0 then begin
  1664.             Client.ByteCount := Client.ByteCount + Count;
  1665.             Data.Send(Client.RcvBuf, Count);
  1666.         end
  1667.         else begin { EOF }
  1668.             if not Client.DataSent then begin
  1669.                 Client.DataSent := TRUE;
  1670.                 PostMessage(Handle, WM_FTPSRV_CLOSE_DATA, 0, LongInt(Data));
  1671.             end;
  1672.         end;
  1673.     except
  1674.         { An exception occured, so we abort the transfer }
  1675.         on E:Exception do begin
  1676.             Client.TransferError    := E.Message;
  1677.             Client.AbortingTransfer := TRUE;
  1678.             PostMessage(FWindowHandle, WM_FTPSRV_ABORT_TRANSFER, 0, LongInt(Data));
  1679.         end;
  1680.     end;
  1681. end;
  1682. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1683. procedure TFtpServer.ClientRetrDataSent(Sender : TObject; Error : Word);
  1684. var
  1685.     Client : TFtpCtrlSocket;
  1686.     Data   : TWSocket;
  1687. begin
  1688.     Data   := TWSocket(Sender);
  1689.     Client := TFtpCtrlSocket(Data.Owner);
  1690.     if Client.AbortingTransfer then
  1691.         Exit;
  1692.     try
  1693.         { Trigger the user event for the received data }
  1694.         TriggerRetrDataSent(Client, Data, Error);
  1695.         if Error <> 0 then
  1696.             raise Exception.Create('Send: error #' + IntToStr(Error));
  1697.         SendNextDataChunk(Client, Data);
  1698.     except
  1699.         { An exception occured, so we abort the transfer }
  1700.         on E:Exception do begin
  1701.             Client.TransferError    := E.Message;
  1702.             Client.AbortingTransfer := TRUE;
  1703.             SendAnswer(Client, Format(msgRetrAborted, [Client.TransferError]));
  1704.             PostMessage(FWindowHandle, WM_FTPSRV_ABORT_TRANSFER, 0, LongInt(Data));
  1705.         end;
  1706.     end;
  1707. end;
  1708. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1709. procedure TFtpServer.CommandSYST(
  1710.     Client      : TFtpCtrlSocket;
  1711.     var Keyword : TFtpString;
  1712.     var Params  : TFtpString;
  1713.     var Answer  : TFtpString);
  1714. begin
  1715.     if Client.FtpState <> ftpcReady then begin
  1716.         Answer := msgNotLogged;
  1717.         Exit;
  1718.     end;
  1719.     Client.CurCmdType := ftpcSYST;
  1720.     Answer            := msgSystem;
  1721. end;
  1722. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1723. procedure TFtpServer.CommandDirectory(
  1724.     Client      : TFtpCtrlSocket;
  1725.     var Keyword : TFtpString;
  1726.     var Params  : TFtpString;
  1727.     var Answer  : TFtpString;
  1728.     Detailed    : Boolean);
  1729. begin
  1730.     if Assigned(Client.DataStream) then begin
  1731.         Client.DataStream.Destroy;
  1732.         Client.DataStream := nil;
  1733.     end;
  1734.     try
  1735.         Params := SlashesToBackSlashes(Params);
  1736.         TriggerBuildDirectory(Client, Params, Detailed);
  1737.         if not Assigned(Client.DataStream) then begin
  1738.             Client.DataStream    := TMemoryStream.Create;
  1739.             Client.HasOpenedFile := TRUE;
  1740.             BuildDirectory(Client, Params, Client.DataStream, Detailed);
  1741.             TriggerAlterDirectory(Client, Params, Detailed);
  1742.             Client.DataStream.Seek(0, 0);
  1743.         end;
  1744.         Client.FilePath := '';
  1745.         Answer := msgDirOpen;
  1746.         StartSendData(Client);
  1747.     except
  1748.         on E:Exception do begin
  1749.             Answer := Format(msgDirFailed, [E.Message])
  1750.         end;
  1751.     end;
  1752. end;
  1753. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1754. procedure TFtpServer.CommandLIST(
  1755.     Client      : TFtpCtrlSocket;
  1756.     var Keyword : TFtpString;
  1757.     var Params  : TFtpString;
  1758.     var Answer  : TFtpString);
  1759. begin
  1760.     if Client.FtpState <> ftpcReady then begin
  1761.         Answer := msgNotLogged;
  1762.         Exit;
  1763.     end;
  1764.     Client.CurCmdType := ftpcLIST;
  1765.     CommandDirectory(Client, KeyWord, Params, Answer, TRUE);
  1766. end;
  1767. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1768. procedure TFtpServer.CommandNLST(
  1769.     Client      : TFtpCtrlSocket;
  1770.     var Keyword : TFtpString;
  1771.     var Params  : TFtpString;
  1772.     var Answer  : TFtpString);
  1773. begin
  1774.     if Client.FtpState <> ftpcReady then begin
  1775.         Answer := msgNotLogged;
  1776.         Exit;
  1777.     end;
  1778.     Client.CurCmdType := ftpcNLST;
  1779.     CommandDirectory(Client, KeyWord, Params, Answer, FALSE);
  1780. end;
  1781. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1782. function FormatUnixDirEntry(F : TSearchRec) : String;
  1783. var
  1784.     Attr : String;
  1785.     Ext  : String;
  1786.     Day, Month, Year : Integer;
  1787.     Hour, Min        : Integer;
  1788. const
  1789.     StrMonth : array [1..12] of String =
  1790.         ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
  1791.          'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
  1792. begin
  1793.     if ((F.Attr and faVolumeID) <> 0) or
  1794.        ((F.Attr and faHidden)   <> 0) then begin
  1795.         { Ignore hidden files and volume ID entries }
  1796.         Result := '';
  1797.         Exit;
  1798.     end;
  1799.     Attr := '-rw-rw-rw-';
  1800.     if (F.Attr and faDirectory) <> 0 then
  1801.         Attr[1] := 'd';
  1802.     if (F.Attr and faReadOnly) <> 0 then begin
  1803.         Attr[3] := '-';
  1804.         Attr[6] := '-';
  1805.         Attr[9] := '-';
  1806.     end;
  1807.     Ext := UpperCase(ExtractFileExt(F.Name));
  1808.     if (Ext = '.EXE') or (Ext = '.COM') or (Ext = '.BAT') then begin
  1809.         Attr[4]  := 'x';
  1810.         Attr[7]  := 'x';
  1811.         Attr[10] := 'x';
  1812.     end;
  1813.     Day   := (HIWORD(F.Time) and $1F);
  1814.     Month := ((HIWORD(F.Time) shr 5) and $0F);
  1815.     Year  := ((HIWORD(F.Time) shr 9) and $3F) + 1980;
  1816. {   Sec   := ((F.Time and $1F) shl 1); }
  1817.     Min   := ((F.Time shr 5) and $3F);
  1818.     Hour  := ((F.Time shr 11) and $1F);
  1819.     Result := Attr + '   1 ftp      ftp  ' + Format('%11d ', [F.Size]);
  1820.     Result := Result + Format('%s %2.2d ', [StrMonth[Month], Day]);
  1821.     if Year = ThisYear then
  1822.         Result := Result + Format('%2.2d:%2.2d ', [Hour, Min])
  1823.     else
  1824.         Result := Result + Format('%5d ', [Year]);
  1825.     Result := Result + F.Name + #13#10;
  1826. end;
  1827. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1828. procedure TFtpServer.BuildDirectory(
  1829.     Client     : TFtpCtrlSocket;
  1830.     var Params : TFtpString;
  1831.     Stream     : TStream;
  1832.     Detailed   : Boolean);
  1833. var
  1834.     F      : TSearchRec;
  1835.     Path   : String;
  1836.     Status : Integer;
  1837.     Buf    : String;
  1838. begin
  1839.     DecodeDate(Now, ThisYear, ThisMonth, ThisDay);
  1840.     if Params = '' then
  1841.         Path := Client.Directory + '*.*'
  1842.     else
  1843.         Path := BuildFilePath(Client.Directory, Params);
  1844.     if Path[Length(Path)] = '' then
  1845.         Path := Path + '*.*';
  1846.     Status := FindFirst(Path, faAnyFile, F);
  1847.     while Status = 0 do begin
  1848.         if Detailed then
  1849.             Buf := FormatUnixDirEntry(F)
  1850.         else
  1851.             Buf := F.Name + #13#10;
  1852.         if Length(Buf) > 0 then
  1853.             Stream.Write(Buf[1], Length(Buf));
  1854.         Status := FindNext(F);
  1855.     end;
  1856.     FindClose(F);
  1857.     if Stream.Size = 0 then begin
  1858.         Buf := Path + ' not found' + #13#10;
  1859.         Stream.Write(Buf[1], Length(Buf));
  1860.     end;
  1861. end;
  1862. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1863. procedure TFtpServer.CommandTYPE(
  1864.     Client      : TFtpCtrlSocket;
  1865.     var Keyword : TFtpString;
  1866.     var Params  : TFtpString;
  1867.     var Answer  : TFtpString);
  1868. var
  1869.     Buf : String;
  1870. begin
  1871.     if Client.FtpState <> ftpcReady then begin
  1872.         Answer := msgNotLogged;
  1873.         Exit;
  1874.     end;
  1875.     Client.CurCmdType := ftpcTYPE;
  1876.     Buf := UpperCase(Trim(Params));
  1877.     if (Buf = 'A') or (Buf = 'I') then begin
  1878.         Answer            := Format(msgTypeOk, [Params]);
  1879.         Client.BinaryMode := (Buf = 'I');
  1880.     end
  1881.     else
  1882.         Answer := Format(msgTypeFailed, [Params]);
  1883. end;
  1884. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1885. procedure TFtpServer.CommandDELE(
  1886.     Client      : TFtpCtrlSocket;
  1887.     var Keyword : TFtpString;
  1888.     var Params  : TFtpString;
  1889.     var Answer  : TFtpString);
  1890. var
  1891.     FileName : TFtpString;
  1892.     Allowed  : Boolean;
  1893. begin
  1894.     if Client.FtpState <> ftpcReady then begin
  1895.         Answer := msgNotLogged;
  1896.         Exit;
  1897.     end;
  1898.     Client.CurCmdType := ftpcDELE;
  1899.     FileName          := BuildFilePath(Client.Directory, Params);
  1900.     TriggerValidateDele(Client, FileName, Allowed);
  1901.     if not Allowed then begin
  1902.         Answer := msgDeleDisabled;
  1903.         Exit;
  1904.     end;
  1905.     if Params = '' then
  1906.         Answer := Format(msgDeleSyntax, [Params])
  1907.     else if FileExists(FileName) then begin
  1908.         if DeleteFile(FileName) then
  1909.             Answer := Format(msgDeleOk, [FileName])
  1910.         else
  1911.             Answer := Format(msgDeleFailed, [FileName]);
  1912.     end
  1913.     else
  1914.         Answer := Format(msgDeleNotExists, [FileName]);
  1915. end;
  1916. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1917. procedure TFtpServer.CommandSIZE(
  1918.     Client      : TFtpCtrlSocket;
  1919.     var Keyword : TFtpString;
  1920.     var Params  : TFtpString;
  1921.     var Answer  : TFtpString);
  1922. var
  1923.     FileName : String;
  1924.     Size     : LongInt;
  1925. begin
  1926.     if Client.FtpState <> ftpcReady then begin
  1927.         Answer := msgNotLogged;
  1928.         Exit;
  1929.     end;
  1930.     Client.CurCmdType := ftpcSIZE;
  1931.     FileName          := BuildFilePath(Client.Directory, Params);
  1932.     if Params = '' then
  1933.         Answer := Format(msgSizeSyntax, [Params])
  1934.     else begin
  1935.         try
  1936.             Size := GetFileSize(FileName);
  1937.             if Size >= 0 then
  1938.                 Answer := Format(msgSizeOk, [Size])
  1939.             else
  1940.                 Answer := Format(msgSizeFailed, ['File not found'])
  1941.         except
  1942.             on E:Exception do begin
  1943.                 Answer := Format(msgSizeFailed, [E.Message])
  1944.             end;
  1945.         end;
  1946.     end;
  1947. end;
  1948. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1949. procedure TFtpServer.CommandREST(
  1950.     Client      : TFtpCtrlSocket;
  1951.     var Keyword : TFtpString;
  1952.     var Params  : TFtpString;
  1953.     var Answer  : TFtpString);
  1954. begin
  1955.     if Client.FtpState <> ftpcReady then begin
  1956.         Answer := msgNotLogged;
  1957.         Exit;
  1958.     end;
  1959.     Client.CurCmdType := ftpcREST;
  1960.     try
  1961.         Client.RestartPos := StrToInt(Params);
  1962.         if Client.RestartPos <= 0 then begin
  1963.             Answer            := msgRestZero;
  1964.             Client.RestartPos := 0;
  1965.         end
  1966.         else
  1967.             Answer := Format(msgRestOk, [Client.RestartPos]);
  1968.     except
  1969.         on E:Exception do begin
  1970.             Answer            := Format(msgRestFailed, [E.Message]);
  1971.             Client.RestartPos := 0;
  1972.         end;
  1973.     end;
  1974. end;
  1975. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  1976. procedure TFtpServer.CommandRNFR(
  1977.     Client      : TFtpCtrlSocket;
  1978.     var Keyword : TFtpString;
  1979.     var Params  : TFtpString;
  1980.     var Answer  : TFtpString);
  1981. var
  1982.     FileName : String;
  1983. begin
  1984.     if Client.FtpState <> ftpcReady then begin
  1985.         Answer := msgNotLogged;
  1986.         Exit;
  1987.     end;
  1988.     Client.CurCmdType   := ftpcRNFR;
  1989.     FileName            := BuildFilePath(Client.Directory, Params);
  1990.     if Params = '' then
  1991.         Answer := Format(msgRnfrSyntax, [Params])
  1992.     else if FileExists(FileName) then begin
  1993.         Client.FromFileName := FileName;
  1994.         Answer              := Format(msgRnfrOk, [FileName])
  1995.     end
  1996.     else
  1997.         Answer := Format(msgRnfrNotExists, [FileName]);
  1998. end;
  1999. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  2000. procedure TFtpServer.CommandRNTO(
  2001.     Client      : TFtpCtrlSocket;
  2002.     var Keyword : TFtpString;
  2003.     var Params  : TFtpString;
  2004.     var Answer  : TFtpString);
  2005. var
  2006.     FileName : String;
  2007. begin
  2008.     if Client.FtpState <> ftpcReady then begin
  2009.         Answer := msgNotLogged;
  2010.         Exit;
  2011.     end;
  2012.     Client.CurCmdType := ftpcRNTO;
  2013.     FileName          := BuildFilePath(Client.Directory, Params);
  2014.     if Params = '' then
  2015.         Answer := Format(msgRntoSyntax, [Params])
  2016.     else if FileExists(FileName) then
  2017.         Answer := Format(msgRntoAlready, [FileName])
  2018.     else if not FileExists(Client.FromFileName) then
  2019.         Answer := Format(msgRntoNotExists, [Client.FromFileName])
  2020.     else begin
  2021.         Client.ToFileName := FileName;
  2022.         if RenameFile(Client.FromFileName, Client.ToFileName) then
  2023.             Answer := Format(msgRntoOk, [Client.FromFileName, Client.ToFileName])
  2024.         else
  2025.             Answer := Format(msgRntoFailed, [Client.FromFileName]);
  2026.     end;
  2027. end;
  2028. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  2029. procedure TFtpServer.CommandNOOP(
  2030.     Client      : TFtpCtrlSocket;
  2031.     var Keyword : TFtpString;
  2032.     var Params  : TFtpString;
  2033.     var Answer  : TFtpString);
  2034. begin
  2035.     Client.CurCmdType := ftpcNOOP;
  2036.     Answer            := Format(MsgNoopOk, [Params]);
  2037. end;
  2038. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  2039. procedure TFtpServer.CommandMKD(
  2040.     Client      : TFtpCtrlSocket;
  2041.     var Keyword : TFtpString;
  2042.     var Params  : TFtpString;
  2043.     var Answer  : TFtpString);
  2044. var
  2045.     FileName : String;
  2046.     Allowed  : Boolean;
  2047. begin
  2048.     if Client.FtpState <> ftpcReady then begin
  2049.         Answer := msgNotLogged;
  2050.         Exit;
  2051.     end;
  2052.     try
  2053.         Client.CurCmdType := ftpcMKD;
  2054.         FileName          := BuildFilePath(Client.Directory, Params);
  2055.         Allowed           := TRUE;
  2056.         TriggerMakeDirectory(Client, FileName, Allowed);
  2057.         if not Allowed then
  2058.             Answer := Format(msgMkdFailed, [FileName])
  2059.         else if Params = '' then
  2060.             Answer := Format(msgMkdSyntax, [Params])
  2061.         else if FileExists(FileName) then
  2062.             Answer := Format(msgMkdAlready, [FileName])
  2063.         else begin
  2064.             {$I-}
  2065.             MkDir(FileName);
  2066.             if IOResult = 0 then
  2067.                 Answer := Format(msgMkdOk, [FileName])
  2068.             else
  2069.                 Answer := Format(msgMkdFailed, [FileName]);
  2070.             {$I+}
  2071.         end;
  2072.     except
  2073.         on E:Exception do begin
  2074.             Answer := Format(msgMkdFailed, [E.Message])
  2075.         end;
  2076.     end;
  2077. end;
  2078. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  2079. procedure TFtpServer.CommandAPPE(
  2080.     Client      : TFtpCtrlSocket;
  2081.     var Keyword : TFtpString;
  2082.     var Params  : TFtpString;
  2083.     var Answer  : TFtpString);
  2084. var
  2085.     Allowed  : Boolean;
  2086.     FilePath : TFtpString;
  2087. begin
  2088.     if Client.FtpState <> ftpcReady then begin
  2089.         Answer := msgNotLogged;
  2090.         Exit;
  2091.     end;
  2092.     try
  2093.         Client.CurCmdType       := ftpcAPPE;
  2094.         Client.FileName         := SlashesToBackSlashes(Params);
  2095.         Client.HasOpenedFile    := FALSE;
  2096.         Client.AbortingTransfer := FALSE;
  2097.         Client.TransferError    := 'Transfer Ok';
  2098.         Allowed                 := TRUE;
  2099.         FilePath                := BuildFilePath(Client.Directory, Client.FileName);
  2100.         TriggerValidatePut(Client, FilePath, Allowed);
  2101.         if not Allowed then begin
  2102.             Answer := msgAppeDisabled;
  2103.             Exit;
  2104.         end;
  2105.         Client.FilePath := FilePath;
  2106.         if Client.PassiveMode then begin
  2107.             Client.DataSocket.OnSessionConnected  := ClientStorSessionConnected;
  2108.             Client.DataSocket.OnSessionClosed     := ClientStorSessionClosed;
  2109.             Client.DataSocket.OnDataAvailable     := ClientStorDataAvailable;
  2110.             Client.DataSocket.OnDataSent          := nil;
  2111.             if Client.PassiveConnected then
  2112.                 Client.DataSocket.OnSessionConnected(Client.DataSocket, 0)
  2113.             else
  2114.                 Client.PassiveStart := TRUE;
  2115.         end
  2116.         else begin
  2117.             Client.DataSocket.Proto               := 'tcp';
  2118.             Client.DataSocket.Addr                := Client.DataAddr;
  2119.             Client.DataSocket.Port                := Client.DataPort;
  2120.             Client.DataSocket.OnSessionConnected  := ClientStorSessionConnected;
  2121.             Client.DataSocket.OnSessionClosed     := ClientStorSessionClosed;
  2122.             Client.DataSocket.OnDataAvailable     := ClientStorDataAvailable;
  2123.             Client.DataSocket.OnDataSent          := nil;
  2124.             Client.DataSocket.LingerOnOff         := wsLingerOff;
  2125.             Client.DataSocket.LingerTimeout       := 0;
  2126.             Client.DataSocket.Connect;
  2127.         end;
  2128.             Client.RestartPos := GetFileSize(Client.FilePath);
  2129.             if Client.RestartPos < 0 then
  2130.                 Client.RestartPos := 0;
  2131.             Answer := Format(msgAppeReady, [Params,Client.RestartPos]);
  2132.     except
  2133.         on E:Exception do begin
  2134.             Answer := Format(msgAppeFailed, [E.Message]);
  2135.         end;
  2136.     end;
  2137. end;
  2138. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  2139. procedure TFtpServer.CommandSTRU(
  2140.     Client      : TFtpCtrlSocket;
  2141.     var Keyword : TFtpString;
  2142.     var Params  : TFtpString;
  2143.     var Answer  : TFtpString);
  2144. begin
  2145.     Client.CurCmdType := ftpcSTRU;
  2146.     Answer            := Format(MsgStruOk, [Params]);
  2147. end;
  2148. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  2149. function DirExists(Dir : String) : Boolean;
  2150. var
  2151.     F : TSearchRec;
  2152. begin
  2153.     Result := (FindFirst(Dir, faDirectory, F) = 0);
  2154.     FindClose(F);
  2155. end;
  2156. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  2157. procedure TFtpServer.CommandRMD(
  2158.     Client      : TFtpCtrlSocket;
  2159.     var Keyword : TFtpString;
  2160.     var Params  : TFtpString;
  2161.     var Answer  : TFtpString);
  2162. var
  2163.     FileName : String;
  2164. begin
  2165.     if Client.FtpState <> ftpcReady then begin
  2166.         Answer := msgNotLogged;
  2167.         Exit;
  2168.     end;
  2169.     Client.CurCmdType := ftpcRMD;
  2170.     FileName          := BuildFilePath(Client.Directory, Params);
  2171.     if Params = '' then
  2172.         Answer := Format(msgMkdSyntax, [Params])
  2173.     else if not DirExists(FileName) then
  2174.         Answer := Format(msgRmdNotExists, [FileName])
  2175.     else begin
  2176.         {$I-}
  2177.         RmDir(FileName);
  2178.         if IOResult = 0 then
  2179.             Answer := Format(msgRmdOk, [FileName])
  2180.         else
  2181.             Answer := Format(msgRmdFailed, [FileName]);
  2182.         {$I+}
  2183.     end;
  2184. end;
  2185. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  2186. procedure TFtpServer.CommandABOR(
  2187.     Client      : TFtpCtrlSocket;
  2188.     var Keyword : TFtpString;
  2189.     var Params  : TFtpString;
  2190.     var Answer  : TFtpString);
  2191. begin
  2192.     if Client.DataSocket.State = wsConnected then begin
  2193.         Client.TransferError    := 'ABORT requested by client';
  2194.         Client.AbortingTransfer := TRUE;
  2195.         Client.DataSocket.Close;
  2196.     end;
  2197.     Answer := msgAborOk;
  2198. end;
  2199. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  2200. procedure TFtpServer.CommandPASV(
  2201.     Client      : TFtpCtrlSocket;
  2202.     var Keyword : TFtpString;
  2203.     var Params  : TFtpString;
  2204.     var Answer  : TFtpString);
  2205. var
  2206.     saddr    : TSockAddrIn;
  2207.     saddrlen : Integer;
  2208.     DataPort : Integer;
  2209.     IPAddr   : TInAddr;
  2210. begin
  2211.     if Client.FtpState <> ftpcReady then begin
  2212.         Answer := msgNotLogged;
  2213.         Exit;
  2214.     end;
  2215.     try
  2216.         { Get our IP address from our control socket }
  2217.         saddrlen := SizeOf(saddr);
  2218.         Client.GetSockName(saddr, saddrlen);
  2219.         IPAddr   := saddr.sin_addr;
  2220.         Client.DataSocket.Close;
  2221.         Client.DataSocket.Addr  := '0.0.0.0';   { Any addr }
  2222.         Client.DataSocket.Port  := '0';         { Any port }
  2223.         Client.DataSocket.Proto := 'tcp';
  2224.         Client.DataSocket.OnSessionAvailable := ClientPassiveSessionAvailable;
  2225.         Client.DataSocket.OnSessionConnected := nil;
  2226.         Client.DataSocket.OnSessionClosed    := nil;
  2227.         Client.DataSocket.OnDataAvailable    := nil;
  2228.         Client.DataSocket.Listen;
  2229. {        if Client.DataSocket.Listen <> 0 then
  2230.             raise Exception.Create('Listen failed'); 18/11/98 }
  2231.         { Get the port assigned by winsock }
  2232.         saddrlen := SizeOf(saddr);
  2233.         Client.DataSocket.GetSockName(saddr, saddrlen);
  2234.         DataPort := WSocket_ntohs(saddr.sin_port);
  2235.         if Client.sin.sin_addr.s_addr = WSocket_htonl($7F000001) then
  2236.             Answer := Format(msgPasvLocal,
  2237.                           [HiByte(DataPort),
  2238.                            LoByte(DataPort)])
  2239.         else
  2240.             Answer := Format(msgPasvRemote,
  2241.                           [ord(IPAddr.S_un_b.s_b1),
  2242.                            ord(IPAddr.S_un_b.s_b2),
  2243.                            ord(IPAddr.S_un_b.s_b3),
  2244.                            ord(IPAddr.S_un_b.s_b4),
  2245.                            HiByte(DataPort),
  2246.                            LoByte(DataPort)]);
  2247.         Client.PassiveMode      := TRUE;
  2248.         Client.PassiveStart     := FALSE;
  2249.         Client.PassiveConnected := FALSE;
  2250.     except
  2251.         on E:Exception do begin
  2252.             Answer := Format(msgPasvExcept, [E.Message]);
  2253.             try
  2254.                 Client.DataSocket.Close;
  2255.             except
  2256.                 { Ignore any exception here }
  2257.             end;
  2258.         end;
  2259.     end;
  2260. end;
  2261. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  2262. end.