492 lines
16 KiB
Plaintext
492 lines
16 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.7 2/8/05 6:08:04 PM RLebeau
|
||
|
Changed CheckOptionAck() to use TextIsSame() instead of SameText()
|
||
|
|
||
|
Rev 1.6 7/23/04 6:41:50 PM RLebeau
|
||
|
TFileStream access right tweak for Put()
|
||
|
|
||
|
Rev 1.5 2/7/2004 7:25:58 PM JPMugaas
|
||
|
Deleted error msg code in error packet. OOPS!!!
|
||
|
|
||
|
Rev 1.4 2/7/2004 7:20:16 PM JPMugaas
|
||
|
DotNET to go!! and YES - I want fries with that :-).
|
||
|
|
||
|
Rev 1.3 2004.02.03 5:44:38 PM czhower
|
||
|
Name changes
|
||
|
|
||
|
Rev 1.2 1/21/2004 4:21:04 PM JPMugaas
|
||
|
InitComponent
|
||
|
|
||
|
Rev 1.1 2003.10.12 6:36:46 PM czhower
|
||
|
Now compiles.
|
||
|
|
||
|
Rev 1.0 11/13/2002 08:03:32 AM JPMugaas
|
||
|
}
|
||
|
|
||
|
unit IdTrivialFTP;
|
||
|
|
||
|
interface
|
||
|
|
||
|
{$i IdCompilerDefines.inc}
|
||
|
|
||
|
uses
|
||
|
Classes,
|
||
|
IdAssignedNumbers,
|
||
|
IdGlobal,
|
||
|
IdTrivialFTPBase,
|
||
|
IdUDPClient;
|
||
|
|
||
|
const
|
||
|
GTransferMode = tfOctet;
|
||
|
GFRequestedBlockSize = 1500;
|
||
|
GReceiveTimeout = 4000;
|
||
|
|
||
|
type
|
||
|
TIdTrivialFTP = class(TIdUDPClient)
|
||
|
protected
|
||
|
FMode: TIdTFTPMode;
|
||
|
FRequestedBlockSize: Integer;
|
||
|
FPeerPort: TIdPort;
|
||
|
FPeerIP: String;
|
||
|
function ModeToStr: string;
|
||
|
procedure CheckOptionAck(const OptionPacket: TIdBytes; Reading: Boolean);
|
||
|
protected
|
||
|
procedure SendAck(const BlockNumber: UInt16);
|
||
|
procedure RaiseError(const ErrorPacket: TIdBytes);
|
||
|
procedure InitComponent; override;
|
||
|
public
|
||
|
procedure Get(const ServerFile: String; DestinationStream: TStream); overload;
|
||
|
procedure Get(const ServerFile, LocalFile: String); overload;
|
||
|
procedure Put(SourceStream: TStream; const ServerFile: String); overload;
|
||
|
procedure Put(const LocalFile, ServerFile: String); overload;
|
||
|
published
|
||
|
property TransferMode: TIdTFTPMode read FMode write FMode Default GTransferMode;
|
||
|
property RequestedBlockSize: Integer read FRequestedBlockSize write FRequestedBlockSize default 1500;
|
||
|
property OnWork;
|
||
|
property OnWorkBegin;
|
||
|
property OnWorkEnd;
|
||
|
end;
|
||
|
|
||
|
implementation
|
||
|
|
||
|
uses
|
||
|
{$IFDEF DOTNET}
|
||
|
IdStreamNET,
|
||
|
{$ELSE}
|
||
|
IdStreamVCL,
|
||
|
{$ENDIF}
|
||
|
IdComponent,
|
||
|
IdExceptionCore,
|
||
|
IdGlobalProtocols,
|
||
|
IdResourceStringsProtocols,
|
||
|
IdStack,
|
||
|
SysUtils;
|
||
|
|
||
|
procedure TIdTrivialFTP.CheckOptionAck(const OptionPacket: TIdBytes; Reading: Boolean);
|
||
|
var
|
||
|
LOptName, LOptValue: String;
|
||
|
LOffset, Idx, OptionIdx: Integer;
|
||
|
LRequestedBlkSize: Integer;
|
||
|
begin
|
||
|
LOffset := 2; // skip packet opcode
|
||
|
|
||
|
try
|
||
|
while LOffset < Length(OptionPacket) do
|
||
|
begin
|
||
|
Idx := ByteIndex(0, OptionPacket, LOffset);
|
||
|
if Idx = -1 then begin
|
||
|
raise EIdTFTPOptionNegotiationFailed.Create('');
|
||
|
end;
|
||
|
|
||
|
LOptName := BytesToString(OptionPacket, LOffset, Idx-LOffset, IndyTextEncoding_ASCII);
|
||
|
LOffset := Idx+1;
|
||
|
|
||
|
Idx := ByteIndex(0, OptionPacket, LOffset);
|
||
|
if Idx = -1 then begin
|
||
|
raise EIdTFTPOptionNegotiationFailed.Create('');
|
||
|
end;
|
||
|
|
||
|
LOptValue := BytesToString(OptionPacket, LOffset, Idx-LOffset, IndyTextEncoding_ASCII);
|
||
|
LOffset := Idx+1;
|
||
|
|
||
|
OptionIdx := PosInStrArray(LOptName, [sBlockSize, sTransferSize], False);
|
||
|
if OptionIdx = -1 then begin
|
||
|
// RLebeau 12/6/2011: workaround for bug in PicoMOD3 devices
|
||
|
if (LOptName = '') and (LOptValue = '') then begin
|
||
|
Continue;
|
||
|
end;
|
||
|
raise EIdTFTPOptionNegotiationFailed.CreateFmt(RSTFTPUnsupportedOption, [LOptName]);
|
||
|
end;
|
||
|
|
||
|
case OptionIdx of
|
||
|
0:
|
||
|
begin
|
||
|
LRequestedBlkSize := IndyStrToInt(LOptValue);
|
||
|
if (LRequestedBlkSize < 8) or (LRequestedBlkSize > 65464) then begin
|
||
|
raise EIdTFTPOptionNegotiationFailed.CreateFmt(RSTFTPUnsupportedOptionValue, [LOptValue, LOptName]);
|
||
|
end;
|
||
|
BufferSize := 4 + LRequestedBlkSize;
|
||
|
end;
|
||
|
1:
|
||
|
begin
|
||
|
if Reading then
|
||
|
begin
|
||
|
// TODO
|
||
|
{
|
||
|
if (IndyStrToInt(LOptValue) is not available) then begin
|
||
|
raise EIdTFTPAllocationExceeded.Create('');
|
||
|
end;
|
||
|
}
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
except
|
||
|
on E: Exception do begin
|
||
|
SendError(Self, FPeerIP, FPeerPort, E);
|
||
|
raise;
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure TIdTrivialFTP.InitComponent;
|
||
|
begin
|
||
|
inherited;
|
||
|
TransferMode := GTransferMode;
|
||
|
Port := IdPORT_TFTP;
|
||
|
FRequestedBlockSize := GFRequestedBlockSize;
|
||
|
ReceiveTimeout := GReceiveTimeout;
|
||
|
end;
|
||
|
|
||
|
procedure TIdTrivialFTP.Get(const ServerFile: String; DestinationStream: TStream);
|
||
|
var
|
||
|
Buffer, LServerFile, LMode, LBlockSize, LBlockOctets, LTransferSize, LTransferOctets: TIdBytes;
|
||
|
DataLen, LOffset: Integer;
|
||
|
ExpectedBlockCtr, RecvdBlockCtr: UInt16;
|
||
|
TerminateTransfer: Boolean;
|
||
|
begin
|
||
|
try
|
||
|
BufferSize := 4 + 512; // 512 as specified by RFC 1350
|
||
|
|
||
|
LServerFile := ToBytes(ServerFile);
|
||
|
LMode := ToBytes(ModeToStr);
|
||
|
LBlockSize := ToBytes(sBlockSize);
|
||
|
LBlockOctets := ToBytes(IntToStr(FRequestedBlockSize));
|
||
|
LTransferSize := ToBytes(sTransferSize);
|
||
|
LTransferOctets := ToBytes(IntToStr(0));
|
||
|
|
||
|
SetLength(Buffer, 2+Length(LServerFile)+1+Length(LMode)+1+Length(LBlockSize)+1+Length(LBlockOctets)+1+Length(LTransferSize)+1+Length(LTransferOctets)+1);
|
||
|
LOffset := 0;
|
||
|
|
||
|
CopyTIdUInt16(GStack.HostToNetwork(UInt16(TFTP_RRQ)), Buffer, LOffset);
|
||
|
Inc(LOffset, 2);
|
||
|
CopyTIdBytes(LServerFile, 0, Buffer, LOffset, Length(LServerFile));
|
||
|
Inc(LOffset, Length(LServerFile));
|
||
|
Buffer[LOffset] := 0;
|
||
|
Inc(LOffset);
|
||
|
CopyTIdBytes(LMode, 0, Buffer, LOffset, Length(LMode));
|
||
|
Inc(LOffset, Length(LMode));
|
||
|
Buffer[LOffset] := 0;
|
||
|
Inc(LOffset);
|
||
|
CopyTIdBytes(LBlockSize, 0, Buffer, LOffset, Length(LBlockSize));
|
||
|
Inc(LOffset, Length(LBlockSize));
|
||
|
Buffer[LOffset] := 0;
|
||
|
Inc(LOffset);
|
||
|
CopyTIdBytes(LBlockOctets, 0, Buffer, LOffset, Length(LBlockOctets));
|
||
|
Inc(LOffset, Length(LBlockOctets));
|
||
|
Buffer[LOffset] := 0;
|
||
|
Inc(LOffset);
|
||
|
CopyTIdBytes(LTransferSize, 0, Buffer, LOffset, Length(LTransferSize));
|
||
|
Inc(LOffset, Length(LTransferSize));
|
||
|
Buffer[LOffset] := 0;
|
||
|
Inc(LOffset);
|
||
|
CopyTIdBytes(LTransferOctets, 0, Buffer, LOffset, Length(LTransferOctets));
|
||
|
Inc(LOffset, Length(LTransferOctets));
|
||
|
Buffer[LOffset] := 0;
|
||
|
|
||
|
SendBuffer(Buffer);
|
||
|
|
||
|
ExpectedBlockCtr := 1;
|
||
|
TerminateTransfer := False;
|
||
|
|
||
|
BeginWork(wmRead);
|
||
|
try
|
||
|
repeat
|
||
|
SetLength(Buffer, BufferSize);
|
||
|
DataLen := ReceiveBuffer(Buffer, FPeerIP, FPeerPort, ReceiveTimeout);
|
||
|
if DataLen <= 0 then begin
|
||
|
// TODO: re-transmit the last sent packet again instead of erroring...
|
||
|
raise EIdTFTPException.Create(RSTimeOut);
|
||
|
end;
|
||
|
SetLength(Buffer, DataLen);
|
||
|
// TODO: validate the correct peer is sending the data...
|
||
|
case GStack.NetworkToHost(BytesToUInt16(Buffer)) of
|
||
|
TFTP_DATA:
|
||
|
begin
|
||
|
RecvdBlockCtr := GStack.NetworkToHost(BytesToUInt16(Buffer, 2));
|
||
|
if RecvdBlockCtr = ExpectedBlockCtr then
|
||
|
begin
|
||
|
DataLen := Length(Buffer) - 4;
|
||
|
try
|
||
|
WriteTIdBytesToStream(DestinationStream, Buffer, DataLen, 4);
|
||
|
DoWork(wmRead, DataLen);
|
||
|
except
|
||
|
on E: Exception do
|
||
|
begin
|
||
|
SendError(Self, FPeerIP, FPeerPort, E);
|
||
|
raise;
|
||
|
end;
|
||
|
end;
|
||
|
SendAck(RecvdBlockCtr);
|
||
|
if RecvdBlockCtr = High(UInt16) then begin
|
||
|
if Length(Buffer) >= BufferSize then begin
|
||
|
// have reached the max block counter allowed, can't validate any more data...
|
||
|
SendError(Self, FPeerIP, FPeerPort, ErrIllegalOperation, '');
|
||
|
raise EIdTFTPException.CreateFmt(RSTFTPUnexpectedOp, [FPeerIP, FPeerPort]);
|
||
|
end;
|
||
|
TerminateTransfer := True; // end of transfer, a block counter cannot wrap back to 0
|
||
|
end else begin
|
||
|
ExpectedBlockCtr := RecvdBlockCtr + 1;
|
||
|
TerminateTransfer := Length(Buffer) < BufferSize;
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
TFTP_ERROR:
|
||
|
begin
|
||
|
RaiseError(Buffer);
|
||
|
end;
|
||
|
TFTP_OACK:
|
||
|
begin
|
||
|
CheckOptionAck(Buffer, True);
|
||
|
SendAck(0);
|
||
|
end;
|
||
|
else
|
||
|
begin
|
||
|
SendError(Self, FPeerIP, FPeerPort, ErrIllegalOperation, '');
|
||
|
raise EIdTFTPException.CreateFmt(RSTFTPUnexpectedOp, [FPeerIP, FPeerPort]);
|
||
|
end;
|
||
|
end;
|
||
|
until TerminateTransfer;
|
||
|
finally
|
||
|
EndWork(wmRead);
|
||
|
end;
|
||
|
finally
|
||
|
Binding.CloseSocket;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure TIdTrivialFTP.Get(const ServerFile, LocalFile: String);
|
||
|
var
|
||
|
fs: TFileStream;
|
||
|
begin
|
||
|
fs := TIdFileCreateStream.Create(LocalFile);
|
||
|
try
|
||
|
Get(ServerFile, fs);
|
||
|
finally
|
||
|
FreeAndNil(fs);
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
function TIdTrivialFTP.ModeToStr: string;
|
||
|
begin
|
||
|
case TransferMode of
|
||
|
tfNetAscii: Result := 'netascii'; {Do not Localize}
|
||
|
tfOctet: Result := 'octet'; {Do not Localize}
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure TIdTrivialFTP.Put(SourceStream: TStream; const ServerFile: String);
|
||
|
var
|
||
|
Buffer, LServerFile, LMode, LBlockSize, LBlockOctets, LTransferSize, LTransferOctets: TIdBytes;
|
||
|
StreamLen: TIdStreamSize;
|
||
|
LOffset, DataLen: Integer;
|
||
|
ExpectedBlockCtr, RecvdBlockCtr, wOp: UInt16;
|
||
|
TerminateTransfer, WaitingForAck: Boolean;
|
||
|
|
||
|
procedure SendDataPacket(const BlockNumber: UInt16);
|
||
|
begin
|
||
|
DataLen := IndyMin(BufferSize-4, StreamLen);
|
||
|
SetLength(Buffer, 4 + DataLen);
|
||
|
CopyTIdUInt16(GStack.HostToNetwork(UInt16(TFTP_DATA)), Buffer, 0);
|
||
|
CopyTIdUInt16(GStack.HostToNetwork(BlockNumber), Buffer, 2);
|
||
|
try
|
||
|
DataLen := ReadTIdBytesFromStream(SourceStream, Buffer, DataLen, 4);
|
||
|
except
|
||
|
on E: Exception do
|
||
|
begin
|
||
|
SendError(Self, FPeerIP, FPeerPort, E);
|
||
|
raise;
|
||
|
end;
|
||
|
end;
|
||
|
SetLength(Buffer, 4 + DataLen);
|
||
|
SendBuffer(FPeerIP, FPeerPort, Buffer);
|
||
|
WaitingForAck := True;
|
||
|
DoWork(wmWrite, DataLen);
|
||
|
Dec(StreamLen, DataLen);
|
||
|
TerminateTransfer := DataLen < (BufferSize - 4);
|
||
|
ExpectedBlockCtr := BlockNumber;
|
||
|
end;
|
||
|
|
||
|
begin
|
||
|
try
|
||
|
BufferSize := 4 + 512; // 512 as specified by RFC 1350
|
||
|
|
||
|
StreamLen := SourceStream.Size - SourceStream.Position;
|
||
|
|
||
|
LServerFile := ToBytes(ServerFile);
|
||
|
LMode := ToBytes(ModeToStr);
|
||
|
LBlockSize := ToBytes(sBlockSize);
|
||
|
LBlockOctets := ToBytes(IntToStr(FRequestedBlockSize));
|
||
|
LTransferSize := ToBytes(sTransferSize);
|
||
|
LTransferOctets := ToBytes(IntToStr(StreamLen));
|
||
|
|
||
|
SetLength(Buffer, 2+Length(LServerFile)+1+Length(LMode)+1+Length(LBlockSize)+1+Length(LBlockOctets)+1+Length(LTransferSize)+1+Length(LTransferOctets)+1);
|
||
|
LOffset := 0;
|
||
|
|
||
|
CopyTIdUInt16(GStack.HostToNetwork(UInt16(TFTP_WRQ)), Buffer, LOffset);
|
||
|
Inc(LOffset, 2);
|
||
|
CopyTIdBytes(LServerFile, 0, Buffer, LOffset, Length(LServerFile));
|
||
|
Inc(LOffset, Length(LServerFile));
|
||
|
Buffer[LOffset] := 0;
|
||
|
Inc(LOffset);
|
||
|
CopyTIdBytes(LMode, 0, Buffer, LOffset, Length(LMode));
|
||
|
Inc(LOffset, Length(LMode));
|
||
|
Buffer[LOffset] := 0;
|
||
|
Inc(LOffset);
|
||
|
CopyTIdBytes(LBlockSize, 0, Buffer, LOffset, Length(LBlockSize));
|
||
|
Inc(LOffset, Length(LBlockSize));
|
||
|
Buffer[LOffset] := 0;
|
||
|
Inc(LOffset);
|
||
|
CopyTIdBytes(LBlockOctets, 0, Buffer, LOffset, Length(LBlockOctets));
|
||
|
Inc(LOffset, Length(LBlockOctets));
|
||
|
Buffer[LOffset] := 0;
|
||
|
Inc(LOffset);
|
||
|
CopyTIdBytes(LTransferSize, 0, Buffer, LOffset, Length(LTransferSize));
|
||
|
Inc(LOffset, Length(LTransferSize));
|
||
|
Buffer[LOffset] := 0;
|
||
|
Inc(LOffset);
|
||
|
CopyTIdBytes(LTransferOctets, 0, Buffer, LOffset, Length(LTransferOctets));
|
||
|
Inc(LOffset, Length(LTransferOctets));
|
||
|
Buffer[LOffset] := 0;
|
||
|
|
||
|
SendBuffer(Buffer);
|
||
|
|
||
|
ExpectedBlockCtr := 0;
|
||
|
TerminateTransfer := False;
|
||
|
|
||
|
BeginWork(wmWrite, StreamLen);
|
||
|
try
|
||
|
repeat
|
||
|
SetLength(Buffer, BufferSize);
|
||
|
DataLen := ReceiveBuffer(Buffer, FPeerIP, FPeerPort, IndyMax(500, ReceiveTimeout));
|
||
|
if DataLen <= 0 then begin
|
||
|
// TODO: re-transmit the last sent packet again instead of erroring...
|
||
|
raise EIdTFTPException.Create(RSTimeOut);
|
||
|
end;
|
||
|
SetLength(Buffer, DataLen);
|
||
|
// TODO: validate the correct peer is sending the data...
|
||
|
wOp := GStack.NetworkToHost(BytesToUInt16(Buffer));
|
||
|
case wOp of
|
||
|
TFTP_ACK:
|
||
|
begin
|
||
|
RecvdBlockCtr := GStack.NetworkToHost(BytesToUInt16(Buffer, 2));
|
||
|
if RecvdBlockCtr = ExpectedBlockCtr then
|
||
|
begin
|
||
|
WaitingForAck := False;
|
||
|
if not TerminateTransfer then
|
||
|
begin
|
||
|
if RecvdBlockCtr = High(UInt16) then
|
||
|
begin
|
||
|
// end of transfer, a block counter cannot wrap back to 0
|
||
|
SendError(Self, FPeerIP, FPeerPort, ErrAllocationExceeded, '');
|
||
|
raise EIdTFTPAllocationExceeded.Create('');
|
||
|
end;
|
||
|
SendDataPacket(RecvdBlockCtr+1);
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
TFTP_OACK:
|
||
|
begin
|
||
|
if ExpectedBlockCtr <> 0 then
|
||
|
begin
|
||
|
SendError(Self, FPeerIP, FPeerPort, ErrIllegalOperation, '');
|
||
|
raise EIdTFTPException.CreateFmt(RSTFTPUnexpectedOp, [FPeerIP, FPeerPort]);
|
||
|
end;
|
||
|
CheckOptionAck(Buffer, False);
|
||
|
SendDataPacket(1);
|
||
|
end;
|
||
|
TFTP_ERROR:
|
||
|
begin
|
||
|
RaiseError(Buffer);
|
||
|
end;
|
||
|
else
|
||
|
begin
|
||
|
SendError(Self, FPeerIP, FPeerPort, ErrIllegalOperation, '');
|
||
|
raise EIdTFTPException.CreateFmt(RSTFTPUnexpectedOp, [FPeerIP, FPeerPort]);
|
||
|
end;
|
||
|
end;
|
||
|
until TerminateTransfer and (not WaitingForAck);
|
||
|
finally
|
||
|
EndWork(wmWrite);
|
||
|
end;
|
||
|
finally
|
||
|
Binding.CloseSocket;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure TIdTrivialFTP.Put(const LocalFile, ServerFile: String);
|
||
|
var
|
||
|
fs: TIdReadFileExclusiveStream;
|
||
|
begin
|
||
|
fs := TIdReadFileExclusiveStream.Create(LocalFile);
|
||
|
try
|
||
|
Put(fs, ServerFile);
|
||
|
finally
|
||
|
fs.Free;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure TIdTrivialFTP.RaiseError(const ErrorPacket: TIdBytes);
|
||
|
var
|
||
|
ErrMsg: string;
|
||
|
begin
|
||
|
ErrMsg := BytesToString(ErrorPacket, 4, Length(ErrorPacket)-4, IndyTextEncoding_ASCII);
|
||
|
case GStack.NetworkToHost(BytesToUInt16(ErrorPacket, 2)) of
|
||
|
ErrFileNotFound: raise EIdTFTPFileNotFound.Create(ErrMsg);
|
||
|
ErrAccessViolation: raise EIdTFTPAccessViolation.Create(ErrMsg);
|
||
|
ErrAllocationExceeded: raise EIdTFTPAllocationExceeded.Create(ErrMsg);
|
||
|
ErrIllegalOperation: raise EIdTFTPIllegalOperation.Create(ErrMsg);
|
||
|
ErrUnknownTransferID: raise EIdTFTPUnknownTransferID.Create(ErrMsg);
|
||
|
ErrFileAlreadyExists: raise EIdTFTPFileAlreadyExists.Create(ErrMsg);
|
||
|
ErrNoSuchUser: raise EIdTFTPNoSuchUser.Create(ErrMsg);
|
||
|
ErrOptionNegotiationFailed: raise EIdTFTPOptionNegotiationFailed.Create(ErrMsg);
|
||
|
else
|
||
|
// usually ErrUndefined (see EIdTFTPException.Message if any)
|
||
|
raise EIdTFTPException.Create(ErrMsg);
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure TIdTrivialFTP.SendAck(const BlockNumber: UInt16);
|
||
|
begin
|
||
|
SendBuffer(FPeerIP, FPeerPort, MakeActPkt(BlockNumber));
|
||
|
end;
|
||
|
|
||
|
end.
|