{ $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.8 2/24/2005 10:01:34 AM JPMugaas Fixed estimation of filesize for variable record length files (V) in z/VM to conform to what was specified in: z/VMTCP/IP User’s Guide Version 5 Release 1.0 This will not always give the same estimate as the server would when listing in Unix format "SITE LISTFORMAT UNIX" because we can't know the block size (we have to assume a size of 4096). Rev 1.7 10/26/2004 10:03:20 PM JPMugaas Updated refs. Rev 1.6 9/7/2004 10:02:30 AM JPMugaas Tightened the VM/BFS parser detector so that valid dates have to start the listing item. This should reduce the likelyhood of error. Rev 1.5 6/28/2004 4:34:18 AM JPMugaas VM_CMS-ftp.marist.edu-7.txt was being detected as VM/BFS instead of VM/CMS causing a date encode error. Rev 1.4 4/19/2004 5:05:32 PM JPMugaas Class rework Kudzu wanted. Rev 1.3 2004.02.03 5:45:22 PM czhower Name changes Rev 1.2 10/19/2003 3:48:12 PM DSiders Added localization comments. Rev 1.1 4/7/2003 04:04:30 PM JPMugaas User can now descover what output a parser may give. Rev 1.0 2/19/2003 04:18:20 AM JPMugaas More things restructured for the new list framework. } unit IdFTPListParseVM; { IBM VM and z/VM parser } interface {$i IdCompilerDefines.inc} uses Classes, IdFTPList, IdFTPListParseBase, IdFTPListTypes; type TIdVMCMSFTPListItem = class(TIdRecFTPListItem) protected FOwnerName : String; FNumberBlocks : Integer; public property RecLength : Integer read FRecLength write FRecLength; property RecFormat : String read FRecFormat write FRecFormat; property NumberRecs : Integer read FNumberRecs write FNumberRecs; property OwnerName : String read FOwnerName write FOwnerName; property NumberBlocks : Integer read FNumberBlocks write FNumberBlocks; end; TIdVMVirtualReaderFTPListItem = class(TIdFTPListItem) protected FNumberRecs : Integer; public constructor Create(AOwner: TCollection); override; property NumberRecs : Integer read FNumberRecs write FNumberRecs; end; TIdVMBFSFTPListItem = class(TIdFTPListItem); TIdFTPLPVMCMS = class(TIdFTPListBaseHeaderOpt) protected class function IsHeader(const AData : String): Boolean; override; class function MakeNewItem(AOwner : TIdFTPListItems) : TIdFTPListItem; override; class function ParseLine(const AItem : TIdFTPListItem; const APath : String = ''): Boolean; override; class function CheckListingAlt(AListing : TStrings; const ASysDescript : String = ''; const ADetails : Boolean = True): Boolean; override; public class function GetIdent : String; override; end; TIdFTPLPVMBFS = 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; end; TIdFTPLVirtualReader = 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; end; // 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 "IdFTPListParseVM"'} {$ENDIF} implementation uses IdException, IdGlobal, IdFTPCommon, IdGlobalProtocols, SysUtils; function IsFileMode(const AStr : String) : Boolean; begin Result := CharIsInSet(AStr,1,'ABCDEFGHIJKLMNOPQRSTUV') and CharIsInSet(AStr,2,'0123456'); end; { TIdFTPLPVMCMS } class function TIdFTPLPVMCMS.CheckListingAlt(AListing: TStrings; const ASysDescript: String; const ADetails: Boolean): Boolean; const VMTypes : array [1..3] of string = ('F','V','DIR'); {do not localize} var LData : String; begin Result := False; if AListing.Count > 0 then begin LData := AListing[0]; if IsFileMode(Trim(Copy(LData, 19, 3))) then begin Result := PosInStrArray(Trim(Copy(LData, 22, 3)), VMTypes) <> -1; if Result then begin Result := IsMMDDYY(Trim(Copy(LData,52,10)),'/'); end; end else begin Result := PosInStrArray(Trim(Copy(LData, 19, 3)), VMTypes) <> -1; if Result then begin Result := (Copy(LData, 56, 1) = '/') and (Copy(LData, 59, 1) = '/'); {do not localize} if not Result then begin Result := (Copy(LData, 58, 1) = '-') and (Copy(LData, 61, 1) = '-'); {do not localize} if not Result then begin Result := (Copy(LData, 48, 1) = '-') and (Copy(LData, 51, 1) = '-'); {do not localize} end; end; end; end; end; end; class function TIdFTPLPVMCMS.GetIdent: String; begin Result := 'VM/CMS'; {do not localize} end; class function TIdFTPLPVMCMS.IsHeader(const AData: String): Boolean; begin Result := Trim(AData) = 'Filename FileType Fm Format Lrecl Records Blocks Date Time' end; class function TIdFTPLPVMCMS.MakeNewItem(AOwner: TIdFTPListItems): TIdFTPListItem; begin Result := TIdVMCMSFTPListItem.Create(AOwner); end; class function TIdFTPLPVMCMS.ParseLine(const AItem: TIdFTPListItem; const APath: String): Boolean; var LBuffer : String; LCols : TStrings; LI : TIdVMCMSFTPListItem; LSize : Int64; LPRecLn,LLRecLn : Integer; LPRecNo, LLRecNo : Integer; LPBkNo,LLBkNo : Integer; LPCol : Integer; begin {Some of this is based on the following: http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=utf-8&threadm=4e7k0p%24t1v%40blackice.winternet.com&rnum=4&prev=/groups%3Fq%3DVM%2BFile%2BRecords%2Bdirectory%26hl%3Den%26lr%3D%26ie%3DUTF-8%26oe%3Dutf-8%26selm%3D4e7k0p%2524t1v%2540blackice.winternet.com%26rnum%3D4 and http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=utf-8&selm=DLspv2.G2w%40epsilon.com&rnum=2 } { 123456789012345678901234567890123456789012345678901234567890123456789012 1 2 3 4 5 6 7 OMA00215 PLAN V 64 28 1 6/26/02 9:33:21 - WEBSHARE DIR - - - 5/30/97 18:44:17 - or README ANONYMOU V 71 26 1 1997-04-02 12:33:20 TCP291 or maybe this: ENDTRACE TCPIP F 80 1 1 1999-07-28 12:24:01 TCM191 123456789012345678901234567890123456789012345678901234567890123456789012 1 2 3 4 5 6 7 or possibly this FILELIST format: Filename FileType Fm Format Lrecl Records Blocks Date Time LASTING GLOBALV A1 V 41 21 1 9/16/91 15:10:32 J43401 NETLOG A0 V 77 1 1 9/12/91 12:36:04 PROFILE EXEC A1 V 17 3 1 9/12/91 12:39:07 DIRUNIX SCRIPT A1 V 77 1216 17 1/04/93 20:30:47 MAIL PROFILE A2 F 80 1 1 10/14/92 16:12:27 BADY2K TEXT A0 V 1 1 1 1/03/102 10:11:12 AUTHORS A1 DIR - - - 9/20/99 10:31:11 --------------- 123456789012345678901234567890123456789012345678901234567890123456789012 1 2 3 4 5 6 7 } LI := AItem as TIdVMCMSFTPListItem; //File Name LI.FileName := Trim(Copy(AItem.Data, 1, 8)); //File Type - extension if LI.Data[9] = ' ' then begin LBuffer := Copy(AItem.Data, 10, 9); end else begin LBuffer := Copy(AItem.Data, 9, 9); end; LBuffer := Trim(LBuffer); if LBuffer <> '' then begin LI.FileName := LI.FileName + '.' + LBuffer; {do not localize} end; //Record format LBuffer := Trim(Copy(AItem.Data, 19, 3)); if IsFileMode(LBuffer) then begin LBuffer := Trim(Copy(AItem.Data, 23, 3)); LPRecLn := 30; LLRecLn := 7; LPRecNo := 37; LLRecNo := 7; LPBkNo := 44; LLBkNo := 8; LPCol := 52; end else begin LPRecLn := 22; LLRecLn := 9; LPRecNo := 31; LLRecNo := 11; LPBkNo := 42; LLBkNo := 11; if (Copy(AItem.Data, 48, 1) = '-') and (Copy(AItem.Data, 51, 1) = '-') then begin {do not localize} LPCol := 44; end else begin LPCol := 54; end; end; LI.RecFormat := LBuffer; if LI.RecFormat = 'DIR' then begin {do not localize} LI.ItemType := ditDirectory; LI.RecLength := 0; end else begin LI.ItemType := ditFile; //Record Length - for files LBuffer := Copy(AItem.Data, LPRecLn, LLRecLn); LI.RecLength := IndyStrToInt(LBuffer, 0); //Record numbers LBuffer := Trim(Copy(AItem.Data, LPRecNo, LLRecNo)); LBuffer := Fetch(LBuffer); LI.NumberRecs := IndyStrToInt(LBuffer, 0); //Number of Blocks { From: http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=utf-8&selm=DLspv2.G2w%40epsilon.com&rnum=2 Block sizes can be 800, 512, 1024, 2048, or 4096, per the whim of the user. IBM loves 4096, but it wastes space (just like on FAT partitions on DOS.) For F files (any type which begins with F), record count times logical record length. For V files, you need to read the file for an exact count, or the block size (times block count) for a good guess. In other words, you're up the creek because you don't KNOW the block size. Use record size times record length for a _maximum_ file size. Anyway, you can not know from the directory list. } LBuffer := Trim(Copy(AItem.Data, LPBkNo, LLBkNo)); LI.NumberBlocks := IndyStrToInt(LBuffer, 0); LI.Size := LI.RecLength * LI.NumberRecs; //File Size - note that this is just an estimiate {From: z/VMTCP/IP User’s Guide Version 5 Release 1.0 © Copyright International Business Machines Corporation 1987, 2004. All rights reserved. For fixed-record (F) format minidisk and SFS files, the size field indicates the actual size of a file. For variable-record (V) format minidisk and SFS files, the size field contains an estimated file size, this being the lesser value determined by: – the number of records in the file and its maximum record length – the size and number of blocks required to maintain the file. For virtual reader files, a size of 0 is always indicated.} if LI.RecFormat = 'V' then begin LSize := LI.NumberBlocks * 4096; if LI.Size > LSize then begin LI.Size := LSize; end; end; if LI.RecFormat = 'DIR' then begin LI.SizeAvail := False; end; end; LCols := TStringList.Create; try // we do things this way for the rest because vm.sc.edu has // a variation on VM/CMS that does directory dates differently //and some columns could be off. //Note that the start position in one server it's column 44 while in others, it's column 54 // handle both cases. LBuffer := Trim(Copy(AItem.Data, LPCol, MaxInt)); SplitDelimitedString(LBuffer, LCols, True); //LCols - 0 - Date //LCols - 1 - Time //LCols - 2 - Owner if present if LCols.Count > 0 then begin //date if IsNumeric(LCols[0], 3) then begin // vm.sc.edu date stamps yyyy-mm-dd LI.ModifiedDate := DateYYMMDD(LCols[0]); end else begin //Note that the date is displayed as 2 digits not 4 digits //mm/dd/yy LI.ModifiedDate := DateMMDDYY(LCols[0]); end; //time LI.ModifiedDate := LI.ModifiedDate + TimeHHMMSS(LCols[1]); //owner if (LCols.Count > 2) and (LCols[2] <> '-') then begin {do not localize} LI.OwnerName := LCols[2]; end; end; finally FreeAndNil(LCols); end; Result := True; end; { TIdFTPLPVMBFS } {List format like: === 05/20/2000 13:38:19 F 1 65758 ’bfsline.cpy’ 05/19/2000 11:02:15 F 1 65758 ’bfsline.txt’ 06/03/2000 12:27:48 F 1 15414 ’bfstest.cpy’ 05/20/2000 13:38:05 F 1 15414 ’bfstest.output’ 05/20/2000 13:38:42 F 1 772902 ’bfswork.output’ 03/31/2000 15:49:27 F 1 782444 ’bfswork.txt’ 05/20/2000 13:39:20 F 1 13930 ’lotsonl.putdata’ 05/19/2000 09:41:21 F 1 13930 ’lotsonl.txt’ 06/15/2000 09:29:25 F 1 278 ’mail.maw’ 05/20/2000 13:39:34 F 1 278 ’mail.putdata’ 05/20/2000 15:30:45 F 1 13930 ’nls.new’ 05/20/2000 14:02:24 F 1 13931 ’nls.txt’ 08/21/2000 10:03:17 F 1 328 ’rock.rules’ 05/20/2000 13:40:05 F 1 58 ’testfil2.putdata’ 04/26/2000 14:34:42 F 1 63 ’testfil2.txt’ 08/21/2000 05:28:40 D - - ’ALTERNATE’ 12/28/2000 17:36:19 D - - ’FIRST === } class function TIdFTPLPVMBFS.CheckListing(AListing: TStrings; const ASysDescript: String; const ADetails: Boolean): Boolean; var s : TStrings; begin Result := False; if AListing.Count > 0 then begin //should have a "'" as the terminator if AListing[0] <> '' then begin if not TextEndsWith(AListing[0], '''') then begin Exit; end; end; s := TStringList.Create; try SplitDelimitedString(AListing[0], s, True); if s.Count > 4 then begin if not IsMMDDYY(s[0], '/') then begin {do not localize} Exit; end; Result := CharIsInSet(s[2], 1, 'FD'); {do not localize} if Result then begin Result := IsNumeric(s[4]) or (s[4] <> '-'); {do not localize} end; end; finally FreeAndNil(s); end; end; end; class function TIdFTPLPVMBFS.GetIdent: String; begin Result := 'VM/BFS'; {do not localize} end; class function TIdFTPLPVMBFS.MakeNewItem(AOwner: TIdFTPListItems): TIdFTPListItem; begin Result := TIdVMBFSFTPListItem.Create(AOwner); end; class function TIdFTPLPVMBFS.ParseLine(const AItem: TIdFTPListItem; const APath: String): Boolean; var LBuffer : String; LCols : TStrings; begin // z/VM Byte File System //This is based on: // // z/VM: TCP/IP Level 430 User's Guide Version 4 Release 3.0 // // http://www.vm.ibm.com/pubs/pdf/hcsk7a10.pdf // LBuffer := AItem.Data; LCols := TStringList.Create; try SplitDelimitedString(Fetch(LBuffer, #39), LCols, True); {do not localize} //0 - date //1 - time //2 - (D) dir or file (F) //3 - not sure what this is //4 - file size AItem.FileName := LBuffer; if TextEndsWith(AItem.FileName, '''') then begin AItem.FileName := Copy(AItem.FileName, 1, Length(AItem.FileName)-1); end; //date if LCols.Count > 0 then begin AItem.ModifiedDate := DateMMDDYY(LCols[0]); end; if LCols.Count > 1 then begin AItem.ModifiedDate := AItem.ModifiedDate + TimeHHMMSS(LCols[1]); end; if LCols.Count > 2 then begin if LCols[2] = 'D' then begin AItem.ItemType := ditDirectory; end else begin AItem.ItemType := ditFile; end; end; //file size if LCols.Count > 4 then begin if IsNumeric(LCols[3]) then begin AItem.Size := IndyStrToInt64(LCols[4], 0); AItem.SizeAvail := True; end else begin AItem.SizeAvail := False; end; end; finally FreeAndNil(LCols); end; Result := True; end; { TIdFTPLVirtualReader } class function TIdFTPLVirtualReader.CheckListing(AListing: TStrings; const ASysDescript: String; const ADetails: Boolean): Boolean; var s : TStrings; begin Result := False; if AListing.Count > 0 then begin s := TStringList.Create; try SplitDelimitedString(AListing[0], s, True); if s.Count > 2 then begin if (Length(s[0]) = 4) and IsNumeric(s[0]) then begin Result := (Length(s[2]) = 8) and (IsNumeric(s[2])); end; end; finally FreeAndNil(s); end; end; end; class function TIdFTPLVirtualReader.GetIdent: String; begin Result := 'VM Virtual Reader'; {do not localize} end; class function TIdFTPLVirtualReader.MakeNewItem(AOwner: TIdFTPListItems): TIdFTPListItem; begin Result := TIdVMVirtualReaderFTPListItem.Create(AOwner); end; class function TIdFTPLVirtualReader.ParseLine(const AItem: TIdFTPListItem; const APath: String): Boolean; var LCols : TStrings; LI : TIdVMVirtualReaderFTPListItem; begin // z/VM Byte File System //This is based on: // // z/VM: TCP/IP Level 430 User's Guide Version 4 Release 3.0 // // http://www.vm.ibm.com/pubs/pdf/hcsk7a10.pdf // LI := AItem as TIdVMVirtualReaderFTPListItem; LCols := TStringList.Create; try // z/VM Virtual Reader (RDR) //Col 0 - spool ID //Col 1 - origin //Col 2 - records //Col 3 - date //Col 4 - time //Col 5 - filename //Col 6 - file type SplitDelimitedString(AItem.Data, LCols, True); if LCols.Count > 5 then begin LI.FileName := LCols[5]; end; if LCols.Count > 6 then begin LI.FileName := LI.FileName + '.' + LCols[6]; {do not localize} end; //record count if LCols.Count > 2 then begin LI.NumberRecs := IndyStrToInt(LCols[2], 0); end; //date if LCols.Count > 3 then begin LI.ModifiedDate := DateYYMMDD(LCols[3]); end; //Time if LCols.Count > 4 then begin LI.ModifiedDate := LI.ModifiedDate + TimeHHMMSS(LCols[1]); end; //Note that IBM does not even try to give an estimate //with reader file sizes when emulating Unix. We can't support file sizes //with this. finally FreeAndNil(LCols); end; Result := True; end; { TIdVMVirtualReaderFTPListItem } constructor TIdVMVirtualReaderFTPListItem.Create(AOwner: TCollection); begin inherited Create(AOwner); //There's no size for things in a virtual reader SizeAvail := False; end; initialization RegisterFTPListParser(TIdFTPLVirtualReader); RegisterFTPListParser(TIdFTPLPVMBFS); RegisterFTPListParser(TIdFTPLPVMCMS); finalization UnRegisterFTPListParser(TIdFTPLVirtualReader); UnRegisterFTPListParser(TIdFTPLPVMBFS); UnRegisterFTPListParser(TIdFTPLPVMCMS); end.