IdTrivialFTP.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. }
  13. {
  14. $Log$
  15. }
  16. {
  17. Rev 1.7 2/8/05 6:08:04 PM RLebeau
  18. Changed CheckOptionAck() to use TextIsSame() instead of SameText()
  19. Rev 1.6 7/23/04 6:41:50 PM RLebeau
  20. TFileStream access right tweak for Put()
  21. Rev 1.5 2/7/2004 7:25:58 PM JPMugaas
  22. Deleted error msg code in error packet. OOPS!!!
  23. Rev 1.4 2/7/2004 7:20:16 PM JPMugaas
  24. DotNET to go!! and YES - I want fries with that :-).
  25. Rev 1.3 2004.02.03 5:44:38 PM czhower
  26. Name changes
  27. Rev 1.2 1/21/2004 4:21:04 PM JPMugaas
  28. InitComponent
  29. Rev 1.1 2003.10.12 6:36:46 PM czhower
  30. Now compiles.
  31. Rev 1.0 11/13/2002 08:03:32 AM JPMugaas
  32. }
  33. unit IdTrivialFTP;
  34. interface
  35. {$i IdCompilerDefines.inc}
  36. uses
  37. Classes,
  38. IdAssignedNumbers,
  39. IdGlobal,
  40. IdTrivialFTPBase,
  41. IdUDPClient;
  42. const
  43. GTransferMode = tfOctet;
  44. GFRequestedBlockSize = 1500;
  45. GReceiveTimeout = 4000;
  46. type
  47. TIdTrivialFTP = class(TIdUDPClient)
  48. protected
  49. FMode: TIdTFTPMode;
  50. FRequestedBlockSize: Integer;
  51. FPeerPort: TIdPort;
  52. FPeerIP: String;
  53. function ModeToStr: string;
  54. procedure CheckOptionAck(const OptionPacket: TIdBytes; Reading: Boolean);
  55. protected
  56. procedure SendAck(const BlockNumber: UInt16);
  57. procedure RaiseError(const ErrorPacket: TIdBytes);
  58. procedure InitComponent; override;
  59. public
  60. procedure Get(const ServerFile: String; DestinationStream: TStream); overload;
  61. procedure Get(const ServerFile, LocalFile: String); overload;
  62. procedure Put(SourceStream: TStream; const ServerFile: String); overload;
  63. procedure Put(const LocalFile, ServerFile: String); overload;
  64. published
  65. property TransferMode: TIdTFTPMode read FMode write FMode Default GTransferMode;
  66. property RequestedBlockSize: Integer read FRequestedBlockSize write FRequestedBlockSize default 1500;
  67. property OnWork;
  68. property OnWorkBegin;
  69. property OnWorkEnd;
  70. end;
  71. implementation
  72. uses
  73. IdComponent,
  74. IdExceptionCore,
  75. IdGlobalProtocols,
  76. IdResourceStringsProtocols,
  77. IdStack,
  78. SysUtils;
  79. procedure TIdTrivialFTP.CheckOptionAck(const OptionPacket: TIdBytes; Reading: Boolean);
  80. var
  81. LOptName, LOptValue: String;
  82. LOffset, Idx, OptionIdx: Integer;
  83. LRequestedBlkSize: Integer;
  84. begin
  85. LOffset := 2; // skip packet opcode
  86. try
  87. while LOffset < Length(OptionPacket) do
  88. begin
  89. Idx := ByteIndex(0, OptionPacket, LOffset);
  90. if Idx = -1 then begin
  91. raise EIdTFTPOptionNegotiationFailed.Create('');
  92. end;
  93. LOptName := BytesToString(OptionPacket, LOffset, Idx-LOffset, IndyTextEncoding_ASCII);
  94. LOffset := Idx+1;
  95. Idx := ByteIndex(0, OptionPacket, LOffset);
  96. if Idx = -1 then begin
  97. raise EIdTFTPOptionNegotiationFailed.Create('');
  98. end;
  99. LOptValue := BytesToString(OptionPacket, LOffset, Idx-LOffset, IndyTextEncoding_ASCII);
  100. LOffset := Idx+1;
  101. OptionIdx := PosInStrArray(LOptName, [sBlockSize, sTransferSize], False);
  102. if OptionIdx = -1 then begin
  103. // RLebeau 12/6/2011: workaround for bug in PicoMOD3 devices
  104. if (LOptName = '') and (LOptValue = '') then begin
  105. Continue;
  106. end;
  107. raise EIdTFTPOptionNegotiationFailed.CreateFmt(RSTFTPUnsupportedOption, [LOptName]);
  108. end;
  109. case OptionIdx of
  110. 0:
  111. begin
  112. LRequestedBlkSize := IndyStrToInt(LOptValue);
  113. if (LRequestedBlkSize < 8) or (LRequestedBlkSize > 65464) then begin
  114. raise EIdTFTPOptionNegotiationFailed.CreateFmt(RSTFTPUnsupportedOptionValue, [LOptValue, LOptName]);
  115. end;
  116. BufferSize := 4 + LRequestedBlkSize;
  117. end;
  118. 1:
  119. begin
  120. if Reading then
  121. begin
  122. // TODO
  123. {
  124. if (IndyStrToInt(LOptValue) is not available) then begin
  125. raise EIdTFTPAllocationExceeded.Create('');
  126. end;
  127. }
  128. end;
  129. end;
  130. end;
  131. end;
  132. except
  133. on E: Exception do begin
  134. SendError(Self, FPeerIP, FPeerPort, E);
  135. raise;
  136. end;
  137. end;
  138. end;
  139. procedure TIdTrivialFTP.InitComponent;
  140. begin
  141. inherited;
  142. TransferMode := GTransferMode;
  143. Port := IdPORT_TFTP;
  144. FRequestedBlockSize := GFRequestedBlockSize;
  145. ReceiveTimeout := GReceiveTimeout;
  146. end;
  147. procedure TIdTrivialFTP.Get(const ServerFile: String; DestinationStream: TStream);
  148. var
  149. Buffer, LServerFile, LMode, LBlockSize, LBlockOctets, LTransferSize, LTransferOctets: TIdBytes;
  150. DataLen, LOffset: Integer;
  151. ExpectedBlockCtr, RecvdBlockCtr: UInt16;
  152. TerminateTransfer: Boolean;
  153. begin
  154. try
  155. BufferSize := 4 + 512; // 512 as specified by RFC 1350
  156. LServerFile := ToBytes(ServerFile);
  157. LMode := ToBytes(ModeToStr);
  158. LBlockSize := ToBytes(sBlockSize);
  159. LBlockOctets := ToBytes(IntToStr(FRequestedBlockSize));
  160. LTransferSize := ToBytes(sTransferSize);
  161. LTransferOctets := ToBytes(IntToStr(0));
  162. SetLength(Buffer, 2+Length(LServerFile)+1+Length(LMode)+1+Length(LBlockSize)+1+Length(LBlockOctets)+1+Length(LTransferSize)+1+Length(LTransferOctets)+1);
  163. LOffset := 0;
  164. CopyTIdUInt16(GStack.HostToNetwork(UInt16(TFTP_RRQ)), Buffer, LOffset);
  165. Inc(LOffset, 2);
  166. CopyTIdBytes(LServerFile, 0, Buffer, LOffset, Length(LServerFile));
  167. Inc(LOffset, Length(LServerFile));
  168. Buffer[LOffset] := 0;
  169. Inc(LOffset);
  170. CopyTIdBytes(LMode, 0, Buffer, LOffset, Length(LMode));
  171. Inc(LOffset, Length(LMode));
  172. Buffer[LOffset] := 0;
  173. Inc(LOffset);
  174. CopyTIdBytes(LBlockSize, 0, Buffer, LOffset, Length(LBlockSize));
  175. Inc(LOffset, Length(LBlockSize));
  176. Buffer[LOffset] := 0;
  177. Inc(LOffset);
  178. CopyTIdBytes(LBlockOctets, 0, Buffer, LOffset, Length(LBlockOctets));
  179. Inc(LOffset, Length(LBlockOctets));
  180. Buffer[LOffset] := 0;
  181. Inc(LOffset);
  182. CopyTIdBytes(LTransferSize, 0, Buffer, LOffset, Length(LTransferSize));
  183. Inc(LOffset, Length(LTransferSize));
  184. Buffer[LOffset] := 0;
  185. Inc(LOffset);
  186. CopyTIdBytes(LTransferOctets, 0, Buffer, LOffset, Length(LTransferOctets));
  187. Inc(LOffset, Length(LTransferOctets));
  188. Buffer[LOffset] := 0;
  189. SendBuffer(Buffer);
  190. ExpectedBlockCtr := 1;
  191. TerminateTransfer := False;
  192. BeginWork(wmRead);
  193. try
  194. repeat
  195. SetLength(Buffer, BufferSize);
  196. DataLen := ReceiveBuffer(Buffer, FPeerIP, FPeerPort, ReceiveTimeout);
  197. if DataLen <= 0 then begin
  198. // TODO: re-transmit the last sent packet again instead of erroring...
  199. raise EIdTFTPException.Create(RSTimeOut);
  200. end;
  201. SetLength(Buffer, DataLen);
  202. // TODO: validate the correct peer is sending the data...
  203. case GStack.NetworkToHost(BytesToUInt16(Buffer)) of
  204. TFTP_DATA:
  205. begin
  206. RecvdBlockCtr := GStack.NetworkToHost(BytesToUInt16(Buffer, 2));
  207. if RecvdBlockCtr = ExpectedBlockCtr then
  208. begin
  209. DataLen := Length(Buffer) - 4;
  210. try
  211. WriteTIdBytesToStream(DestinationStream, Buffer, DataLen, 4);
  212. DoWork(wmRead, DataLen);
  213. except
  214. on E: Exception do
  215. begin
  216. SendError(Self, FPeerIP, FPeerPort, E);
  217. raise;
  218. end;
  219. end;
  220. SendAck(RecvdBlockCtr);
  221. if RecvdBlockCtr = High(UInt16) then begin
  222. if Length(Buffer) >= BufferSize then begin
  223. // have reached the max block counter allowed, can't validate any more data...
  224. SendError(Self, FPeerIP, FPeerPort, ErrIllegalOperation, '');
  225. raise EIdTFTPException.CreateFmt(RSTFTPUnexpectedOp, [FPeerIP, FPeerPort]);
  226. end;
  227. TerminateTransfer := True; // end of transfer, a block counter cannot wrap back to 0
  228. end else begin
  229. ExpectedBlockCtr := RecvdBlockCtr + 1;
  230. TerminateTransfer := Length(Buffer) < BufferSize;
  231. end;
  232. end;
  233. end;
  234. TFTP_ERROR:
  235. begin
  236. RaiseError(Buffer);
  237. end;
  238. TFTP_OACK:
  239. begin
  240. CheckOptionAck(Buffer, True);
  241. SendAck(0);
  242. end;
  243. else
  244. begin
  245. SendError(Self, FPeerIP, FPeerPort, ErrIllegalOperation, '');
  246. raise EIdTFTPException.CreateFmt(RSTFTPUnexpectedOp, [FPeerIP, FPeerPort]);
  247. end;
  248. end;
  249. until TerminateTransfer;
  250. finally
  251. EndWork(wmRead);
  252. end;
  253. finally
  254. Binding.CloseSocket;
  255. end;
  256. end;
  257. procedure TIdTrivialFTP.Get(const ServerFile, LocalFile: String);
  258. var
  259. fs: TFileStream;
  260. begin
  261. fs := TIdFileCreateStream.Create(LocalFile);
  262. try
  263. Get(ServerFile, fs);
  264. finally
  265. FreeAndNil(fs);
  266. end;
  267. end;
  268. function TIdTrivialFTP.ModeToStr: string;
  269. begin
  270. case TransferMode of
  271. tfNetAscii: Result := 'netascii'; {Do not Localize}
  272. tfOctet: Result := 'octet'; {Do not Localize}
  273. end;
  274. end;
  275. procedure TIdTrivialFTP.Put(SourceStream: TStream; const ServerFile: String);
  276. var
  277. Buffer, LServerFile, LMode, LBlockSize, LBlockOctets, LTransferSize, LTransferOctets: TIdBytes;
  278. StreamLen: TIdStreamSize;
  279. LOffset, DataLen: Integer;
  280. ExpectedBlockCtr, RecvdBlockCtr, wOp: UInt16;
  281. TerminateTransfer, WaitingForAck: Boolean;
  282. procedure SendDataPacket(const BlockNumber: UInt16);
  283. begin
  284. DataLen := IndyMin(BufferSize-4, StreamLen);
  285. SetLength(Buffer, 4 + DataLen);
  286. CopyTIdUInt16(GStack.HostToNetwork(UInt16(TFTP_DATA)), Buffer, 0);
  287. CopyTIdUInt16(GStack.HostToNetwork(BlockNumber), Buffer, 2);
  288. try
  289. DataLen := ReadTIdBytesFromStream(SourceStream, Buffer, DataLen, 4);
  290. except
  291. on E: Exception do
  292. begin
  293. SendError(Self, FPeerIP, FPeerPort, E);
  294. raise;
  295. end;
  296. end;
  297. SetLength(Buffer, 4 + DataLen);
  298. SendBuffer(FPeerIP, FPeerPort, Buffer);
  299. WaitingForAck := True;
  300. DoWork(wmWrite, DataLen);
  301. Dec(StreamLen, DataLen);
  302. TerminateTransfer := DataLen < (BufferSize - 4);
  303. ExpectedBlockCtr := BlockNumber;
  304. end;
  305. begin
  306. try
  307. BufferSize := 4 + 512; // 512 as specified by RFC 1350
  308. StreamLen := SourceStream.Size - SourceStream.Position;
  309. LServerFile := ToBytes(ServerFile);
  310. LMode := ToBytes(ModeToStr);
  311. LBlockSize := ToBytes(sBlockSize);
  312. LBlockOctets := ToBytes(IntToStr(FRequestedBlockSize));
  313. LTransferSize := ToBytes(sTransferSize);
  314. LTransferOctets := ToBytes(IntToStr(StreamLen));
  315. SetLength(Buffer, 2+Length(LServerFile)+1+Length(LMode)+1+Length(LBlockSize)+1+Length(LBlockOctets)+1+Length(LTransferSize)+1+Length(LTransferOctets)+1);
  316. LOffset := 0;
  317. CopyTIdUInt16(GStack.HostToNetwork(UInt16(TFTP_WRQ)), Buffer, LOffset);
  318. Inc(LOffset, 2);
  319. CopyTIdBytes(LServerFile, 0, Buffer, LOffset, Length(LServerFile));
  320. Inc(LOffset, Length(LServerFile));
  321. Buffer[LOffset] := 0;
  322. Inc(LOffset);
  323. CopyTIdBytes(LMode, 0, Buffer, LOffset, Length(LMode));
  324. Inc(LOffset, Length(LMode));
  325. Buffer[LOffset] := 0;
  326. Inc(LOffset);
  327. CopyTIdBytes(LBlockSize, 0, Buffer, LOffset, Length(LBlockSize));
  328. Inc(LOffset, Length(LBlockSize));
  329. Buffer[LOffset] := 0;
  330. Inc(LOffset);
  331. CopyTIdBytes(LBlockOctets, 0, Buffer, LOffset, Length(LBlockOctets));
  332. Inc(LOffset, Length(LBlockOctets));
  333. Buffer[LOffset] := 0;
  334. Inc(LOffset);
  335. CopyTIdBytes(LTransferSize, 0, Buffer, LOffset, Length(LTransferSize));
  336. Inc(LOffset, Length(LTransferSize));
  337. Buffer[LOffset] := 0;
  338. Inc(LOffset);
  339. CopyTIdBytes(LTransferOctets, 0, Buffer, LOffset, Length(LTransferOctets));
  340. Inc(LOffset, Length(LTransferOctets));
  341. Buffer[LOffset] := 0;
  342. SendBuffer(Buffer);
  343. ExpectedBlockCtr := 0;
  344. TerminateTransfer := False;
  345. BeginWork(wmWrite, StreamLen);
  346. try
  347. repeat
  348. SetLength(Buffer, BufferSize);
  349. DataLen := ReceiveBuffer(Buffer, FPeerIP, FPeerPort, IndyMax(500, ReceiveTimeout));
  350. if DataLen <= 0 then begin
  351. // TODO: re-transmit the last sent packet again instead of erroring...
  352. raise EIdTFTPException.Create(RSTimeOut);
  353. end;
  354. SetLength(Buffer, DataLen);
  355. // TODO: validate the correct peer is sending the data...
  356. wOp := GStack.NetworkToHost(BytesToUInt16(Buffer));
  357. case wOp of
  358. TFTP_ACK:
  359. begin
  360. RecvdBlockCtr := GStack.NetworkToHost(BytesToUInt16(Buffer, 2));
  361. if RecvdBlockCtr = ExpectedBlockCtr then
  362. begin
  363. WaitingForAck := False;
  364. if not TerminateTransfer then
  365. begin
  366. if RecvdBlockCtr = High(UInt16) then
  367. begin
  368. // end of transfer, a block counter cannot wrap back to 0
  369. SendError(Self, FPeerIP, FPeerPort, ErrAllocationExceeded, '');
  370. raise EIdTFTPAllocationExceeded.Create('');
  371. end;
  372. SendDataPacket(RecvdBlockCtr+1);
  373. end;
  374. end;
  375. end;
  376. TFTP_OACK:
  377. begin
  378. if ExpectedBlockCtr <> 0 then
  379. begin
  380. SendError(Self, FPeerIP, FPeerPort, ErrIllegalOperation, '');
  381. raise EIdTFTPException.CreateFmt(RSTFTPUnexpectedOp, [FPeerIP, FPeerPort]);
  382. end;
  383. CheckOptionAck(Buffer, False);
  384. SendDataPacket(1);
  385. end;
  386. TFTP_ERROR:
  387. begin
  388. RaiseError(Buffer);
  389. end;
  390. else
  391. begin
  392. SendError(Self, FPeerIP, FPeerPort, ErrIllegalOperation, '');
  393. raise EIdTFTPException.CreateFmt(RSTFTPUnexpectedOp, [FPeerIP, FPeerPort]);
  394. end;
  395. end;
  396. until TerminateTransfer and (not WaitingForAck);
  397. finally
  398. EndWork(wmWrite);
  399. end;
  400. finally
  401. Binding.CloseSocket;
  402. end;
  403. end;
  404. procedure TIdTrivialFTP.Put(const LocalFile, ServerFile: String);
  405. var
  406. fs: TIdReadFileExclusiveStream;
  407. begin
  408. fs := TIdReadFileExclusiveStream.Create(LocalFile);
  409. try
  410. Put(fs, ServerFile);
  411. finally
  412. fs.Free;
  413. end;
  414. end;
  415. procedure TIdTrivialFTP.RaiseError(const ErrorPacket: TIdBytes);
  416. var
  417. ErrMsg: string;
  418. begin
  419. ErrMsg := BytesToString(ErrorPacket, 4, Length(ErrorPacket)-4, IndyTextEncoding_ASCII);
  420. case GStack.NetworkToHost(BytesToUInt16(ErrorPacket, 2)) of
  421. ErrFileNotFound: raise EIdTFTPFileNotFound.Create(ErrMsg);
  422. ErrAccessViolation: raise EIdTFTPAccessViolation.Create(ErrMsg);
  423. ErrAllocationExceeded: raise EIdTFTPAllocationExceeded.Create(ErrMsg);
  424. ErrIllegalOperation: raise EIdTFTPIllegalOperation.Create(ErrMsg);
  425. ErrUnknownTransferID: raise EIdTFTPUnknownTransferID.Create(ErrMsg);
  426. ErrFileAlreadyExists: raise EIdTFTPFileAlreadyExists.Create(ErrMsg);
  427. ErrNoSuchUser: raise EIdTFTPNoSuchUser.Create(ErrMsg);
  428. ErrOptionNegotiationFailed: raise EIdTFTPOptionNegotiationFailed.Create(ErrMsg);
  429. else
  430. // usually ErrUndefined (see EIdTFTPException.Message if any)
  431. raise EIdTFTPException.Create(ErrMsg);
  432. end;
  433. end;
  434. procedure TIdTrivialFTP.SendAck(const BlockNumber: UInt16);
  435. begin
  436. SendBuffer(FPeerIP, FPeerPort, MakeActPkt(BlockNumber));
  437. end;
  438. end.