{ $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.21 2/23/2005 6:34:28 PM JPMugaas New property for displaying permissions ina GUI column. Note that this should not be used like a CHMOD because permissions are different on different platforms - you have been warned. Rev 1.20 10/26/2004 9:56:00 PM JPMugaas Updated refs. Rev 1.19 8/5/2004 11:18:16 AM JPMugaas Should fix a parsing problem I introeduced that caused errors with Unitree servers. Rev 1.18 8/4/2004 12:40:12 PM JPMugaas Fix for problem with total line. Rev 1.17 7/15/2004 4:02:48 AM JPMugaas Fix for some FTP servers. In a Unix listing, a : at the end of a filename was wrongly being interpretted as a subdirectory entry in a recursive listing. Rev 1.16 6/14/2004 12:05:54 AM JPMugaas Added support for the following Item types that appear in some Unix listings (particularly a /dev or /tmp dir): FIFO, Socket, Character Device, Block Device. Rev 1.15 6/13/2004 10:44:06 PM JPMugaas Fixed a problem with some servers returning additional columns in the owner and group feilds. Note that they will not be parsed correctly in all cases. That's life. drwx------ 1 BUILTIN NT AUTHORITY 0 Dec 7 2001 System Volume Information Rev 1.14 4/20/2004 4:01:18 PM JPMugaas Fix for nasty typecasting error. The wrong create was being called. Rev 1.13 4/19/2004 5:05:20 PM JPMugaas Class rework Kudzu wanted. Rev 1.12 2004.02.03 5:45:18 PM czhower Name changes Rev 1.11 2004.01.23 9:53:32 PM czhower REmoved unneded check because of CharIsInSet functinoalty. Also was a short circuit which is not permitted. Rev 1.10 1/23/2004 12:49:52 PM SPerry fixed set problems Rev 1.9 1/22/2004 8:29:02 AM JPMugaas Removed Ansi*. Rev 1.8 1/22/2004 7:20:48 AM JPMugaas System.Delete changed to IdDelete so the code can work in NET. Rev 1.7 10/19/2003 3:48:10 PM DSiders Added localization comments. Rev 1.6 9/28/2003 03:02:30 AM JPMugaas Now can handle a few non-standard date types. Rev 1.5 9/3/2003 07:34:40 PM JPMugaas Parsing for /bin/ls with devices now should work again. Rev 1.4 4/7/2003 04:04:26 PM JPMugaas User can now descover what output a parser may give. Rev 1.3 4/3/2003 03:37:36 AM JPMugaas Fixed a bug in the Unix parser causing it not to work properly with Unix BSD servers using the -T switch. Note that when a -T switch s used on a FreeBSD server, the server outputs the millaseconds and an extra column giving the year instead of either the year or time (the regular /bin/ls standard behavior). Rev 1.2 3/3/2003 07:17:58 PM JPMugaas Now honors the FreeBSD -T flag and parses list output from a program using it. Minor changes to the File System component. Rev 1.1 2/19/2003 05:53:14 PM JPMugaas Minor restructures to remove duplicate code and save some work with some formats. The Unix parser had a bug that caused it to give a False positive for Xercom MicroRTOS. Rev 1.0 2/19/2003 02:02:02 AM JPMugaas Individual parsing objects for the new framework. } unit IdFTPListParseUnix; interface {$i IdCompilerDefines.inc} uses Classes, IdFTPList, IdFTPListParseBase, IdFTPListTypes; { Notes: - The Unitree and Unix parsers are closely tied together and share just about all of the same code. The reason is that Unitee is very similar to a Unix dir list except it has an extra column which the Unix line parser can handle in the Unitree type. - The Unix parser can parse MACOS - Peters server (no relationship to this author :-) ). - It is worth noting that the parser does handle /bin/ls -s and -i switches as well as -g and -o. This is important sometimes as the Unix format comes from FTP servers that simply piped output from the Unix /bin/ls command. - This parser also handles recursive lists which is good for mirroring software. } type { Note that for this, I am violating a convention. The violation is that I am putting parsers for two separate servers in the same unit. The reason is this, Unitree has two additional columns (a file family and a file migration status. The line parsing code is the same because I thought it was easier to do that way in this case. } TIdUnixFTPListItem = class(TIdUnixBaseFTPListItem) protected FNumberBlocks : Integer; FInode : Integer; public property NumberBlocks : Integer read FNumberBlocks write FNumberBlocks; property Inode : Integer read FInode write FInode; end; TIdUnitreeFTPListItem = class(TIdUnixFTPListItem) protected FMigrated : Boolean; FFileFamily : String; public property Migrated : Boolean read FMigrated write FMigrated; property FileFamily : String read FFileFamily write FFileFamily; end; TIdFTPLPUnix = class(TIdFTPListBase) protected class function MakeNewItem(AOwner : TIdFTPListItems) : TIdFTPListItem; override; class function InternelChkUnix(const AData : String) : Boolean; virtual; class function IsUnitree(const AData: string): Boolean; virtual; class function IsUnitreeBanner(const AData: String): Boolean; virtual; class function ParseLine(const AItem : TIdFTPListItem; const APath : String = ''): Boolean; override; public class function GetIdent : String; override; class function CheckListing(AListing : TStrings; const ASysDescript : String = ''; const ADetails : Boolean = True): Boolean; override; class function ParseListing(AListing : TStrings; ADir : TIdFTPListItems) : Boolean; override; end; TIdFTPLPUnitree = class(TIdFTPLPUnix) protected class function MakeNewItem(AOwner : TIdFTPListItems) : TIdFTPListItem; override; public class function GetIdent : String; override; end; const UNIX = 'Unix'; {do not localize} UNITREE = 'Unitree'; {do not localize} // RLebeau 2/14/09: this forces C++Builder to link to this unit so // RegisterFTPListParser can be called correctly at program startup... {$IFDEF HAS_DIRECTIVE_HPPEMIT_LINKUNIT} {$HPPEMIT LINKUNIT} {$ELSE} {$HPPEMIT '#pragma link "IdFTPListParseUnix"'} {$ENDIF} implementation uses IdException, IdGlobal, IdFTPCommon, IdGlobalProtocols, {$IFDEF VCL_6_OR_ABOVE}DateUtils,{$ENDIF} SysUtils; { TIdFTPLPUnix } class function TIdFTPLPUnix.CheckListing(AListing: TStrings; const ASysDescript: String; const ADetails: Boolean): Boolean; var i : Integer; begin // TODO: return True if ASysDescript starts with 'Unix'? Result := False; for i := 0 to AListing.Count - 1 do begin if AListing[i] <> '' then begin //workaround for the XBox MediaCenter FTP Server //which returns something like this: // //dr-xr-xr-x 1 ftp ftp 1 Feb 23 00:00 D: //and the trailing : is falsely assuming that a ":" means //a subdirectory entry in a recursive list. if InternelChkUnix(AListing[i]) then begin if GetIdent = UNITREE then begin Result := IsUnitree(AListing[i]); end else begin Result := not IsUnitree(AListing[i]); end; Break; end; if not (IsTotalLine(AListing[i]) or IsSubDirContentsBanner(AListing[i])) then begin Break; end; end; end; end; class function TIdFTPLPUnix.GetIdent: String; begin Result := UNIX; end; class function TIdFTPLPUnix.InternelChkUnix(const AData: String): Boolean; var s : TStrings; LCData : String; begin //pos 1 values // d - dir // - - file // l - symbolic link // b - block device // c - charactor device // p - pipe (FIFO) // s - socket LCData := UpperCase(AData); Result := IsValidUnixPerms(AData); if Result then begin //Do NOT attempt to do Novell Netware Print Services for Unix FTPD in NFS //namespace if we have a block device. if CharIsInSet(LCData, 1, 'CB') then begin Exit; end; //This extra complexity is required to distinguish Unix from //a Novell Netware server in NFS namespace which is somewhat similar //to a Unix listing. Beware. s := TStringList.Create; try SplitDelimitedString(LCData, s, True); if s.Count > 9 then begin Result := PosInStrArray(s[9], ['AM', 'PM']) = -1; {do not localize} if Result then begin // allow localized months longer than 3 characters Result := not ((IndyPos(':', s[8]) = 0) and (StrToMonth(s[6]) > 0)); {do not localize} end; end; finally FreeAndNil(s); end; end else begin //we make an additional check for two additional rows before the //the permissions. These are the inode and block count for the item. //These are specified with the -i and -s parameters. s := TStringList.Create; try SplitDelimitedString(LCData, s, True); if s.Count > 3 then begin if IsNumeric(s[0]) then begin Result := IsValidUnixPerms(S[1]); if not Result then begin Result := IsNumeric(s[1]) and IsValidUnixPerms(S[2]); end; end; end; finally FreeAndNil(s); end; end; end; class function TIdFTPLPUnix.IsUnitree(const AData: string): Boolean; var s : TStrings; begin s := TStringList.Create; try SplitDelimitedString(AData, s, True); Result := (s.Count > 4) and (PosInStrArray(s[4], UnitreeStoreTypes) <> -1); if not Result then begin Result := IsUnitreeBanner(AData); end; finally FreeAndNil(s); end; end; class function TIdFTPLPUnix.IsUnitreeBanner(const AData: String): Boolean; begin Result := TextStartsWith(AData, '/') and TextEndsWith(AData, ').') and (IndyPos('(', AData) > 0); {do not localize} end; class function TIdFTPLPUnix.MakeNewItem(AOwner: TIdFTPListItems): TIdFTPListItem; begin Result := TIdUnixFTPListItem.Create(AOwner); end; class function TIdFTPLPUnix.ParseLine(const AItem: TIdFTPListItem; const APath: String): Boolean; {Note that we also use this parser for Unitree FTP Servers because that server is like Unix except that in Unitree, there's two additional columns before the size. Those are: Storage Type - AR - archived or migrated to tape and DK File family - } type TParseUnixSteps = (pusINode, pusBlocks, pusPerm, pusCount, pusOwner, pusGroup, pusSize, pusMonth, pusDay, pusYear, pusTime, pusName, pusDone); var LStep: TParseUnixSteps; LData, LTmp: String; LInode, LBlocks, LDir, LGPerm, LOPerm, LUPerm, LCount, LOwner, LGroup: String; LName, LSize, LLinkTo: String; wYear, wMonth, wDay: Word; wCurrYear, wCurrMonth, wCurrDay: Word; // wYear, LCurrentMonth, wMonth, wDay: Word; wHour, wMin, wSec, wMSec: Word; ADate: TDateTime; i: Integer; LI : TIdUnixFTPListItem; wDayStr: string; function IsGOSwitches(const AString : String) : Boolean; var s : TStrings; begin //check to see if both the -g and -o switches were used. Both //owner and group are surpressed in that case. We have to check //that so our interpretation does not cause an error. Result := False; s := TStringList.Create; try SplitDelimitedString(AString, s, True); if s.Count > 2 then begin //if either inode or block count were given if IsNumeric(s[0]) then begin s.Delete(0); end; //if both inode and block count were given if IsNumeric(s[0]) then begin s.Delete(0); end; if s.Count > 5 then begin if StrToMonth(s[3]) > 0 then begin Result := IsNumeric(s[4]) and (IsNumeric(s[5]) or (IndyPos(':', s[5]) > 0)); {do not localize} end; end; end; finally FreeAndNil(s); end; end; function FixBonkedYear(const AStrPart : String) : String; var LB : String; begin LB := AStrPart; Result := Fetch(LB); //TODO: use StringsReplace() instead //Result := StringsReplace(Result, ['-', '/'], [' ', ' ']); {do not localize} Result := ReplaceAll(Result, '-', ' '); {do not localize} Result := ReplaceAll(Result, '/', ' '); {do not localize} Result := Result + ' ' + LB; {do not localize} end; begin LI := AItem as TIdUnixFTPListItem; // Get defaults for modified date/time ADate := Now; DecodeDate(ADate, wYear, wMonth, wDay); DecodeTime(ADate, wHour, wMin, wSec, wMSec); LData := AItem.Data; LStep := pusINode; repeat case LStep of pusINode: begin //we do it this way because the column for inode is right justified //and we don't want to create a problem if the -i parameter was never used LTmp := TrimLeft(LData); LTmp := Fetch(LTmp); if IsValidUnixPerms(LTmp) then begin LStep := pusPerm; end else begin //the inode column is right justified LData := TrimLeft(LData); LTmp := Fetch(LData); LData := TrimLeft(LData); LInode := LTmp; LStep := pusBlocks; end; end; pusBlocks: begin //Note that there is an ambigioutity because this value could //be the inode if only the -i switch was used. LTmp := Fetch(LData, ' ', False); {do not localize} if not IsValidUnixPerms(LTmp) then begin LTmp := Fetch(LData); LData := TrimLeft(LData); LBlocks := LTmp; end; LStep := pusPerm; end; pusPerm: begin //1.-rw-rw-rw- LTmp := Fetch(LData); LData := TrimLeft(LData); // Copy the predictable pieces LI.PermissionDisplay := Copy(LTmp, 1, 10); LDir := UpperCase(Copy(LTmp, 1, 1)); LOPerm := Copy(LTmp, 2, 3); LGPerm := Copy(LTmp, 5, 3); LUPerm := Copy(LTmp, 8, 3); LStep := pusCount; end; pusCount: begin LData := TrimLeft(LData); LTmp := Fetch(LData); LData := TrimLeft(LData); //Patch for NetPresenz // "-------r-- 326 1391972 1392298 Nov 22 1995 MegaPhone.sit" */ // "drwxrwxr-x folder 2 May 10 1996 network" */ if TextIsSame(LTmp, 'folder') then begin {do not localize} LStep := pusSize; end else begin //APR //Patch for overflow -r--r--r-- 0526478 128 Dec 30 2002 DE292000 if (Length(LTmp) > 3) and (LTmp[1] = '0') then begin LData := Copy(LTmp, 2, MaxInt) + ' ' + LData; LCount := '0'; end else begin LCount := LTmp; end; //this check is necessary if both the owner and group were surpressed. if IsGOSwitches(AItem.Data) then begin LStep := pusSize; end else begin LStep := pusOwner; end; end; LData := TrimLeft(LData); end; pusOwner: begin LTmp := Fetch(LData); LData := TrimLeft(LData); LOwner := LTmp; LStep := pusGroup; end; pusGroup: begin LTmp := Fetch(LData); LData := TrimLeft(LData); LGroup := LTmp; LStep := pusSize; end; pusSize: begin //Ericsson - Switch FTP returns empty owner //Do not apply Ericson patch to Unitree if IsAlpha(LData, 1, 1) and (GetIdent <> UNITREE) then begin LSize := LGroup; LGroup := LOwner; LOwner := ''; //we do this just after the erickson patch because //a few servers might return additional columns. // //e.g. // //drwx------ 1 BUILTIN NT AUTHORITY 0 Dec 7 2001 System Volume Information if not IsNumeric(LSize) then begin //undo the Ericson patch LOwner := LGroup; LGroup := ''; repeat LGroup := LGroup + ' ' + LSize; LOwner := LGroup; LData := TrimLeft(LData); LSize := Fetch(LData); until IsNumeric(LSize); //delete the initial space we had added in the repeat loop IdDelete(LGroup, 1, 1); end; end else begin LTmp := Fetch(LData); //This is necessary for cases where are char device is listed //e.g. //crw-rw-rw- 1 0 1 11, 42 Aug 8 2000 tcp // //Note sure what 11, 42 is so size is not returned. if IndyPos(',', LTmp) > 0 then begin {do not localize} LData := TrimLeft(LData); Fetch(LData); LData := TrimLeft(LData); LSize := ''; end else begin LSize := LTmp; end; LData := TrimLeft(LData); case PosInStrArray(LSize, UnitreeStoreTypes) of 0 : //AR - archived to tape - migrated begin if AItem is TIdUnitreeFTPListItem then begin (LI as TIdUnitreeFTPListItem).Migrated := True; (LI as TIdUnitreeFTPListItem).FileFamily := Fetch(LData); end; LData := TrimLeft(LData); LSize := Fetch(LData); LData := TrimLeft(LData); end; 1 : //DK - disk begin if AItem is TIdUnitreeFTPListItem then begin (LI as TIdUnitreeFTPListItem).FileFamily := Fetch(LData); end; LData := TrimLeft(LData); LSize := Fetch(LData); LData := TrimLeft(LData); end; end; end; LStep := pusMonth; end; pusMonth: begin // Scan modified MMM // Handle Chinese listings; the month, day, and year may not have spaces between them if IndyPos(ChineseYear, LData) > 0 then begin wYear := IndyStrToInt(Fetch(LData, ChineseYear)); LData := TrimLeft(LData); // Set time info to 00:00:00.999 wHour := 0; wMin := 0; wSec := 0; wMSec := 999; LStep := pusName end; if IndyPos(ChineseDay, LData) > 0 then begin wMonth := IndyStrToInt(Fetch(LData, ChineseMonth)); LData := TrimLeft(LData); wDay := IndyStrToInt(Fetch(LData, ChineseDay)); LData := TrimLeft(LData); if LStep <> pusName then begin LTmp := Fetch(LData); LStep := pusTime; end; Continue; end; //fix up a bonked date such as: //-rw-r--r-- 1 root other 531 09-26 13:45 README3 LData := FixBonkedYear(LData); //we do this in case there's a space LTmp := Fetch(LData); if (Length(LTmp) > 3) and IsNumeric(LTmp) then begin //must be a year wYear := IndyStrToInt(LTmp, wYear); LTmp := Fetch(LData); end; LData := TrimLeft(LData); // HPUX can output the dates like "28. Jan., 16:48", "5. Mai, 05:34" or // "7. Nov. 2004" if TextEndsWith(LTmp, '.') then begin Delete(LTmp, Length(LTmp), 1); end; // Korean listings will have the Korean "month" character DeleteSuffix(LTmp,KoreanMonth); // Just in case DeleteSuffix(LTmp,KoreanEUCMonth); { if IndyPos(KoreanMonth, LTmp) = Length(LTmp) - Length(KoreanMonth) + 1 then begin Delete(LTmp, Length(LTmp) - Length(KoreanMonth) + 1, Length(KoreanMonth)); end; // Japanese listings will have the Japanese "month" character } DeleteSuffix(LTmp,JapaneseMonth); if IsNumeric(LTmp) then begin wMonth := IndyStrToInt(LTmp, wMonth); // HPUX LTmp := Fetch(LData, ' ', False); if TextEndsWith(LTmp, ',') then begin Delete(LTmp, Length(LTmp), 1); end; if TextEndsWith(LTmp, '.') then begin Delete(LTmp, Length(LTmp), 1); end; // Handle dates where the day preceeds a string month (French, Dutch) i := StrToMonth(LTmp); if i > 0 then begin wDay := wMonth; LTmp := Fetch(LData); LData := TrimLeft(LData); wMonth := i; LStep := pusYear; end else begin if wMonth > 12 then begin wDay := wMonth; LTmp := Fetch(LData); LData := TrimLeft(LData); wMonth := IndyStrToInt(LTmp, wMonth); LStep := pusYear; end else begin LStep := pusDay; end; end; end else begin wMonth := StrToMonth(LTmp); LStep := pusDay; // Korean listings can have dates in the form "2004.10.25" if wMonth = 0 then begin wYear := IndyStrToInt(Fetch(LTmp, '.'), wYear); wMonth := IndyStrToInt(Fetch(LTmp, '.'), 0); wDay := IndyStrToInt(LTmp); LStep := pusName; end; end; end; pusDay: begin // Scan DD LTmp := Fetch(LData); LData := TrimLeft(LData); // Korean dates can have their "Day" character as included { if IndyPos(KoreanDay, LTmp) = Length(LTmp) - Length(KoreanDay) + 1 then begin Delete(LTmp, Length(LTmp) - Length(KoreanDay) + 1, Length(KoreanDay)); end; } DeleteSuffix(LTmp,KoreanDay); //Ditto for Japanese DeleteSuffix(LTmp,JapaneseDay); wDay := IndyStrToInt(LTmp, wDay); LStep := pusYear; end; pusYear: begin LTmp := Fetch(LData); //Some localized Japanese listings include a year sybmol DeleteSUffix(LTmp,JapaneseYear); // Not time info, scan year if IndyPos(':', LTmp) = 0 then begin {Do not Localize} wYear := IndyStrToInt(LTmp, wYear); // Set time info to 00:00:00.999 wHour := 0; wMin := 0; wSec := 0; wMSec := 999; LStep := pusName; end else begin // Time info, scan hour, min LStep := pusTime; end; end; pusTime: begin // correct year and Scan hour wYear := AddMissingYear(wDay, wMonth); wHour:= IndyStrToInt(Fetch(LTmp, ':'), 0); {Do not Localize} // Set sec and ms to 0.999 except for Serv-U or FreeBSD with the -T parameter //with the -T parameter, Serve-U returns something like this: // //drwxrwxrwx 1 user group 0 Mar 3 04:49:59 2003 upload // //instead of: // //drwxrwxrwx 1 user group 0 Mar 3 04:49 upload if (IndyPos(':', LTmp) > 0) and (IsNumeric(Fetch(LData, ' ', False))) then begin {Do not localize} // Scan minutes wMin := IndyStrToInt(Fetch(LTmp, ':'), 0); {Do not localize} wSec := IndyStrToInt(Fetch(LTmp, ':'), 0); {Do not localize} wMSec := IndyStrToInt(Fetch(LTmp,':'), 999); {Do not localize} LTmp := Fetch(LData); wYear := IndyStrToInt(LTmp, wYear); end else begin // Scan minutes wMin := IndyStrToInt(Fetch(LTmp, ':'), 0); {Do not localize} wSec := IndyStrToInt(Fetch(LTmp, ':'), 0); {Do not localize} wMSec := IndyStrToInt(Fetch(LTmp), 999); end; LStep := pusName; end; pusName: begin LName := LData; LStep := pusDone; end; end;//case LStep until LStep = pusDone; AItem.ItemType := ditFile; if LDir <> '' then begin case LDir[1] of 'D' : AItem.ItemType := ditDirectory; {Do not Localize} 'L' : AItem.ItemType := ditSymbolicLink; {Do not Localize} 'B' : AItem.ItemType := ditBlockDev; {Do not Localize} 'C' : AItem.ItemType := ditCharDev; {Do not Localize} 'P' : AItem.ItemType := ditFIFO; {Do not Localize} 'S' : AItem.ItemType := ditSocket; {Do not Localize} end; end; LI.UnixOwnerPermissions := LOPerm; LI.UnixGroupPermissions := LGPerm; LI.UnixOtherPermissions := LUPerm; LI.LinkCount := IndyStrToInt(LCount, 0); LI.OwnerName := LOwner; LI.GroupName := LGroup; LI.Size := IndyStrToInt64(LSize, 0); if (wMonth = 2) and (wDay = 29) and (not IsLeapYear(wYear)) then begin {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} LI.ModifiedDate := EncodeDate(wYear, wMonth, wDay - 1) + EncodeTime(wHour, wMin, wSec, wMSec); end else begin LI.ModifiedDate := EncodeDate(wYear, wMonth, wDay) + EncodeTime(wHour, wMin, wSec, wMSec); end; {PATCH: If Indy incorrectly decremented the year then it will be almost a year behind. Certainly well past 90 days and so we will have the day and year in the raw data. (Files that are from within the last 90 days do not show the year as part of the date.)} wdayStr := IntToStr(wDay); while Length(wDayStr) < 2 do begin wDayStr := '0' + wDayStr; {do not localize} end; DecodeDate(Now, wCurrYear, wCurrMonth, wCurrDay); if (wYear < wCurrYear) and ((Now-LI.ModifiedDate) > 90) and (Pos(IntToStr(wMonth) + ' ' + IntToStr(wYear), LI.Data) = 0) and (Pos(IntToStr(wMonth) + ' ' + wDayStr + ' ' + IntToStr(wYear), LI.Data) = 0) and (Pos(monthNames[wMonth] + ' ' + IntToStr(wYear), LI.Data) = 0) and (Pos(monthNames[wMonth] + ' ' + wDayStr + ' ' + IntToStr(wYear), LI.Data) = 0) then begin {sanity check to be sure we aren't making future dates!!} {$IFDEF VCL_6_OR_ABOVE} if IncYear(LI.ModifiedDate) <= (Now + 7) then {$ELSE} if IncMonth(LI.ModifiedDate,12) <= (Now + 7) then {$ENDIF} begin Inc(wYear); LI.ModifiedDate := EncodeDate(wYear, wMonth, wDay) + EncodeTime(wHour, wMin, wSec, wMSec); end; end; if LI.ItemType = ditSymbolicLink then begin i := IndyPos(UNIX_LINKTO_SYM, LName); LLinkTo := Copy(LName, i + 4, Length(LName) - i - 3); LName := Copy(LName, 1, i - 1); //with ls -F (DIR -F in FTP, you will sometimes symbolic links with the linked //to item file name ending with a /. That indicates that the item being pointed to //is a directory if TextEndsWith(LLinkTo, PATH_FILENAME_SEP_UNIX) then begin LI.ItemType := ditSymbolicLinkDir; LLinkTo := Copy(LLinkTo, 1, Length(LLinkTo)-1); end; LI.LinkedItemName := LLinkTo; end; LI.NumberBlocks := IndyStrToInt(LBlocks, 0); LI.Inode := IndyStrToInt(LInode, 0); //with servers using ls -F, / is returned after the name of dir names and a * //will be returned at the end of a file name for an executable program. //Based on info at http://www.skypoint.com/help/tipgettingaround.html //Note that many FTP servers obtain their DIR lists by piping output from the /bin/ls -l command. //The -F parameter does work with ftp.netscape.com and I have also tested a NcFTP server //which simulates the output of the ls command. if CharIsInSet(LName, Length(LName), PATH_FILENAME_SEP_UNIX + '*') then begin {Do not localize} LName := Copy(LName, 1, Length(LName)-1); end; if APath <> '' then begin // a path can sometimes come into the form of: // pub: // or // ./pub // //Deal with both cases LI.LocalFileName := LName; LName := APath + PATH_FILENAME_SEP_UNIX + LName; if TextStartsWith(LName, UNIX_CURDIR) then begin IdDelete(LName, 1, Length(UNIX_CURDIR)); if TextStartsWith(LName, PATH_FILENAME_SEP_UNIX) then begin IdDelete(LName, 1, Length(PATH_FILENAME_SEP_UNIX)); end; end; end; LI.FileName := LName; Result := True; end; class function TIdFTPLPUnix.ParseListing(AListing: TStrings; ADir: TIdFTPListItems): Boolean; var i : Integer; LPathSpec : String; LItem : TIdFTPListItem; begin for i := 0 to AListing.Count-1 do begin if not ((AListing[i] = '') or IsTotalLine(AListing[i]) or IsUnixLsErr(AListing[i]) or IsUnitreeBanner(AListing[i])) then begin //workaround for the XBox MediaCenter FTP Server //which returns something like this: // //dr-xr-xr-x 1 ftp ftp 1 Feb 23 00:00 D: //and the trailing : is falsely assuming that a ":" means //a subdirectory entry in a recursive list. if (not InternelChkUnix(AListing[i])) and IsSubDirContentsBanner(AListing[i]) then begin LPathSpec := Copy(AListing[i], 1, Length(AListing[i])-1); end else begin LItem := MakeNewItem(ADir); LItem.Data := AListing[i]; Result := ParseLine(LItem, LPathSpec); if not Result then begin FreeAndNil(LItem); Exit; end; end; end; end; Result := True; end; { TIdFTPLPUnitree } class function TIdFTPLPUnitree.GetIdent: String; begin Result := UNITREE; end; class function TIdFTPLPUnitree.MakeNewItem(AOwner: TIdFTPListItems): TIdFTPListItem; begin Result := TIdUnitreeFTPListItem.Create(AOwner); end; initialization RegisterFTPListParser(TIdFTPLPUnix); RegisterFTPListParser(TIdFTPLPUnitree); finalization UnRegisterFTPListParser(TIdFTPLPUnix); UnRegisterFTPListParser(TIdFTPLPUnitree); end.