FTPClient.Mod 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. MODULE FTPClient; (** AUTHOR "TF"; PURPOSE "FTP client services"; *)
  2. IMPORT Streams, Kernel, Objects, IP, DNS, TCP, Strings, KernelLog;
  3. CONST
  4. ResOk = 0;
  5. ResFailed = 1;
  6. ResAlreadyOpen = 2;
  7. ResServerNotFound = 3;
  8. ResNoConnection = 4;
  9. ResUserPassError = 5;
  10. ResServerNotReady = 6;
  11. ResServerFailed = 7;
  12. FileActionOk = 250; CommandOk = 200; DataConnectionOpen = 125; FileStatusOk = 150;
  13. EnterPassword = 330; NeedPassword = 331; PathNameCreated = 257; UserLoggedIn = 230;
  14. ActvTimeout = 60 * 1000;
  15. Debug = FALSE;
  16. TYPE
  17. FTPEntry* = OBJECT
  18. VAR
  19. full* : ARRAY 331 OF CHAR;
  20. flags* : ARRAY 11 OF CHAR;
  21. type* : ARRAY 4 OF CHAR;
  22. user*, group*, size* : ARRAY 9 OF CHAR;
  23. d0*, d1*, d2* : ARRAY 13 OF CHAR;
  24. filename* : ARRAY 256 OF CHAR;
  25. visible* : BOOLEAN;
  26. END FTPEntry;
  27. FTPListing* = POINTER TO ARRAY OF FTPEntry;
  28. (** FTP client object must be used by a single process *)
  29. FTPClient* = OBJECT
  30. VAR
  31. open : BOOLEAN;
  32. busy : BOOLEAN;
  33. connection : TCP.Connection; (* control connection to the server *)
  34. dataCon : TCP.Connection;
  35. dataIP : IP.Adr;
  36. dataPort : LONGINT;
  37. w : Streams.Writer; (* writer oo the control connection *)
  38. r : Streams.Reader; (* reader on the control connection *)
  39. msg- : ARRAY 4096 OF CHAR;
  40. code : LONGINT;
  41. passiveTransfer : BOOLEAN;
  42. actvListener : TCP.Connection;
  43. actvTimeout : Objects.Timer;
  44. listing- : FTPListing;
  45. nofEntries- : LONGINT;
  46. PROCEDURE &Init*;
  47. BEGIN
  48. NEW(actvTimeout)
  49. END Init;
  50. PROCEDURE Open*(CONST host, user, password : ARRAY OF CHAR; port : LONGINT; VAR res : WORD);
  51. VAR fadr : IP.Adr;
  52. BEGIN {EXCLUSIVE}
  53. res := 0;
  54. busy := FALSE; open := FALSE;
  55. IF open THEN res := ResAlreadyOpen; RETURN END;
  56. DNS.HostByName(host, fadr, res);
  57. IF res = DNS.Ok THEN
  58. NEW(connection);
  59. connection.Open(TCP.NilPort, fadr, port, res);
  60. IF res = TCP.Ok THEN
  61. Streams.OpenWriter(w, connection.Send);
  62. Streams.OpenReader(r, connection.Receive);
  63. ReadResponse(code, msg);
  64. IF (code >= 200) & (code < 300) THEN
  65. IF Login(user, password) THEN open := TRUE;
  66. (* Set binary transfer mode - anything else seems useless *)
  67. w.String("TYPE I"); w.Ln; w.Update;
  68. ReadResponse(code, msg);
  69. IF code # CommandOk THEN res := ResServerFailed END
  70. ELSE res := ResUserPassError
  71. END
  72. ELSE res := ResServerNotReady
  73. END
  74. ELSE res := ResNoConnection
  75. END;
  76. IF ~open THEN connection.Close(); w := NIL; r := NIL END
  77. ELSE res := ResServerNotFound
  78. END
  79. END Open;
  80. PROCEDURE Login(CONST user, password : ARRAY OF CHAR) : BOOLEAN;
  81. BEGIN
  82. w.String("USER "); w.String(user); w.Ln; w.Update;
  83. ReadResponse(code, msg);
  84. IF (code = EnterPassword) OR (code = NeedPassword) THEN
  85. w.String("PASS "); w.String(password); w.Ln; w.Update;
  86. ReadResponse(code, msg);
  87. IF (code = UserLoggedIn) OR (code = EnterPassword) (* why ? *) THEN
  88. RETURN TRUE
  89. ELSE
  90. RETURN FALSE
  91. END
  92. ELSIF code = UserLoggedIn THEN RETURN TRUE
  93. ELSE RETURN FALSE
  94. END
  95. END Login;
  96. PROCEDURE ReadResponse(VAR code : LONGINT; VAR reply : ARRAY OF CHAR);
  97. VAR temp : ARRAY 1024 OF CHAR; tcode: ARRAY 4 OF CHAR; t : LONGINT;
  98. stop : BOOLEAN;
  99. BEGIN
  100. r.Int(code, FALSE); COPY("", reply);
  101. IF r.Peek() = "-" THEN (* multi line response *)
  102. stop := FALSE;
  103. REPEAT
  104. r.Ln(temp); Strings.Append(reply, temp); tcode[0] := CHR(10); tcode[1] := 0X;
  105. Strings.Append(reply, tcode);
  106. tcode[0] := temp[0]; tcode[1] := temp[1]; tcode[2] := temp[2]; tcode[3] := 0X;
  107. Strings.StrToInt(tcode, t);
  108. IF (t = code) & (temp[3] # "-") THEN stop := TRUE END;
  109. UNTIL stop OR (r.res # 0)
  110. ELSE
  111. r.Ln(temp); Strings.Append(reply, temp);
  112. END;
  113. END ReadResponse;
  114. PROCEDURE Close*(VAR res : WORD);
  115. BEGIN
  116. w.String("QUIT"); w.Ln; w.Update;
  117. ReadResponse(code, msg);
  118. IF (code >= 200) & (code < 300) THEN res := 0 ELSE res := code END;
  119. connection.Close; w := NIL; r := NIL;
  120. open := FALSE
  121. END Close;
  122. PROCEDURE IsAlive*() : BOOLEAN;
  123. VAR state: LONGINT;
  124. BEGIN
  125. state := connection.state;
  126. IF (state IN TCP.ClosedStates) OR (state = 5) THEN RETURN FALSE
  127. ELSE RETURN TRUE END
  128. END IsAlive;
  129. PROCEDURE IsNum(ch : CHAR) : BOOLEAN;
  130. BEGIN
  131. RETURN (ch >= '0') & (ch <='9')
  132. END IsNum;
  133. PROCEDURE GetDataConnection( VAR res : WORD);
  134. VAR ch : CHAR; i, j : LONGINT; ipstr : ARRAY 16 OF CHAR; p0, p1, port : LONGINT;
  135. str : ARRAY 32 OF CHAR;
  136. PROCEDURE Fail;
  137. BEGIN
  138. res := -1; r.SkipLn
  139. END Fail;
  140. BEGIN
  141. IF passiveTransfer THEN
  142. w.String("PASV"); w.Ln; w.Update;
  143. r.Int(code, FALSE);
  144. IF Debug THEN
  145. KernelLog.String("PASV");
  146. KernelLog.String("code = "); KernelLog.Int(code, 0); KernelLog.Ln;
  147. END;
  148. END;
  149. IF passiveTransfer & (code >= 200) & (code < 300) THEN
  150. (* search for a number *)
  151. REPEAT ch := r.Get() UNTIL IsNum(ch) OR (r.res # 0);
  152. IF r.res # 0 THEN Fail; RETURN END;
  153. (* read ip adr *)
  154. j := 0; i := 0;
  155. WHILE (r.res = 0) & (j < 4) DO
  156. IF ch = "," THEN ch := "."; INC(j) END;
  157. KernelLog.Char(ch);
  158. IF j < 4 THEN ipstr[i] := ch; INC(i); ch := r.Get() END
  159. END;
  160. ipstr[i] := 0X;
  161. IF Debug THEN
  162. KernelLog.String("ipstr = "); KernelLog.String(ipstr); KernelLog.Ln;
  163. END;
  164. IF r.res # 0 THEN Fail; RETURN END;
  165. (* read the port *)
  166. r.Int(p0, FALSE); ch := r.Get();
  167. IF ch # "," THEN Fail; RETURN END;
  168. r.Int(p1, FALSE);
  169. r.SkipLn;
  170. port := p0 * 256 + p1;
  171. IF Debug THEN
  172. KernelLog.String(ipstr); KernelLog.Ln;
  173. KernelLog.Int(port, 0); KernelLog.Ln;
  174. END;
  175. dataIP := IP.StrToAdr(ipstr);
  176. dataPort := port;
  177. ELSE
  178. IF passiveTransfer THEN r.SkipLn END; (* skip the negative reply message to PASV *)
  179. passiveTransfer := FALSE;
  180. (* trying to find an unused local tcp port within the limits of FTP *)
  181. NEW(actvListener);
  182. actvListener.Open(TCP.NilPort, IP.NilAdr, TCP.NilPort, res);
  183. IP.AdrToStr(connection.int.localAdr, str);
  184. i := 0; WHILE (str[i] # 0X) DO IF (str[i] = ".") THEN str[i] := "," END; INC(i) END;
  185. str[i] := ","; str[i+1] := 0X;
  186. w.String("PORT ");
  187. w.String(str);
  188. w.Int(actvListener.lport DIV 100H, 0);
  189. w.Char(",");
  190. w.Int(actvListener.lport MOD 100H, 0);
  191. w.Ln; w.Update;
  192. ReadResponse(code, msg);
  193. IF Debug THEN
  194. KernelLog.String("code = "); KernelLog.Int(code, 0); KernelLog.Ln;
  195. KernelLog.String("msg = "); KernelLog.String(msg); KernelLog.Ln;
  196. END;
  197. END
  198. END GetDataConnection;
  199. PROCEDURE ActvTimeoutHandler;
  200. BEGIN
  201. actvListener.Close
  202. END ActvTimeoutHandler;
  203. PROCEDURE WaitEstablished(c: TCP.Connection);
  204. VAR t: Kernel.MilliTimer;
  205. BEGIN
  206. ASSERT(c # NIL);
  207. IF (c.state # TCP.Established) THEN
  208. Kernel.SetTimer(t, 500);
  209. WHILE (c.state # TCP.Established) & ~Kernel.Expired(t) DO
  210. Objects.Yield
  211. END
  212. END
  213. END WaitEstablished;
  214. PROCEDURE OpenDataConnection(VAR connection : TCP.Connection; VAR res : WORD);
  215. BEGIN
  216. IF passiveTransfer THEN
  217. NEW(connection); connection.Open(TCP.NilPort, dataIP, dataPort, res)
  218. ELSE
  219. Objects.SetTimeout(actvTimeout, ActvTimeoutHandler, ActvTimeout);
  220. actvListener.Accept(connection, res);
  221. IF Debug THEN
  222. KernelLog.String("res = "); KernelLog.Int(res, 0); KernelLog.Ln;
  223. END;
  224. Objects.CancelTimeout(actvTimeout);
  225. actvListener.Close;
  226. IF (res = TCP.Ok) THEN
  227. WaitEstablished(connection);
  228. END;
  229. IF Debug THEN
  230. KernelLog.String("Active connection established"); KernelLog.Ln;
  231. END
  232. END
  233. END OpenDataConnection;
  234. PROCEDURE OpenPut*(CONST remoteName : ARRAY OF CHAR; VAR outw : Streams.Writer; VAR res : WORD);
  235. BEGIN
  236. IF ~open OR busy THEN res := -2; RETURN END;
  237. GetDataConnection(res);
  238. IF res # 0 THEN RETURN END;
  239. w.String("STOR "); w.String(remoteName); w.Ln; w.Update;
  240. ReadResponse(code, msg);
  241. IF Debug THEN
  242. KernelLog.String("code = "); KernelLog.Int(code, 0); KernelLog.Ln;
  243. KernelLog.String("msg = "); KernelLog.String(msg); KernelLog.Ln;
  244. END;
  245. IF (code = FileStatusOk) OR (code = FileActionOk) OR (code = DataConnectionOpen) THEN
  246. OpenDataConnection(dataCon, res);
  247. IF Debug THEN
  248. KernelLog.String("ODC"); KernelLog.String("res = "); KernelLog.Int(res, 0); KernelLog.Ln;
  249. END;
  250. IF res = 0 THEN
  251. busy := TRUE;
  252. Streams.OpenWriter(outw, dataCon.Send)
  253. END
  254. ELSE res := -1
  255. END
  256. END OpenPut;
  257. PROCEDURE ClosePut*(VAR res : WORD);
  258. BEGIN
  259. busy := FALSE;
  260. IF dataCon # NIL THEN
  261. dataCon.Close;
  262. dataCon := NIL
  263. END;
  264. ReadResponse(code, msg);
  265. IF (code >= 200) & (code < 300) THEN res := 0 ELSE res := code END;
  266. IF Debug THEN
  267. KernelLog.String("Result after close put"); KernelLog.Ln;
  268. KernelLog.String("code = "); KernelLog.Int(code, 0); KernelLog.Ln;
  269. KernelLog.String("msg = "); KernelLog.String(msg); KernelLog.Ln
  270. END
  271. END ClosePut;
  272. PROCEDURE OpenGet*(CONST remoteName : ARRAY OF CHAR; VAR r : Streams.Reader; VAR res : WORD);
  273. BEGIN
  274. IF ~open OR busy THEN res := -2; RETURN END;
  275. busy := TRUE;
  276. GetDataConnection(res);
  277. IF res # 0 THEN RETURN END;
  278. w.String("RETR "); w.String(remoteName); w.Ln; w.Update;
  279. ReadResponse(code, msg);
  280. IF Debug THEN
  281. KernelLog.String("code = "); KernelLog.Int(code, 0); KernelLog.Ln;
  282. KernelLog.String("msg = "); KernelLog.String(msg); KernelLog.Ln;
  283. END;
  284. IF (code = FileStatusOk) OR (code = FileActionOk) OR (code = DataConnectionOpen) THEN
  285. OpenDataConnection(dataCon, res);
  286. IF Debug THEN
  287. KernelLog.String("ODC"); KernelLog.String("res = "); KernelLog.Int(res, 0); KernelLog.Ln;
  288. END;
  289. IF res = 0 THEN
  290. Streams.OpenReader(r, dataCon.Receive)
  291. END
  292. ELSE res := -1
  293. END
  294. END OpenGet;
  295. PROCEDURE CloseGet*(VAR res : WORD);
  296. BEGIN
  297. IF dataCon # NIL THEN
  298. dataCon.Close;
  299. dataCon := NIL
  300. END;
  301. busy := FALSE;
  302. ReadResponse(code, msg);
  303. IF (code >= 200) & (code < 300) THEN res := 0 ELSE res := code END;
  304. IF Debug THEN
  305. KernelLog.String("Result after close get"); KernelLog.Ln;
  306. KernelLog.String("code = "); KernelLog.Int(code, 0); KernelLog.Ln;
  307. KernelLog.String("msg = "); KernelLog.String(msg); KernelLog.Ln
  308. END
  309. END CloseGet;
  310. PROCEDURE DeleteFile*(CONST remoteName : ARRAY OF CHAR; VAR res : WORD);
  311. BEGIN
  312. IF ~open OR busy THEN res := -2; RETURN END;
  313. w.String("DELE "); w.String(remoteName); w.Ln; w.Update;
  314. ReadResponse(code, msg);
  315. IF (code >= 200) & (code <300) THEN res := ResOk ELSE res := ResFailed END
  316. END DeleteFile;
  317. PROCEDURE ChangeDir*(CONST dir : ARRAY OF CHAR; VAR res : WORD);
  318. BEGIN
  319. IF ~open OR busy THEN res := -2; RETURN END;
  320. w.String("CWD "); w.String(dir); w.Ln; w.Update;
  321. ReadResponse(code, msg);
  322. IF (code >= 200) & (code <300) THEN res := ResOk ELSE res := ResFailed END
  323. END ChangeDir;
  324. PROCEDURE MakeDir*(CONST dir : ARRAY OF CHAR; VAR res : WORD);
  325. BEGIN
  326. IF ~open OR busy THEN res := -2; RETURN END;
  327. w.String("MKD "); w.String(dir); w.Ln; w.Update;
  328. ReadResponse(code, msg);
  329. IF (code >= 200) & (code <300) THEN res := ResOk ELSE res := ResFailed END
  330. END MakeDir;
  331. PROCEDURE RemoveDir*(CONST dir : ARRAY OF CHAR; VAR res : WORD);
  332. BEGIN
  333. IF ~open OR busy THEN res := -2; RETURN END;
  334. w.String("RMD "); w.String(dir); w.Ln; w.Update;
  335. ReadResponse(code, msg);
  336. IF (code >= 200) & (code <300) THEN res := ResOk ELSE res := ResFailed END
  337. END RemoveDir;
  338. PROCEDURE RenameFile*(CONST currentName, newName : ARRAY OF CHAR; VAR res : WORD);
  339. BEGIN
  340. IF ~open OR busy THEN res := -2; RETURN END;
  341. w.String("RNFR "); w.String(currentName); w.Ln; w.Update;
  342. ReadResponse(code, msg);
  343. IF (code = 350) THEN
  344. w.String("RNTO "); w.String(newName); w.Ln; w.Update;
  345. ReadResponse(code, msg);
  346. IF code = 250 THEN res := ResOk
  347. ELSE res := ResFailed
  348. END
  349. ELSE res := ResFailed
  350. END
  351. END RenameFile;
  352. PROCEDURE EnumerateNames*;
  353. VAR
  354. res : WORD;
  355. r : Streams.Reader; s, filename : ARRAY 256 OF CHAR;
  356. flags : ARRAY 11 OF CHAR;
  357. type : ARRAY 4 OF CHAR;
  358. user, group, size : ARRAY 9 OF CHAR;
  359. d0, d1, d2: ARRAY 13 OF CHAR;
  360. sr : Streams.StringReader;
  361. entry : FTPEntry;
  362. BEGIN
  363. IF ~open OR busy THEN res := -2; RETURN END;
  364. IF Debug THEN
  365. KernelLog.String("Enumerate Dir"); KernelLog.Ln;
  366. END;
  367. GetDataConnection(res);
  368. IF res # 0 THEN RETURN END;
  369. w.String("NLST"); w.Ln; w.Update;
  370. ReadResponse(code, msg);
  371. IF Debug THEN
  372. KernelLog.String("code = "); KernelLog.Int(code, 0); KernelLog.Ln;
  373. KernelLog.String("msg = "); KernelLog.String(msg); KernelLog.Ln;
  374. END;
  375. IF (code = FileStatusOk) OR (code = FileActionOk) OR (code = DataConnectionOpen) THEN
  376. IF Debug THEN
  377. KernelLog.String("Open data connection"); KernelLog.Ln;
  378. END;
  379. OpenDataConnection(dataCon, res);
  380. IF Debug THEN
  381. KernelLog.String("ODC"); KernelLog.String("res = "); KernelLog.Int(res, 0); KernelLog.Ln;
  382. END;
  383. IF res = 0 THEN
  384. Streams.OpenReader(r, dataCon.Receive);
  385. NEW(sr, 256); NEW(listing, 16); nofEntries := 0;
  386. REPEAT
  387. r.Ln(s);
  388. IF r.res = 0 THEN
  389. sr.Set(s); NEW(entry);
  390. COPY("", flags);
  391. COPY("", type);
  392. COPY("", user);
  393. COPY("", group);
  394. COPY("", size);
  395. COPY("", d0);
  396. COPY("", d1);
  397. COPY("", d2);
  398. sr.Ln(filename);
  399. COPY(flags, entry.flags);
  400. COPY(type, entry.type);
  401. COPY(user, entry.user);
  402. COPY(group, entry.group);
  403. COPY(size, entry.size);
  404. COPY(d0, entry.d0);
  405. COPY(d1, entry.d1);
  406. COPY(d2, entry.d2);
  407. COPY(filename, entry.filename);
  408. COPY(s, entry.full);
  409. AddFTPEntryToListing(entry);
  410. (* IF Debug THEN
  411. KernelLog.String("flags = "); KernelLog.String(flags); KernelLog.Ln;
  412. KernelLog.String("type = "); KernelLog.String(type); KernelLog.Ln;
  413. KernelLog.String("user = "); KernelLog.String(user); KernelLog.Ln;
  414. KernelLog.String("group = "); KernelLog.String(group); KernelLog.Ln;
  415. KernelLog.String("size = "); KernelLog.String(size); KernelLog.Ln;
  416. KernelLog.String("date = "); KernelLog.String(d0); KernelLog.String(d1);KernelLog.String(d2);KernelLog.Ln;
  417. KernelLog.String("filename = "); KernelLog.String(filename); KernelLog.Ln;
  418. KernelLog.Ln;
  419. END
  420. *) END
  421. UNTIL r.res # 0
  422. END;
  423. IF (dataCon # NIL) THEN dataCon.Close; END;
  424. ReadResponse(code, msg);
  425. IF Debug THEN
  426. KernelLog.String("Result after Dir"); KernelLog.Ln;
  427. KernelLog.String("code = "); KernelLog.Int(code, 0); KernelLog.Ln;
  428. KernelLog.String("msg = "); KernelLog.String(msg); KernelLog.Ln;
  429. END
  430. ELSE res := ResFailed
  431. END;
  432. dataCon := NIL
  433. END EnumerateNames;
  434. PROCEDURE EnumerateDir*(CONST args : ARRAY OF CHAR);
  435. VAR res : WORD;
  436. r : Streams.Reader; s, filename : ARRAY 256 OF CHAR;
  437. flags : ARRAY 11 OF CHAR;
  438. type : ARRAY 4 OF CHAR;
  439. user, group, size : ARRAY 9 OF CHAR;
  440. d0, d1, d2: ARRAY 13 OF CHAR;
  441. sr : Streams.StringReader;
  442. entry : FTPEntry;
  443. ch : CHAR;
  444. (*
  445. PROCEDURE FixLengthStr(r : Streams.Reader; len : LONGINT; VAR s : ARRAY OF CHAR);
  446. VAR i : LONGINT;
  447. BEGIN
  448. WHILE (len > 0) & (r.res = 0) DO
  449. s[i] := r.Get();
  450. DEC(len); INC(i)
  451. END;
  452. s[i] := 0X
  453. END FixLengthStr;
  454. *)
  455. BEGIN
  456. IF ~open OR busy THEN res := -2; RETURN END;
  457. IF Debug THEN
  458. KernelLog.String("Enumerate Dir"); KernelLog.Ln;
  459. END;
  460. GetDataConnection(res);
  461. IF res # 0 THEN RETURN END;
  462. w.String("LIST");
  463. IF args # "" THEN w.String(" "); w.String(args) END;
  464. w.Ln; w.Update;
  465. ReadResponse(code, msg);
  466. IF Debug THEN
  467. KernelLog.String("code = "); KernelLog.Int(code, 0); KernelLog.Ln;
  468. KernelLog.String("msg = "); KernelLog.String(msg); KernelLog.Ln;
  469. END;
  470. IF (code = FileStatusOk) OR (code = FileActionOk) OR (code = DataConnectionOpen) THEN
  471. IF Debug THEN
  472. KernelLog.String("Open data connection"); KernelLog.Ln;
  473. END;
  474. OpenDataConnection(dataCon, res);
  475. IF Debug THEN
  476. KernelLog.String("ODC"); KernelLog.String("res = "); KernelLog.Int(res, 0); KernelLog.Ln;
  477. END;
  478. IF res = 0 THEN
  479. Streams.OpenReader(r, dataCon.Receive);
  480. NEW(sr, 256); NEW(listing, 16); nofEntries := 0;
  481. REPEAT
  482. r.Ln(s);
  483. IF r.res = 0 THEN
  484. sr.Set(s); NEW(entry);
  485. (* KernelLog.String("s = "); KernelLog.String(s); KernelLog.Ln;
  486. FixLengthStr(sr, 10, flags); sr.SkipBytes(1);
  487. FixLengthStr(sr, 3, type); sr.SkipBytes(1);
  488. FixLengthStr(sr, 8, user); sr.SkipBytes(1);
  489. FixLengthStr(sr, 8, group); sr.SkipBytes(1);
  490. FixLengthStr(sr, 8, size); sr.SkipBytes(1);
  491. FixLengthStr(sr, 12, date); sr.SkipBytes(1); *)
  492. ch := sr.Peek();
  493. IF (ch = "-") OR (ch = "d") OR (ch = "l") THEN (* unix *)
  494. sr.Token(flags); sr.SkipWhitespace;
  495. sr.Token(type); sr.SkipWhitespace;
  496. sr.Token(user); sr.SkipWhitespace;
  497. sr.Token(group); sr.SkipWhitespace;
  498. sr.Token(size); sr.SkipWhitespace;
  499. sr.Token(d0); sr.SkipWhitespace;
  500. sr.Token(d1); sr.SkipWhitespace;
  501. sr.Token(d2); sr.SkipWhitespace;
  502. sr.Ln(filename);
  503. ELSE (* windows *)
  504. COPY("", type);
  505. COPY("", user);
  506. COPY("", group);
  507. COPY("", size);
  508. COPY("", d2);
  509. sr.Token(d0); sr.SkipWhitespace;
  510. sr.Token(d1); sr.SkipWhitespace;
  511. sr.Token(flags); sr.SkipWhitespace;
  512. sr.Ln(filename);
  513. IF flags # "<DIR>" THEN COPY(flags, size); COPY("", flags) END
  514. END;
  515. COPY(flags, entry.flags);
  516. COPY(type, entry.type);
  517. COPY(user, entry.user);
  518. COPY(group, entry.group);
  519. COPY(size, entry.size);
  520. COPY(d0, entry.d0);
  521. COPY(d1, entry.d1);
  522. COPY(d2, entry.d2);
  523. COPY(filename, entry.filename);
  524. COPY(s, entry.full);
  525. AddFTPEntryToListing(entry);
  526. (* IF Debug THEN
  527. KernelLog.String("flags = "); KernelLog.String(flags); KernelLog.Ln;
  528. KernelLog.String("type = "); KernelLog.String(type); KernelLog.Ln;
  529. KernelLog.String("user = "); KernelLog.String(user); KernelLog.Ln;
  530. KernelLog.String("group = "); KernelLog.String(group); KernelLog.Ln;
  531. KernelLog.String("size = "); KernelLog.String(size); KernelLog.Ln;
  532. KernelLog.String("date = "); KernelLog.String(d0); KernelLog.String(d1);KernelLog.String(d2);KernelLog.Ln;
  533. KernelLog.String("filename = "); KernelLog.String(filename); KernelLog.Ln;
  534. KernelLog.Ln;
  535. END
  536. *) END
  537. UNTIL r.res # 0
  538. END;
  539. IF (dataCon # NIL) THEN dataCon.Close; END;
  540. ReadResponse(code, msg);
  541. IF Debug THEN
  542. KernelLog.String("Result after Dir"); KernelLog.Ln;
  543. KernelLog.String("code = "); KernelLog.Int(code, 0); KernelLog.Ln;
  544. KernelLog.String("msg = "); KernelLog.String(msg); KernelLog.Ln;
  545. END
  546. ELSE res := ResFailed
  547. END;
  548. dataCon := NIL
  549. END EnumerateDir;
  550. PROCEDURE AddFTPEntryToListing(entry : FTPEntry);
  551. VAR newList : FTPListing;
  552. i : LONGINT;
  553. BEGIN
  554. INC(nofEntries);
  555. IF (nofEntries > LEN(listing)) THEN
  556. NEW(newList, LEN(listing)*2);
  557. FOR i := 0 TO LEN(listing)-1 DO newList[i] := listing[i] END;
  558. listing := newList;
  559. END;
  560. listing[nofEntries-1] := entry;
  561. END AddFTPEntryToListing;
  562. PROCEDURE GetCurrentDir*(VAR dir : ARRAY OF CHAR; VAR res : WORD);
  563. VAR p : LONGINT;
  564. BEGIN
  565. IF ~open OR busy THEN res := -2; RETURN END;
  566. w.String("PWD"); w.Ln; w.Update;
  567. ReadResponse(code, msg);
  568. KernelLog.String("msg = "); KernelLog.String(msg); KernelLog.Ln;
  569. IF code = PathNameCreated THEN
  570. COPY(msg, dir);
  571. p := Strings.Pos('"', dir);
  572. IF p >= 0 THEN
  573. Strings.Delete(dir, 0, p + 1);
  574. p := Strings.Pos('"', dir); Strings.Delete(dir, p, Strings.Length(dir) - p)
  575. ELSE
  576. p := Strings.Pos(' ', dir); Strings.Delete(dir, p, Strings.Length(dir) - p)
  577. END
  578. ELSE COPY("", dir); res := ResFailed
  579. END;
  580. END GetCurrentDir;
  581. PROCEDURE Raw*(CONST cmd : ARRAY OF CHAR; VAR res : WORD);
  582. VAR extMsg : ARRAY 4096 OF CHAR;
  583. command : ARRAY 32 OF CHAR; arguments : ARRAY 512 OF CHAR;
  584. BEGIN
  585. IF ~open OR busy THEN res := -2; RETURN END;
  586. SplitCommand(cmd, command, arguments);
  587. Strings.LowerCase(command);
  588. IF command = "list" THEN EnumerateDir(arguments)
  589. ELSE
  590. w.String(cmd); w.Ln; w.Update;
  591. ReadResponse(code, extMsg);
  592. KernelLog.String("code = "); KernelLog.Int(code, 0);
  593. KernelLog.String(" , msg = "); KernelLog.String(extMsg); KernelLog.Ln
  594. END;
  595. res := 0
  596. END Raw;
  597. PROCEDURE SplitCommand(CONST cmd : ARRAY OF CHAR; VAR command, args : ARRAY OF CHAR);
  598. VAR sr : Streams.StringReader;
  599. BEGIN
  600. NEW(sr, 512);
  601. sr.Set(cmd);
  602. sr.Token(command); sr.SkipWhitespace;
  603. sr.Ln(args);
  604. END SplitCommand;
  605. END FTPClient;
  606. END FTPClient.
  607. SystemTools.Free FTPClient~
  608. Color Codes
  609. Highlight
  610. Types and Procedures
  611. Lock Acquire / Lock Release
  612. Preferred notation (comment)
  613. Unsafe / Temporary / Stupid / requires attention
  614. Permanent Comment
  615. Assertion
  616. Debug