IdFTPListParseUnix.pas 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858
  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.21 2/23/2005 6:34:28 PM JPMugaas
  18. New property for displaying permissions ina GUI column. Note that this
  19. should not be used like a CHMOD because permissions are different on
  20. different platforms - you have been warned.
  21. Rev 1.20 10/26/2004 9:56:00 PM JPMugaas
  22. Updated refs.
  23. Rev 1.19 8/5/2004 11:18:16 AM JPMugaas
  24. Should fix a parsing problem I introeduced that caused errors with Unitree
  25. servers.
  26. Rev 1.18 8/4/2004 12:40:12 PM JPMugaas
  27. Fix for problem with total line.
  28. Rev 1.17 7/15/2004 4:02:48 AM JPMugaas
  29. Fix for some FTP servers. In a Unix listing, a : at the end of a filename
  30. was wrongly being interpretted as a subdirectory entry in a recursive
  31. listing.
  32. Rev 1.16 6/14/2004 12:05:54 AM JPMugaas
  33. Added support for the following Item types that appear in some Unix listings
  34. (particularly a /dev or /tmp dir):
  35. FIFO, Socket, Character Device, Block Device.
  36. Rev 1.15 6/13/2004 10:44:06 PM JPMugaas
  37. Fixed a problem with some servers returning additional columns in the owner
  38. and group feilds. Note that they will not be parsed correctly in all cases.
  39. That's life.
  40. drwx------ 1 BUILTIN NT AUTHORITY 0 Dec 7 2001
  41. System Volume Information
  42. Rev 1.14 4/20/2004 4:01:18 PM JPMugaas
  43. Fix for nasty typecasting error. The wrong create was being called.
  44. Rev 1.13 4/19/2004 5:05:20 PM JPMugaas
  45. Class rework Kudzu wanted.
  46. Rev 1.12 2004.02.03 5:45:18 PM czhower
  47. Name changes
  48. Rev 1.11 2004.01.23 9:53:32 PM czhower
  49. REmoved unneded check because of CharIsInSet functinoalty. Also was a short
  50. circuit which is not permitted.
  51. Rev 1.10 1/23/2004 12:49:52 PM SPerry
  52. fixed set problems
  53. Rev 1.9 1/22/2004 8:29:02 AM JPMugaas
  54. Removed Ansi*.
  55. Rev 1.8 1/22/2004 7:20:48 AM JPMugaas
  56. System.Delete changed to IdDelete so the code can work in NET.
  57. Rev 1.7 10/19/2003 3:48:10 PM DSiders
  58. Added localization comments.
  59. Rev 1.6 9/28/2003 03:02:30 AM JPMugaas
  60. Now can handle a few non-standard date types.
  61. Rev 1.5 9/3/2003 07:34:40 PM JPMugaas
  62. Parsing for /bin/ls with devices now should work again.
  63. Rev 1.4 4/7/2003 04:04:26 PM JPMugaas
  64. User can now descover what output a parser may give.
  65. Rev 1.3 4/3/2003 03:37:36 AM JPMugaas
  66. Fixed a bug in the Unix parser causing it not to work properly with Unix BSD
  67. servers using the -T switch. Note that when a -T switch s used on a FreeBSD
  68. server, the server outputs the millaseconds and an extra column giving the
  69. year instead of either the year or time (the regular /bin/ls standard
  70. behavior).
  71. Rev 1.2 3/3/2003 07:17:58 PM JPMugaas
  72. Now honors the FreeBSD -T flag and parses list output from a program using
  73. it. Minor changes to the File System component.
  74. Rev 1.1 2/19/2003 05:53:14 PM JPMugaas
  75. Minor restructures to remove duplicate code and save some work with some
  76. formats. The Unix parser had a bug that caused it to give a False positive
  77. for Xercom MicroRTOS.
  78. Rev 1.0 2/19/2003 02:02:02 AM JPMugaas
  79. Individual parsing objects for the new framework.
  80. }
  81. unit IdFTPListParseUnix;
  82. interface
  83. {$i IdCompilerDefines.inc}
  84. uses
  85. Classes,
  86. IdFTPList, IdFTPListParseBase, IdFTPListTypes;
  87. {
  88. Notes:
  89. - The Unitree and Unix parsers are closely tied together and share just
  90. about all of the same code. The reason is that Unitee is very similar to
  91. a Unix dir list except it has an extra column which the Unix line parser
  92. can handle in the Unitree type.
  93. - The Unix parser can parse MACOS - Peters server (no relationship to this
  94. author :-) ).
  95. - It is worth noting that the parser does handle /bin/ls -s and -i switches as
  96. well as -g and -o. This is important sometimes as the Unix format comes
  97. from FTP servers that simply piped output from the Unix /bin/ls command.
  98. - This parser also handles recursive lists which is good for mirroring software.
  99. }
  100. type
  101. {
  102. Note that for this, I am violating a convention.
  103. The violation is that I am putting parsers for two separate servers
  104. in the same unit.
  105. The reason is this, Unitree has two additional columns (a file family
  106. and a file migration status. The line parsing code is the same because
  107. I thought it was easier to do that way in this case.
  108. }
  109. TIdUnixFTPListItem = class(TIdUnixBaseFTPListItem)
  110. protected
  111. FNumberBlocks : Integer;
  112. FInode : Integer;
  113. public
  114. property NumberBlocks : Integer read FNumberBlocks write FNumberBlocks;
  115. property Inode : Integer read FInode write FInode;
  116. end;
  117. TIdUnitreeFTPListItem = class(TIdUnixFTPListItem)
  118. protected
  119. FMigrated : Boolean;
  120. FFileFamily : String;
  121. public
  122. property Migrated : Boolean read FMigrated write FMigrated;
  123. property FileFamily : String read FFileFamily write FFileFamily;
  124. end;
  125. TIdFTPLPUnix = class(TIdFTPListBase)
  126. protected
  127. class function MakeNewItem(AOwner : TIdFTPListItems) : TIdFTPListItem; override;
  128. class function InternelChkUnix(const AData : String) : Boolean; virtual;
  129. class function IsUnitree(const AData: string): Boolean; virtual;
  130. class function IsUnitreeBanner(const AData: String): Boolean; virtual;
  131. class function ParseLine(const AItem : TIdFTPListItem; const APath : String = ''): Boolean; override;
  132. public
  133. class function GetIdent : String; override;
  134. class function CheckListing(AListing : TStrings; const ASysDescript : String = ''; const ADetails : Boolean = True): Boolean; override;
  135. class function ParseListing(AListing : TStrings; ADir : TIdFTPListItems) : Boolean; override;
  136. end;
  137. TIdFTPLPUnitree = class(TIdFTPLPUnix)
  138. protected
  139. class function MakeNewItem(AOwner : TIdFTPListItems) : TIdFTPListItem; override;
  140. public
  141. class function GetIdent : String; override;
  142. end;
  143. const
  144. UNIX = 'Unix'; {do not localize}
  145. UNITREE = 'Unitree'; {do not localize}
  146. // RLebeau 2/14/09: this forces C++Builder to link to this unit so
  147. // RegisterFTPListParser can be called correctly at program startup...
  148. {$IFDEF HAS_DIRECTIVE_HPPEMIT_LINKUNIT}
  149. {$HPPEMIT LINKUNIT}
  150. {$ELSE}
  151. {$HPPEMIT '#pragma link "IdFTPListParseUnix"'}
  152. {$ENDIF}
  153. implementation
  154. uses
  155. IdException,
  156. IdGlobal, IdFTPCommon, IdGlobalProtocols,
  157. {$IFDEF HAS_UNIT_DateUtils}DateUtils,{$ENDIF}
  158. SysUtils;
  159. { TIdFTPLPUnix }
  160. class function TIdFTPLPUnix.CheckListing(AListing: TStrings;
  161. const ASysDescript: String; const ADetails: Boolean): Boolean;
  162. var
  163. i : Integer;
  164. begin
  165. // TODO: return True if ASysDescript starts with 'Unix'?
  166. Result := False;
  167. for i := 0 to AListing.Count - 1 do
  168. begin
  169. if AListing[i] <> '' then begin
  170. //workaround for the XBox MediaCenter FTP Server
  171. //which returns something like this:
  172. //
  173. //dr-xr-xr-x 1 ftp ftp 1 Feb 23 00:00 D:
  174. //and the trailing : is falsely assuming that a ":" means
  175. //a subdirectory entry in a recursive list.
  176. if InternelChkUnix(AListing[i]) then begin
  177. if GetIdent = UNITREE then begin
  178. Result := IsUnitree(AListing[i]);
  179. end else begin
  180. Result := not IsUnitree(AListing[i]);
  181. end;
  182. Break;
  183. end;
  184. if not (IsTotalLine(AListing[i]) or IsSubDirContentsBanner(AListing[i])) then begin
  185. Break;
  186. end;
  187. end;
  188. end;
  189. end;
  190. class function TIdFTPLPUnix.GetIdent: String;
  191. begin
  192. Result := UNIX;
  193. end;
  194. class function TIdFTPLPUnix.InternelChkUnix(const AData: String): Boolean;
  195. var
  196. s : TStrings;
  197. LCData : String;
  198. begin
  199. //pos 1 values
  200. // d - dir
  201. // - - file
  202. // l - symbolic link
  203. // b - block device
  204. // c - charactor device
  205. // p - pipe (FIFO)
  206. // s - socket
  207. LCData := UpperCase(AData);
  208. Result := IsValidUnixPerms(AData);
  209. if Result then begin
  210. //Do NOT attempt to do Novell Netware Print Services for Unix FTPD in NFS
  211. //namespace if we have a block device.
  212. if CharIsInSet(LCData, 1, 'CB') then begin
  213. Exit;
  214. end;
  215. //This extra complexity is required to distinguish Unix from
  216. //a Novell Netware server in NFS namespace which is somewhat similar
  217. //to a Unix listing. Beware.
  218. s := TStringList.Create;
  219. try
  220. SplitDelimitedString(LCData, s, True);
  221. if s.Count > 9 then begin
  222. Result := PosInStrArray(s[9], ['AM', 'PM']) = -1; {do not localize}
  223. if Result then begin
  224. // allow localized months longer than 3 characters
  225. Result := not ((IndyPos(':', s[8]) = 0) and (StrToMonth(s[6]) > 0)); {do not localize}
  226. end;
  227. end;
  228. finally
  229. FreeAndNil(s);
  230. end;
  231. end else begin
  232. //we make an additional check for two additional rows before the
  233. //the permissions. These are the inode and block count for the item.
  234. //These are specified with the -i and -s parameters.
  235. s := TStringList.Create;
  236. try
  237. SplitDelimitedString(LCData, s, True);
  238. if s.Count > 3 then begin
  239. if IsNumeric(s[0]) then begin
  240. Result := IsValidUnixPerms(S[1]);
  241. if not Result then begin
  242. Result := IsNumeric(s[1]) and IsValidUnixPerms(S[2]);
  243. end;
  244. end;
  245. end;
  246. finally
  247. FreeAndNil(s);
  248. end;
  249. end;
  250. end;
  251. class function TIdFTPLPUnix.IsUnitree(const AData: string): Boolean;
  252. var
  253. s : TStrings;
  254. begin
  255. s := TStringList.Create;
  256. try
  257. SplitDelimitedString(AData, s, True);
  258. Result := (s.Count > 4) and (PosInStrArray(s[4], UnitreeStoreTypes) <> -1);
  259. if not Result then begin
  260. Result := IsUnitreeBanner(AData);
  261. end;
  262. finally
  263. FreeAndNil(s);
  264. end;
  265. end;
  266. class function TIdFTPLPUnix.IsUnitreeBanner(const AData: String): Boolean;
  267. begin
  268. Result := TextStartsWith(AData, '/') and TextEndsWith(AData, ').') and (IndyPos('(', AData) > 0); {do not localize}
  269. end;
  270. class function TIdFTPLPUnix.MakeNewItem(AOwner: TIdFTPListItems): TIdFTPListItem;
  271. begin
  272. Result := TIdUnixFTPListItem.Create(AOwner);
  273. end;
  274. class function TIdFTPLPUnix.ParseLine(const AItem: TIdFTPListItem;
  275. const APath: String): Boolean;
  276. {Note that we also use this parser for Unitree FTP Servers because that server
  277. is like Unix except that in Unitree, there's two additional columns before the size.
  278. Those are:
  279. Storage Type - AR - archived or migrated to tape and DK
  280. File family -
  281. }
  282. type
  283. TParseUnixSteps = (pusINode, pusBlocks, pusPerm, pusCount, pusOwner, pusGroup,
  284. pusSize, pusMonth, pusDay, pusYear, pusTime, pusName, pusDone);
  285. var
  286. LStep: TParseUnixSteps;
  287. LData, LTmp: String;
  288. LInode, LBlocks, LDir, LGPerm, LOPerm, LUPerm, LCount, LOwner, LGroup: String;
  289. LName, LSize, LLinkTo: String;
  290. wYear, wMonth, wDay: Word;
  291. wCurrYear, wCurrMonth, wCurrDay: Word;
  292. // wYear, LCurrentMonth, wMonth, wDay: Word;
  293. wHour, wMin, wSec, wMSec: Word;
  294. ADate: TDateTime;
  295. i: Integer;
  296. LI : TIdUnixFTPListItem;
  297. wDayStr: string;
  298. function IsGOSwitches(const AString : String) : Boolean;
  299. var
  300. s : TStrings;
  301. begin
  302. //check to see if both the -g and -o switches were used. Both
  303. //owner and group are surpressed in that case. We have to check
  304. //that so our interpretation does not cause an error.
  305. Result := False;
  306. s := TStringList.Create;
  307. try
  308. SplitDelimitedString(AString, s, True);
  309. if s.Count > 2 then begin
  310. //if either inode or block count were given
  311. if IsNumeric(s[0]) then begin
  312. s.Delete(0);
  313. end;
  314. //if both inode and block count were given
  315. if IsNumeric(s[0]) then begin
  316. s.Delete(0);
  317. end;
  318. if s.Count > 5 then begin
  319. if StrToMonth(s[3]) > 0 then begin
  320. Result := IsNumeric(s[4]) and (IsNumeric(s[5]) or (IndyPos(':', s[5]) > 0)); {do not localize}
  321. end;
  322. end;
  323. end;
  324. finally
  325. FreeAndNil(s);
  326. end;
  327. end;
  328. function FixBonkedYear(const AStrPart : String) : String;
  329. var
  330. LB : String;
  331. begin
  332. LB := AStrPart;
  333. Result := Fetch(LB);
  334. //TODO: use StringsReplace() instead
  335. //Result := StringsReplace(Result, ['-', '/'], [' ', ' ']); {do not localize}
  336. Result := ReplaceAll(Result, '-', ' '); {do not localize}
  337. Result := ReplaceAll(Result, '/', ' '); {do not localize}
  338. Result := Result + ' ' + LB; {do not localize}
  339. end;
  340. begin
  341. LI := AItem as TIdUnixFTPListItem;
  342. // Get defaults for modified date/time
  343. ADate := Now;
  344. DecodeDate(ADate, wYear, wMonth, wDay);
  345. DecodeTime(ADate, wHour, wMin, wSec, wMSec);
  346. LData := AItem.Data;
  347. LStep := pusINode;
  348. repeat
  349. case LStep of
  350. pusINode: begin
  351. //we do it this way because the column for inode is right justified
  352. //and we don't want to create a problem if the -i parameter was never used
  353. LTmp := TrimLeft(LData);
  354. LTmp := Fetch(LTmp);
  355. if IsValidUnixPerms(LTmp) then begin
  356. LStep := pusPerm;
  357. end else begin
  358. //the inode column is right justified
  359. LData := TrimLeft(LData);
  360. LTmp := Fetch(LData);
  361. LData := TrimLeft(LData);
  362. LInode := LTmp;
  363. LStep := pusBlocks;
  364. end;
  365. end;
  366. pusBlocks: begin
  367. //Note that there is an ambigioutity because this value could
  368. //be the inode if only the -i switch was used.
  369. LTmp := Fetch(LData, ' ', False); {do not localize}
  370. if not IsValidUnixPerms(LTmp) then begin
  371. LTmp := Fetch(LData);
  372. LData := TrimLeft(LData);
  373. LBlocks := LTmp;
  374. end;
  375. LStep := pusPerm;
  376. end;
  377. pusPerm: begin //1.-rw-rw-rw-
  378. LTmp := Fetch(LData);
  379. LData := TrimLeft(LData);
  380. // Copy the predictable pieces
  381. LI.PermissionDisplay := Copy(LTmp, 1, 10);
  382. LDir := UpperCase(Copy(LTmp, 1, 1));
  383. LOPerm := Copy(LTmp, 2, 3);
  384. LGPerm := Copy(LTmp, 5, 3);
  385. LUPerm := Copy(LTmp, 8, 3);
  386. LStep := pusCount;
  387. end;
  388. pusCount: begin
  389. LData := TrimLeft(LData);
  390. LTmp := Fetch(LData);
  391. LData := TrimLeft(LData);
  392. //Patch for NetPresenz
  393. // "-------r-- 326 1391972 1392298 Nov 22 1995 MegaPhone.sit" */
  394. // "drwxrwxr-x folder 2 May 10 1996 network" */
  395. if TextIsSame(LTmp, 'folder') then begin {do not localize}
  396. LStep := pusSize;
  397. end else begin
  398. //APR
  399. //Patch for overflow -r--r--r-- 0526478 128 Dec 30 2002 DE292000
  400. if (Length(LTmp) > 3) and (LTmp[1] = '0') then begin
  401. LData := Copy(LTmp, 2, MaxInt) + ' ' + LData;
  402. LCount := '0';
  403. end else begin
  404. LCount := LTmp;
  405. end;
  406. //this check is necessary if both the owner and group were surpressed.
  407. if IsGOSwitches(AItem.Data) then begin
  408. LStep := pusSize;
  409. end else begin
  410. LStep := pusOwner;
  411. end;
  412. end;
  413. LData := TrimLeft(LData);
  414. end;
  415. pusOwner: begin
  416. LTmp := Fetch(LData);
  417. LData := TrimLeft(LData);
  418. LOwner := LTmp;
  419. LStep := pusGroup;
  420. end;
  421. pusGroup: begin
  422. LTmp := Fetch(LData);
  423. LData := TrimLeft(LData);
  424. LGroup := LTmp;
  425. LStep := pusSize;
  426. end;
  427. pusSize: begin
  428. //Ericsson - Switch FTP returns empty owner
  429. //Do not apply Ericson patch to Unitree
  430. if IsAlpha(LData, 1, 1) and (GetIdent <> UNITREE) then begin
  431. LSize := LGroup;
  432. LGroup := LOwner;
  433. LOwner := '';
  434. //we do this just after the erickson patch because
  435. //a few servers might return additional columns.
  436. //
  437. //e.g.
  438. //
  439. //drwx------ 1 BUILTIN NT AUTHORITY 0 Dec 7 2001 System Volume Information
  440. if not IsNumeric(LSize) then begin
  441. //undo the Ericson patch
  442. LOwner := LGroup;
  443. LGroup := '';
  444. repeat
  445. LGroup := LGroup + ' ' + LSize;
  446. LOwner := LGroup;
  447. LData := TrimLeft(LData);
  448. LSize := Fetch(LData);
  449. until IsNumeric(LSize);
  450. //delete the initial space we had added in the repeat loop
  451. IdDelete(LGroup, 1, 1);
  452. end;
  453. end else begin
  454. LTmp := Fetch(LData);
  455. //This is necessary for cases where are char device is listed
  456. //e.g.
  457. //crw-rw-rw- 1 0 1 11, 42 Aug 8 2000 tcp
  458. //
  459. //Note sure what 11, 42 is so size is not returned.
  460. if IndyPos(',', LTmp) > 0 then begin {do not localize}
  461. LData := TrimLeft(LData);
  462. Fetch(LData);
  463. LData := TrimLeft(LData);
  464. LSize := '';
  465. end else begin
  466. LSize := LTmp;
  467. end;
  468. LData := TrimLeft(LData);
  469. case PosInStrArray(LSize, UnitreeStoreTypes) of
  470. 0 : //AR - archived to tape - migrated
  471. begin
  472. if AItem is TIdUnitreeFTPListItem then begin
  473. (LI as TIdUnitreeFTPListItem).Migrated := True;
  474. (LI as TIdUnitreeFTPListItem).FileFamily := Fetch(LData);
  475. end;
  476. LData := TrimLeft(LData);
  477. LSize := Fetch(LData);
  478. LData := TrimLeft(LData);
  479. end;
  480. 1 : //DK - disk
  481. begin
  482. if AItem is TIdUnitreeFTPListItem then begin
  483. (LI as TIdUnitreeFTPListItem).FileFamily := Fetch(LData);
  484. end;
  485. LData := TrimLeft(LData);
  486. LSize := Fetch(LData);
  487. LData := TrimLeft(LData);
  488. end;
  489. end;
  490. end;
  491. LStep := pusMonth;
  492. end;
  493. pusMonth: begin // Scan modified MMM
  494. // Handle Chinese listings; the month, day, and year may not have spaces between them
  495. if IndyPos(ChineseYear, LData) > 0 then begin
  496. wYear := IndyStrToInt(Fetch(LData, ChineseYear));
  497. LData := TrimLeft(LData);
  498. // Set time info to 00:00:00.999
  499. wHour := 0;
  500. wMin := 0;
  501. wSec := 0;
  502. wMSec := 999;
  503. LStep := pusName
  504. end;
  505. if IndyPos(ChineseDay, LData) > 0 then begin
  506. wMonth := IndyStrToInt(Fetch(LData, ChineseMonth));
  507. LData := TrimLeft(LData);
  508. wDay := IndyStrToInt(Fetch(LData, ChineseDay));
  509. LData := TrimLeft(LData);
  510. if LStep <> pusName then begin
  511. LTmp := Fetch(LData);
  512. LStep := pusTime;
  513. end;
  514. Continue;
  515. end;
  516. //fix up a bonked date such as:
  517. //-rw-r--r-- 1 root other 531 09-26 13:45 README3
  518. LData := FixBonkedYear(LData);
  519. //we do this in case there's a space
  520. LTmp := Fetch(LData);
  521. if (Length(LTmp) > 3) and IsNumeric(LTmp) then begin
  522. //must be a year
  523. wYear := IndyStrToInt(LTmp, wYear);
  524. LTmp := Fetch(LData);
  525. end;
  526. LData := TrimLeft(LData);
  527. // HPUX can output the dates like "28. Jan., 16:48", "5. Mai, 05:34" or
  528. // "7. Nov. 2004"
  529. if TextEndsWith(LTmp, '.') then begin
  530. Delete(LTmp, Length(LTmp), 1);
  531. end;
  532. // Korean listings will have the Korean "month" character
  533. DeleteSuffix(LTmp,KoreanMonth);
  534. // Just in case
  535. DeleteSuffix(LTmp,KoreanEUCMonth);
  536. { if IndyPos(KoreanMonth, LTmp) = Length(LTmp) - Length(KoreanMonth) + 1 then
  537. begin
  538. Delete(LTmp, Length(LTmp) - Length(KoreanMonth) + 1, Length(KoreanMonth));
  539. end;
  540. // Japanese listings will have the Japanese "month" character
  541. } DeleteSuffix(LTmp,JapaneseMonth);
  542. if IsNumeric(LTmp) then begin
  543. wMonth := IndyStrToInt(LTmp, wMonth);
  544. // HPUX
  545. LTmp := Fetch(LData, ' ', False);
  546. if TextEndsWith(LTmp, ',') then begin
  547. Delete(LTmp, Length(LTmp), 1);
  548. end;
  549. if TextEndsWith(LTmp, '.') then begin
  550. Delete(LTmp, Length(LTmp), 1);
  551. end;
  552. // Handle dates where the day preceeds a string month (French, Dutch)
  553. i := StrToMonth(LTmp);
  554. if i > 0 then begin
  555. wDay := wMonth;
  556. LTmp := Fetch(LData);
  557. LData := TrimLeft(LData);
  558. wMonth := i;
  559. LStep := pusYear;
  560. end else begin
  561. if wMonth > 12 then begin
  562. wDay := wMonth;
  563. LTmp := Fetch(LData);
  564. LData := TrimLeft(LData);
  565. wMonth := IndyStrToInt(LTmp, wMonth);
  566. LStep := pusYear;
  567. end else begin
  568. LStep := pusDay;
  569. end;
  570. end;
  571. end else begin
  572. wMonth := StrToMonth(LTmp);
  573. LStep := pusDay;
  574. // Korean listings can have dates in the form "2004.10.25"
  575. if wMonth = 0 then begin
  576. wYear := IndyStrToInt(Fetch(LTmp, '.'), wYear);
  577. wMonth := IndyStrToInt(Fetch(LTmp, '.'), 0);
  578. wDay := IndyStrToInt(LTmp);
  579. LStep := pusName;
  580. end;
  581. end;
  582. end;
  583. pusDay: begin // Scan DD
  584. LTmp := Fetch(LData);
  585. LData := TrimLeft(LData);
  586. // Korean dates can have their "Day" character as included
  587. { if IndyPos(KoreanDay, LTmp) = Length(LTmp) - Length(KoreanDay) + 1 then
  588. begin
  589. Delete(LTmp, Length(LTmp) - Length(KoreanDay) + 1, Length(KoreanDay));
  590. end; }
  591. DeleteSuffix(LTmp,KoreanDay);
  592. //Ditto for Japanese
  593. DeleteSuffix(LTmp,JapaneseDay);
  594. wDay := IndyStrToInt(LTmp, wDay);
  595. LStep := pusYear;
  596. end;
  597. pusYear: begin
  598. LTmp := Fetch(LData);
  599. //Some localized Japanese listings include a year sybmol
  600. DeleteSUffix(LTmp,JapaneseYear);
  601. // Not time info, scan year
  602. if IndyPos(':', LTmp) = 0 then begin {Do not Localize}
  603. wYear := IndyStrToInt(LTmp, wYear);
  604. // Set time info to 00:00:00.999
  605. wHour := 0;
  606. wMin := 0;
  607. wSec := 0;
  608. wMSec := 999;
  609. LStep := pusName;
  610. end else begin
  611. // Time info, scan hour, min
  612. LStep := pusTime;
  613. end;
  614. end;
  615. pusTime: begin
  616. // correct year and Scan hour
  617. wYear := AddMissingYear(wDay, wMonth);
  618. wHour:= IndyStrToInt(Fetch(LTmp, ':'), 0); {Do not Localize}
  619. // Set sec and ms to 0.999 except for Serv-U or FreeBSD with the -T parameter
  620. //with the -T parameter, Serve-U returns something like this:
  621. //
  622. //drwxrwxrwx 1 user group 0 Mar 3 04:49:59 2003 upload
  623. //
  624. //instead of:
  625. //
  626. //drwxrwxrwx 1 user group 0 Mar 3 04:49 upload
  627. if (IndyPos(':', LTmp) > 0) and (IsNumeric(Fetch(LData, ' ', False))) then begin {Do not localize}
  628. // Scan minutes
  629. wMin := IndyStrToInt(Fetch(LTmp, ':'), 0); {Do not localize}
  630. wSec := IndyStrToInt(Fetch(LTmp, ':'), 0); {Do not localize}
  631. wMSec := IndyStrToInt(Fetch(LTmp,':'), 999); {Do not localize}
  632. LTmp := Fetch(LData);
  633. wYear := IndyStrToInt(LTmp, wYear);
  634. end else begin
  635. // Scan minutes
  636. wMin := IndyStrToInt(Fetch(LTmp, ':'), 0); {Do not localize}
  637. wSec := IndyStrToInt(Fetch(LTmp, ':'), 0); {Do not localize}
  638. wMSec := IndyStrToInt(Fetch(LTmp), 999);
  639. end;
  640. LStep := pusName;
  641. end;
  642. pusName: begin
  643. LName := LData;
  644. LStep := pusDone;
  645. end;
  646. end;//case LStep
  647. until LStep = pusDone;
  648. AItem.ItemType := ditFile;
  649. if LDir <> '' then begin
  650. case LDir[1] of
  651. 'D' : AItem.ItemType := ditDirectory; {Do not Localize}
  652. 'L' : AItem.ItemType := ditSymbolicLink; {Do not Localize}
  653. 'B' : AItem.ItemType := ditBlockDev; {Do not Localize}
  654. 'C' : AItem.ItemType := ditCharDev; {Do not Localize}
  655. 'P' : AItem.ItemType := ditFIFO; {Do not Localize}
  656. 'S' : AItem.ItemType := ditSocket; {Do not Localize}
  657. end;
  658. end;
  659. LI.UnixOwnerPermissions := LOPerm;
  660. LI.UnixGroupPermissions := LGPerm;
  661. LI.UnixOtherPermissions := LUPerm;
  662. LI.LinkCount := IndyStrToInt(LCount, 0);
  663. LI.OwnerName := LOwner;
  664. LI.GroupName := LGroup;
  665. LI.Size := IndyStrToInt64(LSize, 0);
  666. if (wMonth = 2) and (wDay = 29) and (not IsLeapYear(wYear)) then
  667. begin
  668. {temporary workaround for Leap Year, February 29th. Encode with day - 1, but do NOT decrement wDay since this will give us the wrong day when we adjust/re-calculate the date later}
  669. LI.ModifiedDate := EncodeDate(wYear, wMonth, wDay - 1) + EncodeTime(wHour, wMin, wSec, wMSec);
  670. end else begin
  671. LI.ModifiedDate := EncodeDate(wYear, wMonth, wDay) + EncodeTime(wHour, wMin, wSec, wMSec);
  672. end;
  673. {PATCH: If Indy incorrectly decremented the year then it will be almost a year behind.
  674. Certainly well past 90 days and so we will have the day and year in the raw data.
  675. (Files that are from within the last 90 days do not show the year as part of the date.)}
  676. wdayStr := IntToStr(wDay);
  677. while Length(wDayStr) < 2 do begin
  678. wDayStr := '0' + wDayStr; {do not localize}
  679. end;
  680. DecodeDate(Now, wCurrYear, wCurrMonth, wCurrDay);
  681. if (wYear < wCurrYear) and ((Now-LI.ModifiedDate) > 90) and
  682. (Pos(IntToStr(wMonth) + ' ' + IntToStr(wYear), LI.Data) = 0) and
  683. (Pos(IntToStr(wMonth) + ' ' + wDayStr + ' ' + IntToStr(wYear), LI.Data) = 0) and
  684. (Pos(monthNames[wMonth] + ' ' + IntToStr(wYear), LI.Data) = 0) and
  685. (Pos(monthNames[wMonth] + ' ' + wDayStr + ' ' + IntToStr(wYear), LI.Data) = 0) then
  686. begin
  687. {sanity check to be sure we aren't making future dates!!}
  688. {$IFDEF VCL_6_OR_ABOVE}
  689. if IncYear(LI.ModifiedDate) <= (Now + 7) then
  690. {$ELSE}
  691. if IncMonth(LI.ModifiedDate,12) <= (Now + 7) then
  692. {$ENDIF}
  693. begin
  694. Inc(wYear);
  695. if (wMonth = 2) and (wDay = 29) and (not IsLeapYear(wYear)) then
  696. begin
  697. {temporary workaround for Leap Year, February 29th. Encode with day - 1, but do NOT decrement wDay since this will give us the wrong day when we adjust/re-calculate the date later}
  698. LI.ModifiedDate := EncodeDate(wYear, wMonth, wDay - 1) + EncodeTime(wHour, wMin, wSec, wMSec);
  699. end else begin
  700. LI.ModifiedDate := EncodeDate(wYear, wMonth, wDay) + EncodeTime(wHour, wMin, wSec, wMSec);
  701. end;
  702. end;
  703. end;
  704. if LI.ItemType = ditSymbolicLink then begin
  705. i := IndyPos(UNIX_LINKTO_SYM, LName);
  706. LLinkTo := Copy(LName, i + 4, Length(LName) - i - 3);
  707. LName := Copy(LName, 1, i - 1);
  708. //with ls -F (DIR -F in FTP, you will sometimes symbolic links with the linked
  709. //to item file name ending with a /. That indicates that the item being pointed to
  710. //is a directory
  711. if TextEndsWith(LLinkTo, PATH_FILENAME_SEP_UNIX) then begin
  712. LI.ItemType := ditSymbolicLinkDir;
  713. LLinkTo := Copy(LLinkTo, 1, Length(LLinkTo)-1);
  714. end;
  715. LI.LinkedItemName := LLinkTo;
  716. end;
  717. LI.NumberBlocks := IndyStrToInt(LBlocks, 0);
  718. LI.Inode := IndyStrToInt(LInode, 0);
  719. //with servers using ls -F, / is returned after the name of dir names and a *
  720. //will be returned at the end of a file name for an executable program.
  721. //Based on info at http://www.skypoint.com/help/tipgettingaround.html
  722. //Note that many FTP servers obtain their DIR lists by piping output from the /bin/ls -l command.
  723. //The -F parameter does work with ftp.netscape.com and I have also tested a NcFTP server
  724. //which simulates the output of the ls command.
  725. if CharIsInSet(LName, Length(LName), PATH_FILENAME_SEP_UNIX + '*') then begin {Do not localize}
  726. LName := Copy(LName, 1, Length(LName)-1);
  727. end;
  728. if APath <> '' then begin
  729. // a path can sometimes come into the form of:
  730. // pub:
  731. // or
  732. // ./pub
  733. //
  734. //Deal with both cases
  735. LI.LocalFileName := LName;
  736. LName := APath + PATH_FILENAME_SEP_UNIX + LName;
  737. if TextStartsWith(LName, UNIX_CURDIR) then begin
  738. IdDelete(LName, 1, Length(UNIX_CURDIR));
  739. if TextStartsWith(LName, PATH_FILENAME_SEP_UNIX) then begin
  740. IdDelete(LName, 1, Length(PATH_FILENAME_SEP_UNIX));
  741. end;
  742. end;
  743. end;
  744. LI.FileName := LName;
  745. Result := True;
  746. end;
  747. class function TIdFTPLPUnix.ParseListing(AListing: TStrings;
  748. ADir: TIdFTPListItems): Boolean;
  749. var
  750. i : Integer;
  751. LPathSpec : String;
  752. LItem : TIdFTPListItem;
  753. begin
  754. for i := 0 to AListing.Count-1 do begin
  755. if not ((AListing[i] = '') or IsTotalLine(AListing[i]) or IsUnixLsErr(AListing[i]) or IsUnitreeBanner(AListing[i])) then begin
  756. //workaround for the XBox MediaCenter FTP Server
  757. //which returns something like this:
  758. //
  759. //dr-xr-xr-x 1 ftp ftp 1 Feb 23 00:00 D:
  760. //and the trailing : is falsely assuming that a ":" means
  761. //a subdirectory entry in a recursive list.
  762. if (not InternelChkUnix(AListing[i])) and IsSubDirContentsBanner(AListing[i]) then begin
  763. LPathSpec := Copy(AListing[i], 1, Length(AListing[i])-1);
  764. end else begin
  765. LItem := MakeNewItem(ADir);
  766. LItem.Data := AListing[i];
  767. Result := ParseLine(LItem, LPathSpec);
  768. if not Result then begin
  769. FreeAndNil(LItem);
  770. Exit;
  771. end;
  772. end;
  773. end;
  774. end;
  775. Result := True;
  776. end;
  777. { TIdFTPLPUnitree }
  778. class function TIdFTPLPUnitree.GetIdent: String;
  779. begin
  780. Result := UNITREE;
  781. end;
  782. class function TIdFTPLPUnitree.MakeNewItem(AOwner: TIdFTPListItems): TIdFTPListItem;
  783. begin
  784. Result := TIdUnitreeFTPListItem.Create(AOwner);
  785. end;
  786. initialization
  787. RegisterFTPListParser(TIdFTPLPUnix);
  788. RegisterFTPListParser(TIdFTPLPUnitree);
  789. finalization
  790. UnRegisterFTPListParser(TIdFTPLPUnix);
  791. UnRegisterFTPListParser(TIdFTPLPUnitree);
  792. end.