| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313 |
- {
- $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.17 2/10/2005 2:24:38 PM JPMugaas
- Minor Restructures for some new UnixTime Service components.
- Rev 1.16 1/17/2005 7:29:12 PM JPMugaas
- Now uses new TIdBuffer functionality.
- Rev 1.15 1/9/2005 6:08:06 PM JPMugaas
- Payload size now specified for CC_GET_FILE.
- Now will raise exception if you specify a packet size less than 512.
- Rev 1.12 11/12/2004 8:37:36 AM JPMugaas
- Minor compile error. OOPS!!!
- Rev 1.11 11/11/2004 11:22:54 PM JPMugaas
- Removed an $IFDEF that's no longer needed.
- Rev 1.10 11/8/2004 8:36:04 PM JPMugaas
- Added value for command that may appear later.
- Rev 1.9 11/7/2004 11:34:16 PM JPMugaas
- Now uses inherited methods again. The inherited methods now use the Binding
- methods we used here.
- Rev 1.8 11/6/2004 1:46:34 AM JPMugaas
- Minor bug fix for when there is no data in a reply to CC_GET_PRO.
- Rev 1.7 11/5/2004 7:55:02 PM JPMugaas
- Changed to use, Connect, Recv, Send, and Disconnect instead of ReceiveFrom
- and SendTo. This should improve performance as we do make repeated contacts
- to the host and UDP connect will cause the stack to filter out packets that
- aren't from the peer. There should only be one DNS resolution per session
- making this more efficient (cutting down to about 87 seconds to get a dir).
- Rev 1.4 10/31/2004 1:49:58 AM JPMugaas
- Now uses item type from TIdFTPList for dirs and files. We don't use Skip
- items or end of dir marker items.
- Rev 1.2 10/30/2004 10:23:58 PM JPMugaas
- Should be much faster.
- Rev 1.1 10/30/2004 7:04:26 PM JPMugaas
- FSP Upload.
- Rev 1.0 10/29/2004 12:34:20 PM JPMugaas
- File Services Protocol implementation started
- }
- unit IdFSP;
- interface
- {$i IdCompilerDefines.inc}
- uses
- Classes,
- IdException,
- IdFTPList,
- IdGlobal,
- IdThreadSafe,
- IdUDPClient;
- {This is based on:
- http://cvs.sourceforge.net/viewcvs.py/fsp/fsp/doc/PROTOCOL?rev=1.4&view=markup
- and the Java Lib at fsp.sourceforge.net was also referenced.
- I have verified this on a CygWin build of the FSP Server at fsp.sourceforge.net.
- }
- {
- FSP Packet format:
- HEADER - size = Fixed size 12 bytes. Always present.
- DATA - size = defined in header (DATA_LENGTH)
- XTRA DATA- size = packet_size - header_size (12) - DATA_LENGTH
- Maximal data size DATA_LENGTH + XTRA_DATA length is 1024. Clients and servers
- are not required to support XTRA DATA (but in current FSP implementation does).
- If XTRA DATA are provided, there must be also contained in MESSAGE_CHECKSUM.
- HEADER FORMAT (12 bytes)
- byte FSP_COMMAND
- byte MESSAGE_CHECKSUM
- word KEY
- word SEQUENCE
- word DATA_LENGTH
- long FILE_POSITION
- MESSAGE_CHECKSUM
- Entire packet (HEADER + DATA + XTRA DATA) is checksumed. When computing a
- checksum use zero in place of MESSAGE_CHECKSUM header field.
- Due to some unknown reason, method of computing checksums is different in each
- direction. For packets travelling from server to client initial checksum
- value is zero, otherwise it is HEADER + DATA + XTRA DATA size.
- Checksums in server->client direction are computed as follows:
- /* assume that we have already zeroed checksum in packet */
- unsigned int sum,checksum;
- for(t = packet_start, sum = 0; t < packet_end; sum += *t++);
- checksum= sum + (sum >> 8);
- KEY
- Client's message to server contain a KEY value that is the same as the KEY
- value of the previous message received from the server. KEY is choosen random
- by server.
- }
- {
- CC_VERSION 0x10- Get server version string and setup
- request
- file position: ignored
- data: not used
- xtra data: not used
- reply
- file position: size of optional extra version data
- data: ASCIIZ Server version string
- xtra data: optional extra version data
- byte - FLAGS
- bit 0 set - server does logging
- bit 1 set - server is read only
- bit 2 set - reverse lookup required
- bit 3 set - server is in private mode
- bit 4 set - thruput control
- if bit 4 is set thruput info follows
- long - max_thruput allowed (in bytes/sec)
- word - max. packet size supported by server
- }
- const
- IdPORT_FSP = 21;
- HSIZE = 12; //header size
- DEF_MAXSPACE = 1012; //data length
- DEF_MAXSIZE = DEF_MAXSPACE+HSIZE; //default maximum packet size
- //commands
- CC_VERSION = $10; //Get server version string and setup
- CC_INFO = $11; //return server's extended info block
- CC_ERR = $40; //error response from server
- CC_GET_DIR = $41; // get a directory listing
- CC_GET_FILE = $42; // get a file
- CC_UP_LOAD = $43; // open a file for writing
- CC_INSTALL = $44; // close and install file opened for writing
- CC_DEL_FILE = $45; // delete a file
- CC_DEL_DIR = $46; // delete a directory
- CC_GET_PRO = $47; // get directory protection
- CC_SET_PRO = $48; // set directory protection
- CC_MAKE_DIR = $49; // create a directory
- CC_BYE = $4A; // finish a session
- CC_GRAB_FILE = $4B; // atomic get+delete a file
- CC_GRAB_DONE = $4C; // atomic get+delete a file done
- CC_STAT = $4D; // get information about file/directory
- CC_RENAME = $4E; // rename file or directory
- CC_CH_PASSW = $4F; // change password
- //Reserved commands:
- CC_LIMIT = $80;
- { commands > 0x7F will have extended
- header. No such extensions or commands
- which uses that are known today. This
- header will be used in protocol version 3. }
- CC_TEST = $81; //reserved for testing of new header
- RDTYPE_END = $00;
- RDTYPE_FILE = $01;
- RDTYPE_DIR = $02;
- RDTYPE_SKIP = $2A; //42
- MINTIMEOUT = 1340; //1.34 seconds
- MAXTIMEOUT = 300000; //300 seconds
- type
- EIdFSPException = class(EIdException);
- EIdFSPFileAlreadyExists = class(EIdFSPException);
- EIdFSPFileNotFound = class(EIdFSPException);
- EIdFSPProtException = class(EIdFSPException);
- EIdFSPPacketTooSmall = class(EIdFSPException);
- {
- RDIRENT.HEADER types:
- RDTYPE_END 0x00
- RDTYPE_FILE 0x01
- RDTYPE_DIR 0x02
- RDTYPE_SKIP 0x2A
- }
- TIdFSPStatInfo = class(TCollectionItem)
- protected
- FModifiedDateGMT : TDateTime;
- FModifiedDate: TDateTime;
- //Size is Int64 in case FSP 3 has an expansion, otherise, it can only handle
- //file sizes up 4 GB's. It's not a bug, it's a feature.
- FSize: Int64;
- FItemType :TIdDirItemType;
- published
- property ItemType :TIdDirItemType read FItemType write FItemType;
- property Size: Int64 read FSize write FSize;
- property ModifiedDate: TDateTime read FModifiedDate write FModifiedDate;
- property ModifiedDateGMT : TDateTime read FModifiedDateGMT write FModifiedDateGMT;
- end;
- TIdFSPListItem = class(TIdFSPStatInfo)
- protected
- FFileName: string;
- published
- property FileName: string read FFileName write FFileName;
- end;
- TIdFSPListItems = class(TCollection)
- protected
- function GetItems(AIndex: Integer): TIdFSPListItem;
- procedure SetItems(AIndex: Integer; const Value: TIdFSPListItem);
- public
- function Add: TIdFSPListItem;
- constructor Create; reintroduce;
- function ParseEntries(const AData : TIdBytes; const ADataLen : UInt32) : Boolean;
- function IndexOf(AItem: TIdFSPListItem): Integer;
- property Items[AIndex: Integer]: TIdFSPListItem read GetItems write SetItems; default;
- end;
- TIdFSPDirInfo = class(TObject)
- protected
- FOwnsDir,
- FCanDeleteFiles,
- FCanAddFiles,
- FCanMakeDir,
- FOnlyOwnerCanReadFiles,
- FHasReadMe,
- FCanBeListed,
- FCanRenameFiles : Boolean;
- FReadMe : String;
- public
- property OwnsDir : Boolean read FOwnsDir write FOwnsDir;
- property CanDeleteFiles : Boolean read FCanDeleteFiles write FCanDeleteFiles;
- property CanAddFiles : Boolean read FCanAddFiles write FCanAddFiles;
- property CanMakeDir : Boolean read FCanMakeDir write FCanMakeDir;
- property OnlyOwnerCanReadFiles : Boolean read FOnlyOwnerCanReadFiles write FOnlyOwnerCanReadFiles;
- property HasReadMe : Boolean read FHasReadMe write FHasReadMe;
- {
- Compatibility
- Versions older than 2.8.1b6 do not uses bits 6 and 7. This
- causes that directory can be listable even it do not have
- 6th bit set.
- }
- property CanBeListed : Boolean read FCanBeListed write FCanBeListed;
- property CanRenameFiles : Boolean read FCanRenameFiles write FCanRenameFiles;
- property ReadMe : String read FReadMe write FReadMe;
- end;
- TIdFSPPacket = class(TObject)
- protected
- FCmd: Byte;
- FFilePosition: UInt32;
- FData: TIdBytes;
- FDataLen : Word;
- FExtraData: TIdBytes;
- // FExtraDataLen : UInt32;
- FSequence: Word;
- FKey: Word;
- FValid : Boolean;
- public
- constructor Create;
- function WritePacket : TIdBytes;
- procedure ReadPacket(const AData : TIdBytes; const ALen : UInt32);
- property Valid : Boolean read FValid;
- property Cmd : Byte read FCmd write FCmd;
- property Key : Word read FKey write FKey;
- property Sequence : Word read FSequence write FSequence;
- property FilePosition : UInt32 read FFilePosition write FFilePosition;
- property Data : TIdBytes read FData write FData;
- property DataLen : Word read FDataLen write FDataLen;
- property ExtraData : TIdBytes read FExtraData write FExtraData;
- // property WritePacket : TIdBytes read GetWritePacket write SetWritePacket;
- end;
- TIdFSPLogEvent = procedure (Sender : TObject; APacket : TIdFSPPacket) of object;
- TIdFSP = class(TIdUDPClient)
- protected
- FConEstablished : Boolean;
- FSequence : Word;
- FKey : Word;
- FSystemDesc: string;
- FSystemServerLogs : Boolean;
- FSystemReadOnly : Boolean;
- FSystemReverseLookupRequired : Boolean;
- FSystemPrivateMode : Boolean;
- FSystemAcceptsExtraData : Boolean;
- FThruputControl : Boolean;
- FServerMaxThruPut : UInt32; //bytes per sec
- FServerMaxPacketSize : Word; //maximum packet size server supports
- FClientMaxPacketSize : Word; //maximum packet we wish to support
- FDirectoryListing: TIdFSPListItems;
- FDirInfo : TIdFSPDirInfo;
- FStatInfo : TIdFSPStatInfo;
- FOnRecv, FOnSend : TIdFSPLogEvent;
- FAbortFlag : TIdThreadSafeBoolean;
- FInCmd : TIdThreadSafeBoolean;
- //note: This is optimized for performance - DO NOT MESS with it even if you don't like it
- //or think its wrong. There is a performance penalty that is noticable with downloading,
- //uploading, and dirs because those use a series of packets - not one and we limited in
- //packet size. We also do not want to eat CPU cycles excessively which I've noticed
- //with previous code.
- procedure SendCmdOnce(ACmdPacket, ARecvPacket : TIdFSPPacket; var VTempBuf : TIdBytes; const ARaiseException : Boolean=True); overload;
- procedure SendCmdOnce(const ACmd : Byte; const AData, AExtraData : TIdBytes;
- const AFilePosition : Int64; //in case FSP 3.0 does support more than 4GB
- var VData, VExtraData : TIdBytes; const ARaiseException : Boolean=True); overload;
- procedure SendCmd(ACmdPacket, ARecvPacket : TIdFSPPacket; var VTempBuf : TIdBytes; const ARaiseException : Boolean=True); overload;
- procedure SendCmd(const ACmd : Byte; const AData, AExtraData : TIdBYtes;
- const AFilePosition : Int64; //in case FSP 3.0 does support more than 4GB
- var VData, VExtraData : TIdBytes; const ARaiseException : Boolean=True); overload;
- procedure SendCmd(const ACmd : Byte; const AData : TIdBYtes;
- const AFilePosition : Int64; //in case FSP 3.0 does support more than 4GB
- var VData, VExtraData : TIdBytes; const ARaiseException : Boolean=True); overload;
- procedure ParseDirInfo(const ABuf, AExtraBuf: TIdBytes; ADir : TIdFSPDirInfo);
- function MaxBufferSize : Word;
- function PrefPayloadSize : Word;
- procedure SetClientMaxPacketSize(const AValue: Word);
- public
- constructor Create(AOwner: TComponent); override;
- destructor Destroy; override;
- procedure Connect; override; //this is so we can use it similarly to FTP
- procedure Disconnect; override;
- procedure Version;
- procedure AbortCmd;
- procedure Delete(const AFilename: string);
- procedure RemoveDir(const ADirName: string);
- procedure Rename(const ASourceFile, ADestFile: string);
- procedure MakeDir(const ADirName: string);
- //this is so we can use it similarly to FTP
- //and also sends a BYE command which is the courteous thing to do.
- procedure List; overload;
- procedure List(const ASpecifier: string); overload;
- procedure GetDirInfo(const ADIR : String); overload;
- procedure GetDirInfo(const ADIR : String; ADirInfo : TIdFSPDirInfo); overload;
- procedure GetStatInfo(const APath : String);
- procedure Get(const ASourceFile, ADestFile: string; const ACanOverwrite: Boolean = False;
- AResume: Boolean = False); overload;
- procedure Get(const ASourceFile: string; ADest: TStream; AResume: Boolean = False); overload;
- procedure Put(const ASource: TStream; const ADestFile: string; const AGMTTime : TDateTime = 0); overload;
- procedure Put(const ASourceFile: string; const ADestFile: string=''); overload;
- property SystemDesc: string read FSystemDesc;
- property SystemServerLogs : Boolean read FSystemServerLogs;
- property SystemReadOnly : Boolean read FSystemReadOnly;
- property SystemReverseLookupRequired : Boolean read FSystemReverseLookupRequired;
- property SystemPrivateMode : Boolean read FSystemPrivateMode;
- property SystemAcceptsExtraData : Boolean read FSystemAcceptsExtraData;
- property ThruputControl : Boolean read FThruputControl;
- property ServerMaxThruPut : UInt32 read FServerMaxThruPut;
- property ServerMaxPacketSize : Word read FServerMaxPacketSize;
- property ClientMaxPacketSize : Word read FClientMaxPacketSize write SetClientMaxPacketSize;
- property DirectoryListing: TIdFSPListItems read FDirectoryListing;
- property DirInfo : TIdFSPDirInfo read FDirInfo;
- property StatInfo : TIdFSPStatInfo read FStatInfo;
- published
- property Port default IdPORT_FSP;
- property OnWork;
- property OnWorkBegin;
- property OnWorkEnd;
- property OnRecv : TIdFSPLogEvent read FOnRecv write FOnRecv;
- property OnSend : TIdFSPLogEvent read FOnSend write FOnSend;
- end;
- implementation
- uses
- //facilitate inlining only.
- {$IF DEFINED(KYLIXCOMPAT)}
- Libc,
- {$ELSEIF DEFINED(USE_VCL_POSIX)}
- Posix.SysSelect,
- Posix.SysTime,
- Posix.Unistd,
- {$ELSEIF DEFINED(WINDOWS) AND (DEFINED(USE_INLINE) OR DEFINED(DCC_2009_OR_ABOVE))}
- Windows,
- {$IFEND}
- IdComponent, IdGlobalProtocols, IdResourceStringsProtocols, IdStack, SysUtils;
- function ParseASCIIZPos(const ABytes: TIdBytes ; const ALen : UInt32; var VPos : UInt32): String;
- var
- i : UInt32;
- begin
- if VPos < ALen then begin
- for i := VPos to ALen-1 do begin
- if ABytes[i] = 0 then begin
- Break;
- end;
- end;
- VPos := i;
- Result := BytesToString(ABytes, i);
- end else begin
- Result := '';
- end;
- end;
- function ParseASCIIZLen(const ABytes : TIdBytes; const ALen : UInt32) : String;
- var
- LPos : UInt32;
- begin
- LPos := 0;
- Result := ParseASCIIZPos(ABytes, ALen, LPos);
- end;
- function ParseASCIIZ(const ABytes : TIdBytes) : String;
- var
- LPos : UInt32;
- begin
- LPos := 0;
- Result := ParseASCIIZPos(ABytes, Length(ABytes), LPos);
- end;
- procedure ParseStatInfo(const AData : TIdBytes; VL : TIdFSPStatInfo; var VI : UInt32);
- var
- LC : UInt32;
- begin
- //we don't parse the file type because there is some variation between CC_GET_DIR and CC_STAT
- CopyBytesToHostUInt32(AData, VI, LC);
- VL.FModifiedDateGMT := UnixDateTimeToDelphiDateTime(LC);
- VL.FModifiedDate := UTCTimeToLocalTime(VL.FModifiedDateGMT);
- Inc(VI, 4);
- CopyBytesToHostUInt32(AData, VI, LC);
- VL.Size := LC;
- Inc(VI, 5); //we want to skip over the type byte we processed earlier
- end;
- { TIdFSP }
- constructor TIdFSP.Create(AOwner: TComponent);
- begin
- inherited Create(AOwner);
- FAbortFlag := TIdThreadSafeBoolean.Create;
- FAbortFlag.Value := False;
- //you have to use FPort or this will cause a stack overflow
- FPort := IdPORT_FSP;
- FSequence := 0;
- FKey := 0;
- FDirInfo := TIdFSPDirInfo.Create;
- FDirectoryListing := TIdFSPListItems.Create;
- FStatInfo := TIdFSPStatInfo.Create(nil);
- BroadcastEnabled := False;
- FConEstablished := False;
- FClientMaxPacketSize := DEF_MAXSIZE;
- FInCmd := TIdThreadSafeBoolean.Create;
- FInCmd.Value := False;
- end;
- destructor TIdFSP.Destroy;
- begin
- Disconnect;
- FDirInfo.Free;
- FDirectoryListing.Free;
- FStatInfo.Free;
- FAbortFlag.Free;
- FInCmd.Free;
- inherited Destroy;
- end;
- procedure TIdFSP.Connect;
- begin
- FSequence := 1;
- FKey := 0;
- FServerMaxThruPut := 0;
- FServerMaxPacketSize := DEF_MAXSIZE;
- inherited Connect;
- end;
- procedure TIdFSP.Disconnect;
- var
- LBuf, LData, LExtra : TIdBytes;
- begin
- AbortCmd;
- if FConEstablished then begin
- SetLength(LBuf, 0);
- SendCmd(CC_BYE, LBuf, 0, LData, LExtra);
- inherited Disconnect;
- end;
- FConEstablished := False;
- end;
- procedure TIdFSP.Get(const ASourceFile: string; ADest: TStream; AResume: Boolean);
- var
- LSendPacket : TIdFSPPacket;
- LRecvPacket : TIdFSPPacket;
- LLen : Integer;
- LTmpBuf : TIdBytes;
- begin
- SetLength(LTmpBuf, MaxBufferSize);
- LSendPacket := TIdFSPPacket.Create;
- try
- LRecvPacket := TIdFSPPacket.Create;
- try
- if AResume then begin
- LSendPacket.FFilePosition := ADest.Position;
- end else begin
- LSendPacket.FFilePosition := 0;
- end;
- LSendPacket.Cmd := CC_GET_FILE;
- LSendPacket.FData := ToBytes(ASourceFile+#0);
- LSendPacket.FDataLen := Length(LSendPacket.FData);
- //specify a preferred block size
- SetLength(LSendPacket.FExtraData, 2);
- CopyTIdNetworkUInt16(PrefPayloadSize, LSendPacket.FExtraData, 0);
-
- BeginWork(wmRead);
- try
- repeat
- SendCmd(LSendPacket, LRecvPacket, LTmpBuf);
- LLen := LRecvPacket.FDataLen; //Length(LRecvPacket.Data);
- if LLen > 0 then begin
- ADest.WriteBuffer(PByte(LRecvPacket.Data)^, LLen);
- DoWork(wmRead, LLen);
- Inc(LSendPacket.FFilePosition, LLen);
- end else begin
- Break;
- end;
- until False;
- finally
- EndWork(wmRead);
- end;
- finally
- LRecvPacket.Free;
- end;
- finally
- LSendPacket.Free;
- end;
- end;
- procedure TIdFSP.Get(const ASourceFile, ADestFile: string; const ACanOverwrite: Boolean; AResume: Boolean);
- var
- LDestStream: TStream;
- begin
- if ACanOverwrite and (not AResume) then begin
- SysUtils.DeleteFile(ADestFile);
- LDestStream := TIdFileCreateStream.Create(ADestFile);
- end
- else if (not ACanOverwrite) and AResume then begin
- LDestStream := TIdAppendFileStream.Create(ADestFile);
- end
- else begin
- raise EIdFSPFileAlreadyExists.Create(RSDestinationFileAlreadyExists);
- end;
- try
- Get(ASourceFile, LDestStream, AResume);
- finally
- LDestStream.Free;
- end;
- end;
- procedure TIdFSP.GetDirInfo(const ADIR: String);
- begin
- GetDirInfo(ADir, FDirInfo);
- end;
- procedure TIdFSP.List;
- begin
- List('/');
- end;
- procedure TIdFSP.List(const ASpecifier: string);
- var
- LSendPacket : TIdFSPPacket;
- LRecvPacket : TIdFSPPacket;
- LTmpBuf : TIdBytes;
- LSpecifier: String;
- begin
- LSpecifier := ASpecifier;
- if LSpecifier = '' then begin
- LSpecifier := '/';
- end;
- SetLength(LTmpBuf, MaxBufferSize);
- LSendPacket := TIdFSPPacket.Create;
- try
- LRecvPacket := TIdFSPPacket.Create;
- try
- LSendPacket.Cmd := CC_GET_DIR;
- LSendPacket.FFilePosition := 0;
- SetLength(LRecvPacket.FData, MaxBufferSize);
- SetLength(LSendPacket.FExtraData, 2);
- CopyTIdNetworkUInt16(PrefPayloadSize, LSendPacket.FExtraData, 0);
- FDirectoryListing.Clear;
- repeat
- LSendPacket.Data := ToBytes(LSpecifier+#0);
- LSendPacket.DataLen := Length(LSendPacket.Data);
- SendCmd(LSendPacket,LRecvPacket,LTmpBuf);
- if LRecvPacket.DataLen > 0 then begin
- Inc(LSendPacket.FFilePosition, LRecvPacket.DataLen);
- end else begin
- Break;
- end;
- if LRecvPacket.DataLen < PrefPayloadSize then begin
- Break;
- end;
- until FDirectoryListing.ParseEntries(LRecvPacket.FData, LRecvPacket.FDataLen);
- finally
- LRecvPacket.Free;
- end;
- finally
- LSendPacket.Free;
- end;
- end;
- procedure TIdFSP.SendCmd(const ACmd: Byte; const AData, AExtraData: TIdBytes;
- const AFilePosition: Int64; var VData, VExtraData: TIdBytes;
- const ARaiseException : Boolean = True);
- var
- LSendPacket : TIdFSPPacket;
- LRecvPacket : TIdFSPPacket;
- LTmpBuf : TIdBytes;
- begin
- SetLength(LTmpBuf, MaxBufferSize);
- LSendPacket := TIdFSPPacket.Create;
- try
- LRecvPacket := TIdFSPPacket.Create;
- try
- LSendPacket.Cmd := ACmd;
- LSendPacket.FilePosition := AFilePosition;
- LSendPacket.Data := AData;
- LSendPacket.FDataLen := Length(AData);
- LSendPacket.ExtraData := AExtraData;
- SendCmd(LSendPacket, LRecvPacket, LTmpBuf, ARaiseException);
- VData := LRecvPacket.Data;
- VExtraData := LRecvPacket.ExtraData;
- finally
- LRecvPacket.Free;
- end;
- finally
- LSendPacket.Free;
- end;
- end;
- procedure TIdFSP.SendCmd(const ACmd: Byte; const AData: TIdBytes;
- const AFilePosition: Int64; var VData, VExtraData: TIdBytes;
- const ARaiseException : Boolean = True);
- var
- LExtraData : TIdBytes;
- begin
- SetLength(LExtraData, 0);
- SendCmd(ACmd, AData, LExtraData, AFilePosition, VData, VExtraData, ARaiseException);
- end;
- procedure TIdFSP.Version;
- var
- LData, LBuf, LExtraBuf : TIdBytes;
- LDetails : Byte;
- begin
- {
- we use this instead of SendCmd because of the following note
- in the protocol specification
- FILE SERVICE PROTOCOL VERSION 2, OFFICIAL PROTOCOL DEFINITION, FSP v2,
- Document version 0.17, Last updated 25 Dec 2004
- (http://fsp.sourceforge.net/doc/PROTOCOL.txt):
- Note
- Some fsp servers do not responds to this command,
- because this command is used by FSP scanners and
- servers do not wishes to be detected.
- }
- SetLength(LData, 0);
- SendCmdOnce(CC_VERSION, LData, LData, 0, LBuf, LExtraBuf);
- if LData <> nil then begin
- FSystemDesc := ParseASCIIZ(LBuf);
- if LExtraBuf <> nil then begin
- LDetails := LExtraBuf[0];
- //bit 0 set - server does logging
- FSystemServerLogs := (LDetails and $01) = $01;
- //bit 1 set - server is read only
- FSystemReadOnly := (LDetails and $02) = $02;
- //bit 2 set - reverse lookup required
- FSystemReverseLookupRequired := (LDetails and $04) = $04;
- //bit 3 set - server is in private mode
- FSystemPrivateMode := (LDetails and $08) = $08;
- //if bit 4 is set thruput info follows
- FThruputControl := (LDetails and $10) = $10;
- //bit 5 set - server accept XTRA
- //DATA on input
- FSystemAcceptsExtraData := (LDetails and $20) = $20;
- //long - max_thruput allowed (in bytes/sec)
- //word - max. packet size supported by server
- if FThruputControl then begin
- if Length(LExtraBuf) > 4 then begin
- CopyBytesToHostUInt32(LExtraBuf, 1, FServerMaxThruPut);
- if Length(LExtraBuf) > 6 then begin
- CopyBytesToHostUInt16(LExtraBuf, 5, FServerMaxPacketSize);
- end;
- end;
- end else
- begin
- if Length(LExtraBuf) > 2 then begin
- CopyBytesToHostUInt16(LExtraBuf, 1, FServerMaxPacketSize);
- end;
- end;
- end;
- end;
- end;
- procedure TIdFSP.SendCmd(ACmdPacket, ARecvPacket: TIdFSPPacket;
- var VTempBuf : TIdBytes; const ARaiseException : Boolean = True);
- var
- LLen : Integer;
- LSendBuf : TIdBytes;
- LMSec : Integer;
- begin
- FInCmd.Value := True;
- try
- Inc(FSequence);
- FAbortFlag.Value := False;
- //we don't set the temp buff size here for speed.
- ACmdPacket.Key := FKey;
- ACmdPacket.Sequence := FSequence;
- LMSec := MINTIMEOUT;
- LSendBuf := ACmdPacket.WritePacket;
- //It's very important that you have some way of aborting this loop
- //if you do not and the server does not reply, this can go for infinity.
- //AbortCmd is ThreadSafe.
- while not FAbortFlag.Value do
- begin
- SendBuffer(LSendBuf);
- if Assigned(FOnSend) then begin
- FOnSend(Self, ACmdPacket);
- end;
- IndySleep(5); //this is so we don't eat up all of the CPU
- LLen := ReceiveBuffer(VTempBuf, LMsec);
- ARecvPacket.ReadPacket(VTempBuf, LLen);
- if ARecvPacket.Valid then begin
- if Assigned(FOnRecv) then begin
- FOnRecv(Self, ARecvPacket);
- end;
- if ARecvPacket.Sequence = FSequence then begin
- Break;
- end;
- end;
- LMSec := Round(LMSec * 1.5);
- if LMSec > MAXTIMEOUT then begin
- LMSec := MAXTIMEOUT;
- end;
- end;
- if not FAbortFlag.Value then begin
- FKey := ARecvPacket.Key;
- end;
- FAbortFlag.Value := False;
- if (ARecvPacket.Cmd = CC_ERR) and ARaiseException then begin
- raise EIdFSPProtException.Create(ParseASCIIZLen(ARecvPacket.Data, ARecvPacket.DataLen));
- end;
- finally
- FInCmd.Value := False;
- end;
- end;
- procedure TIdFSP.GetStatInfo(const APath: String);
- var
- LData, LBuf,LExtraBuf : TIdBytes;
- i : UInt32;
- begin
- {
- data format is the same as in directory listing with exception
- that there is no file name appended. If file do not exists or
- there is other problem (no access rights) return type of file is
- 0.
- struct STAT {
- long time;
- long size;
- byte type;
- }
- i := 0;
- LData := ToBytes(APath + #0);
- SendCmd(CC_STAT, LData, 0, LBuf, LExtraBuf);
- if Length(LBuf) > 8 then begin
- case LBuf[8] of
- 0 : //file not found
- begin
- raise EIdFSPFileNotFound.Create(RSFSPNotFound);
- end;
- RDTYPE_FILE :
- begin
- FStatInfo.ItemType := ditFile;
- end;
- RDTYPE_DIR :
- begin
- FStatInfo.ItemType := ditDirectory;
- end;
- end;
- ParseStatInfo(LBuf, FStatInfo, i);
- end;
- end;
- procedure TIdFSP.Put(const ASource: TStream; const ADestFile: string; const AGMTTime: TDateTime);
- var
- LUnixDate : UInt32;
- LSendPacket : TIdFSPPacket;
- LRecvPacket : TIdFSPPacket;
- LPosition : UInt32;
- LLen : Integer;
- LTmpBuf : TIdBytes;
- begin
- LPosition := 0;
- SetLength(LTmpBuf, MaxBufferSize);
- LSendPacket := TIdFSPPacket.Create;
- try
- LRecvPacket := TIdFSPPacket.Create;
- try
- SetLength(LSendPacket.FData, PrefPayloadSize);
- LSendPacket.Cmd := CC_UP_LOAD;
- repeat
- LLen := ASource.Read(PByte(LSendPacket.FData)^, PrefPayloadSize);
- if LLen <= 0 then begin
- Break;
- end;
- LSendPacket.FDataLen := LLen;
- LSendPacket.FilePosition := LPosition;
- SendCmd(LSendPacket, LRecvPacket, LTmpBuf);
- Inc(LPosition, LLen);
- until False;
- //send the Install packet
- LSendPacket.Cmd := CC_INSTALL;
- LSendPacket.FilePosition := 0;
- LSendPacket.Data := ToBytes(ADestFile+#0);
- LSendPacket.FDataLen := Length(LSendPacket.Data);
- //File date - optional
- if AGMTTime = 0 then begin
- SetLength(LSendPacket.FExtraData, 0);
- end else begin
- LUnixDate := DateTimeToUnix(AGMTTime);
- SetLength(LSendPacket.FExtraData, 4);
- CopyTIdNetworkUInt32(LUnixDate, LSendPacket.FExtraData, 0);
- end;
- SendCmd(LSendPacket, LRecvPacket, LTmpBuf);
- finally
- LRecvPacket.Free;
- end;
- finally
- LSendPacket.Free;
- end;
- end;
- procedure TIdFSP.Put(const ASourceFile, ADestFile: string);
- var
- LSourceStream: TStream;
- LDestFileName : String;
- begin
- LDestFileName := ADestFile;
- if LDestFileName = '' then begin
- LDestFileName := ExtractFileName(ASourceFile);
- end;
- LSourceStream := TIdReadFileExclusiveStream.Create(ASourceFile);
- try
- Put(LSourceStream, LDestFileName, GetGMTDateByName(ASourceFile));
- finally
- LSourceStream.Free;
- end;
- end;
- procedure TIdFSP.Delete(const AFilename: string);
- var
- LData : TIdBytes;
- LBuf, LExBuf : TIdBytes;
- begin
- LData := ToBytes(AFilename+#0);
- SendCmd(CC_DEL_FILE, LData, 0, LBuf, LExBuf);
- end;
- procedure TIdFSP.MakeDir(const ADirName: string);
- var
- LData : TIdBytes;
- LBuf, LExBuf : TIdBytes;
- begin
- LData := ToBytes(ADirName+#0);
- SendCmd(CC_MAKE_DIR, LData, 0, LBuf, LExBuf);
- ParseDirInfo(LBuf, LExBuf, FDirInfo);
- end;
- procedure TIdFSP.RemoveDir(const ADirName: string);
- var
- LData : TIdBytes;
- LBuf, LExBuf : TIdBytes;
- begin
- LData := ToBytes(ADirName+#0);
- SendCmd(CC_DEL_DIR, LData, 0, LBuf, LExBuf);
- end;
- procedure TIdFSP.Rename(const ASourceFile, ADestFile: string);
- var
- LBuf, LData, LDataExt : TIdBytes;
- begin
- SetLength(LData, 0);
- SetLength(LDataExt, 0);
- LBuf := ToBytes(ASourceFile+#0+ADestFile);
- SendCmd(CC_RENAME, LBuf, 0, LData, LDataExt);
- end;
- procedure TIdFSP.ParseDirInfo(const ABuf, AExtraBuf: TIdBytes; ADir : TIdFSPDirInfo);
- begin
- ADir.ReadMe := ParseASCIIZ(ABuf);
- if AExtraBuf <> nil then begin
- //0 - caller owns the directory
- ADir.OwnsDir := (AExtraBuf[0] and $01) = $01;
- //1 - files can be deleted from this dir
- ADir.CanDeleteFiles := (AExtraBuf[0] and $02) = $02;
- // 2 - files can be added to this dir
- ADir.CanAddFiles := (AExtraBuf[0] and $04) = $04;
- //3 - new subdirectories can be created
- ADir.CanMakeDir := (AExtraBuf[0] and $08) = $08;
- //4 - files are NOT readable by non-owners
- ADir.OnlyOwnerCanReadFiles := (AExtraBuf[0] and $10) = $10;
- //5 - directory contain an readme file
- ADir.HasReadMe := (AExtraBuf[0] and $20) = $20;
- //6 - directory can be listed
- ADir.CanBeListed := (AExtraBuf[0] and $40) = $40;
- //7 - files can be renamed in this directory
- ADir.CanRenameFiles := (AExtraBuf[0] and $80) = $80;
- end;
- end;
- procedure TIdFSP.GetDirInfo(const ADIR: String; ADirInfo: TIdFSPDirInfo);
- var
- LData, LBuf, LExtraBuf : TIdBytes;
- begin
- LData := ToBytes(ADIR+#0);
- SendCmd(CC_GET_PRO, LData, 0, LBuf, LExtraBuf);
- ParseDirInfo(LBuf, LExtraBuf, ADirInfo);
- end;
- procedure TIdFSP.SendCmdOnce(ACmdPacket, ARecvPacket: TIdFSPPacket;
- var VTempBuf: TIdBytes; const ARaiseException: Boolean);
- var
- LLen : Integer;
- LBuf : TIdBytes;
- LSendBuf : TIdBytes;
- //This is for where there may not be a reply to a command from a server.
- begin
- Inc(FSequence);
- SetLength(LBuf, MaxBufferSize);
- ACmdPacket.Key := FKey;
- ACmdPacket.Sequence := FSequence;
- LSendBuf := ACmdPacket.WritePacket;
- SendBuffer(LSendBuf);
- if Assigned(FOnSend) then begin
- FOnSend(Self, ACmdPacket);
- end;
- repeat
- LLen := ReceiveBuffer(LBuf, MINTIMEOUT);
- if LLen = 0 then begin
- Break;
- end;
- ARecvPacket.ReadPacket(LBuf, LLen);
- if ARecvPacket.Valid then begin
- if Assigned(FOnRecv) then begin
- FOnRecv(Self, ARecvPacket);
- end;
- if (ARecvPacket.Sequence = FSequence) then begin
- FKey := ARecvPacket.Key;
- Break;
- end;
- end;
- until False;
- if (ARecvPacket.Cmd = CC_ERR) and ARaiseException then begin
- raise EIdFSPProtException.Create(ParseASCIIZLen(ARecvPacket.Data, ARecvPacket.DataLen));
- end;
- end;
- procedure TIdFSP.SendCmdOnce(const ACmd: Byte; const AData,
- AExtraData: TIdBytes; const AFilePosition: Int64; var VData,
- VExtraData: TIdBytes; const ARaiseException: Boolean);
- var
- LSendPacket : TIdFSPPacket;
- LRecvPacket : TIdFSPPacket;
- LTmpBuf : TIdBytes;
- begin
- SetLength(LTmpBuf, MaxBufferSize);
- LSendPacket := TIdFSPPacket.Create;
- try
- LRecvPacket := TIdFSPPacket.Create;
- try
- LSendPacket.Cmd := ACmd;
- LSendPacket.FilePosition := AFilePosition;
- LSendPacket.Data := AData;
- LSendPacket.FDataLen := Length(AData);
- LSendPacket.ExtraData := AExtraData;
- SendCmdOnce(LSendPacket, LRecvPacket, LTmpBuf, ARaiseException);
- VData := LRecvPacket.Data;
- VExtraData := LRecvPacket.ExtraData;
- finally
- LRecvPacket.Free;
- end;
- finally
- LSendPacket.Free;
- end;
- end;
- function TIdFSP.MaxBufferSize: Word;
- //use only for calculating buffer for reading UDP packet
- begin
- Result := IndyMax(FClientMaxPacketSize, DEF_MAXSIZE);
- Result := IndyMax(FServerMaxPacketSize, Result);
- Inc(Result, HSIZE); //just in case
- end;
- function TIdFSP.PrefPayloadSize: Word;
- //maximum size of the data feild we want to use
- begin
- Result := IndyMin(FClientMaxPacketSize, FServerMaxPacketSize);
- Dec(Result, HSIZE);
- end;
- procedure TIdFSP.SetClientMaxPacketSize(const AValue: Word);
- begin
- //maximal size required by RFC
- //note that 512 gives a payload of 500 bytes in a packet
- if AValue < 512 then begin
- raise EIdFSPPacketTooSmall.Create(RSFSPPacketTooSmall);
- end;
- FClientMaxPacketSize := AValue;
- end;
- procedure TIdFSP.AbortCmd;
- begin
- //we don't want to go into the abort loop if there is no command
- //being send. If that happens, your program could hang.
- if FInCmd.Value then
- begin
- FAbortFlag.Value := True;
- repeat
- IndySleep(5);
- //we need to wait until the SendCmd routine catches the Abort
- //request so you don't get an AV in a worker thread.
- until not FAbortFlag.Value;
- end;
- end;
- { TIdFSPPacket }
- constructor TIdFSPPacket.Create;
- begin
- inherited Create;
- FCmd := 0;
- FFilePosition := 0;
- FDataLen := 0;
- SetLength(FData, 0);
- SetLength(FExtraData, 0);
- FSequence := 0;
- FKey := 0;
- end;
- function TIdFSPPacket.WritePacket : TIdBytes;
- var
- LExtraDataLen, LW : Word;
- LC, LSum : UInt32;
- i : Integer;
- //ported from:
- //http://cvs.sourceforge.net/viewcvs.py/fsp/javalib/FSPpacket.java?rev=1.6&view=markup
- begin
- LExtraDataLen := Length(FExtraData);
- SetLength(Result, HSIZE + FDataLen + LExtraDataLen);
- //cmd
- Result[0] := Cmd;
- //checksum
- Result[1] := 0; //this will be the checksum value
- //key
- LW := GStack.HostToNetwork(FKey);
- CopyTIdUInt16(LW, Result, 2);
- // sequence
- LW := GStack.HostToNetwork(FSequence);
- CopyTIdUInt16(LW, Result, 4);
- // data length
- LW := GStack.HostToNetwork(FDataLen);
- CopyTIdUInt16(LW, Result, 6);
- // position
- LC := GStack.HostToNetwork(FFilePosition);
- CopyTIdUInt32(LC, Result, 8);
- //end of header section
- //data section
- if FDataLen > 0 then begin
- CopyTIdBytes(FData, 0, Result, HSIZE, FDataLen);
- end;
- //extra data section
- if LExtraDataLen > 0 then begin
- CopyTIdBytes(FExtraData, 0, Result, HSIZE+FDataLen, LExtraDataLen);
- end;
- //checksum
- LSum := Length(Result);
- for i := Length(Result)-1 downto 0 do begin
- Inc(LSum, Result[i]);
- end;
- Result[1] := Byte(LSum+(LSum shr 8));
- end;
- procedure TIdFSPPacket.ReadPacket(const AData : TIdBytes; const ALen : UInt32);
- var
- LSum, LnSum, LcSum : UInt32; //UInt32 to prevent a range-check error
- LW : Word;
- LExtraDataLen : UInt32;
- begin
- FValid := False;
- if ALen < HSIZE then begin
- Exit;
- end;
- //check data length
- FDataLen := BytesToUInt16(AData, 6);
- FDataLen := GStack.NetworkToHost(FDataLen);
- if FDataLen > ALen then begin
- Exit;
- end;
- //validate checksum
- LSum := AData[1]; //checksum
- LnSum := ALen;
- for LW := ALen-1 downto 0 do begin
- if LW <> 1 then begin // skip the checksum byte
- Inc(LnSum, AData[LW]);
- end;
- end;
- lcSum := Byte(LnSum + (LnSum shr 8));
- if LcSum <> LSum then begin
- Exit;
- end;
- //command
- FCmd := AData[0];
- //key
- FKey := BytesToUInt16(AData, 2);
- FKey := GStack.NetworkToHost(FKey);
- // sequence
- FSequence := BytesToUInt16(AData, 4);
- FSequence := GStack.NetworkToHost(FSequence);
- //file position
- FFilePosition := BytesToUInt32(AData, 8);
- FFilePosition := GStack.NetworkToHost(FFilePosition);
- //extract data
- if FDataLen > 0 then begin
- SetLength(FData, FDataLen);
- CopyTIdBytes(AData, HSIZE, FData, 0, FDataLen);
- end else begin
- SetLength(FData, 0);
- end;
- //extract extra data
- LExtraDataLen := ALen - (HSIZE+FDataLen);
- if LExtraDataLen > 0 then begin
- SetLength(FExtraData, LExtraDataLen);
- CopyTIdBytes(AData, HSIZE+FDataLen, FExtraData, 0, LExtraDataLen);
- end else begin
- SetLength(FExtraData, 0);
- end;
- FValid := True;
- end;
- { TIdFSPListItems }
- function TIdFSPListItems.Add: TIdFSPListItem;
- begin
- Result := TIdFSPListItem(inherited Add);
- end;
- constructor TIdFSPListItems.Create;
- begin
- inherited Create(TIdFSPListItem);
- end;
- function TIdFSPListItems.GetItems(AIndex: Integer): TIdFSPListItem;
- begin
- Result := TIdFSPListItem(inherited Items[AIndex]);
- end;
- function TIdFSPListItems.IndexOf(AItem: TIdFSPListItem): Integer;
- Var
- i: Integer;
- begin
- for i := 0 to Count - 1 do begin
- if AItem = Items[i] then begin
- Result := i;
- Exit;
- end;
- end;
- Result := -1;
- end;
- function TIdFSPListItems.ParseEntries(const AData: TIdBytes; const ADataLen : UInt32) : Boolean;
- var
- i : UInt32;
- LI : TIdFSPListItem;
- LSkip : Boolean;
- begin
- Result := False;
- i := 0;
- repeat
- if i >= (ADataLen-9) then begin
- Exit;
- end;
- LI := nil;
- LSkip := False;
- case AData[i+8] of
- RDTYPE_END:
- begin
- Result := True;
- Exit;
- end;
- RDTYPE_FILE:
- begin
- LI := Add;
- LI.ItemType := ditFile;
- end;
- RDTYPE_DIR:
- begin
- LI := Add;
- LI.ItemType := ditDirectory;
- end;
- RDTYPE_SKIP:
- begin
- LSkip := True;
- end
- else begin
- Exit;
- end;
- end;
- if LSkip then begin
- Inc(i, 8);
- end else begin
- ParseStatInfo(AData, LI, i);
- LI.FileName := ParseASCIIZPos(AData, ADataLen, i);
- end;
- repeat
- Inc(i);
- until (i and $03) = 0;
- until False;
- end;
- procedure TIdFSPListItems.SetItems(AIndex: Integer; const Value: TIdFSPListItem);
- begin
- inherited Items[AIndex] := Value;
- end;
- end.
|