{ $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.11 2/16/2005 7:26:52 AM JPMugaas Should handle Microsoft IIS on Windows XP Professional if the FtpDirBrowseShowLongDate metadata is enabled. That causes digit years to be outputted instead of two digit years. Rev 1.10 10/26/2004 10:03:22 PM JPMugaas Updated refs. Rev 1.9 9/7/2004 10:01:12 AM JPMugaas FIxed problem parsing: drwx------ 1 user group 0 Sep 07 09:20 xxx It was mistakenly being detected as Windows NT because there was a - in the fifth and eigth position in the string. The fix is to detect to see if the other chactors in thbat column are numbers. I did the same thing to the another part of the detection so that something similar doesn't happen there with "-" in Unix listings causing false WindowsNT detection. Rev 1.8 6/5/2004 3:02:10 PM JPMugaas Indicates SizeAvail = False for a directory. That is the standard MS-DOS Format. Rev 1.7 4/20/2004 4:01:14 PM JPMugaas Fix for nasty typecasting error. The wrong create was being called. Rev 1.6 4/19/2004 5:05:16 PM JPMugaas Class rework Kudzu wanted. Rev 1.5 2004.02.03 5:45:16 PM czhower Name changes Rev 1.4 1/22/2004 4:56:12 PM SPerry fixed set problems Rev 1.3 1/22/2004 7:20:54 AM JPMugaas System.Delete changed to IdDelete so the code can work in NET. Rev 1.2 10/19/2003 3:48:16 PM DSiders Added localization comments. Rev 1.1 9/27/2003 10:45:50 PM JPMugaas Added support for an alternative date format. Rev 1.0 2/19/2003 02:01:54 AM JPMugaas Individual parsing objects for the new framework. } unit IdFTPListParseWindowsNT; interface {$i IdCompilerDefines.inc} uses Classes, IdFTPList, IdFTPListParseBase; { Note: This parser comes from the code in Indy 9.0's MS-DOS parser. It has been renamed Windows NT here because that is more accurate than the old name. This is really the standard Microsoft IIS FTP Service format. We have tested this parser with Windows NT 4.0, Windows 2000, and Windows XP. This parser also handles recursive dir lists. } type TIdWindowsNTFTPListItem = class(TIdFTPListItem); TIdFTPLPWindowsNT = class(TIdFTPListBase) protected class function MakeNewItem(AOwner : TIdFTPListItems) : TIdFTPListItem; override; 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; const WINNTID = 'Windows NT'; {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 "IdFTPListParseWindowsNT"'} {$ENDIF} { Thanks to Craig Peterson of Scooter Software for his verison of TIdFTPLPWindowsNT.CheckListing. } implementation uses IdException, IdGlobal, IdFTPCommon, IdGlobalProtocols, SysUtils; { TIdFTPLPWindowsNT } { IMPORTANT!!! This parser actually handles some variations in the IIS FTP Server. In addition, it also handles some similar formats such as one found in Windows CE and in Rhinosoft's -h:DOS DIR parameter. To do all of this, the detector routine must use string positions because there are some relatively similar but unrelated patterns. Since this is such a highly used parser, it's a good idea to have raw directory lines in comments with the line position 1 so that it could be cut and pasted into a test program. In addition, I use something like " 1 2 3 4 5 1234567890123456789012345678901234567890123456789012345678901234567890 " above a section so that we more easily see the column positions in the string. The header is not part of the actual listing. } class function TIdFTPLPWindowsNT.CheckListing(AListing: TStrings; const ASysDescript: String; const ADetails: Boolean): Boolean; var SDir, sSize : String; i : Integer; SData : String; begin //maybe, we are dealing with this pattern { 1 2 3 4 5 1234567890123456789012345678901234567890123456789012345678901234567890 2002-09-02 18:48 DOS dir 2 } // //or { 1 2 3 4 5 1234567890123456789012345678901234567890123456789012345678901234567890 2002-09-02 19:06 9,730 DOS file 2 } // // //Those were obtained from soem comments in some FileZilla source-code. //FtpListResult.cpp //Note that none of that GNU source-code was used. // //I personally came accross the following when on Microsoft IIS //FTP Service on WIndowsXP Pro when I enabled the "FtpDirBrowseShowLongDate" //metadata property. // { 1 2 3 4 5 1234567890123456789012345678901234567890123456789012345678901234567890 02-16-2005 04:16AM pub } //Also, this really should cover a dir form that might be used on some FTP servers. //Serv-U uses this if you specify -h:"DOS" when retreiving a DIR with LIST: // { 1 2 3 4 5 1234567890123456789012345678901234567890123456789012345678901234567890 09/09/2008 03:51 PM . 09/09/2008 03:51 PM .. 04/29/2008 01:39 PM 802 00index.txt 09/09/2008 03:51 PM allegrosurf 09/09/2008 03:51 PM FTP Voyager SDK 09/09/2008 03:51 PM ftptree 09/09/2008 03:51 PM ftpvoyager 09/22/2008 10:28 AM OpenSSL 09/09/2008 03:51 PM serv-u 09/09/2008 03:51 PM VISIT www.RhinoSoft.com 09/09/2008 03:51 PM WinKey 09/09/2008 03:51 PM zaep } {Some Windows CE servers might return something like this: { 1 2 3 4 5 1234567890123456789012345678901234567890123456789012345678901234567890 " 01-01-98 08:00AM Flash File Store 01-01-98 08:00AM SDMMC Disk 06-26-06 10:49AM install 06-21-06 01:59PM 1033 GACLOG.TXT 06-21-06 12:32PM 12 iqdbsett.iqd 03-21-03 04:02AM SmartSystems 03-21-03 04:00AM ftpdcmds 03-21-03 04:00AM ConnMgr 03-21-03 04:00AM CabFiles 03-20-03 07:59PM profiles 03-20-03 07:59PM Program Files 03-20-03 07:59PM My Documents 03-20-03 07:59PM Temp 03-20-03 07:59PM Windows " } {Treck Embedded FTP (treck.com) right-justifies : 1 2 3 4 5 1234567890123456789012345678901234567890123456789012345678901234567890 07-24-10 10:12PM FOUND.000 03-23-10 02:28PM 28674 4500PMOD.ZIP } Result := False; for i := 0 to AListing.Count - 1 do begin if (AListing[i] <> '') and (not IsSubDirContentsBanner(AListing[i])) then begin SData := UpperCase(AListing[i]); if IndyPos(' ', SData) in [22..26] then begin {do not localize} sDir := ''; {do not localize} end; sSize := Copy(SData, 20, 19); { Handle Windows CE listings with 2 less spaces for sizes 1 2 3 4 5 1234567890123456789012345678901234567890123456789012345678901234567890 04-15-09 07:44 4608 AxcessE.exe } if (Length(sSize) = 19) and IsNumeric(sSize[17]) and (sSize[18] = ' ') then begin SetLength(sSize, 17); end; sSize := ReplaceAll(TrimLeft(sSize), ',', ''); //VM/BFS does share the date/time format as MS-DOS for the first two columns // if ((CharIsInSet(SData, 3, ['/', '-'])) and (CharIsInSet(SData, 6, ['/', '-']))) then if IsMMDDYY(Copy(SData, 1, 8), '-') or IsMMDDYY(Copy(SData, 1, 8), '/') then begin if sDir = '' then begin {do not localize} Result := not IsVMBFS(SData); end else begin if (sDir = '') and (IndyStrToInt64(sSize, -1) <> -1) then begin //may be a file - see if we can get the size if sDir is empty Result := not IsVMBFS(SData); end; end; end else if IsYYYYMMDD(SData) then begin if sDir = '' then begin {do not localize} Result := not IsVMBFS(SData); end else if (sDir = '') and (IndyStrToInt64(sSize, -1) <> -1) then begin {Do not Localize} //may be a file - see if we can get the size if sDir is empty Result := not IsVMBFS(SData); end; end else if IsMMDDYY(SData, '-') then begin {do not localize} { It might be like this: 1 2 3 4 5 1234567890123456789012345678901234567890123456789012345678901234567890 02-16-2005 04:16AM pub 02-14-2005 07:22AM 9112103 ethereal-setup-0.10.9.exe } if sDir = '' then begin {do not localize} Result := not IsVMBFS(SData); end else if (sDir = '') and (IndyStrToInt64(sSize, -1) <> -1) then begin {Do not Localize} Result := not IsVMBFS(SData); end; end; end; end; end; class function TIdFTPLPWindowsNT.GetIdent: String; begin Result := WINNTID; end; class function TIdFTPLPWindowsNT.MakeNewItem(AOwner: TIdFTPListItems): TIdFTPListItem; begin Result := TIdWindowsNTFTPListItem.Create(AOwner); end; class function TIdFTPLPWindowsNT.ParseLine(const AItem: TIdFTPListItem; const APath: String): Boolean; var LModified: string; LTime: string; LName: string; LValue: string; LBuffer: string; LPosMarker : Integer; begin LPosMarker := 1; //Note that there is quite a bit of duplicate code in this if. //That is because there are two similar forms but the dates are in //different forms and have to be processed differently. if IsNumeric(AItem.Data, 4) and (not IsNumeric(AItem.Data, 1, 5)) then begin LModified := Copy(AItem.Data, 1, 4) + '/' + {do not localize} Copy(AItem.Data, 6, 2) + '/' + {do not localize} Copy(AItem.Data, 9, 2) + ' '; {do not localize} LBuffer := Trim(Copy(AItem.Data, 11, MaxInt)); // Scan time info LTime := Fetch(LBuffer); // Scan optional letter in a[m]/p[m] LModified := LModified + LTime; // Convert modified to date time try AItem.ModifiedDate := DateYYMMDD(Fetch(LModified)); AItem.ModifiedDate := AItem.ModifiedDate + TimeHHMMSS(LModified); except AItem.ModifiedDate := 0.0; end; end else begin LBuffer := AItem.Data; //get the date LModified := Fetch(LBuffer); LBuffer := TrimLeft(LBuffer); // Scan time info LTime := Fetch(LBuffer); // Scan optional letter in a[m]/p[m] LModified := LModified + ' ' + LTime; {do not localize} // Convert modified to date time try AItem.ModifiedDate := DateMMDDYY(Fetch(LModified)); AItem.ModifiedDate := AItem.ModifiedDate + TimeHHMMSS(LModified); except AItem.ModifiedDate := 0.0; end; end; repeat LBuffer := Trim(LBuffer); // Scan file size or dir marker LValue := Fetch(LBuffer); // Strip commas or StrToInt64Def will barf if IndyPos(',', LValue) <> 0 then begin {Do not Localize} LValue := ReplaceAll(LValue, ',', ''); {Do not Localize} end; // What did we get? if TextIsSame(LValue, '') then begin {Do not Localize} AItem.ItemType := ditDirectory; //must contain 17 spaces for WinCE pattern if TextStartsWith(LBuffer,' ') then begin LPosMarker := 18; //Treck FTP server doesn't do any padding; all others must contain 9 spaces end else if TextStartsWith(LBuffer,' ') then begin LPosMarker := 10; end; AItem.SizeAvail := False; Break; end else begin if not TextIsSame(LValue, 'AM') then begin if TextIsSame(LValue, 'PM') then begin AItem.ModifiedDate := AItem.ModifiedDate + EncodeTime(12,0,0,0); end else begin AItem.ItemType := ditFile; AItem.Size := IndyStrToInt64(LValue, 0); break; end; end; end; until False; // Rest of the buffer is item name AItem.LocalFileName := LName; LName := Copy(LBuffer, LPosMarker, MaxInt); if APath <> '' then begin //MS_DOS_CURDIR AItem.LocalFileName := LName; LName := APath + PATH_FILENAME_SEP_DOS + LName; if TextStartsWith(LName, MS_DOS_CURDIR) then begin IdDelete(LName, 1, Length(MS_DOS_CURDIR)); end; end; AItem.FileName := LName; Result := True; end; class function TIdFTPLPWindowsNT.ParseListing(AListing: TStrings; ADir: TIdFTPListItems): Boolean; var i : Integer; LPathSpec : String; LItem : TIdFTPListItem; begin for i := 0 to AListing.Count -1 do begin if AListing[i] <> '' then begin if 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; initialization RegisterFTPListParser(TIdFTPLPWindowsNT); finalization UnRegisterFTPListParser(TIdFTPLPWindowsNT); end.