mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-20 03:59:26 +02:00
1246 lines
34 KiB
ObjectPascal
1246 lines
34 KiB
ObjectPascal
{ lFTP CopyRight (C) 2005-2008 Ales Katona
|
|
|
|
This library is Free software; you can rediStribute it and/or modify it
|
|
under the terms of the GNU Library General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or (at your
|
|
option) any later version.
|
|
|
|
This program is diStributed in the hope that it will be useful, but WITHOUT
|
|
ANY WARRANTY; withOut even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
|
|
for more details.
|
|
|
|
You should have received a Copy of the GNU Library General Public License
|
|
along with This library; if not, Write to the Free Software Foundation,
|
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
This license has been modified. See File LICENSE for more inFormation.
|
|
Should you find these sources withOut a LICENSE File, please contact
|
|
me at ales@chello.sk
|
|
}
|
|
|
|
unit lFTP;
|
|
|
|
{$mode objfpc}{$H+}
|
|
{$inline on}
|
|
{$macro on}
|
|
//{$define debug}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, lNet, lTelnet;
|
|
|
|
const
|
|
DEFAULT_FTP_PORT = 1025;
|
|
|
|
type
|
|
TLFTP = class;
|
|
TLFTPClient = class;
|
|
|
|
TLFTPStatus = (fsNone, fsCon, fsUser, fsPass, fsPasv, fsPort, fsList, fsRetr,
|
|
fsStor, fsType, fsCWD, fsMKD, fsRMD, fsDEL, fsRNFR, fsRNTO,
|
|
fsSYS, fsFeat, fsPWD, fsHelp, fsLast);
|
|
|
|
TLFTPStatusSet = set of TLFTPStatus;
|
|
|
|
TLFTPStatusRec = record
|
|
Status: TLFTPStatus;
|
|
Args: array[1..2] of string;
|
|
end;
|
|
|
|
TLFTPTransferMethod = (ftActive, ftPassive);
|
|
|
|
TLFTPClientStatusEvent = procedure (aSocket: TLSocket;
|
|
const aStatus: TLFTPStatus) of object;
|
|
|
|
{ TLFTPStatusStack }
|
|
|
|
{ TLFTPStatusFront }
|
|
{$DEFINE __front_type__ := TLFTPStatusRec}
|
|
{$i lcontainersh.inc}
|
|
TLFTPStatusFront = TLFront;
|
|
|
|
TLFTP = class(TLComponent, ILDirect)
|
|
protected
|
|
FControl: TLTelnetClient;
|
|
FData: TLTcp;//TLTcpList;
|
|
FSending: Boolean;
|
|
FTransferMethod: TLFTPTransferMethod;
|
|
FFeatureList: TStringList;
|
|
FFeatureString: string;
|
|
|
|
function GetConnected: Boolean; virtual;
|
|
|
|
function GetTimeout: Integer;
|
|
procedure SetTimeout(const Value: Integer);
|
|
|
|
function GetSession: TLSession;
|
|
procedure SetSession(const AValue: TLSession);
|
|
procedure SetCreator(AValue: TLComponent); override;
|
|
|
|
function GetSocketClass: TLSocketClass;
|
|
procedure SetSocketClass(Value: TLSocketClass);
|
|
public
|
|
constructor Create(aOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
|
|
function Get(out aData; const aSize: Integer; aSocket: TLSocket = nil): Integer; virtual; abstract;
|
|
function GetMessage(out msg: string; aSocket: TLSocket = nil): Integer; virtual; abstract;
|
|
|
|
function Send(const aData; const aSize: Integer; aSocket: TLSocket = nil): Integer; virtual; abstract;
|
|
function SendMessage(const msg: string; aSocket: TLSocket = nil): Integer; virtual; abstract;
|
|
|
|
public
|
|
property Connected: Boolean read GetConnected;
|
|
property Timeout: Integer read GetTimeout write SetTimeout;
|
|
property SocketClass: TLSocketClass read GetSocketClass write SetSocketClass;
|
|
property ControlConnection: TLTelnetClient read FControl;
|
|
property DataConnection: TLTCP read FData;
|
|
property TransferMethod: TLFTPTransferMethod read FTransferMethod write FTransferMethod default ftPassive;
|
|
property Session: TLSession read GetSession write SetSession;
|
|
property FeatureList: TStringList read FFeatureList;
|
|
end;
|
|
|
|
{ TLFTPTelnetClient }
|
|
|
|
TLFTPTelnetClient = class(TLTelnetClient)
|
|
protected
|
|
function React(const Operation, Command: Char):boolean; override;
|
|
end;
|
|
|
|
{ TLFTPClient }
|
|
|
|
TLFTPClient = class(TLFTP, ILClient)
|
|
protected
|
|
FStatus: TLFTPStatusFront;
|
|
FCommandFront: TLFTPStatusFront;
|
|
FStoreFile: TFileStream;
|
|
FExpectedBinary: Boolean;
|
|
FPipeLine: Boolean;
|
|
FPassword: string;
|
|
FPWD: string;
|
|
FStatusFlags: array[TLFTPStatus] of Boolean;
|
|
|
|
FOnError: TLSocketErrorEvent;
|
|
FOnReceive: TLSocketEvent;
|
|
FOnSent: TLSocketProgressEvent;
|
|
FOnControl: TLSocketEvent;
|
|
FOnConnect: TLSocketEvent;
|
|
FOnSuccess: TLFTPClientStatusEvent;
|
|
FOnFailure: TLFTPClientStatusEvent;
|
|
|
|
FChunkSize: Word;
|
|
FLastPort: Word;
|
|
FStartPort: Word;
|
|
FStatusSet: TLFTPStatusSet;
|
|
FSL: TStringList; // for evaluation, I want to prevent constant create/free
|
|
procedure OnRe(aSocket: TLSocket);
|
|
procedure OnDs(aSocket: TLSocket);
|
|
procedure OnSe(aSocket: TLSocket);
|
|
procedure OnEr(const msg: string; aSocket: TLSocket);
|
|
|
|
procedure OnControlEr(const msg: string; aSocket: TLSocket);
|
|
procedure OnControlRe(aSocket: TLSocket);
|
|
procedure OnControlCo(aSocket: TLSocket);
|
|
procedure OnControlDs(aSocket: TLSocket);
|
|
|
|
procedure ClearStatusFlags;
|
|
|
|
function GetCurrentStatus: TLFTPStatus;
|
|
function GetTransfer: Boolean;
|
|
|
|
function GetEcho: Boolean;
|
|
procedure SetEcho(const Value: Boolean);
|
|
|
|
procedure ParsePWD(const s: string);
|
|
|
|
function GetConnected: Boolean; override;
|
|
|
|
function GetBinary: Boolean;
|
|
procedure SetBinary(const Value: Boolean);
|
|
|
|
function CanContinue(const aStatus: TLFTPStatus; const Arg1, Arg2: string): Boolean;
|
|
|
|
function CleanInput(var s: string): Integer;
|
|
|
|
procedure SetStartPor(const Value: Word);
|
|
|
|
procedure EvaluateFeatures;
|
|
procedure EvaluateAnswer(const Ans: string);
|
|
|
|
procedure PasvPort;
|
|
|
|
function User(const aUserName: string): Boolean;
|
|
function Password(const aPassword: string): Boolean;
|
|
|
|
procedure SendChunk(const Event: Boolean);
|
|
|
|
procedure ExecuteFrontCommand;
|
|
public
|
|
constructor Create(aOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
|
|
function Get(out aData; const aSize: Integer; aSocket: TLSocket = nil): Integer; override;
|
|
function GetMessage(out msg: string; aSocket: TLSocket = nil): Integer; override;
|
|
|
|
function Send(const aData; const aSize: Integer; aSocket: TLSocket = nil): Integer; override;
|
|
function SendMessage(const msg: string; aSocket: TLSocket = nil): Integer; override;
|
|
|
|
function Connect(const aHost: string; const aPort: Word = 21): Boolean; virtual; overload;
|
|
function Connect: Boolean; virtual; overload;
|
|
|
|
function Authenticate(const aUsername, aPassword: string): Boolean;
|
|
|
|
function GetData(out aData; const aSize: Integer): Integer;
|
|
function GetDataMessage: string;
|
|
|
|
function Retrieve(const FileName: string): Boolean;
|
|
function Put(const FileName: string): Boolean; virtual; // because of LCLsocket
|
|
|
|
function ChangeDirectory(const DestPath: string): Boolean;
|
|
function MakeDirectory(const DirName: string): Boolean;
|
|
function RemoveDirectory(const DirName: string): Boolean;
|
|
|
|
function DeleteFile(const FileName: string): Boolean;
|
|
function Rename(const FromName, ToName: string): Boolean;
|
|
public
|
|
procedure List(const FileName: string = '');
|
|
procedure Nlst(const FileName: string = '');
|
|
procedure SystemInfo;
|
|
procedure ListFeatures;
|
|
procedure PresentWorkingDirectory;
|
|
procedure Help(const Arg: string);
|
|
|
|
procedure Disconnect(const Forced: Boolean = True); override;
|
|
|
|
procedure CallAction; override;
|
|
public
|
|
property StatusSet: TLFTPStatusSet read FStatusSet write FStatusSet;
|
|
property ChunkSize: Word read FChunkSize write FChunkSize;
|
|
property Binary: Boolean read GetBinary write SetBinary;
|
|
property PipeLine: Boolean read FPipeLine write FPipeLine;
|
|
property Echo: Boolean read GetEcho write SetEcho;
|
|
property StartPort: Word read FStartPort write FStartPort default DEFAULT_FTP_PORT;
|
|
property Transfer: Boolean read GetTransfer;
|
|
property CurrentStatus: TLFTPStatus read GetCurrentStatus;
|
|
property PresentWorkingDirectoryString: string read FPWD;
|
|
|
|
property OnError: TLSocketErrorEvent read FOnError write FOnError;
|
|
property OnConnect: TLSocketEvent read FOnConnect write FOnConnect;
|
|
property OnSent: TLSocketProgressEvent read FOnSent write FOnSent;
|
|
property OnReceive: TLSocketEvent read FOnReceive write FOnReceive;
|
|
property OnControl: TLSocketEvent read FOnControl write FOnControl;
|
|
property OnSuccess: TLFTPClientStatusEvent read FOnSuccess write FOnSuccess;
|
|
property OnFailure: TLFTPClientStatusEvent read FOnFailure write FOnFailure;
|
|
end;
|
|
|
|
function FTPStatusToStr(const aStatus: TLFTPStatus): string;
|
|
|
|
implementation
|
|
|
|
uses
|
|
SysUtils, Math;
|
|
|
|
const
|
|
FLE = #13#10;
|
|
|
|
EMPTY_REC: TLFTPStatusRec = (Status: fsNone; Args: ('', ''));
|
|
|
|
FTPStatusStr: array[TLFTPStatus] of string = ('None', 'Connect', 'Authenticate', 'Password',
|
|
'Passive', 'Active', 'List', 'Retrieve',
|
|
'Store', 'Type', 'CWD', 'MKDIR',
|
|
'RMDIR', 'Delete', 'RenameFrom',
|
|
'RenameTo', 'System', 'Features',
|
|
'PWD', 'HELP', 'LAST');
|
|
|
|
procedure Writedbg(const ar: array of const);
|
|
{$ifdef debug}
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if High(ar) >= 0 then
|
|
for i := 0 to High(ar) do
|
|
case ar[i].vtype of
|
|
vtInteger: Write(ar[i].vinteger);
|
|
vtString: Write(ar[i].vstring^);
|
|
vtAnsiString: Write(AnsiString(ar[i].vpointer));
|
|
vtBoolean: Write(ar[i].vboolean);
|
|
vtChar: Write(ar[i].vchar);
|
|
vtExtended: Write(Extended(ar[i].vpointer^));
|
|
end;
|
|
Writeln;
|
|
end;
|
|
{$else}
|
|
begin
|
|
end;
|
|
{$endif}
|
|
|
|
function MakeStatusRec(const aStatus: TLFTPStatus; const Arg1, Arg2: string): TLFTPStatusRec;
|
|
begin
|
|
Result.Status := aStatus;
|
|
Result.Args[1] := Arg1;
|
|
Result.Args[2] := Arg2;
|
|
end;
|
|
|
|
function FTPStatusToStr(const aStatus: TLFTPStatus): string;
|
|
begin
|
|
Result := FTPStatusStr[aStatus];
|
|
end;
|
|
|
|
{$i lcontainers.inc}
|
|
|
|
{ TLFTP }
|
|
|
|
function TLFTP.GetSession: TLSession;
|
|
begin
|
|
Result := FControl.Session;
|
|
end;
|
|
|
|
procedure TLFTP.SetSession(const AValue: TLSession);
|
|
begin
|
|
FControl.Session := aValue;
|
|
FData.Session := aValue;
|
|
end;
|
|
|
|
procedure TLFTP.SetCreator(AValue: TLComponent);
|
|
begin
|
|
inherited SetCreator(AValue);
|
|
|
|
FControl.Creator := AValue;
|
|
FData.Creator := AValue;
|
|
end;
|
|
|
|
function TLFTP.GetConnected: Boolean;
|
|
begin
|
|
Result := FControl.Connected;
|
|
end;
|
|
|
|
function TLFTP.GetTimeout: Integer;
|
|
begin
|
|
Result := FControl.Timeout;
|
|
end;
|
|
|
|
procedure TLFTP.SetTimeout(const Value: Integer);
|
|
begin
|
|
FControl.Timeout := Value;
|
|
FData.Timeout := Value;
|
|
end;
|
|
|
|
function TLFTP.GetSocketClass: TLSocketClass;
|
|
begin
|
|
Result := FControl.SocketClass;
|
|
end;
|
|
|
|
procedure TLFTP.SetSocketClass(Value: TLSocketClass);
|
|
begin
|
|
FControl.SocketClass := Value;
|
|
FData.SocketClass := Value;
|
|
end;
|
|
|
|
constructor TLFTP.Create(aOwner: TComponent);
|
|
begin
|
|
inherited Create(aOwner);
|
|
|
|
FHost := '';
|
|
FPort := 21;
|
|
|
|
FControl := TLFTPTelnetClient.Create(nil);
|
|
FControl.Creator := Self;
|
|
|
|
FData := TLTcp.Create(nil);
|
|
FData.Creator := Self;
|
|
FData.SocketClass := TLSocket;
|
|
|
|
FTransferMethod := ftPassive; // let's be modern
|
|
|
|
FFeatureList := TStringList.Create;
|
|
end;
|
|
|
|
destructor TLFTP.Destroy;
|
|
begin
|
|
FControl.Free;
|
|
FData.Free;
|
|
FFeatureList.Free;
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
{ TLFTPTelnetClient }
|
|
|
|
function TLFTPTelnetClient.React(const Operation, Command: Char):boolean;
|
|
begin
|
|
result:=false;
|
|
// don't do a FUCK since they broke Telnet in FTP as per-usual
|
|
end;
|
|
|
|
{ TLFTPClient }
|
|
|
|
constructor TLFTPClient.Create(aOwner: TComponent);
|
|
const
|
|
DEFAULT_CHUNK = 8192;
|
|
begin
|
|
inherited Create(aOwner);
|
|
|
|
FControl.OnReceive := @OnControlRe;
|
|
FControl.OnConnect := @OnControlCo;
|
|
FControl.OnError := @OnControlEr;
|
|
FControl.OnDisconnect := @OnControlDs;
|
|
|
|
FData.OnReceive := @OnRe;
|
|
FData.OnDisconnect := @OnDs;
|
|
FData.OnCanSend := @OnSe;
|
|
FData.OnError := @OnEr;
|
|
|
|
FStatusSet := [fsNone..fsLast]; // full Event set
|
|
FPassWord := '';
|
|
FChunkSize := DEFAULT_CHUNK;
|
|
FStartPort := DEFAULT_FTP_PORT;
|
|
FSL := TStringList.Create;
|
|
FLastPort := FStartPort;
|
|
|
|
ClearStatusFlags;
|
|
|
|
FStatus := TLFTPStatusFront.Create(EMPTY_REC);
|
|
FCommandFront := TLFTPStatusFront.Create(EMPTY_REC);
|
|
|
|
FStoreFile := nil;
|
|
end;
|
|
|
|
destructor TLFTPClient.Destroy;
|
|
begin
|
|
Disconnect(True);
|
|
FSL.Free;
|
|
FStatus.Free;
|
|
FCommandFront.Free;
|
|
if Assigned(FStoreFile) then
|
|
FreeAndNil(FStoreFile);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TLFTPClient.OnRe(aSocket: TLSocket);
|
|
begin
|
|
if Assigned(FOnReceive) then
|
|
FOnReceive(aSocket);
|
|
end;
|
|
|
|
procedure TLFTPClient.OnDs(aSocket: TLSocket);
|
|
begin
|
|
FSending := False;
|
|
Writedbg(['Disconnected']);
|
|
end;
|
|
|
|
procedure TLFTPClient.OnSe(aSocket: TLSocket);
|
|
begin
|
|
if Connected and FSending then
|
|
SendChunk(True);
|
|
end;
|
|
|
|
procedure TLFTPClient.OnEr(const msg: string; aSocket: TLSocket);
|
|
begin
|
|
FSending := False;
|
|
if Assigned(FOnError) then
|
|
FOnError(msg, aSocket);
|
|
end;
|
|
|
|
procedure TLFTPClient.OnControlEr(const msg: string; aSocket: TLSocket);
|
|
begin
|
|
FSending := False;
|
|
|
|
if Assigned(FOnFailure) then begin
|
|
while not FStatus.Empty do
|
|
FOnFailure(aSocket, FStatus.Remove.Status);
|
|
end else
|
|
FStatus.Clear;
|
|
|
|
ClearStatusFlags;
|
|
|
|
if Assigned(FOnError) then
|
|
FOnError(msg, aSocket);
|
|
end;
|
|
|
|
procedure TLFTPClient.OnControlRe(aSocket: TLSocket);
|
|
begin
|
|
if Assigned(FOnControl) then
|
|
FOnControl(aSocket);
|
|
end;
|
|
|
|
procedure TLFTPClient.OnControlCo(aSocket: TLSocket);
|
|
begin
|
|
if Assigned(FOnConnect) then
|
|
FOnConnect(aSocket);
|
|
end;
|
|
|
|
procedure TLFTPClient.OnControlDs(aSocket: TLSocket);
|
|
begin
|
|
if Assigned(FOnError) then
|
|
FOnError('Connection lost', aSocket);
|
|
end;
|
|
|
|
procedure TLFTPClient.ClearStatusFlags;
|
|
var
|
|
s: TLFTPStatus;
|
|
begin
|
|
for s := fsNone to fsLast do
|
|
FStatusFlags[s] := False;
|
|
end;
|
|
|
|
function TLFTPClient.GetCurrentStatus: TLFTPStatus;
|
|
begin
|
|
Result := FStatus.First.Status;
|
|
end;
|
|
|
|
function TLFTPClient.GetTransfer: Boolean;
|
|
begin
|
|
Result := FData.Connected;
|
|
end;
|
|
|
|
function TLFTPClient.GetEcho: Boolean;
|
|
begin
|
|
Result := FControl.OptionIsSet(TS_ECHO);
|
|
end;
|
|
|
|
function TLFTPClient.GetConnected: Boolean;
|
|
begin
|
|
Result := FStatusFlags[fsCon] and inherited;
|
|
end;
|
|
|
|
function TLFTPClient.GetBinary: Boolean;
|
|
begin
|
|
Result := FStatusFlags[fsType];
|
|
end;
|
|
|
|
function TLFTPClient.CanContinue(const aStatus: TLFTPStatus; const Arg1,
|
|
Arg2: string): Boolean;
|
|
begin
|
|
Result := FPipeLine or FStatus.Empty;
|
|
if not Result then
|
|
FCommandFront.Insert(MakeStatusRec(aStatus, Arg1, Arg2));
|
|
end;
|
|
|
|
function TLFTPClient.CleanInput(var s: string): Integer;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
FSL.Text := s;
|
|
for i := 0 to FSL.Count - 1 do
|
|
if Length(FSL[i]) > 0 then
|
|
EvaluateAnswer(FSL[i]);
|
|
|
|
s := StringReplace(s, FLE, LineEnding, [rfReplaceAll]);
|
|
i := Pos('PASS', s);
|
|
if i > 0 then
|
|
s := Copy(s, 1, i-1) + 'PASS';
|
|
Result := Length(s);
|
|
end;
|
|
|
|
procedure TLFTPClient.SetStartPor(const Value: Word);
|
|
begin
|
|
FStartPort := Value;
|
|
if Value > FLastPort then
|
|
FLastPort := Value;
|
|
end;
|
|
|
|
procedure TLFTPClient.EvaluateFeatures;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
FFeatureList.Clear;
|
|
if Length(FFeatureString) = 0 then
|
|
Exit;
|
|
|
|
FFeatureList.Text := FFeatureString;
|
|
FFeatureString := '';
|
|
FFeatureList.Delete(0);
|
|
|
|
i := 0;
|
|
while i < FFeatureList.Count do begin
|
|
if (Length(Trim(FFeatureList[i])) = 0)
|
|
or (FFeatureList[i][1] <> ' ') then begin
|
|
FFeatureList.Delete(i);
|
|
Continue;
|
|
end;
|
|
|
|
FFeatureList[i] := Trim(FFeatureList[i]);
|
|
|
|
Inc(i);
|
|
end;
|
|
end;
|
|
|
|
procedure TLFTPClient.SetEcho(const Value: Boolean);
|
|
begin
|
|
if Value then
|
|
FControl.SetOption(TS_ECHO)
|
|
else
|
|
FControl.UnSetOption(TS_ECHO);
|
|
end;
|
|
|
|
procedure TLFTPClient.ParsePWD(const s: string);
|
|
var
|
|
i: Integer;
|
|
IsIn: Boolean = False;
|
|
begin
|
|
FPWD := '';
|
|
for i := 1 to Length(s) do begin
|
|
if s[i] = '"' then begin
|
|
IsIn := not IsIn;
|
|
Continue;
|
|
end;
|
|
if IsIn then
|
|
FPWD := FPWD + s[i];
|
|
end;
|
|
end;
|
|
|
|
procedure TLFTPClient.SetBinary(const Value: Boolean);
|
|
const
|
|
TypeBool: array[Boolean] of string = ('A', 'I');
|
|
begin
|
|
if CanContinue(fsType, BoolToStr(Value), '') then begin
|
|
FExpectedBinary := Value;
|
|
FStatus.Insert(MakeStatusRec(fsType, '', ''));
|
|
FControl.SendMessage('TYPE ' + TypeBool[Value] + FLE);
|
|
end;
|
|
end;
|
|
|
|
procedure TLFTPClient.EvaluateAnswer(const Ans: string);
|
|
|
|
function GetNum: Integer;
|
|
begin
|
|
Result := -1;
|
|
if (Length(Ans) >= 3)
|
|
and (Ans[1] in ['0'..'9'])
|
|
and (Ans[2] in ['0'..'9'])
|
|
and (Ans[3] in ['0'..'9']) then
|
|
Result := StrToInt(Copy(Ans, 1, 3));
|
|
end;
|
|
|
|
procedure ParsePortIP(s: string);
|
|
var
|
|
i, l: Integer;
|
|
aIP: string;
|
|
aPort: Word;
|
|
sl: TStringList;
|
|
begin
|
|
if Length(s) >= 15 then begin
|
|
sl := TStringList.Create;
|
|
for i := Length(s) downto 5 do
|
|
if s[i] = ',' then Break;
|
|
while (i <= Length(s)) and (s[i] in ['0'..'9', ',']) do Inc(i);
|
|
if not (s[i] in ['0'..'9', ',']) then Dec(i);
|
|
l := 0;
|
|
while s[i] in ['0'..'9', ','] do begin
|
|
Inc(l);
|
|
Dec(i);
|
|
end;
|
|
Inc(i);
|
|
s := Copy(s, i, l);
|
|
sl.CommaText := s;
|
|
aIP := sl[0] + '.' + sl[1] + '.' + sl[2] + '.' + sl[3];
|
|
try
|
|
aPort := (StrToInt(sl[4]) * 256) + StrToInt(sl[5]);
|
|
except
|
|
aPort := 0;
|
|
end;
|
|
Writedbg(['Server PASV addr/port - ', aIP, ' : ', aPort]);
|
|
if (aPort > 0) and FData.Connect(aIP, aPort) then
|
|
Writedbg(['Connected after PASV']);
|
|
sl.Free;
|
|
FStatus.Remove;
|
|
end;
|
|
end;
|
|
|
|
procedure SendFile;
|
|
begin
|
|
FStoreFile.Position := 0;
|
|
FSending := True;
|
|
SendChunk(False);
|
|
end;
|
|
|
|
function ValidResponse(const Answer: string): Boolean; inline;
|
|
begin
|
|
Result := (Length(Ans) >= 3) and
|
|
(Ans[1] in ['1'..'5']) and
|
|
(Ans[2] in ['0'..'9']) and
|
|
(Ans[3] in ['0'..'9']);
|
|
|
|
if Result then
|
|
Result := (Length(Ans) = 3) or ((Length(Ans) > 3) and (Ans[4] = ' '));
|
|
end;
|
|
|
|
procedure Eventize(const aStatus: TLFTPStatus; const Res: Boolean);
|
|
begin
|
|
FStatus.Remove;
|
|
if Res then begin
|
|
if Assigned(FOnSuccess) and (aStatus in FStatusSet) then
|
|
FOnSuccess(FData.Iterator, aStatus);
|
|
end else begin
|
|
if Assigned(FOnFailure) and (aStatus in FStatusSet) then
|
|
FOnFailure(FData.Iterator, aStatus);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
x: Integer;
|
|
begin
|
|
x := GetNum;
|
|
Writedbg(['WOULD EVAL: ', FTPStatusStr[FStatus.First.Status], ' with value: ',
|
|
x, ' from "', Ans, '"']);
|
|
if FStatus.First.Status = fsFeat then
|
|
FFeatureString := FFeatureString + Ans + FLE; // we need to parse this later
|
|
|
|
if ValidResponse(Ans) then
|
|
if not FStatus.Empty then begin
|
|
Writedbg(['EVAL: ', FTPStatusStr[FStatus.First.Status], ' with value: ', x]);
|
|
case FStatus.First.Status of
|
|
fsCon : case x of
|
|
220:
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := True;
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
else
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := False;
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
fsUser : case x of
|
|
230:
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := True;
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
331,
|
|
332:
|
|
begin
|
|
FStatus.Remove;
|
|
Password(FPassword);
|
|
end;
|
|
else
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := False;
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
fsPass : case x of
|
|
230:
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := True;
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
else
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := False;
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
fsPasv : case x of
|
|
227: ParsePortIP(Ans);
|
|
300..600: FStatus.Remove;
|
|
end;
|
|
|
|
fsPort : case x of
|
|
200:
|
|
begin
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
else
|
|
begin
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
fsType : case x of
|
|
200:
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := FExpectedBinary;
|
|
Writedbg(['Binary mode: ', FExpectedBinary]);
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
else
|
|
begin
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
fsRetr : case x of
|
|
125, 150: begin { Do nothing } end;
|
|
226:
|
|
begin
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
else
|
|
begin
|
|
FData.Disconnect(True); // break on purpose, otherwise we get invalidated ugly
|
|
Writedbg(['Disconnecting data connection']);
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
fsStor : case x of
|
|
125, 150: SendFile;
|
|
|
|
226:
|
|
begin
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
else
|
|
begin
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
fsCWD : case x of
|
|
200, 250:
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := True;
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
else
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := False;
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
fsPWD : case x of
|
|
257:
|
|
begin
|
|
ParsePWD(Ans);
|
|
FStatusFlags[FStatus.First.Status] := True;
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
else
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := False;
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
fsHelp : case x of
|
|
211, 214:
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := True;
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
else
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := False;
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
fsList : case x of
|
|
125, 150: begin { do nothing } end;
|
|
226:
|
|
begin
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
else
|
|
begin
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
fsMKD : case x of
|
|
250, 257:
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := True;
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
else
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := False;
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
fsRMD,
|
|
fsDEL : case x of
|
|
250:
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := True;
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
else
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := False;
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
fsRNFR : case x of
|
|
350:
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := True;
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
else
|
|
begin
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
|
|
fsRNTO : case x of
|
|
250:
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := True;
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
else
|
|
begin
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
fsFeat : case x of
|
|
200..299:
|
|
begin
|
|
FStatusFlags[FStatus.First.Status] := True;
|
|
EvaluateFeatures;
|
|
Eventize(FStatus.First.Status, True);
|
|
end;
|
|
else
|
|
begin
|
|
FFeatureString := '';
|
|
Eventize(FStatus.First.Status, False);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
if FStatus.Empty and not FCommandFront.Empty then
|
|
ExecuteFrontCommand;
|
|
end;
|
|
|
|
procedure TLFTPClient.PasvPort;
|
|
|
|
function StringPair(const aPort: Word): string;
|
|
begin
|
|
Result := IntToStr(aPort div 256);
|
|
Result := Result + ',' + IntToStr(aPort mod 256);
|
|
end;
|
|
|
|
function StringIP: string;
|
|
begin
|
|
Result := StringReplace(FControl.Connection.Iterator.LocalAddress, '.', ',',
|
|
[rfReplaceAll]) + ',';
|
|
end;
|
|
|
|
begin
|
|
if FTransferMethod = ftActive then begin
|
|
Writedbg(['Sent PORT']);
|
|
FData.Disconnect(True);
|
|
FData.Listen(FLastPort);
|
|
FStatus.Insert(MakeStatusRec(fsPort, '', ''));
|
|
FControl.SendMessage('PORT ' + StringIP + StringPair(FLastPort) + FLE);
|
|
|
|
if FLastPort < 65535 then
|
|
Inc(FLastPort)
|
|
else
|
|
FLastPort := FStartPort;
|
|
end else begin
|
|
Writedbg(['Sent PASV']);
|
|
FStatus.Insert(MakeStatusRec(fsPasv, '', ''));
|
|
FControl.SendMessage('PASV' + FLE);
|
|
end;
|
|
end;
|
|
|
|
function TLFTPClient.User(const aUserName: string): Boolean;
|
|
begin
|
|
Result := not FPipeLine;
|
|
if CanContinue(fsUser, aUserName, '') then begin
|
|
FStatus.Insert(MakeStatusRec(fsUser, '', ''));
|
|
FControl.SendMessage('USER ' + aUserName + FLE);
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
function TLFTPClient.Password(const aPassword: string): Boolean;
|
|
begin
|
|
Result := not FPipeLine;
|
|
if CanContinue(fsPass, aPassword, '') then begin
|
|
FStatus.Insert(MakeStatusRec(fsPass, '', ''));
|
|
FControl.SendMessage('PASS ' + aPassword + FLE);
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TLFTPClient.SendChunk(const Event: Boolean);
|
|
var
|
|
Buf: array[0..65535] of Byte;
|
|
n: Integer;
|
|
Sent: Integer;
|
|
begin
|
|
repeat
|
|
n := FStoreFile.Read(Buf, FChunkSize);
|
|
if n > 0 then begin
|
|
Sent := FData.Send(Buf, n);
|
|
if Event and Assigned(FOnSent) and (Sent > 0) then
|
|
FOnSent(FData.Iterator, Sent);
|
|
if Sent < n then
|
|
FStoreFile.Position := FStoreFile.Position - (n - Sent); // so it's tried next time
|
|
end else begin
|
|
if Assigned(FOnSent) then
|
|
FOnSent(FData.Iterator, 0);
|
|
FreeAndNil(FStoreFile);
|
|
FSending := False;
|
|
{$hint this one calls freeinstance which doesn't pass}
|
|
FData.Disconnect(False);
|
|
end;
|
|
until (n = 0) or (Sent = 0);
|
|
end;
|
|
|
|
procedure TLFTPClient.ExecuteFrontCommand;
|
|
begin
|
|
with FCommandFront.First do
|
|
case Status of
|
|
fsNone : Exit;
|
|
fsUser : User(Args[1]);
|
|
fsPass : Password(Args[1]);
|
|
fsList : List(Args[1]);
|
|
fsRetr : Retrieve(Args[1]);
|
|
fsStor : Put(Args[1]);
|
|
fsCWD : ChangeDirectory(Args[1]);
|
|
fsMKD : MakeDirectory(Args[1]);
|
|
fsRMD : RemoveDirectory(Args[1]);
|
|
fsDEL : DeleteFile(Args[1]);
|
|
fsRNFR : Rename(Args[1], Args[2]);
|
|
fsSYS : SystemInfo;
|
|
fsPWD : PresentWorkingDirectory;
|
|
fsHelp : Help(Args[1]);
|
|
fsType : SetBinary(StrToBool(Args[1]));
|
|
fsFeat : ListFeatures;
|
|
end;
|
|
FCommandFront.Remove;
|
|
end;
|
|
|
|
function TLFTPClient.Get(out aData; const aSize: Integer; aSocket: TLSocket): Integer;
|
|
var
|
|
s: string;
|
|
begin
|
|
Result := 0;
|
|
|
|
if FControl.Get(aData, aSize, aSocket) > 0 then begin
|
|
SetLength(s, Result);
|
|
Move(aData, PChar(s)^, Result);
|
|
Result := CleanInput(s);
|
|
Move(s[1], aData, Min(Length(s), aSize));
|
|
end;
|
|
end;
|
|
|
|
function TLFTPClient.GetMessage(out msg: string; aSocket: TLSocket): Integer;
|
|
begin
|
|
Result := FControl.GetMessage(msg, aSocket);
|
|
if Result > 0 then
|
|
Result := CleanInput(msg);
|
|
end;
|
|
|
|
function TLFTPClient.Send(const aData; const aSize: Integer; aSocket: TLSocket
|
|
): Integer;
|
|
begin
|
|
Result := FControl.Send(aData, aSize);
|
|
end;
|
|
|
|
function TLFTPClient.SendMessage(const msg: string; aSocket: TLSocket
|
|
): Integer;
|
|
begin
|
|
Result := FControl.SendMessage(msg);
|
|
end;
|
|
|
|
function TLFTPClient.GetData(out aData; const aSize: Integer): Integer;
|
|
begin
|
|
Result := FData.Iterator.Get(aData, aSize);
|
|
end;
|
|
|
|
function TLFTPClient.GetDataMessage: string;
|
|
begin
|
|
Result := '';
|
|
if Assigned(FData.Iterator) then
|
|
FData.Iterator.GetMessage(Result);
|
|
end;
|
|
|
|
function TLFTPClient.Connect(const aHost: string; const aPort: Word): Boolean;
|
|
begin
|
|
Result := False;
|
|
Disconnect(True);
|
|
if FControl.Connect(aHost, aPort) then begin
|
|
FHost := aHost;
|
|
FPort := aPort;
|
|
FStatus.Insert(MakeStatusRec(fsCon, '', ''));
|
|
Result := True;
|
|
end;
|
|
if FData.Eventer <> FControl.Connection.Eventer then
|
|
FData.Eventer := FControl.Connection.Eventer;
|
|
end;
|
|
|
|
function TLFTPClient.Connect: Boolean;
|
|
begin
|
|
Result := Connect(FHost, FPort);
|
|
end;
|
|
|
|
function TLFTPClient.Authenticate(const aUsername, aPassword: string): Boolean;
|
|
begin
|
|
FPassword := aPassWord;
|
|
Result := User(aUserName);
|
|
end;
|
|
|
|
function TLFTPClient.Retrieve(const FileName: string): Boolean;
|
|
begin
|
|
Result := not FPipeLine;
|
|
if CanContinue(fsRetr, FileName, '') then begin
|
|
PasvPort;
|
|
FStatus.Insert(MakeStatusRec(fsRetr, '', ''));
|
|
FControl.SendMessage('RETR ' + FileName + FLE);
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
function TLFTPClient.Put(const FileName: string): Boolean;
|
|
begin
|
|
Result := not FPipeLine;
|
|
if FileExists(FileName) and CanContinue(fsStor, FileName, '') then begin
|
|
FStoreFile := TFileStream.Create(FileName, fmOpenRead);
|
|
PasvPort;
|
|
FStatus.Insert(MakeStatusRec(fsStor, '', ''));
|
|
FControl.SendMessage('STOR ' + ExtractFileName(FileName) + FLE);
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
function TLFTPClient.ChangeDirectory(const DestPath: string): Boolean;
|
|
begin
|
|
Result := not FPipeLine;
|
|
if CanContinue(fsCWD, DestPath, '') then begin
|
|
FStatus.Insert(MakeStatusRec(fsCWD, '', ''));
|
|
FStatusFlags[fsCWD] := False;
|
|
FControl.SendMessage('CWD ' + DestPath + FLE);
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
function TLFTPClient.MakeDirectory(const DirName: string): Boolean;
|
|
begin
|
|
Result := not FPipeLine;
|
|
if CanContinue(fsMKD, DirName, '') then begin
|
|
FStatus.Insert(MakeStatusRec(fsMKD, '', ''));
|
|
FStatusFlags[fsMKD] := False;
|
|
FControl.SendMessage('MKD ' + DirName + FLE);
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
function TLFTPClient.RemoveDirectory(const DirName: string): Boolean;
|
|
begin
|
|
Result := not FPipeLine;
|
|
if CanContinue(fsRMD, DirName, '') then begin
|
|
FStatus.Insert(MakeStatusRec(fsRMD, '', ''));
|
|
FStatusFlags[fsRMD] := False;
|
|
FControl.SendMessage('RMD ' + DirName + FLE);
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
function TLFTPClient.DeleteFile(const FileName: string): Boolean;
|
|
begin
|
|
Result := not FPipeLine;
|
|
if CanContinue(fsDEL, FileName, '') then begin
|
|
FStatus.Insert(MakeStatusRec(fsDEL, '', ''));
|
|
FStatusFlags[fsDEL] := False;
|
|
FControl.SendMessage('DELE ' + FileName + FLE);
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
function TLFTPClient.Rename(const FromName, ToName: string): Boolean;
|
|
begin
|
|
Result := not FPipeLine;
|
|
if CanContinue(fsRNFR, FromName, ToName) then begin
|
|
FStatus.Insert(MakeStatusRec(fsRNFR, '', ''));
|
|
FStatusFlags[fsRNFR] := False;
|
|
FControl.SendMessage('RNFR ' + FromName + FLE);
|
|
|
|
FStatus.Insert(MakeStatusRec(fsRNTO, '', ''));
|
|
FStatusFlags[fsRNTO] := False;
|
|
FControl.SendMessage('RNTO ' + ToName + FLE);
|
|
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TLFTPClient.List(const FileName: string = '');
|
|
begin
|
|
if CanContinue(fsList, FileName, '') then begin
|
|
PasvPort;
|
|
FStatus.Insert(MakeStatusRec(fsList, '', ''));
|
|
if Length(FileName) > 0 then
|
|
FControl.SendMessage('LIST ' + FileName + FLE)
|
|
else
|
|
FControl.SendMessage('LIST' + FLE);
|
|
end;
|
|
end;
|
|
|
|
procedure TLFTPClient.Nlst(const FileName: string);
|
|
begin
|
|
if CanContinue(fsList, FileName, '') then begin
|
|
PasvPort;
|
|
FStatus.Insert(MakeStatusRec(fsList, '', ''));
|
|
if Length(FileName) > 0 then
|
|
FControl.SendMessage('NLST ' + FileName + FLE)
|
|
else
|
|
FControl.SendMessage('NLST' + FLE);
|
|
end;
|
|
end;
|
|
|
|
procedure TLFTPClient.SystemInfo;
|
|
begin
|
|
if CanContinue(fsSYS, '', '') then
|
|
FControl.SendMessage('SYST' + FLE);
|
|
end;
|
|
|
|
procedure TLFTPClient.ListFeatures;
|
|
begin
|
|
if CanContinue(fsFeat, '', '') then begin
|
|
FStatus.Insert(MakeStatusRec(fsFeat, '', ''));
|
|
FControl.SendMessage('FEAT' + FLE);
|
|
end;
|
|
end;
|
|
|
|
procedure TLFTPClient.PresentWorkingDirectory;
|
|
begin
|
|
if CanContinue(fsPWD, '', '') then begin
|
|
FStatus.Insert(MakeStatusRec(fsPWD, '', ''));
|
|
FControl.SendMessage('PWD' + FLE);
|
|
end;
|
|
end;
|
|
|
|
procedure TLFTPClient.Help(const Arg: string);
|
|
begin
|
|
if CanContinue(fsHelp, Arg, '') then begin
|
|
FStatus.Insert(MakeStatusRec(fsHelp, Arg, ''));
|
|
FControl.SendMessage('HELP ' + Arg + FLE);
|
|
end;
|
|
end;
|
|
|
|
procedure TLFTPClient.Disconnect(const Forced: Boolean = True);
|
|
begin
|
|
FControl.Disconnect(Forced);
|
|
FStatus.Clear;
|
|
FData.Disconnect(Forced);
|
|
FLastPort := FStartPort;
|
|
ClearStatusFlags;
|
|
FCommandFront.Clear;
|
|
end;
|
|
|
|
procedure TLFTPClient.CallAction;
|
|
begin
|
|
TLFTPTelnetClient(FControl).CallAction;
|
|
end;
|
|
|
|
initialization
|
|
Randomize;
|
|
|
|
end.
|
|
|