723 lines
22 KiB
Plaintext
723 lines
22 KiB
Plaintext
|
{
|
||
|
$Project$
|
||
|
$Workfile$
|
||
|
$Revision$
|
||
|
$DateUTC$
|
||
|
$Id$
|
||
|
|
||
|
This file is part of the Indy (Internet Direct) project, and is offered
|
||
|
under the dual-licensing agreement described on the Indy website.
|
||
|
(http://www.indyproject.org/)
|
||
|
|
||
|
Copyright:
|
||
|
(c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
|
||
|
}
|
||
|
{
|
||
|
$Log$
|
||
|
}
|
||
|
{
|
||
|
Rev 1.6 2/7/2004 7:20:20 PM JPMugaas
|
||
|
DotNET to go!! and YES - I want fries with that :-).
|
||
|
|
||
|
Rev 1.5 2004.02.03 5:44:38 PM czhower
|
||
|
Name changes
|
||
|
|
||
|
Rev 1.4 1/21/2004 4:21:06 PM JPMugaas
|
||
|
InitComponent
|
||
|
|
||
|
Rev 1.3 10/25/2003 06:52:20 AM JPMugaas
|
||
|
Updated for new API changes and tried to restore some functionality.
|
||
|
|
||
|
Rev 1.2 2003.10.24 10:43:12 AM czhower
|
||
|
TIdSTream to dos
|
||
|
|
||
|
Rev 1.1 2003.10.12 6:36:48 PM czhower
|
||
|
Now compiles.
|
||
|
|
||
|
Rev 1.0 11/13/2002 08:03:42 AM JPMugaas
|
||
|
}
|
||
|
|
||
|
unit IdTrivialFTPServer;
|
||
|
|
||
|
interface
|
||
|
|
||
|
{$i IdCompilerDefines.inc}
|
||
|
|
||
|
uses
|
||
|
Classes,
|
||
|
{$IFDEF HAS_UNIT_Generics_Collections}
|
||
|
System.Generics.Collections,
|
||
|
{$ENDIF}
|
||
|
IdAssignedNumbers,
|
||
|
IdGlobal,
|
||
|
IdThreadSafe,
|
||
|
IdTrivialFTPBase,
|
||
|
IdSocketHandle,
|
||
|
IdUDPServer
|
||
|
{$IFDEF HAS_GENERICS_TObjectList}
|
||
|
, IdThread
|
||
|
{$ENDIF}
|
||
|
;
|
||
|
|
||
|
type
|
||
|
TPeerInfo = record
|
||
|
PeerIP: string;
|
||
|
PeerPort: Integer;
|
||
|
end;
|
||
|
|
||
|
TAccessFileEvent = procedure (Sender: TObject; var FileName: String; const PeerInfo: TPeerInfo;
|
||
|
var GrantAccess: Boolean; var AStream: TStream; var FreeStreamOnComplete: Boolean) of object;
|
||
|
TTransferCompleteEvent = procedure (Sender: TObject; const Success: Boolean;
|
||
|
const PeerInfo: TPeerInfo; var AStream: TStream; const WriteOperation: Boolean) of object;
|
||
|
|
||
|
TIdTFTPThreadList = TIdThreadSafeObjectList{$IFDEF HAS_GENERICS_TObjectList}<TIdThread>{$ENDIF};
|
||
|
|
||
|
TIdTrivialFTPServer = class(TIdUDPServer)
|
||
|
protected
|
||
|
FThreadList: TIdTFTPThreadList;
|
||
|
FOnTransferComplete: TTransferCompleteEvent;
|
||
|
FOnReadFile,
|
||
|
FOnWriteFile: TAccessFileEvent;
|
||
|
function StrToMode(mode: string): TIdTFTPMode;
|
||
|
protected
|
||
|
procedure DoReadFile(FileName: String; const Mode: TIdTFTPMode;
|
||
|
const PeerInfo: TPeerInfo; RequestedBlockSize: Integer; IncludeTransferSize: Boolean); virtual;
|
||
|
procedure DoWriteFile(FileName: String; const Mode: TIdTFTPMode;
|
||
|
const PeerInfo: TPeerInfo; RequestedBlockSize: Integer; RequestedTransferSize: TIdStreamSize); virtual;
|
||
|
procedure DoTransferComplete(const Success: Boolean; const PeerInfo: TPeerInfo; var SourceStream: TStream; const WriteOperation: Boolean); virtual;
|
||
|
procedure DoUDPRead(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle); override;
|
||
|
procedure InitComponent; override;
|
||
|
public
|
||
|
//should deactivate server, check all threads finished, before destroying
|
||
|
function ActiveThreads:Integer;
|
||
|
destructor Destroy;override;
|
||
|
published
|
||
|
property OnReadFile: TAccessFileEvent read FOnReadFile write FOnReadFile;
|
||
|
property OnWriteFile: TAccessFileEvent read FOnWriteFile write FOnWriteFile;
|
||
|
property OnTransferComplete: TTransferCompleteEvent read FOnTransferComplete write FOnTransferComplete;
|
||
|
property DefaultPort default IdPORT_TFTP;
|
||
|
end;
|
||
|
|
||
|
implementation
|
||
|
|
||
|
uses
|
||
|
{$IFDEF DOTNET}
|
||
|
{$IFDEF USE_INLINE}
|
||
|
System.Threading,
|
||
|
{$ENDIF}
|
||
|
{$ENDIF}
|
||
|
{$IFDEF VCL_2010_OR_ABOVE}
|
||
|
{$IFDEF WINDOWS}
|
||
|
Windows,
|
||
|
{$ENDIF}
|
||
|
{$ENDIF}
|
||
|
{$IFDEF USE_VCL_POSIX}
|
||
|
Posix.SysSelect,
|
||
|
Posix.SysTime,
|
||
|
{$ENDIF}
|
||
|
{$IFDEF DOTNET}
|
||
|
IdStreamNET,
|
||
|
{$ELSE}
|
||
|
IdStreamVCL,
|
||
|
{$ENDIF}
|
||
|
IdExceptionCore,
|
||
|
IdGlobalProtocols,
|
||
|
IdResourceStringsProtocols,
|
||
|
IdStack,
|
||
|
{$IFNDEF HAS_GENERICS_TObjectList}
|
||
|
IdThread,
|
||
|
{$ENDIF}
|
||
|
{$IFDEF VCL_XE3_OR_ABOVE}
|
||
|
System.Types,
|
||
|
{$ENDIF}
|
||
|
IdUDPClient,
|
||
|
SysUtils;
|
||
|
|
||
|
type
|
||
|
TIdTFTPServerThread = class(TIdThread)
|
||
|
protected
|
||
|
FStream: TStream;
|
||
|
FBlkCounter: UInt16;
|
||
|
FResponse: TIdBytes;
|
||
|
FRetryCtr: Integer;
|
||
|
FUDPClient: TIdUDPClient;
|
||
|
FRequestedBlkSize: Integer;
|
||
|
FEOT, FFreeStrm: Boolean;
|
||
|
FOwner: TIdTrivialFTPServer;
|
||
|
procedure AfterRun; override;
|
||
|
procedure BeforeRun; override;
|
||
|
function HandleRunException(AException: Exception): Boolean; override;
|
||
|
procedure TransferComplete;
|
||
|
public
|
||
|
constructor Create(AOwner: TIdTrivialFTPServer; const Mode: TIdTFTPMode;
|
||
|
const PeerInfo: TPeerInfo; AStream: TStream; const FreeStreamOnTerminate: Boolean;
|
||
|
const RequestedBlockSize: Integer); reintroduce;
|
||
|
destructor Destroy; override;
|
||
|
end;
|
||
|
|
||
|
TIdTFTPServerSendFileThread = class(TIdTFTPServerThread)
|
||
|
private
|
||
|
FSendTransferSize: Boolean;
|
||
|
protected
|
||
|
procedure BeforeRun; override;
|
||
|
procedure Run; override;
|
||
|
public
|
||
|
constructor Create(AOwner: TIdTrivialFTPServer; const Mode: TIdTFTPMode;
|
||
|
const PeerInfo: TPeerInfo; AStream: TStream; const FreeStreamOnTerminate: Boolean;
|
||
|
const RequestedBlockSize: Integer; SendTransferSize: Boolean); reintroduce;
|
||
|
end;
|
||
|
|
||
|
TIdTFTPServerReceiveFileThread = class(TIdTFTPServerThread)
|
||
|
protected
|
||
|
FTransferSize: TIdStreamSize;
|
||
|
FReceivedSize: TIdStreamSize;
|
||
|
protected
|
||
|
procedure BeforeRun; override;
|
||
|
{$IFNDEF DOTNET}
|
||
|
function HandleRunException(AException: Exception): Boolean; override;
|
||
|
{$ENDIF}
|
||
|
procedure Run; override;
|
||
|
public
|
||
|
constructor Create(AOwner: TIdTrivialFTPServer; const Mode: TIdTFTPMode;
|
||
|
const PeerInfo: TPeerInfo; AStream: TStream; const FreeStreamOnTerminate: Boolean;
|
||
|
const RequestedBlockSize: Integer; const RequestedTransferSize: TIdStreamSize); reintroduce;
|
||
|
end;
|
||
|
|
||
|
{ TIdTrivialFTPServer }
|
||
|
|
||
|
procedure TIdTrivialFTPServer.InitComponent;
|
||
|
begin
|
||
|
inherited InitComponent;
|
||
|
DefaultPort := IdPORT_TFTP;
|
||
|
FThreadList := TIdTFTPThreadList.Create;
|
||
|
end;
|
||
|
|
||
|
procedure TIdTrivialFTPServer.DoReadFile(FileName: String; const Mode: TIdTFTPMode;
|
||
|
const PeerInfo: TPeerInfo; RequestedBlockSize: Integer; IncludeTransferSize: Boolean);
|
||
|
var
|
||
|
CanRead,
|
||
|
FreeOnComplete: Boolean;
|
||
|
LStream: TStream;
|
||
|
begin
|
||
|
CanRead := True;
|
||
|
LStream := nil;
|
||
|
FreeOnComplete := True;
|
||
|
|
||
|
{$IFNDEF DOTNET}
|
||
|
try
|
||
|
{$ENDIF}
|
||
|
|
||
|
if Assigned(FOnReadFile) then begin
|
||
|
FOnReadFile(Self, FileName, PeerInfo, CanRead, LStream, FreeOnComplete);
|
||
|
end;
|
||
|
if not CanRead then begin
|
||
|
raise EIdTFTPAccessViolation.CreateFmt(RSTFTPAccessDenied, [FileName]);
|
||
|
end;
|
||
|
if LStream = nil then begin
|
||
|
LStream := TIdReadFileExclusiveStream.Create(FileName);
|
||
|
FreeOnComplete := True;
|
||
|
end;
|
||
|
|
||
|
TIdTFTPServerSendFileThread.Create(Self, Mode, PeerInfo, LStream, FreeOnComplete, RequestedBlockSize, IncludeTransferSize);
|
||
|
|
||
|
{$IFNDEF DOTNET}
|
||
|
except
|
||
|
// TODO: implement this in a platform-neutral manner. EFOpenError is VCL-specific
|
||
|
on E: EFOpenError do begin
|
||
|
IndyRaiseOuterException(EIdTFTPFileNotFound.Create(E.Message));
|
||
|
end;
|
||
|
end;
|
||
|
{$ENDIF}
|
||
|
end;
|
||
|
|
||
|
procedure TIdTrivialFTPServer.DoTransferComplete(const Success: Boolean;
|
||
|
const PeerInfo: TPeerInfo; var SourceStream: TStream; const WriteOperation: Boolean);
|
||
|
begin
|
||
|
if Assigned(FOnTransferComplete) then begin
|
||
|
FOnTransferComplete(Self, Success, PeerInfo, SourceStream, WriteOperation)
|
||
|
end else begin
|
||
|
FreeAndNil(SourceStream); // free the stream regardless, unless the component user steps up to the plate
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure TIdTrivialFTPServer.DoUDPRead(AThread: TIdUDPListenerThread;
|
||
|
const AData: TIdBytes; ABinding: TIdSocketHandle);
|
||
|
var
|
||
|
wOp: UInt16;
|
||
|
FileName, LOptName, LOptValue: String;
|
||
|
Idx, LOffset, RequestedBlkSize: Integer;
|
||
|
RequestedTxSize: Int64;
|
||
|
Mode: TIdTFTPMode;
|
||
|
PeerInfo: TPeerInfo;
|
||
|
begin
|
||
|
inherited DoUDPRead(AThread, AData, ABinding);
|
||
|
try
|
||
|
if Length(AData) > 1 then begin
|
||
|
wOp := GStack.NetworkToHost(BytesToUInt16(AData));
|
||
|
end else begin
|
||
|
wOp := 0;
|
||
|
end;
|
||
|
|
||
|
if not (wOp in [TFTP_RRQ, TFTP_WRQ]) then begin
|
||
|
raise EIdTFTPIllegalOperation.CreateFmt(RSTFTPUnexpectedOp, [ABinding.PeerIP, ABinding.PeerPort]);
|
||
|
end;
|
||
|
|
||
|
LOffset := 2;
|
||
|
|
||
|
Idx := ByteIndex(0, AData, LOffset);
|
||
|
if Idx = -1 then begin
|
||
|
raise EIdTFTPIllegalOperation.CreateFmt(RSTFTPUnexpectedOp, [ABinding.PeerIP, ABinding.PeerPort]);
|
||
|
end;
|
||
|
|
||
|
FileName := BytesToString(AData, LOffset, Idx-LOffset, IndyTextEncoding_ASCII);
|
||
|
LOffset := Idx+1;
|
||
|
|
||
|
Idx := ByteIndex(0, AData, LOffset);
|
||
|
if Idx = -1 then begin
|
||
|
raise EIdTFTPIllegalOperation.CreateFmt(RSTFTPUnexpectedOp, [ABinding.PeerIP, ABinding.PeerPort]);
|
||
|
end;
|
||
|
|
||
|
Mode := StrToMode(BytesToString(AData, LOffset, Idx-LOffset, IndyTextEncoding_ASCII));
|
||
|
LOffset := Idx+1;
|
||
|
|
||
|
RequestedBlkSize := 512;
|
||
|
RequestedTxSize := -1;
|
||
|
|
||
|
while LOffset < Length(AData) do
|
||
|
begin
|
||
|
Idx := ByteIndex(0, AData, LOffset);
|
||
|
if Idx = -1 then begin
|
||
|
raise EIdTFTPIllegalOperation.CreateFmt(RSTFTPUnexpectedOp, [ABinding.PeerIP, ABinding.PeerPort]);
|
||
|
end;
|
||
|
|
||
|
LOptName := BytesToString(AData, LOffset, Idx-LOffset, IndyTextEncoding_ASCII);
|
||
|
LOffset := Idx+1;
|
||
|
|
||
|
Idx := ByteIndex(0, AData, LOffset);
|
||
|
if Idx = -1 then begin
|
||
|
raise EIdTFTPIllegalOperation.CreateFmt(RSTFTPUnexpectedOp, [ABinding.PeerIP, ABinding.PeerPort]);
|
||
|
end;
|
||
|
|
||
|
LOptValue := BytesToString(AData, LOffset, Idx-LOffset, IndyTextEncoding_ASCII);
|
||
|
LOffset := Idx+1;
|
||
|
|
||
|
if TextStartsWith(LOptName, sBlockSize) then
|
||
|
begin
|
||
|
RequestedBlkSize := IndyStrToInt(LOptValue);
|
||
|
if (RequestedBlkSize < 8) or (RequestedBlkSize > 65464) then begin
|
||
|
raise EIdTFTPOptionNegotiationFailed.CreateFmt(RSTFTPUnsupportedOptionValue, [LOptValue, LOptName]);
|
||
|
end;
|
||
|
end
|
||
|
else if TextStartsWith(LOptName, sTransferSize) then
|
||
|
begin
|
||
|
RequestedTxSize := IndyStrToInt64(LOptValue);
|
||
|
if wOp = TFTP_RRQ then begin
|
||
|
if RequestedTxSize <> 0 then begin
|
||
|
raise EIdTFTPOptionNegotiationFailed.CreateFmt(RSTFTPUnsupportedOptionValue, [LOptValue, LOptName]);
|
||
|
end;
|
||
|
end
|
||
|
else if RequestedTxSize > High(TIdStreamSize) then begin
|
||
|
raise EIdTFTPOptionNegotiationFailed.CreateFmt(RSTFTPUnsupportedOptionValue, [LOptValue, LOptName]);
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
PeerInfo.PeerIP := ABinding.PeerIP;
|
||
|
PeerInfo.PeerPort := ABinding.PeerPort;
|
||
|
|
||
|
if wOp = TFTP_RRQ then begin
|
||
|
DoReadFile(FileName, Mode, PeerInfo, RequestedBlkSize, RequestedTxSize = 0);
|
||
|
end else begin
|
||
|
DoWriteFile(FileName, Mode, PeerInfo, RequestedBlkSize, TIdStreamSize(RequestedTxSize));
|
||
|
end;
|
||
|
except
|
||
|
on E: EIdTFTPException do begin
|
||
|
SendError(Self, ABinding.PeerIP, ABinding.PeerPort, E);
|
||
|
end;
|
||
|
on E: Exception do begin
|
||
|
SendError(Self, ABinding.PeerIP, ABinding.PeerPort, E);
|
||
|
raise;
|
||
|
end;
|
||
|
end; { try..except }
|
||
|
end;
|
||
|
|
||
|
// TODO: move this into IdGlobal.pas
|
||
|
procedure AdjustStreamSize(const AStream: TStream; const ASize: TIdStreamSize);
|
||
|
var
|
||
|
LStreamPos: TIdStreamSize;
|
||
|
begin
|
||
|
LStreamPos := AStream.Position;
|
||
|
AStream.Size := ASize;
|
||
|
// Must reset to original value in cases where size changes position
|
||
|
if AStream.Position <> LStreamPos then begin
|
||
|
AStream.Position := LStreamPos;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure TIdTrivialFTPServer.DoWriteFile(FileName: String; const Mode: TIdTFTPMode;
|
||
|
const PeerInfo: TPeerInfo; RequestedBlockSize: Integer; RequestedTransferSize: TIdStreamSize);
|
||
|
var
|
||
|
CanWrite,
|
||
|
FreeOnComplete: Boolean;
|
||
|
LStream: TStream;
|
||
|
begin
|
||
|
CanWrite := True;
|
||
|
LStream := nil;
|
||
|
FreeOnComplete := True;
|
||
|
|
||
|
{$IFNDEF DOTNET}
|
||
|
try
|
||
|
{$ENDIF}
|
||
|
|
||
|
if Assigned(FOnWriteFile) then begin
|
||
|
FOnWriteFile(Self, FileName, PeerInfo, CanWrite, LStream, FreeOnComplete);
|
||
|
end;
|
||
|
if not CanWrite then begin
|
||
|
raise EIdTFTPAccessViolation.CreateFmt(RSTFTPAccessDenied, [FileName]);
|
||
|
end;
|
||
|
if LStream = nil then begin
|
||
|
LStream := TIdFileCreateStream.Create(FileName);
|
||
|
FreeOnComplete := True;
|
||
|
end;
|
||
|
|
||
|
if RequestedTransferSize >= 0 then
|
||
|
begin
|
||
|
try
|
||
|
AdjustStreamSize(LStream, RequestedTransferSize);
|
||
|
except
|
||
|
IndyRaiseOuterException(EIdTFTPAllocationExceeded.CreateFmt(RSTFTPDiskFull, [0]));
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
TIdTFTPServerReceiveFileThread.Create(Self, Mode, PeerInfo, LStream, FreeOnComplete, RequestedBlockSize, RequestedTransferSize);
|
||
|
|
||
|
{$IFNDEF DOTNET}
|
||
|
except
|
||
|
// TODO: implement this in a platform-neutral manner. EFCreateError is VCL-specific
|
||
|
on E: EFCreateError do begin
|
||
|
IndyRaiseOuterException(EIdTFTPAllocationExceeded.Create(E.Message));
|
||
|
end;
|
||
|
end;
|
||
|
{$ENDIF}
|
||
|
end;
|
||
|
|
||
|
function TIdTrivialFTPServer.StrToMode(mode: string): TIdTFTPMode;
|
||
|
begin
|
||
|
case PosInStrArray(mode, ['octet', 'binary', 'netascii'], False) of {Do not Localize}
|
||
|
0, 1: Result := tfOctet;
|
||
|
2: Result := tfNetAscii;
|
||
|
else
|
||
|
raise EIdTFTPIllegalOperation.CreateFmt(RSTFTPUnsupportedTrxMode, [mode]); // unknown mode
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
destructor TIdTrivialFTPServer.Destroy;
|
||
|
begin
|
||
|
{
|
||
|
if (not ThreadedEvent) and (ActiveThreads>0) then
|
||
|
begin
|
||
|
//some kind of error/warning about deadlock or possible AV due to
|
||
|
//soon-to-be invalid pointer in the threads? (FOwner: TIdTrivialFTPServer;)
|
||
|
//raise CantFreeYet?
|
||
|
end;
|
||
|
}
|
||
|
|
||
|
//wait for threads to finish before we shutdown
|
||
|
//should we set thread[i].terminated, or just wait?
|
||
|
if ThreadedEvent then
|
||
|
begin
|
||
|
while FThreadList.Count > 0 do
|
||
|
begin
|
||
|
IndySleep(100);
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
FreeAndNil(FThreadList);
|
||
|
inherited Destroy;
|
||
|
end;
|
||
|
|
||
|
function TIdTrivialFTPServer.ActiveThreads: Integer;
|
||
|
begin
|
||
|
Result := FThreadList.Count;
|
||
|
end;
|
||
|
|
||
|
{ TIdTFTPServerThread }
|
||
|
|
||
|
constructor TIdTFTPServerThread.Create(AOwner: TIdTrivialFTPServer;
|
||
|
const Mode: TIdTFTPMode; const PeerInfo: TPeerInfo; AStream: TStream;
|
||
|
const FreeStreamOnTerminate: boolean; const RequestedBlockSize: Integer);
|
||
|
begin
|
||
|
inherited Create(False);
|
||
|
FreeOnTerminate := True;
|
||
|
FStream := AStream;
|
||
|
FFreeStrm := FreeStreamOnTerminate;
|
||
|
FOwner := AOwner;
|
||
|
FUDPClient := TIdUDPClient.Create(nil);
|
||
|
FUDPClient.IPVersion := FOwner.IPVersion;
|
||
|
FUDPClient.ReceiveTimeout := 1500;
|
||
|
FUDPClient.Host := PeerInfo.PeerIP;
|
||
|
FUDPClient.Port := PeerInfo.PeerPort;
|
||
|
FUDPClient.BufferSize := RequestedBlockSize + 4;
|
||
|
FOwner.FThreadList.Add(Self);
|
||
|
end;
|
||
|
|
||
|
destructor TIdTFTPServerThread.Destroy;
|
||
|
begin
|
||
|
if FFreeStrm then begin
|
||
|
FreeAndNil(FStream);
|
||
|
end;
|
||
|
FreeAndNil(FUDPClient);
|
||
|
FOwner.FThreadList.Remove(Self);
|
||
|
inherited Destroy;
|
||
|
end;
|
||
|
|
||
|
procedure TIdTFTPServerThread.AfterRun;
|
||
|
begin
|
||
|
if FOwner.ThreadedEvent then begin
|
||
|
TransferComplete;
|
||
|
end else begin
|
||
|
Synchronize(TransferComplete);
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure TIdTFTPServerThread.BeforeRun;
|
||
|
begin
|
||
|
FBlkCounter := 0;
|
||
|
FRetryCtr := 0;
|
||
|
FEOT := False;
|
||
|
|
||
|
if FUDPClient.BufferSize <> 516 then
|
||
|
begin
|
||
|
FResponse := ToBytes(GStack.HostToNetwork(UInt16(TFTP_OACK)));
|
||
|
AppendString(FResponse, sBlockSize, -1, IndyTextEncoding_ASCII);
|
||
|
AppendByte(FResponse, 0);
|
||
|
AppendString(FResponse, IntToStr(FUDPClient.BufferSize - 4), -1, IndyTextEncoding_ASCII);
|
||
|
AppendByte(FResponse, 0);
|
||
|
end else begin
|
||
|
SetLength(FResponse, 0);
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
function TIdTFTPServerThread.HandleRunException(AException: Exception): Boolean;
|
||
|
begin
|
||
|
Result := False;
|
||
|
SendError(FUDPClient, AException);
|
||
|
end;
|
||
|
|
||
|
procedure TIdTFTPServerThread.TransferComplete;
|
||
|
var
|
||
|
PeerInfo: TPeerInfo;
|
||
|
begin
|
||
|
PeerInfo.PeerIP := FUDPClient.Host;
|
||
|
PeerInfo.PeerPort := FUDPClient.Port;
|
||
|
FOwner.DoTransferComplete(FEOT, PeerInfo, FStream, Self is TIdTFTPServerReceiveFileThread);
|
||
|
end;
|
||
|
|
||
|
{ TIdTFTPServerSendFileThread }
|
||
|
|
||
|
constructor TIdTFTPServerSendFileThread.Create(AOwner: TIdTrivialFTPServer;
|
||
|
const Mode: TIdTFTPMode; const PeerInfo: TPeerInfo; AStream: TStream;
|
||
|
const FreeStreamOnTerminate: boolean; const RequestedBlockSize: Integer;
|
||
|
SendTransferSize: Boolean);
|
||
|
begin
|
||
|
inherited Create(AOwner, Mode, PeerInfo, AStream, FreeStreamOnTerminate, RequestedBlockSize);
|
||
|
FSendTransferSize := SendTransferSize;
|
||
|
end;
|
||
|
|
||
|
procedure TIdTFTPServerSendFileThread.BeforeRun;
|
||
|
begin
|
||
|
inherited BeforeRun;
|
||
|
if FSendTransferSize then
|
||
|
begin
|
||
|
if Length(FResponse) = 0 then begin
|
||
|
FResponse := ToBytes(GStack.HostToNetwork(UInt16(TFTP_OACK)));
|
||
|
end;
|
||
|
AppendString(FResponse, sTransferSize, -1, IndyTextEncoding_ASCII);
|
||
|
AppendByte(FResponse, 0);
|
||
|
AppendString(FResponse, IntToStr(FStream.Size - FStream.Position), -1, IndyTextEncoding_ASCII);
|
||
|
AppendByte(FResponse, 0);
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure TIdTFTPServerSendFileThread.Run;
|
||
|
var
|
||
|
Buffer: TIdBytes;
|
||
|
LPeerIP: string;
|
||
|
LPeerPort: TIdPort;
|
||
|
i: Integer;
|
||
|
begin
|
||
|
if Length(FResponse) = 0 then begin // generate a new response packet for client
|
||
|
if FBlkCounter = High(UInt16) then begin
|
||
|
raise EIdTFTPAllocationExceeded.Create('');
|
||
|
end;
|
||
|
Inc(FBlkCounter);
|
||
|
SetLength(FResponse, FUDPClient.BufferSize);
|
||
|
CopyTIdUInt16(GStack.HostToNetwork(UInt16(TFTP_DATA)), FResponse, 0);
|
||
|
CopyTIdUInt16(GStack.HostToNetwork(FBlkCounter), FResponse, 2);
|
||
|
i := ReadTIdBytesFromStream(FStream, FResponse, FUDPClient.BufferSize - 4, 4);
|
||
|
SetLength(FResponse, 4 + i);
|
||
|
if i < (FUDPClient.BufferSize - 4) then begin
|
||
|
FEOT := True;
|
||
|
end;
|
||
|
FRetryCtr := 0;
|
||
|
end;
|
||
|
if FRetryCtr = 3 then begin
|
||
|
raise EIdTFTPIllegalOperation.Create(RSTimeOut); // Timeout
|
||
|
end;
|
||
|
FUDPClient.SendBuffer(FResponse);
|
||
|
SetLength(Buffer, FUDPClient.BufferSize);
|
||
|
i := FUDPClient.ReceiveBuffer(Buffer, LPeerIP, LPeerPort);
|
||
|
if i <= 0 then begin
|
||
|
if FEOT then begin
|
||
|
Stop;
|
||
|
Exit;
|
||
|
end;
|
||
|
Inc(FRetryCtr);
|
||
|
Exit;
|
||
|
end;
|
||
|
SetLength(Buffer, i);
|
||
|
// TODO: validate the correct peer is sending the data...
|
||
|
case GStack.NetworkToHost(BytesToUInt16(Buffer)) of
|
||
|
TFTP_ACK:
|
||
|
begin
|
||
|
i := GStack.NetworkToHost(BytesToUInt16(Buffer, 2));
|
||
|
if i = FBlkCounter then begin
|
||
|
SetLength(FResponse, 0);
|
||
|
end;
|
||
|
if FEOT then begin
|
||
|
Stop;
|
||
|
Exit;
|
||
|
end;
|
||
|
end;
|
||
|
TFTP_DATA:
|
||
|
begin
|
||
|
raise EIdTFTPIllegalOperation.CreateFmt(RSTFTPUnexpectedOp, [FUDPClient.Host, FUDPClient.Port]);
|
||
|
end;
|
||
|
TFTP_ERROR:
|
||
|
begin
|
||
|
Abort;
|
||
|
end;
|
||
|
else
|
||
|
begin
|
||
|
raise EIdTFTPIllegalOperation.CreateFmt(RSTFTPUnexpectedOp, [FUDPClient.Host, FUDPClient.Port]);
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
{ TIdTFTPServerReceiveFileThread }
|
||
|
|
||
|
constructor TIdTFTPServerReceiveFileThread.Create(AOwner: TIdTrivialFTPServer;
|
||
|
const Mode: TIdTFTPMode; const PeerInfo: TPeerInfo; AStream: TStream;
|
||
|
const FreeStreamOnTerminate: Boolean; const RequestedBlockSize: Integer;
|
||
|
const RequestedTransferSize: TIdStreamSize);
|
||
|
begin
|
||
|
inherited Create(AOwner, Mode, PeerInfo, AStream, FreeStreamOnTerminate, RequestedBlockSize);
|
||
|
FTransferSize := RequestedTransferSize;
|
||
|
end;
|
||
|
|
||
|
procedure TIdTFTPServerReceiveFileThread.BeforeRun;
|
||
|
begin
|
||
|
inherited BeforeRun;
|
||
|
FReceivedSize := 0;
|
||
|
if FTransferSize <> -1 then
|
||
|
begin
|
||
|
if Length(FResponse) = 0 then begin
|
||
|
FResponse := ToBytes(GStack.HostToNetwork(UInt16(TFTP_OACK)));
|
||
|
end;
|
||
|
AppendString(FResponse, sTransferSize, -1, IndyTextEncoding_ASCII);
|
||
|
AppendByte(FResponse, 0);
|
||
|
AppendString(FResponse, IntToStr(FTransferSize), -1, IndyTextEncoding_ASCII);
|
||
|
AppendByte(FResponse, 0);
|
||
|
end;
|
||
|
if Length(FResponse) > 0 then begin
|
||
|
// RLebeau: sending an OACK instead of an ACK, so expect
|
||
|
// the next packet received to be a DATA packet...
|
||
|
FBlkCounter := 1;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
{$IFNDEF DOTNET}
|
||
|
function TIdTFTPServerReceiveFileThread.HandleRunException(AException: Exception): Boolean;
|
||
|
begin
|
||
|
// TODO: implement this in a platform-neutral manner. EWriteError is VCL-specific
|
||
|
if AException is EWriteError then
|
||
|
begin
|
||
|
Result := False;
|
||
|
SendError(FUDPClient, ErrAllocationExceeded, IndyFormat(RSTFTPDiskFull, [FStream.Position]));
|
||
|
Exit;
|
||
|
end;
|
||
|
Result := inherited HandleRunException(AException);
|
||
|
end;
|
||
|
{$ENDIF}
|
||
|
|
||
|
procedure TIdTFTPServerReceiveFileThread.Run;
|
||
|
var
|
||
|
Buffer: TIdBytes;
|
||
|
LPeerIP: string;
|
||
|
LPeerPort: TIdPort;
|
||
|
i: TIdStreamSize;
|
||
|
begin
|
||
|
if Length(FResponse) = 0 then begin
|
||
|
FResponse := MakeActPkt(FBlkCounter);
|
||
|
if FBlkCounter = High(UInt16) then begin
|
||
|
FEOT := True;
|
||
|
end else begin
|
||
|
Inc(FBlkCounter);
|
||
|
end;
|
||
|
FRetryCtr := 0;
|
||
|
end;
|
||
|
if FRetryCtr = 3 then begin
|
||
|
raise EIdTFTPIllegalOperation.Create(RSTimeOut); // Timeout
|
||
|
end;
|
||
|
FUDPClient.SendBuffer(FResponse);
|
||
|
SetLength(Buffer, FUDPClient.BufferSize);
|
||
|
i := FUDPClient.ReceiveBuffer(Buffer, LPeerIP, LPeerPort);
|
||
|
if i <= 0 then begin
|
||
|
if FEOT then begin
|
||
|
Stop;
|
||
|
Exit;
|
||
|
end;
|
||
|
Inc(FRetryCtr);
|
||
|
Exit;
|
||
|
end;
|
||
|
SetLength(Buffer, i);
|
||
|
// TODO: validate the correct peer is sending the data...
|
||
|
case GStack.NetworkToHost(BytesToUInt16(Buffer)) of
|
||
|
TFTP_ACK:
|
||
|
begin
|
||
|
raise EIdTFTPIllegalOperation.CreateFmt(RSTFTPUnexpectedOp, [FUDPClient.Host, FUDPClient.Port]);
|
||
|
end;
|
||
|
TFTP_DATA:
|
||
|
begin
|
||
|
i := GStack.NetworkToHost(BytesToUInt16(Buffer, 2));
|
||
|
if i = FBlkCounter then
|
||
|
begin
|
||
|
if FEOT then begin
|
||
|
raise EIdTFTPAllocationExceeded.CreateFmt(RSTFTPDiskFull, [FStream.Position]);
|
||
|
end;
|
||
|
if (FTransferSize >= 0) and ((FTransferSize - FReceivedSize) < (Length(Buffer) - 4)) then
|
||
|
begin
|
||
|
WriteTIdBytesToStream(FStream, Buffer, FTransferSize - FReceivedSize, 4);
|
||
|
FReceivedSize := FTransferSize;
|
||
|
FEOT := True;
|
||
|
raise EIdTFTPAllocationExceeded.CreateFmt(RSTFTPDiskFull, [FStream.Position]);
|
||
|
end;
|
||
|
WriteTIdBytesToStream(FStream, Buffer, Length(Buffer) - 4, 4);
|
||
|
Inc(FReceivedSize, Length(Buffer) - 4);
|
||
|
SetLength(FResponse, 0);
|
||
|
FEOT := Length(Buffer) < (FUDPClient.BufferSize - 4);
|
||
|
end;
|
||
|
end;
|
||
|
TFTP_ERROR:
|
||
|
begin
|
||
|
Abort;
|
||
|
end;
|
||
|
else
|
||
|
begin
|
||
|
raise EIdTFTPIllegalOperation.CreateFmt(RSTFTPUnexpectedOp, [FUDPClient.Host, FUDPClient.Port]);
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
end.
|