1 {==============================================================================|
2 | Project : Ararat Synapse                                       | 004.000.000 |
3 |==============================================================================|
4 | Content: FTP client                                                          |
5 |==============================================================================|
6 | Copyright (c)1999-2011, Lukas Gebauer                                        |
7 | All rights reserved.                                                         |
8 |                                                                              |
9 | Redistribution and use in source and binary forms, with or without           |
10 | modification, are permitted provided that the following conditions are met:  |
11 |                                                                              |
12 | Redistributions of source code must retain the above copyright notice, this  |
13 | list of conditions and the following disclaimer.                             |
14 |                                                                              |
15 | Redistributions in binary form must reproduce the above copyright notice,    |
16 | this list of conditions and the following disclaimer in the documentation    |
17 | and/or other materials provided with the distribution.                       |
18 |                                                                              |
19 | Neither the name of Lukas Gebauer nor the names of its contributors may      |
20 | be used to endorse or promote products derived from this software without    |
21 | specific prior written permission.                                           |
22 |                                                                              |
23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"  |
24 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE    |
25 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE   |
26 | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR  |
27 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL       |
28 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR   |
29 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER   |
30 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT           |
31 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY    |
32 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH  |
33 | DAMAGE.                                                                      |
34 |==============================================================================|
35 | The Initial Developer of the Original Code is Lukas Gebauer (Czech Republic).|
36 | Portions created by Lukas Gebauer are Copyright (c) 1999-2010.               |
37 | All Rights Reserved.                                                         |
38 |==============================================================================|
39 | Contributor(s):                                                              |
40 |   Petr Esner <petr.esner@atlas.cz>                                           |
41 |==============================================================================|
42 | History: see HISTORY.HTM from distribution package                           |
43 |          (Found at URL: http://www.ararat.cz/synapse/)                       |
44 |==============================================================================}
45 
46 {: @abstract(FTP client protocol)
47 
48 Used RFC: RFC-959, RFC-2228, RFC-2428
49 }
50 
51 {$IFDEF FPC}
52   {$MODE DELPHI}
53 {$ENDIF}
54 {$H+}
55 {$TYPEINFO ON}// Borland changed defualt Visibility from Public to Published
56                 // and it requires RTTI to be generated $M+
57 {$M+}
58 
59 {$IFDEF UNICODE}
60   {$WARN IMPLICIT_STRING_CAST OFF}
61   {$WARN IMPLICIT_STRING_CAST_LOSS OFF}
62 {$ENDIF}
63 
64 unit ftpsend;
65 
66 interface
67 
68 uses
69   SysUtils, Classes,
70   blcksock, synautil, synaip, synsock;
71 
72 const
73   cFtpProtocol = '21';
74   cFtpDataProtocol = '20';
75 
76   {:Terminating value for TLogonActions}
77   FTP_OK = 255;
78   {:Terminating value for TLogonActions}
79   FTP_ERR = 254;
80 
81 type
82   {:Array for holding definition of logon sequence.}
83   TLogonActions = array [0..17] of byte;
84 
85   {:Procedural type for OnStatus event. Sender is calling @link(TFTPSend) object.
86    Value is FTP command or reply to this comand. (if it is reply, Response
87    is @True).}
88   TFTPStatus = procedure(Sender: TObject; Response: Boolean;
89     const Value: string) of object;
90 
91   {: @abstract(Object for holding file information) parsed from directory
92    listing of FTP server.}
93   TFTPListRec = class(TObject)
94   private
95     FFileName: String;
96     FDirectory: Boolean;
97     FReadable: Boolean;
98     FFileSize: int64;
99     FFileTime: TDateTime;
100     FOriginalLine: string;
101     FMask: string;
102     FPermission: String;
103   public
104     {: You can assign another TFTPListRec to this object.}
105     procedure Assign(Value: TFTPListRec); virtual;
106     {:name of file}
107     property FileName: string read FFileName write FFileName;
108     {:if name is subdirectory not file.}
109     property Directory: Boolean read FDirectory write FDirectory;
110     {:if you have rights to read}
111     property Readable: Boolean read FReadable write FReadable;
112     {:size of file in bytes}
113     property FileSize: int64 read FFileSize write FFileSize;
114     {:date and time of file. Local server timezone is used. Any timezone
115      conversions was not done!}
116     property FileTime: TDateTime read FFileTime write FFileTime;
117     {:original unparsed line}
118     property OriginalLine: string read FOriginalLine write FOriginalLine;
119     {:mask what was used for parsing}
120     property Mask: string read FMask write FMask;
121     {:permission string (depending on used mask!)}
122     property Permission: string read FPermission write FPermission;
123   end;
124 
125   {:@abstract(This is TList of TFTPListRec objects.)
126    This object is used for holding lististing of all files information in listed
127    directory on FTP server.}
128   TFTPList = class(TObject)
129   protected
130     FList: TList;
131     FLines: TStringList;
132     FMasks: TStringList;
133     FUnparsedLines: TStringList;
134     Monthnames: string;
135     BlockSize: string;
136     DirFlagValue: string;
137     FileName: string;
138     VMSFileName: string;
139     Day: string;
140     Month: string;
141     ThreeMonth: string;
142     YearTime: string;
143     Year: string;
144     Hours: string;
145     HoursModif: Ansistring;
146     Minutes: string;
147     Seconds: string;
148     Size: Ansistring;
149     Permissions: Ansistring;
150     DirFlag: string;
GetListItemnull151     function GetListItem(Index: integer): TFTPListRec; virtual;
ParseEPLFnull152     function ParseEPLF(Value: string): Boolean; virtual;
153     procedure ClearStore; virtual;
ParseByMasknull154     function ParseByMask(Value, NextValue, Mask: ansistring): Integer; virtual;
CheckValuesnull155     function CheckValues: Boolean; virtual;
156     procedure FillRecord(const Value: TFTPListRec); virtual;
157   public
158     {:Constructor. You not need create this object, it is created by TFTPSend
159      class as their property.}
160     constructor Create;
161     destructor Destroy; override;
162 
163     {:Clear list.}
164     procedure Clear; virtual;
165 
166     {:count of holded @link(TFTPListRec) objects}
Countnull167     function Count: integer; virtual;
168 
169     {:Assigns one list to another}
170     procedure Assign(Value: TFTPList); virtual;
171 
172     {:try to parse raw directory listing in @link(lines) to list of
173      @link(TFTPListRec).}
174     procedure ParseLines; virtual;
175 
176     {:By this property you have access to list of @link(TFTPListRec).
177      This is for compatibility only. Please, use @link(Items) instead.}
178     property List: TList read FList;
179 
180     {:By this property you have access to list of @link(TFTPListRec).}
181     property Items[Index: Integer]: TFTPListRec read GetListItem; default;
182 
183     {:Set of lines with RAW directory listing for @link(parseLines)}
184     property Lines: TStringList read FLines;
185 
186     {:Set of masks for directory listing parser. It is predefined by default,
187     however you can modify it as you need. (for example, you can add your own
188     definition mask.) Mask is same as mask used in TotalCommander.}
189     property Masks: TStringList read FMasks;
190 
191     {:After @link(ParseLines) it holding lines what was not sucessfully parsed.}
192     property UnparsedLines: TStringList read FUnparsedLines;
193   end;
194 
195   {:@abstract(Implementation of FTP protocol.)
196    Note: Are you missing properties for setting Username and Password? Look to
197    parent @link(TSynaClient) object! (Username and Password have default values
198    for "anonymous" FTP login)
199 
200    Are you missing properties for specify server address and port? Look to
201    parent @link(TSynaClient) too!}
202   TFTPSend = class(TSynaClient)
203   protected
204     FOnStatus: TFTPStatus;
205     FSock: TTCPBlockSocket;
206     FDSock: TTCPBlockSocket;
207     FResultCode: Integer;
208     FResultString: string;
209     FFullResult: TStringList;
210     FAccount: string;
211     FFWHost: string;
212     FFWPort: string;
213     FFWUsername: string;
214     FFWPassword: string;
215     FFWMode: integer;
216     FDataStream: TMemoryStream;
217     FDataIP: string;
218     FDataPort: string;
219     FDirectFile: Boolean;
220     FDirectFileName: string;
221     FCanResume: Boolean;
222     FPassiveMode: Boolean;
223     FForceDefaultPort: Boolean;
224     FForceOldPort: Boolean;
225     FFtpList: TFTPList;
226     FBinaryMode: Boolean;
227     FAutoTLS: Boolean;
228     FIsTLS: Boolean;
229     FIsDataTLS: Boolean;
230     FTLSonData: Boolean;
231     FFullSSL: Boolean;
Authnull232     function Auth(Mode: integer): Boolean; virtual;
Connectnull233     function Connect: Boolean; virtual;
InternalStornull234     function InternalStor(const Command: string; RestoreAt: int64): Boolean; virtual;
DataSocketnull235     function DataSocket: Boolean; virtual;
AcceptDataSocketnull236     function AcceptDataSocket: Boolean; virtual;
237     procedure DoStatus(Response: Boolean; const Value: string); virtual;
238   public
239     {:Custom definition of login sequence. You can use this when you set
240      @link(FWMode) to value -1.}
241     CustomLogon: TLogonActions;
242 
243     constructor Create;
244     destructor Destroy; override;
245 
246     {:Waits and read FTP server response. You need this only in special cases!}
ReadResultnull247     function ReadResult: Integer; virtual;
248 
249     {:Parse remote side information of data channel from value string (returned
250      by PASV command). This function you need only in special cases!}
251     procedure ParseRemote(Value: string); virtual;
252 
253     {:Parse remote side information of data channel from value string (returned
254      by EPSV command). This function you need only in special cases!}
255     procedure ParseRemoteEPSV(Value: string); virtual;
256 
257     {:Send Value as FTP command to FTP server. Returned result code is result of
258      this function.
259      This command is good for sending site specific command, or non-standard
260      commands.}
FTPCommandnull261     function FTPCommand(const Value: string): integer; virtual;
262 
263     {:Connect and logon to FTP server. If you specify any FireWall, connect to
264      firewall and throw them connect to FTP server. Login sequence depending on
265      @link(FWMode).}
Loginnull266     function Login: Boolean; virtual;
267 
268     {:Logoff and disconnect from FTP server.}
Logoutnull269     function Logout: Boolean; virtual;
270 
271     {:Break current transmission of data. (You can call this method from
272      Sock.OnStatus event, or from another thread.)}
273     procedure Abort; virtual;
274 
275     {:Break current transmission of data. It is same as Abort, but it send abort
276      telnet commands prior ABOR FTP command. Some servers need it. (You can call
277      this method from Sock.OnStatus event, or from another thread.)}
278     procedure TelnetAbort; virtual;
279 
280     {:Download directory listing of Directory on FTP server. If Directory is
281      empty string, download listing of current working directory.
282      If NameList is @true, download only names of files in directory.
283      (internally use NLST command instead LIST command)
284      If NameList is @false, returned list is also parsed to @link(FTPList)
285      property.}
Listnull286     function List(Directory: string; NameList: Boolean): Boolean; virtual;
287 
288     {:Read data from FileName on FTP server. If Restore is @true and server
289      supports resume dowloads, download is resumed. (received is only rest
290      of file)}
RetrieveFilenull291     function RetrieveFile(const FileName: string; Restore: Boolean): Boolean; virtual;
292 
293     {:Send data to FileName on FTP server. If Restore is @true and server
294      supports resume upload, upload is resumed. (send only rest of file)
295      In this case if remote file is same length as local file, nothing will be
296      done. If remote file is larger then local, resume is disabled and file is
297      transfered from begin!}
StoreFilenull298     function StoreFile(const FileName: string; Restore: Boolean): Boolean; virtual;
299 
300     {:Send data to FTP server and assing unique name for this file.}
StoreUniqueFilenull301     function StoreUniqueFile: Boolean; virtual;
302 
303     {:Append data to FileName on FTP server.}
AppendFilenull304     function AppendFile(const FileName: string): Boolean; virtual;
305 
306     {:Rename on FTP server file with OldName to NewName.}
RenameFilenull307     function RenameFile(const OldName, NewName: string): Boolean; virtual;
308 
309     {:Delete file FileName on FTP server.}
DeleteFilenull310     function DeleteFile(const FileName: string): Boolean; virtual;
311 
312     {:Return size of Filename file on FTP server. If command failed (i.e. not
313      implemented), return -1.}
FileSizenull314     function FileSize(const FileName: string): int64; virtual;
315 
316     {:Send NOOP command to FTP server for preserve of disconnect by inactivity
317      timeout.}
NoOpnull318     function NoOp: Boolean; virtual;
319 
320     {:Change currect working directory to Directory on FTP server.}
ChangeWorkingDirnull321     function ChangeWorkingDir(const Directory: string): Boolean; virtual;
322 
323     {:walk to upper directory on FTP server.}
ChangeToParentDirnull324     function ChangeToParentDir: Boolean; virtual;
325 
326     {:walk to root directory on FTP server. (May not work with all servers properly!)}
ChangeToRootDirnull327     function ChangeToRootDir: Boolean; virtual;
328 
329     {:Delete Directory on FTP server.}
DeleteDirnull330     function DeleteDir(const Directory: string): Boolean; virtual;
331 
332     {:Create Directory on FTP server.}
CreateDirnull333     function CreateDir(const Directory: string): Boolean; virtual;
334 
335     {:Return current working directory on FTP server.}
GetCurrentDirnull336     function GetCurrentDir: String; virtual;
337 
338     {:Establish data channel to FTP server and retrieve data.
339      This function you need only in special cases, i.e. when you need to implement
340      some special unsupported FTP command!}
DataReadnull341     function DataRead(const DestStream: TStream): Boolean; virtual;
342 
343     {:Establish data channel to FTP server and send data.
344      This function you need only in special cases, i.e. when you need to implement
345      some special unsupported FTP command.}
DataWritenull346     function DataWrite(const SourceStream: TStream): Boolean; virtual;
347   published
348     {:After FTP command contains result number of this operation.}
349     property ResultCode: Integer read FResultCode;
350 
351     {:After FTP command contains main line of result.}
352     property ResultString: string read FResultString;
353 
354     {:After any FTP command it contains all lines of FTP server reply.}
355     property FullResult: TStringList read FFullResult;
356 
357     {:Account information used in some cases inside login sequence.}
358     property Account: string read FAccount Write FAccount;
359 
360     {:Address of firewall. If empty string (default), firewall not used.}
361     property FWHost: string read FFWHost Write FFWHost;
362 
363     {:port of firewall. standard value is same port as ftp server used. (21)}
364     property FWPort: string read FFWPort Write FFWPort;
365 
366     {:Username for login to firewall. (if needed)}
367     property FWUsername: string read FFWUsername Write FFWUsername;
368 
369     {:password for login to firewall. (if needed)}
370     property FWPassword: string read FFWPassword Write FFWPassword;
371 
372     {:Type of Firewall. Used only if you set some firewall address. Supported
373      predefined firewall login sequences are described by comments in source
374      file where you can see pseudocode decribing each sequence.}
375     property FWMode: integer read FFWMode Write FFWMode;
376 
377     {:Socket object used for TCP/IP operation on control channel. Good for
378      seting OnStatus hook, etc.}
379     property Sock: TTCPBlockSocket read FSock;
380 
381     {:Socket object used for TCP/IP operation on data channel. Good for seting
382      OnStatus hook, etc.}
383     property DSock: TTCPBlockSocket read FDSock;
384 
385     {:If you not use @link(DirectFile) mode, all data transfers is made to or
386      from this stream.}
387     property DataStream: TMemoryStream read FDataStream;
388 
389     {:After data connection is established, contains remote side IP of this
390      connection.}
391     property DataIP: string read FDataIP;
392 
393     {:After data connection is established, contains remote side port of this
394      connection.}
395     property DataPort: string read FDataPort;
396 
397     {:Mode of data handling by data connection. If @False, all data operations
398      are made to or from @link(DataStream) TMemoryStream.
399      If @true, data operations is made directly to file in your disk. (filename
400      is specified by @link(DirectFileName) property.) Dafault is @False!}
401     property DirectFile: Boolean read FDirectFile Write FDirectFile;
402 
403     {:Filename for direct disk data operations.}
404     property DirectFileName: string read FDirectFileName Write FDirectFileName;
405 
406     {:Indicate after @link(Login) if remote server support resume downloads and
407      uploads.}
408     property CanResume: Boolean read FCanResume;
409 
410     {:If true (default value), all transfers is made by passive method.
411      It is safer method for various firewalls.}
412     property PassiveMode: Boolean read FPassiveMode Write FPassiveMode;
413 
414     {:Force to listen for dataconnection on standard port (20). Default is @false,
415      dataconnections will be made to any non-standard port reported by PORT FTP
416      command. This setting is not used, if you use passive mode.}
417     property ForceDefaultPort: Boolean read FForceDefaultPort Write FForceDefaultPort;
418 
419     {:When is @true, then is disabled EPSV and EPRT support. However without this
420      commands you cannot use IPv6! (Disabling of this commands is needed only
421      when you are behind some crap firewall/NAT.}
422     property ForceOldPort: Boolean read FForceOldPort Write FForceOldPort;
423 
424     {:You may set this hook for monitoring FTP commands and replies.}
425     property OnStatus: TFTPStatus read FOnStatus write FOnStatus;
426 
427     {:After LIST command is here parsed list of files in given directory.}
428     property FtpList: TFTPList read FFtpList;
429 
430     {:if @true (default), then data transfers is in binary mode. If this is set
431      to @false, then ASCII mode is used.}
432     property BinaryMode: Boolean read FBinaryMode Write FBinaryMode;
433 
434     {:if is true, then if server support upgrade to SSL/TLS mode, then use them.}
435     property AutoTLS: Boolean read FAutoTLS Write FAutoTLS;
436 
437     {:if server listen on SSL/TLS port, then you set this to true.}
438     property FullSSL: Boolean read FFullSSL Write FFullSSL;
439 
440     {:Signalise, if control channel is in SSL/TLS mode.}
441     property IsTLS: Boolean read FIsTLS;
442 
443     {:Signalise, if data transfers is in SSL/TLS mode.}
444     property IsDataTLS: Boolean read FIsDataTLS;
445 
446     {:If @true (default), then try to use SSL/TLS on data transfers too.
447      If @false, then SSL/TLS is used only for control connection.}
448     property TLSonData: Boolean read FTLSonData write FTLSonData;
449   end;
450 
451 {:A very useful function, and example of use can be found in the TFtpSend object.
452  Dowload specified file from FTP server to LocalFile.}
FtpGetFilenull453 function FtpGetFile(const IP, Port, FileName, LocalFile,
454   User, Pass: string): Boolean;
455 
456 {:A very useful function, and example of use can be found in the TFtpSend object.
457  Upload specified LocalFile to FTP server.}
FtpPutFilenull458 function FtpPutFile(const IP, Port, FileName, LocalFile,
459   User, Pass: string): Boolean;
460 
461 {:A very useful function, and example of use can be found in the TFtpSend object.
462  Initiate transfer of file between two FTP servers.}
FtpInterServerTransfernull463 function FtpInterServerTransfer(
464   const FromIP, FromPort, FromFile, FromUser, FromPass: string;
465   const ToIP, ToPort, ToFile, ToUser, ToPass: string): Boolean;
466 
467 implementation
468 
469 constructor TFTPSend.Create;
470 begin
471   inherited Create;
472   FFullResult := TStringList.Create;
473   FDataStream := TMemoryStream.Create;
474   FSock := TTCPBlockSocket.Create;
475   FSock.Owner := self;
476   FSock.ConvertLineEnd := True;
477   FDSock := TTCPBlockSocket.Create;
478   FDSock.Owner := self;
479   FFtpList := TFTPList.Create;
480   FTimeout := 300000;
481   FTargetPort := cFtpProtocol;
482   FUsername := 'anonymous';
483   FPassword := 'anonymous@' + FSock.LocalName;
484   FDirectFile := False;
485   FPassiveMode := True;
486   FForceDefaultPort := False;
487   FForceOldPort := false;
488   FAccount := '';
489   FFWHost := '';
490   FFWPort := cFtpProtocol;
491   FFWUsername := '';
492   FFWPassword := '';
493   FFWMode := 0;
494   FBinaryMode := True;
495   FAutoTLS := False;
496   FFullSSL := False;
497   FIsTLS := False;
498   FIsDataTLS := False;
499   FTLSonData := True;
500 end;
501 
502 destructor TFTPSend.Destroy;
503 begin
504   FDSock.Free;
505   FSock.Free;
506   FFTPList.Free;
507   FDataStream.Free;
508   FFullResult.Free;
509   inherited Destroy;
510 end;
511 
512 procedure TFTPSend.DoStatus(Response: Boolean; const Value: string);
513 begin
514   if assigned(OnStatus) then
515     OnStatus(Self, Response, Value);
516 end;
517 
ReadResultnull518 function TFTPSend.ReadResult: Integer;
519 var
520   s, c: AnsiString;
521 begin
522   FFullResult.Clear;
523   c := '';
524   repeat
525     s := FSock.RecvString(FTimeout);
526     if c = '' then
527       if length(s) > 3 then
528         if s[4] in [' ', '-'] then
529           c :=Copy(s, 1, 3);
530     FResultString := s;
531     FFullResult.Add(s);
532     DoStatus(True, s);
533     if FSock.LastError <> 0 then
534       Break;
535   until (c <> '') and (Pos(c + ' ', s) = 1);
536   Result := StrToIntDef(c, 0);
537   FResultCode := Result;
538 end;
539 
TFTPSend.FTPCommandnull540 function TFTPSend.FTPCommand(const Value: string): integer;
541 begin
542   FSock.Purge;
543   FSock.SendString(Value + CRLF);
544   DoStatus(False, Value);
545   Result := ReadResult;
546 end;
547 
548 // based on idea by Petr Esner <petr.esner@atlas.cz>
Authnull549 function TFTPSend.Auth(Mode: integer): Boolean;
550 const
551   //if not USER <username> then
552   //  if not PASS <password> then
553   //    if not ACCT <account> then ERROR!
554   //OK!
555   Action0: TLogonActions =
556     (0, FTP_OK, 3,
557      1, FTP_OK, 6,
558      2, FTP_OK, FTP_ERR,
559      0, 0, 0, 0, 0, 0, 0, 0, 0);
560 
561   //if not USER <FWusername> then
562   //  if not PASS <FWPassword> then ERROR!
563   //if SITE <FTPServer> then ERROR!
564   //if not USER <username> then
565   //  if not PASS <password> then
566   //    if not ACCT <account> then ERROR!
567   //OK!
568   Action1: TLogonActions =
569     (3, 6, 3,
570      4, 6, FTP_ERR,
571      5, FTP_ERR, 9,
572      0, FTP_OK, 12,
573      1, FTP_OK, 15,
574      2, FTP_OK, FTP_ERR);
575 
576   //if not USER <FWusername> then
577   //  if not PASS <FWPassword> then ERROR!
578   //if USER <UserName>'@'<FTPServer> then OK!
579   //if not PASS <password> then
580   //  if not ACCT <account> then ERROR!
581   //OK!
582   Action2: TLogonActions =
583     (3, 6, 3,
584      4, 6, FTP_ERR,
585      6, FTP_OK, 9,
586      1, FTP_OK, 12,
587      2, FTP_OK, FTP_ERR,
588      0, 0, 0);
589 
590   //if not USER <FWusername> then
591   //  if not PASS <FWPassword> then ERROR!
592   //if not USER <username> then
593   //  if not PASS <password> then
594   //    if not ACCT <account> then ERROR!
595   //OK!
596   Action3: TLogonActions =
597     (3, 6, 3,
598      4, 6, FTP_ERR,
599      0, FTP_OK, 9,
600      1, FTP_OK, 12,
601      2, FTP_OK, FTP_ERR,
602      0, 0, 0);
603 
604   //OPEN <FTPserver>
605   //if not USER <username> then
606   //  if not PASS <password> then
607   //    if not ACCT <account> then ERROR!
608   //OK!
609   Action4: TLogonActions =
610     (7, 3, 3,
611      0, FTP_OK, 6,
612      1, FTP_OK, 9,
613      2, FTP_OK, FTP_ERR,
614      0, 0, 0, 0, 0, 0);
615 
616   //if USER <UserName>'@'<FTPServer> then OK!
617   //if not PASS <password> then
618   //  if not ACCT <account> then ERROR!
619   //OK!
620   Action5: TLogonActions =
621     (6, FTP_OK, 3,
622      1, FTP_OK, 6,
623      2, FTP_OK, FTP_ERR,
624      0, 0, 0, 0, 0, 0, 0, 0, 0);
625 
626   //if not USER <FWUserName>@<FTPServer> then
627   //  if not PASS <FWPassword> then ERROR!
628   //if not USER <username> then
629   //  if not PASS <password> then
630   //    if not ACCT <account> then ERROR!
631   //OK!
632   Action6: TLogonActions =
633     (8, 6, 3,
634      4, 6, FTP_ERR,
635      0, FTP_OK, 9,
636      1, FTP_OK, 12,
637      2, FTP_OK, FTP_ERR,
638      0, 0, 0);
639 
640   //if USER <UserName>@<FTPServer> <FWUserName> then ERROR!
641   //if not PASS <password> then
642   //  if not ACCT <account> then ERROR!
643   //OK!
644   Action7: TLogonActions =
645     (9, FTP_ERR, 3,
646      1, FTP_OK, 6,
647      2, FTP_OK, FTP_ERR,
648      0, 0, 0, 0, 0, 0, 0, 0, 0);
649 
650   //if not USER <UserName>@<FWUserName>@<FTPServer> then
651   //  if not PASS <Password>@<FWPassword> then
652   //    if not ACCT <account> then ERROR!
653   //OK!
654   Action8: TLogonActions =
655     (10, FTP_OK, 3,
656      11, FTP_OK, 6,
657      2, FTP_OK, FTP_ERR,
658      0, 0, 0, 0, 0, 0, 0, 0, 0);
659 var
660   FTPServer: string;
661   LogonActions: TLogonActions;
662   i: integer;
663   s: string;
664   x: integer;
665 begin
666   Result := False;
667   if FFWHost = '' then
668     Mode := 0;
669   if (FTargetPort = cFtpProtocol) or (FTargetPort = '21') then
670     FTPServer := FTargetHost
671   else
672     FTPServer := FTargetHost + ':' + FTargetPort;
673   case Mode of
674     -1:
675       LogonActions := CustomLogon;
676     1:
677       LogonActions := Action1;
678     2:
679       LogonActions := Action2;
680     3:
681       LogonActions := Action3;
682     4:
683       LogonActions := Action4;
684     5:
685       LogonActions := Action5;
686     6:
687       LogonActions := Action6;
688     7:
689       LogonActions := Action7;
690     8:
691       LogonActions := Action8;
692   else
693     LogonActions := Action0;
694   end;
695   i := 0;
696   repeat
697     case LogonActions[i] of
698       0:  s := 'USER ' + FUserName;
699       1:  s := 'PASS ' + FPassword;
700       2:  s := 'ACCT ' + FAccount;
701       3:  s := 'USER ' + FFWUserName;
702       4:  s := 'PASS ' + FFWPassword;
703       5:  s := 'SITE ' + FTPServer;
704       6:  s := 'USER ' + FUserName + '@' + FTPServer;
705       7:  s := 'OPEN ' + FTPServer;
706       8:  s := 'USER ' + FFWUserName + '@' + FTPServer;
707       9:  s := 'USER ' + FUserName + '@' + FTPServer + ' ' + FFWUserName;
708       10: s := 'USER ' + FUserName + '@' + FFWUserName + '@' + FTPServer;
709       11: s := 'PASS ' + FPassword + '@' + FFWPassword;
710     end;
711     x := FTPCommand(s);
712     x := x div 100;
713     if (x <> 2) and (x <> 3) then
714       Exit;
715     i := LogonActions[i + x - 1];
716     case i of
717       FTP_ERR:
718         Exit;
719       FTP_OK:
720         begin
721           Result := True;
722           Exit;
723         end;
724     end;
725   until False;
726 end;
727 
728 
TFTPSend.Connectnull729 function TFTPSend.Connect: Boolean;
730 begin
731   FSock.CloseSocket;
732   FSock.Bind(FIPInterface, cAnyPort);
733   if FSock.LastError = 0 then
734     if FFWHost = '' then
735       FSock.Connect(FTargetHost, FTargetPort)
736     else
737       FSock.Connect(FFWHost, FFWPort);
738   if FSock.LastError = 0 then
739     if FFullSSL then
740       FSock.SSLDoConnect;
741   Result := FSock.LastError = 0;
742 end;
743 
Loginnull744 function TFTPSend.Login: Boolean;
745 var
746   x: integer;
747 begin
748   Result := False;
749   FCanResume := False;
750   if not Connect then
751     Exit;
752   FIsTLS := FFullSSL;
753   FIsDataTLS := False;
754   repeat
755     x := ReadResult div 100;
756   until x <> 1;
757   if x <> 2 then
758     Exit;
759   if FAutoTLS and not(FIsTLS) then
760     if (FTPCommand('AUTH TLS') div 100) = 2 then
761     begin
762       FSock.SSLDoConnect;
763       FIsTLS := FSock.LastError = 0;
764       if not FIsTLS then
765       begin
766         Result := False;
767         Exit;
768       end;
769     end;
770   if not Auth(FFWMode) then
771     Exit;
772   if FIsTLS then
773   begin
774     FTPCommand('PBSZ 0');
775     if FTLSonData then
776       FIsDataTLS := (FTPCommand('PROT P') div 100) = 2;
777     if not FIsDataTLS then
778       FTPCommand('PROT C');
779   end;
780   FTPCommand('TYPE I');
781   FTPCommand('STRU F');
782   FTPCommand('MODE S');
783   if FTPCommand('REST 0') = 350 then
784     if FTPCommand('REST 1') = 350 then
785     begin
786       FTPCommand('REST 0');
787       FCanResume := True;
788     end;
789   Result := True;
790 end;
791 
Logoutnull792 function TFTPSend.Logout: Boolean;
793 begin
794   Result := (FTPCommand('QUIT') div 100) = 2;
795   FSock.CloseSocket;
796 end;
797 
798 procedure TFTPSend.ParseRemote(Value: string);
799 var
800   n: integer;
801   nb, ne: integer;
802   s: string;
803   x: integer;
804 begin
805   Value := trim(Value);
806   nb := Pos('(',Value);
807   ne := Pos(')',Value);
808   if (nb = 0) or (ne = 0) then
809   begin
810     nb:=RPos(' ',Value);
811     s:=Copy(Value, nb + 1, Length(Value) - nb);
812   end
813   else
814   begin
815     s:=Copy(Value,nb+1,ne-nb-1);
816   end;
817   for n := 1 to 4 do
818     if n = 1 then
819       FDataIP := Fetch(s, ',')
820     else
821       FDataIP := FDataIP + '.' + Fetch(s, ',');
822   x := StrToIntDef(Fetch(s, ','), 0) * 256;
823   x := x + StrToIntDef(Fetch(s, ','), 0);
824   FDataPort := IntToStr(x);
825 end;
826 
827 procedure TFTPSend.ParseRemoteEPSV(Value: string);
828 var
829   n: integer;
830   s, v: AnsiString;
831 begin
832   s := SeparateRight(Value, '(');
833   s := Trim(SeparateLeft(s, ')'));
834   Delete(s, Length(s), 1);
835   v := '';
836   for n := Length(s) downto 1 do
837     if s[n] in ['0'..'9'] then
838       v := s[n] + v
839     else
840       Break;
841   FDataPort := v;
842   FDataIP := FTargetHost;
843 end;
844 
TFTPSend.DataSocketnull845 function TFTPSend.DataSocket: boolean;
846 var
847   s: string;
848 begin
849   Result := False;
850   if FIsDataTLS then
851     FPassiveMode := True;
852   if FPassiveMode then
853   begin
854     if FSock.IP6used then
855       s := '2'
856     else
857       s := '1';
858     if FSock.IP6used and not(FForceOldPort) and ((FTPCommand('EPSV ' + s) div 100) = 2) then
859     begin
860       ParseRemoteEPSV(FResultString);
861     end
862     else
863       if FSock.IP6used then
864         Exit
865       else
866       begin
867         if (FTPCommand('PASV') div 100) <> 2 then
868           Exit;
869         ParseRemote(FResultString);
870       end;
871     FDSock.CloseSocket;
872     FDSock.Bind(FIPInterface, cAnyPort);
873     FDSock.Connect(FDataIP, FDataPort);
874     Result := FDSock.LastError = 0;
875   end
876   else
877   begin
878     FDSock.CloseSocket;
879     if FForceDefaultPort then
880       s := cFtpDataProtocol
881     else
882       s := '0';
883     //data conection from same interface as command connection
884     FDSock.Bind(FSock.GetLocalSinIP, s);
885     if FDSock.LastError <> 0 then
886       Exit;
887     FDSock.SetLinger(True, 10000);
888     FDSock.Listen;
889     FDSock.GetSins;
890     FDataIP := FDSock.GetLocalSinIP;
891     FDataIP := FDSock.ResolveName(FDataIP);
892     FDataPort := IntToStr(FDSock.GetLocalSinPort);
893     if FSock.IP6used and (not FForceOldPort) then
894     begin
895       if IsIp6(FDataIP) then
896         s := '2'
897       else
898         s := '1';
899       s := 'EPRT |' + s +'|' + FDataIP + '|' + FDataPort + '|';
900       Result := (FTPCommand(s) div 100) = 2;
901     end;
902     if not Result and IsIP(FDataIP) then
903     begin
904       s := ReplaceString(FDataIP, '.', ',');
905       s := 'PORT ' + s + ',' + IntToStr(FDSock.GetLocalSinPort div 256)
906         + ',' + IntToStr(FDSock.GetLocalSinPort mod 256);
907       Result := (FTPCommand(s) div 100) = 2;
908     end;
909   end;
910 end;
911 
AcceptDataSocketnull912 function TFTPSend.AcceptDataSocket: Boolean;
913 var
914   x: TSocket;
915 begin
916   if FPassiveMode then
917     Result := True
918   else
919   begin
920     Result := False;
921     if FDSock.CanRead(FTimeout) then
922     begin
923       x := FDSock.Accept;
924       if not FDSock.UsingSocks then
925         FDSock.CloseSocket;
926       FDSock.Socket := x;
927       Result := True;
928     end;
929   end;
930   if Result and FIsDataTLS then
931   begin
932     FDSock.SSL.Assign(FSock.SSL);
933     FDSock.SSLDoConnect;
934     Result := FDSock.LastError = 0;
935   end;
936 end;
937 
TFTPSend.DataReadnull938 function TFTPSend.DataRead(const DestStream: TStream): Boolean;
939 var
940   x: integer;
941 begin
942   Result := False;
943   try
944     if not AcceptDataSocket then
945       Exit;
946     FDSock.RecvStreamRaw(DestStream, FTimeout);
947     FDSock.CloseSocket;
948     x := ReadResult;
949     Result := (x div 100) = 2;
950   finally
951     FDSock.CloseSocket;
952   end;
953 end;
954 
TFTPSend.DataWritenull955 function TFTPSend.DataWrite(const SourceStream: TStream): Boolean;
956 var
957   x: integer;
958   b: Boolean;
959 begin
960   Result := False;
961   try
962     if not AcceptDataSocket then
963       Exit;
964     FDSock.SendStreamRaw(SourceStream);
965     b := FDSock.LastError = 0;
966     FDSock.CloseSocket;
967     x := ReadResult;
968     Result := b and ((x div 100) = 2);
969   finally
970     FDSock.CloseSocket;
971   end;
972 end;
973 
TFTPSend.Listnull974 function TFTPSend.List(Directory: string; NameList: Boolean): Boolean;
975 var
976   x: integer;
977 begin
978   Result := False;
979   FDataStream.Clear;
980   FFTPList.Clear;
981   if Directory <> '' then
982     Directory := ' ' + Directory;
983   FTPCommand('TYPE A');
984   if not DataSocket then
985     Exit;
986   if NameList then
987     x := FTPCommand('NLST' + Directory)
988   else
989     x := FTPCommand('LIST' + Directory);
990   if (x div 100) <> 1 then
991     Exit;
992   Result := DataRead(FDataStream);
993   if (not NameList) and Result then
994   begin
995     FDataStream.Position := 0;
996     FFTPList.Lines.LoadFromStream(FDataStream);
997     FFTPList.ParseLines;
998   end;
999   FDataStream.Position := 0;
1000 end;
1001 
TFTPSend.RetrieveFilenull1002 function TFTPSend.RetrieveFile(const FileName: string; Restore: Boolean): Boolean;
1003 var
1004   RetrStream: TStream;
1005 begin
1006   Result := False;
1007   if FileName = '' then
1008     Exit;
1009   if not DataSocket then
1010     Exit;
1011   Restore := Restore and FCanResume;
1012   if FDirectFile then
1013     if Restore and FileExists(FDirectFileName) then
1014       RetrStream := TFileStream.Create(FDirectFileName,
1015         fmOpenReadWrite  or fmShareExclusive)
1016     else
1017       RetrStream := TFileStream.Create(FDirectFileName,
1018         fmCreate or fmShareDenyWrite)
1019   else
1020     RetrStream := FDataStream;
1021   try
1022     if FBinaryMode then
1023       FTPCommand('TYPE I')
1024     else
1025       FTPCommand('TYPE A');
1026     if Restore then
1027     begin
1028       RetrStream.Position := RetrStream.Size;
1029       if (FTPCommand('REST ' + IntToStr(RetrStream.Size)) div 100) <> 3 then
1030         Exit;
1031     end
1032     else
1033       if RetrStream is TMemoryStream then
1034         TMemoryStream(RetrStream).Clear;
1035     if (FTPCommand('RETR ' + FileName) div 100) <> 1 then
1036       Exit;
1037     Result := DataRead(RetrStream);
1038     if not FDirectFile then
1039       RetrStream.Position := 0;
1040   finally
1041     if FDirectFile then
1042       RetrStream.Free;
1043   end;
1044 end;
1045 
TFTPSend.InternalStornull1046 function TFTPSend.InternalStor(const Command: string; RestoreAt: int64): Boolean;
1047 var
1048   SendStream: TStream;
1049   StorSize: int64;
1050 begin
1051   Result := False;
1052   if FDirectFile then
1053     if not FileExists(FDirectFileName) then
1054       Exit
1055     else
1056       SendStream := TFileStream.Create(FDirectFileName,
1057         fmOpenRead or fmShareDenyWrite)
1058   else
1059     SendStream := FDataStream;
1060   try
1061     if not DataSocket then
1062       Exit;
1063     if FBinaryMode then
1064       FTPCommand('TYPE I')
1065     else
1066       FTPCommand('TYPE A');
1067     StorSize := SendStream.Size;
1068     if not FCanResume then
1069       RestoreAt := 0;
1070     if (StorSize > 0) and (RestoreAt = StorSize) then
1071     begin
1072       Result := True;
1073       Exit;
1074     end;
1075     if RestoreAt > StorSize then
1076       RestoreAt := 0;
1077     FTPCommand('ALLO ' + IntToStr(StorSize - RestoreAt));
1078     if FCanResume then
1079       if (FTPCommand('REST ' + IntToStr(RestoreAt)) div 100) <> 3 then
1080         Exit;
1081     SendStream.Position := RestoreAt;
1082     if (FTPCommand(Command) div 100) <> 1 then
1083       Exit;
1084     Result := DataWrite(SendStream);
1085   finally
1086     if FDirectFile then
1087       SendStream.Free;
1088   end;
1089 end;
1090 
StoreFilenull1091 function TFTPSend.StoreFile(const FileName: string; Restore: Boolean): Boolean;
1092 var
1093   RestoreAt: int64;
1094 begin
1095   Result := False;
1096   if FileName = '' then
1097     Exit;
1098   RestoreAt := 0;
1099   Restore := Restore and FCanResume;
1100   if Restore then
1101   begin
1102     RestoreAt := Self.FileSize(FileName);
1103     if RestoreAt < 0 then
1104       RestoreAt := 0;
1105   end;
1106   Result := InternalStor('STOR ' + FileName, RestoreAt);
1107 end;
1108 
TFTPSend.StoreUniqueFilenull1109 function TFTPSend.StoreUniqueFile: Boolean;
1110 begin
1111   Result := InternalStor('STOU', 0);
1112 end;
1113 
AppendFilenull1114 function TFTPSend.AppendFile(const FileName: string): Boolean;
1115 begin
1116   Result := False;
1117   if FileName = '' then
1118     Exit;
1119   Result := InternalStor('APPE ' + FileName, 0);
1120 end;
1121 
NoOpnull1122 function TFTPSend.NoOp: Boolean;
1123 begin
1124   Result := (FTPCommand('NOOP') div 100) = 2;
1125 end;
1126 
TFTPSend.RenameFilenull1127 function TFTPSend.RenameFile(const OldName, NewName: string): Boolean;
1128 begin
1129   Result := False;
1130   if (FTPCommand('RNFR ' + OldName) div 100) <> 3  then
1131     Exit;
1132   Result := (FTPCommand('RNTO ' + NewName) div 100) = 2;
1133 end;
1134 
TFTPSend.DeleteFilenull1135 function TFTPSend.DeleteFile(const FileName: string): Boolean;
1136 begin
1137   Result := (FTPCommand('DELE ' + FileName) div 100) = 2;
1138 end;
1139 
TFTPSend.FileSizenull1140 function TFTPSend.FileSize(const FileName: string): int64;
1141 var
1142   s: string;
1143 begin
1144   Result := -1;
1145   if (FTPCommand('SIZE ' + FileName) div 100) = 2 then
1146   begin
1147     s := Trim(SeparateRight(ResultString, ' '));
1148     s := Trim(SeparateLeft(s, ' '));
1149     {$IFDEF VER100}
1150       Result := StrToIntDef(s, -1);
1151     {$ELSE}
1152       Result := StrToInt64Def(s, -1);
1153     {$ENDIF}
1154   end;
1155 end;
1156 
TFTPSend.ChangeWorkingDirnull1157 function TFTPSend.ChangeWorkingDir(const Directory: string): Boolean;
1158 begin
1159   Result := (FTPCommand('CWD ' + Directory) div 100) = 2;
1160 end;
1161 
ChangeToParentDirnull1162 function TFTPSend.ChangeToParentDir: Boolean;
1163 begin
1164   Result := (FTPCommand('CDUP') div 100) = 2;
1165 end;
1166 
ChangeToRootDirnull1167 function TFTPSend.ChangeToRootDir: Boolean;
1168 begin
1169   Result := ChangeWorkingDir('/');
1170 end;
1171 
DeleteDirnull1172 function TFTPSend.DeleteDir(const Directory: string): Boolean;
1173 begin
1174   Result := (FTPCommand('RMD ' + Directory) div 100) = 2;
1175 end;
1176 
TFTPSend.CreateDirnull1177 function TFTPSend.CreateDir(const Directory: string): Boolean;
1178 begin
1179   Result := (FTPCommand('MKD ' + Directory) div 100) = 2;
1180 end;
1181 
TFTPSend.GetCurrentDirnull1182 function TFTPSend.GetCurrentDir: String;
1183 begin
1184   Result := '';
1185   if (FTPCommand('PWD') div 100) = 2 then
1186   begin
1187     Result := SeparateRight(FResultString, '"');
1188     Result := Trim(Separateleft(Result, '"'));
1189   end;
1190 end;
1191 
1192 procedure TFTPSend.Abort;
1193 begin
1194   FSock.SendString('ABOR' + CRLF);
1195   FDSock.StopFlag := True;
1196 end;
1197 
1198 procedure TFTPSend.TelnetAbort;
1199 begin
1200   FSock.SendString(#$FF + #$F4 + #$FF + #$F2);
1201   Abort;
1202 end;
1203 
1204 {==============================================================================}
1205 
1206 procedure TFTPListRec.Assign(Value: TFTPListRec);
1207 begin
1208   FFileName := Value.FileName;
1209   FDirectory := Value.Directory;
1210   FReadable := Value.Readable;
1211   FFileSize := Value.FileSize;
1212   FFileTime := Value.FileTime;
1213   FOriginalLine := Value.OriginalLine;
1214   FMask := Value.Mask;
1215 end;
1216 
1217 constructor TFTPList.Create;
1218 begin
1219   inherited Create;
1220   FList := TList.Create;
1221   FLines := TStringList.Create;
1222   FMasks := TStringList.Create;
1223   FUnparsedLines := TStringList.Create;
1224   //various UNIX
1225   FMasks.add('pppppppppp $!!!S*$TTT$DD$hh mm ss$YYYY$n*');
1226   FMasks.add('pppppppppp $!!!S*$DD$TTT$hh mm ss$YYYY$n*');
1227   FMasks.add('pppppppppp $!!!S*$TTT$DD$UUUUU$n*');  //mostly used UNIX format
1228   FMasks.add('pppppppppp $!!!S*$DD$TTT$UUUUU$n*');
1229   //MacOS
1230   FMasks.add('pppppppppp $!!S*$TTT$DD$UUUUU$n*');
1231   FMasks.add('pppppppppp $!S*$TTT$DD$UUUUU$n*');
1232   //Novell
1233   FMasks.add('d            $!S*$TTT$DD$UUUUU$n*');
1234   //Windows
1235   FMasks.add('MM DD YY  hh mmH !S* n*');
1236   FMasks.add('MM DD YY  hh mmH $ d!n*');
1237   FMasks.add('MM DD YYYY  hh mmH !S* n*');
1238   FMasks.add('MM DD YYYY  hh mmH $ d!n*');
1239   FMasks.add('DD MM YYYY  hh mmH !S* n*');
1240   FMasks.add('DD MM YYYY  hh mmH $ d!n*');
1241   //VMS
1242   FMasks.add('v*$  DD TTT YYYY hh mm');
1243   FMasks.add('v*$!DD TTT YYYY hh mm');
1244   FMasks.add('n*$                 YYYY MM DD hh mm$S*');
1245   //AS400
1246   FMasks.add('!S*$MM DD YY hh mm ss !n*');
1247   FMasks.add('!S*$DD MM YY hh mm ss !n*');
1248   FMasks.add('n*!S*$MM DD YY hh mm ss d');
1249   FMasks.add('n*!S*$DD MM YY hh mm ss d');
1250   //VxWorks
1251   FMasks.add('$S*    TTT DD YYYY  hh mm ss $n* $ d');
1252   FMasks.add('$S*    TTT DD YYYY  hh mm ss $n*');
1253   //Distinct
1254   FMasks.add('d    $S*$TTT DD YYYY  hh mm$n*');
1255   FMasks.add('d    $S*$TTT DD$hh mm$n*');
1256   //PC-NFSD
1257   FMasks.add('nnnnnnnn.nnn  dSSSSSSSSSSS MM DD YY  hh mmH');
1258   //VOS
1259   FMasks.add('-   SSSSS            YY MM DD hh mm ss  n*');
1260   FMasks.add('- d=  SSSSS  YY MM DD hh mm ss  n*');
1261   //Unissys ClearPath
1262   FMasks.add('nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn               SSSSSSSSS MM DD YYYY hh mm');
1263   FMasks.add('n*\x                                               SSSSSSSSS MM DD YYYY hh mm');
1264   //IBM
1265   FMasks.add('-     SSSSSSSSSSSS           d   MM DD YYYY   hh mm  n*');
1266   //OS9
1267   FMasks.add('-         YY MM DD hhmm d                        SSSSSSSSS n*');
1268   //tandem
1269   FMasks.add('nnnnnnnn                   SSSSSSS DD TTT YY hh mm ss');
1270   //MVS
1271   FMasks.add('-             YYYY MM DD                     SSSSS   d=O n*');
1272   //BullGCOS8
1273   FMasks.add('             $S* MM DD YY hh mm ss  !n*');
1274   FMasks.add('d            $S* MM DD YY           !n*');
1275   //BullGCOS7
1276   FMasks.add('                                         TTT DD  YYYY n*');
1277   FMasks.add('  d                                                   n*');
1278 end;
1279 
1280 destructor TFTPList.Destroy;
1281 begin
1282   Clear;
1283   FList.Free;
1284   FLines.Free;
1285   FMasks.Free;
1286   FUnparsedLines.Free;
1287   inherited Destroy;
1288 end;
1289 
1290 procedure TFTPList.Clear;
1291 var
1292   n:integer;
1293 begin
1294   for n := 0 to FList.Count - 1 do
1295     if Assigned(FList[n]) then
1296       TFTPListRec(FList[n]).Free;
1297   FList.Clear;
1298   FLines.Clear;
1299   FUnparsedLines.Clear;
1300 end;
1301 
TFTPList.Countnull1302 function TFTPList.Count: integer;
1303 begin
1304   Result := FList.Count;
1305 end;
1306 
GetListItemnull1307 function TFTPList.GetListItem(Index: integer): TFTPListRec;
1308 begin
1309   Result := nil;
1310   if Index < Count then
1311     Result := TFTPListRec(FList[Index]);
1312 end;
1313 
1314 procedure TFTPList.Assign(Value: TFTPList);
1315 var
1316   flr: TFTPListRec;
1317   n: integer;
1318 begin
1319   Clear;
1320   for n := 0 to Value.Count - 1 do
1321   begin
1322     flr := TFTPListRec.Create;
1323     flr.Assign(Value[n]);
1324     Flist.Add(flr);
1325   end;
1326   Lines.Assign(Value.Lines);
1327   Masks.Assign(Value.Masks);
1328   UnparsedLines.Assign(Value.UnparsedLines);
1329 end;
1330 
1331 procedure TFTPList.ClearStore;
1332 begin
1333   Monthnames := '';
1334   BlockSize := '';
1335   DirFlagValue := '';
1336   FileName := '';
1337   VMSFileName := '';
1338   Day := '';
1339   Month := '';
1340   ThreeMonth := '';
1341   YearTime := '';
1342   Year := '';
1343   Hours := '';
1344   HoursModif := '';
1345   Minutes := '';
1346   Seconds := '';
1347   Size := '';
1348   Permissions := '';
1349   DirFlag := '';
1350 end;
1351 
TFTPList.ParseByMasknull1352 function TFTPList.ParseByMask(Value, NextValue, Mask: AnsiString): Integer;
1353 var
1354   Ivalue, IMask: integer;
1355   MaskC, LastMaskC: AnsiChar;
1356   c: AnsiChar;
1357   s: string;
1358 begin
1359   ClearStore;
1360   Result := 0;
1361   if Value = '' then
1362     Exit;
1363   if Mask = '' then
1364     Exit;
1365   Ivalue := 1;
1366   IMask := 1;
1367   Result := 1;
1368   LastMaskC := ' ';
1369   while Imask <= Length(mask) do
1370   begin
1371     if (Mask[Imask] <> '*') and (Ivalue > Length(Value)) then
1372     begin
1373       Result := 0;
1374       Exit;
1375     end;
1376     MaskC := Mask[Imask];
1377     if Ivalue > Length(Value) then
1378       Exit;
1379     c := Value[Ivalue];
1380     case MaskC of
1381       'n':
1382         FileName := FileName + c;
1383       'v':
1384         VMSFileName := VMSFileName + c;
1385       '.':
1386         begin
1387           if c in ['.', ' '] then
1388             FileName := TrimSP(FileName) + '.'
1389           else
1390           begin
1391             Result := 0;
1392             Exit;
1393           end;
1394         end;
1395       'D':
1396         Day := Day + c;
1397       'M':
1398         Month := Month + c;
1399       'T':
1400         ThreeMonth := ThreeMonth + c;
1401       'U':
1402         YearTime := YearTime + c;
1403       'Y':
1404         Year := Year + c;
1405       'h':
1406         Hours := Hours + c;
1407       'H':
1408         HoursModif := HoursModif + c;
1409       'm':
1410         Minutes := Minutes + c;
1411       's':
1412         Seconds := Seconds + c;
1413       'S':
1414         Size := Size + c;
1415       'p':
1416         Permissions := Permissions + c;
1417       'd':
1418         DirFlag := DirFlag + c;
1419       'x':
1420         if c <> ' ' then
1421           begin
1422             Result := 0;
1423             Exit;
1424           end;
1425       '*':
1426         begin
1427           s := '';
1428           if LastMaskC in ['n', 'v'] then
1429           begin
1430             if Imask = Length(Mask) then
1431               s := Copy(Value, IValue, Maxint)
1432             else
1433               while IValue <= Length(Value) do
1434               begin
1435                 if Value[Ivalue] = ' ' then
1436                   break;
1437                 s := s + Value[Ivalue];
1438                 Inc(Ivalue);
1439               end;
1440             if LastMaskC = 'n' then
1441               FileName := FileName + s
1442             else
1443               VMSFileName := VMSFileName + s;
1444           end
1445           else
1446           begin
1447             while IValue <= Length(Value) do
1448             begin
1449               if not(Value[Ivalue] in ['0'..'9']) then
1450                 break;
1451               s := s + Value[Ivalue];
1452               Inc(Ivalue);
1453             end;
1454             case LastMaskC of
1455               'S':
1456                 Size := Size + s;
1457             end;
1458           end;
1459           Dec(IValue);
1460         end;
1461       '!':
1462         begin
1463           while IValue <= Length(Value) do
1464           begin
1465             if Value[Ivalue] = ' ' then
1466               break;
1467             Inc(Ivalue);
1468           end;
1469           while IValue <= Length(Value) do
1470           begin
1471             if Value[Ivalue] <> ' ' then
1472               break;
1473             Inc(Ivalue);
1474           end;
1475           Dec(IValue);
1476         end;
1477       '$':
1478         begin
1479           while IValue <= Length(Value) do
1480           begin
1481             if not(Value[Ivalue] in [' ', #9]) then
1482               break;
1483             Inc(Ivalue);
1484           end;
1485           Dec(IValue);
1486         end;
1487       '=':
1488         begin
1489           s := '';
1490           case LastmaskC of
1491             'S':
1492               begin
1493                 while Imask <= Length(Mask) do
1494                 begin
1495                   if not(Mask[Imask] in ['0'..'9']) then
1496                     break;
1497                   s := s + Mask[Imask];
1498                   Inc(Imask);
1499                 end;
1500                 Dec(Imask);
1501                 BlockSize := s;
1502               end;
1503             'T':
1504               begin
1505                 Monthnames := Copy(Mask, IMask, 12 * 3);
1506                 Inc(IMask, 12 * 3);
1507               end;
1508             'd':
1509               begin
1510                 Inc(Imask);
1511                 DirFlagValue := Mask[Imask];
1512               end;
1513           end;
1514         end;
1515       '\':
1516         begin
1517           Value := NextValue;
1518           IValue := 0;
1519           Result := 2;
1520         end;
1521     end;
1522     Inc(Ivalue);
1523     Inc(Imask);
1524     LastMaskC := MaskC;
1525   end;
1526 end;
1527 
CheckValuesnull1528 function TFTPList.CheckValues: Boolean;
1529 var
1530   x, n: integer;
1531 begin
1532   Result := false;
1533   if FileName <> '' then
1534   begin
1535     if pos('?', VMSFilename) > 0 then
1536       Exit;
1537     if pos('*', VMSFilename) > 0 then
1538       Exit;
1539   end;
1540   if VMSFileName <> '' then
1541     if pos(';', VMSFilename) <= 0 then
1542       Exit;
1543   if (FileName = '') and (VMSFileName = '') then
1544     Exit;
1545   if Permissions <> '' then
1546   begin
1547     if length(Permissions) <> 10 then
1548       Exit;
1549     for n := 1 to 10 do
1550       if not(Permissions[n] in
1551         ['a', 'b', 'c', 'd', 'h', 'l', 'p', 'r', 's', 't', 'w', 'x', 'y', '-']) then
1552         Exit;
1553   end;
1554   if Day <> '' then
1555   begin
1556     Day := TrimSP(Day);
1557     x := StrToIntDef(day, -1);
1558     if (x < 1) or (x > 31) then
1559       Exit;
1560   end;
1561   if Month <> '' then
1562   begin
1563     Month := TrimSP(Month);
1564     x := StrToIntDef(Month, -1);
1565     if (x < 1) or (x > 12) then
1566       Exit;
1567   end;
1568   if Hours <> '' then
1569   begin
1570     Hours := TrimSP(Hours);
1571     x := StrToIntDef(Hours, -1);
1572     if (x < 0) or (x > 24) then
1573       Exit;
1574   end;
1575   if HoursModif <> '' then
1576   begin
1577     if not (HoursModif[1] in ['a', 'A', 'p', 'P']) then
1578       Exit;
1579   end;
1580   if Minutes <> '' then
1581   begin
1582     Minutes := TrimSP(Minutes);
1583     x := StrToIntDef(Minutes, -1);
1584     if (x < 0) or (x > 59) then
1585       Exit;
1586   end;
1587   if Seconds <> '' then
1588   begin
1589     Seconds := TrimSP(Seconds);
1590     x := StrToIntDef(Seconds, -1);
1591     if (x < 0) or (x > 59) then
1592       Exit;
1593   end;
1594   if Size <> '' then
1595   begin
1596     Size := TrimSP(Size);
1597     for n := 1 to Length(Size) do
1598       if not (Size[n] in ['0'..'9']) then
1599         Exit;
1600   end;
1601 
1602   if length(Monthnames) = (12 * 3) then
1603     for n := 1 to 12 do
1604       CustomMonthNames[n] := Copy(Monthnames, ((n - 1) * 3) + 1, 3);
1605   if ThreeMonth <> '' then
1606   begin
1607     x := GetMonthNumber(ThreeMonth);
1608     if (x = 0) then
1609       Exit;
1610   end;
1611   if YearTime <> '' then
1612   begin
1613     YearTime := ReplaceString(YearTime, '-', ':');
1614     if pos(':', YearTime) > 0 then
1615     begin
1616       if (GetTimeFromstr(YearTime) = -1) then
1617         Exit;
1618     end
1619     else
1620     begin
1621       YearTime := TrimSP(YearTime);
1622       x := StrToIntDef(YearTime, -1);
1623       if (x = -1) then
1624         Exit;
1625       if (x < 1900) or (x > 2100) then
1626         Exit;
1627     end;
1628   end;
1629   if Year <> '' then
1630   begin
1631     Year := TrimSP(Year);
1632     x := StrToIntDef(Year, -1);
1633     if (x = -1) then
1634       Exit;
1635     if Length(Year) = 4 then
1636     begin
1637       if not((x > 1900) and (x < 2100)) then
1638         Exit;
1639     end
1640     else
1641       if Length(Year) = 2 then
1642       begin
1643         if not((x >= 0) and (x <= 99)) then
1644           Exit;
1645       end
1646       else
1647         if Length(Year) = 3 then
1648         begin
1649           if not((x >= 100) and (x <= 110)) then
1650             Exit;
1651         end
1652         else
1653           Exit;
1654   end;
1655   Result := True;
1656 end;
1657 
1658 procedure TFTPList.FillRecord(const Value: TFTPListRec);
1659 var
1660   s: string;
1661   x: integer;
1662   myear: Word;
1663   mmonth: Word;
1664   mday: Word;
1665   mhours, mminutes, mseconds: word;
1666   n: integer;
1667 begin
1668   s := DirFlagValue;
1669   if s = '' then
1670     s := 'D';
1671   s := Uppercase(s);
1672   Value.Directory :=  s = Uppercase(DirFlag);
1673   if FileName <> '' then
1674     Value.FileName := SeparateLeft(Filename, ' -> ');
1675   if VMSFileName <> '' then
1676   begin
1677     Value.FileName := VMSFilename;
1678     Value.Directory := Pos('.DIR;',VMSFilename) > 0;
1679   end;
1680   Value.FileName := TrimSPRight(Value.FileName);
1681   Value.Readable := not Value.Directory;
1682   if BlockSize <> '' then
1683     x := StrToIntDef(BlockSize, 1)
1684   else
1685     x := 1;
1686   {$IFDEF VER100}
1687   Value.FileSize := x * StrToIntDef(Size, 0);
1688   {$ELSE}
1689   Value.FileSize := x * StrToInt64Def(Size, 0);
1690   {$ENDIF}
1691 
1692   DecodeDate(Date,myear,mmonth,mday);
1693   mhours := 0;
1694   mminutes := 0;
1695   mseconds := 0;
1696 
1697   if Day <> '' then
1698     mday := StrToIntDef(day, 1);
1699   if Month <> '' then
1700     mmonth := StrToIntDef(Month, 1);
1701   if length(Monthnames) = (12 * 3) then
1702     for n := 1 to 12 do
1703       CustomMonthNames[n] := Copy(Monthnames, ((n - 1) * 3) + 1, 3);
1704   if ThreeMonth <> '' then
1705     mmonth := GetMonthNumber(ThreeMonth);
1706   if Year <> '' then
1707   begin
1708     myear := StrToIntDef(Year, 0);
1709     if (myear <= 99) and (myear > 50) then
1710       myear := myear + 1900;
1711     if myear <= 50 then
1712       myear := myear + 2000;
1713   end;
1714   if YearTime <> '' then
1715   begin
1716     if pos(':', YearTime) > 0 then
1717     begin
1718       YearTime := TrimSP(YearTime);
1719       mhours := StrToIntDef(Separateleft(YearTime, ':'), 0);
1720       mminutes := StrToIntDef(SeparateRight(YearTime, ':'), 0);
1721       if (Encodedate(myear, mmonth, mday)
1722         + EncodeTime(mHours, mminutes, 0, 0)) > now then
1723         Dec(mYear);
1724     end
1725     else
1726       myear := StrToIntDef(YearTime, 0);
1727   end;
1728   if Minutes <> '' then
1729     mminutes := StrToIntDef(Minutes, 0);
1730   if Seconds <> '' then
1731     mseconds := StrToIntDef(Seconds, 0);
1732   if Hours <> '' then
1733   begin
1734     mHours := StrToIntDef(Hours, 0);
1735     if HoursModif <> '' then
1736       if Uppercase(HoursModif[1]) = 'P' then
1737         if mHours <> 12 then
1738           mHours := MHours + 12;
1739   end;
1740   Value.FileTime := Encodedate(myear, mmonth, mday)
1741     + EncodeTime(mHours, mminutes, mseconds, 0);
1742   if Permissions <> '' then
1743   begin
1744     Value.Permission := Permissions;
1745     Value.Readable := Uppercase(permissions)[2] = 'R';
1746     if Uppercase(permissions)[1] = 'D' then
1747     begin
1748       Value.Directory := True;
1749       Value.Readable := false;
1750     end
1751     else
1752       if Uppercase(permissions)[1] = 'L' then
1753         Value.Directory := True;
1754   end;
1755 end;
1756 
ParseEPLFnull1757 function TFTPList.ParseEPLF(Value: string): Boolean;
1758 var
1759   s, os: string;
1760   flr: TFTPListRec;
1761 begin
1762   Result := False;
1763   if Value <> '' then
1764     if Value[1] = '+' then
1765     begin
1766       os := Value;
1767       Delete(Value, 1, 1);
1768       flr := TFTPListRec.create;
1769       flr.FileName := SeparateRight(Value, #9);
1770       s := Fetch(Value, ',');
1771       while s <> '' do
1772       begin
1773         if s[1] = #9 then
1774           Break;
1775         case s[1] of
1776           '/':
1777             flr.Directory := true;
1778           'r':
1779             flr.Readable := true;
1780           's':
1781             {$IFDEF VER100}
1782             flr.FileSize := StrToIntDef(Copy(s, 2, Length(s) - 1), 0);
1783             {$ELSE}
1784             flr.FileSize := StrToInt64Def(Copy(s, 2, Length(s) - 1), 0);
1785             {$ENDIF}
1786           'm':
1787             flr.FileTime := (StrToIntDef(Copy(s, 2, Length(s) - 1), 0) / 86400)
1788               + 25569;
1789         end;
1790         s := Fetch(Value, ',');
1791       end;
1792       if flr.FileName <> '' then
1793       if (flr.Directory and ((flr.FileName = '.') or (flr.FileName = '..')))
1794         or (flr.FileName = '') then
1795         flr.free
1796       else
1797       begin
1798         flr.OriginalLine := os;
1799         flr.Mask := 'EPLF';
1800         Flist.Add(flr);
1801         Result := True;
1802       end;
1803     end;
1804 end;
1805 
1806 procedure TFTPList.ParseLines;
1807 var
1808   flr: TFTPListRec;
1809   n, m: Integer;
1810   S: string;
1811   x: integer;
1812   b: Boolean;
1813 begin
1814   n := 0;
1815   while n < Lines.Count do
1816   begin
1817     if n = Lines.Count - 1 then
1818       s := ''
1819     else
1820       s := Lines[n + 1];
1821     b := False;
1822     x := 0;
1823     if ParseEPLF(Lines[n]) then
1824     begin
1825       b := True;
1826       x := 1;
1827     end
1828     else
1829       for m := 0 to Masks.Count - 1 do
1830       begin
1831         x := ParseByMask(Lines[n], s, Masks[m]);
1832         if x > 0 then
1833           if CheckValues then
1834           begin
1835             flr := TFTPListRec.create;
1836             FillRecord(flr);
1837             flr.OriginalLine := Lines[n];
1838             flr.Mask := Masks[m];
1839             if flr.Directory and ((flr.FileName = '.') or (flr.FileName = '..')) then
1840               flr.free
1841             else
1842               Flist.Add(flr);
1843             b := True;
1844             Break;
1845           end;
1846       end;
1847     if not b then
1848       FUnparsedLines.Add(Lines[n]);
1849     Inc(n);
1850     if x > 1 then
1851       Inc(n, x - 1);
1852   end;
1853 end;
1854 
1855 {==============================================================================}
1856 
FtpGetFilenull1857 function FtpGetFile(const IP, Port, FileName, LocalFile,
1858   User, Pass: string): Boolean;
1859 begin
1860   Result := False;
1861   with TFTPSend.Create do
1862   try
1863     if User <> '' then
1864     begin
1865       Username := User;
1866       Password := Pass;
1867     end;
1868     TargetHost := IP;
1869     TargetPort := Port;
1870     if not Login then
1871       Exit;
1872     DirectFileName := LocalFile;
1873     DirectFile:=True;
1874     Result := RetrieveFile(FileName, False);
1875     Logout;
1876   finally
1877     Free;
1878   end;
1879 end;
1880 
FtpPutFilenull1881 function FtpPutFile(const IP, Port, FileName, LocalFile,
1882   User, Pass: string): Boolean;
1883 begin
1884   Result := False;
1885   with TFTPSend.Create do
1886   try
1887     if User <> '' then
1888     begin
1889       Username := User;
1890       Password := Pass;
1891     end;
1892     TargetHost := IP;
1893     TargetPort := Port;
1894     if not Login then
1895       Exit;
1896     DirectFileName := LocalFile;
1897     DirectFile:=True;
1898     Result := StoreFile(FileName, False);
1899     Logout;
1900   finally
1901     Free;
1902   end;
1903 end;
1904 
FtpInterServerTransfernull1905 function FtpInterServerTransfer(
1906   const FromIP, FromPort, FromFile, FromUser, FromPass: string;
1907   const ToIP, ToPort, ToFile, ToUser, ToPass: string): Boolean;
1908 var
1909   FromFTP, ToFTP: TFTPSend;
1910   s: string;
1911   x: integer;
1912 begin
1913   Result := False;
1914   FromFTP := TFTPSend.Create;
1915   toFTP := TFTPSend.Create;
1916   try
1917     if FromUser <> '' then
1918     begin
1919       FromFTP.Username := FromUser;
1920       FromFTP.Password := FromPass;
1921     end;
1922     if ToUser <> '' then
1923     begin
1924       ToFTP.Username := ToUser;
1925       ToFTP.Password := ToPass;
1926     end;
1927     FromFTP.TargetHost := FromIP;
1928     FromFTP.TargetPort := FromPort;
1929     ToFTP.TargetHost := ToIP;
1930     ToFTP.TargetPort := ToPort;
1931     if not FromFTP.Login then
1932       Exit;
1933     if not ToFTP.Login then
1934       Exit;
1935     if (FromFTP.FTPCommand('PASV') div 100) <> 2 then
1936       Exit;
1937     FromFTP.ParseRemote(FromFTP.ResultString);
1938     s := ReplaceString(FromFTP.DataIP, '.', ',');
1939     s := 'PORT ' + s + ',' + IntToStr(StrToIntDef(FromFTP.DataPort, 0) div 256)
1940       + ',' + IntToStr(StrToIntDef(FromFTP.DataPort, 0) mod 256);
1941     if (ToFTP.FTPCommand(s) div 100) <> 2 then
1942       Exit;
1943     x := ToFTP.FTPCommand('RETR ' + FromFile);
1944     if (x div 100) <> 1 then
1945       Exit;
1946     x := FromFTP.FTPCommand('STOR ' + ToFile);
1947     if (x div 100) <> 1 then
1948       Exit;
1949     FromFTP.Timeout := 21600000;
1950     x := FromFTP.ReadResult;
1951     if (x  div 100) <> 2 then
1952       Exit;
1953     ToFTP.Timeout := 21600000;
1954     x := ToFTP.ReadResult;
1955     if (x div 100) <> 2 then
1956       Exit;
1957     Result := True;
1958   finally
1959     ToFTP.Free;
1960     FromFTP.Free;
1961   end;
1962 end;
1963 
1964 end.
1965