{ $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.12 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.11 10/26/2004 10:03:22 PM JPMugaas Updated refs. Rev 1.10 7/31/2004 1:08:24 PM JPMugaas Now should handle listings without time. Rev 1.9 6/21/2004 10:57:42 AM JPMugaas Now indicates that ModifiedDate and File Size are not available if VMS returns an error in the entry. Rev 1.8 6/11/2004 9:35:08 AM DSiders Added "Do not Localize" comments. Rev 1.7 6/7/2004 3:47:48 PM JPMugaas VMS Recursive Dir listings now supported. This is done with a [...]. Note that VMS does have some strange syntaxes with their file system. Rev 1.6 4/20/2004 4:01:16 PM JPMugaas Fix for nasty typecasting error. The wrong create was being called. Rev 1.5 4/19/2004 5:05:18 PM JPMugaas Class rework Kudzu wanted. Rev 1.4 2004.02.03 5:45:16 PM czhower Name changes Rev 1.3 10/19/2003 3:48:12 PM DSiders Added localization comments. Rev 1.2 10/1/2003 12:53:08 AM JPMugaas Indicated that VMS returns block sizes. Note that in VMS, the traditional block size is 512 bytes (this is a fixed constant). Rev 1.1 4/7/2003 04:04:36 PM JPMugaas User can now descover what output a parser may give. Rev 1.0 2/19/2003 02:01:58 AM JPMugaas Individual parsing objects for the new framework. } unit IdFTPListParseVMS; { This parser works with VMS (OpenVMS) systems including UCX, MadGoat, Multinet, VMS TCPWare, plus some non-multinet systems. } interface {$i IdCompilerDefines.inc} uses Classes, IdFTPList, IdFTPListParseBase, IdFTPListTypes; type TIdVMSFTPListItem = class(TIdOwnerFTPListItem) protected FGroupName : String; FVMSOwnerPermissions: String; FVMSWorldPermissions: String; FVMSSystemPermissions: String; FVMSGroupPermissions: String; FNumberBlocks : Integer; FBlockSize : Integer; FVersion : Integer; public property GroupName : String read FGroupName write FGroupName; //VMS File Protections //These are different than Unix. See: //See http://www.djesys.com/vms/freevms/mentor/vms_prot.html#prvs property VMSSystemPermissions : String read FVMSSystemPermissions write FVMSSystemPermissions; property VMSOwnerPermissions : String read FVMSOwnerPermissions write FVMSOwnerPermissions; property VMSGroupPermissions : String read FVMSGroupPermissions write FVMSGroupPermissions; property VMSWorldPermissions : String read FVMSWorldPermissions write FVMSWorldPermissions; property Version : Integer read FVersion write FVersion; property NumberBlocks : Integer read FNumberBlocks write FNumberBlocks; property BlockSize : Integer read FBlockSize write FBlockSize; end; TIdFTPLPVMS = class(TIdFTPListBase) protected class function MakeNewItem(AOwner : TIdFTPListItems) : TIdFTPListItem; override; class function IsVMSHeader(const AData: String): Boolean; class function IsVMSFooter(const AData: String): Boolean; class function IsContinuedLine(const AData: String): Boolean; 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; // 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 "IdFTPListParseVMS"'} {$ENDIF} implementation uses IdGlobal, IdFTPCommon, IdGlobalProtocols, IdStrings, SysUtils; { TIdFTPLPVMS } class function TIdFTPLPVMS.CheckListing(AListing: TStrings; const ASysDescript: String; const ADetails: Boolean): Boolean; var LData : String; i : Integer; begin Result := False; for i := 0 to AListing.Count - 1 do begin if AListing[i] <> '' then begin LData := AListing[i]; Result := Length(LData) > 1; if Result then begin Result := IsVMSHeader(LData); //see if file listing starts a file name if not Result then begin LData := Fetch(LData); Fetch(LData, ';'); {do not localize} Result := IsNumeric(LData); end; end; Break; end; end; end; class function TIdFTPLPVMS.GetIdent: String; begin Result := 'VMS'; {do not localize} end; class function TIdFTPLPVMS.IsContinuedLine(const AData: String): Boolean; begin Result := TextStartsWith(AData, ' ') and (IndyPos(';', AData) = 0); {do not localize} end; class function TIdFTPLPVMS.IsVMSFooter(const AData: String): Boolean; var LData : String; begin //The bottum banner may be in the following forms: //Total of 1 file, 0 blocks. //Total of 6 Files, 1582 Blocks. //Total of 90 files. //Grand total of 87 directories, 2593 files, 2220036 blocks. //*.*; <%RMS-E-FNF, file not found> //VMS returns TOTAL at the end. We test for " files" at the end of the line //so we don't break something with another parser. LData := UpperCase(AData); Result := TextStartsWith(LData, 'TOTAL OF ') or {do not localize} TextStartsWith(LData, 'GRAND TOTAL OF '); {do not localize} if Result then begin Result := (IndyPos(' FILE', LData) > 9); {do not localize} if not Result then begin Result := Fetch(LData) = '*.*;'; {do not localize} end; end; end; class function TIdFTPLPVMS.IsVMSHeader(const AData: String): Boolean; begin Result := TextEndsWith(AData, ']') and {Do not localize} (IndyPos(':[', AData) > 0); {Do not localize} end; class function TIdFTPLPVMS.MakeNewItem(AOwner: TIdFTPListItems): TIdFTPListItem; begin Result := TIdVMSFTPListItem.Create(AOwner); end; class function TIdFTPLPVMS.ParseLine(const AItem: TIdFTPListItem; const APath: String): Boolean; var LBuffer, LBuf2, LLine : String; LDay, LMonth, LYear : Integer; //LHour, LMinute, LSec : Integer; LCols : TStrings; LOwnerIdx : Integer; LVMSError : Boolean; LI : TIdVMSFTPListItem; begin { 1 2 3 4 5 6 1234567890123456789012345678901234567890123456789012345678901234567890 BILF.C;2 13 5-JUL-1991 12:00 [1,1] (RWED,RWED,RE,RE) and non-MutliNet VMS: CII-MANUAL.TEX;1 213/216 29-JAN-1996 03:33:12 [ANONYMOU,ANONYMOUS] (RWED,RWED,,) or possibly VMS TCPware V5.5-3 .WELCOME;1 2 13-FEB-2002 23:32:40.47 } LI := AItem as TIdVMSFTPListItem; LVMSError := False; LLine := LI.Data; // Charon VAX 5.4.2 uses tabs between some of its columns and spaces between others LLine := ReplaceAll(LLine, #9, ' '); //File Name //We do this in a roundabout way because spaces in VMS files may actually //be legal and that throws of a typical non-position based column parser. //this assumes that the file contains a ";". In VMS, this separates the name //from the version number. LBuffer := Fetch(LLine, ';'); {do not localize} LI.LocalFileName := LowerCase(LBuffer); LBuf2 := Fetch(LLine); //Some FTP servers might follow the filename with a tab and than //give an error such as this: //1KBTEST.PTF;10#9No privilege for attempted operation LI.Version := IndyStrToInt(LBuf2, 0); LBuffer := LBuffer + ';' + LBuf2; {do not localize} //Dirs have to be processed differently then //files because a version mark and .DIR exctension //are not used to CWD into a subdir although they are //listed in a dir listed. if (IndyPos('.DIR;', LBuffer) > 0) then {do not localize} begin AItem.ItemType := ditDirectory; //note that you can NOT simply do a Fetch('.') to extract the dir name //you use with a CD because the period is also a separator between pathes // //e.g. // //[VMSSERV.FILES]ALARM.DIR;1 1/3 5-MAR-1993 18:09 if IndyPos(PATH_FILENAME_SEP_VMS, LBuffer) = 0 then begin LBuf2 := ''; end else begin LBuf2 := Fetch(LBuffer, PATH_FILENAME_SEP_VMS) + PATH_FILENAME_SEP_VMS; {Do not localize} end; AItem.FileName := LBuf2 + Fetch(LBuffer, '.'); {do not localize} AItem.LocalFileName := LowerCase(AItem.FileName); end else begin AItem.ItemType := ditFile; AItem.FileName := LBuffer; end; if APath <> '' then begin AItem.FileName := APath + AItem.FileName; end; LCols := TStringList.Create; try SplitDelimitedString(LLine, LCols, True); LOwnerIdx := 3; //if this isn't numeric, there may be an error that is //is reported in the File list. Do not parse the line further. if LCols.Count > 0 then begin LBuffer := LCols[0]; LBuffer := Fetch(LBuffer, '/'); if IsNumeric(LBuffer) then begin //File Size LI.NumberBlocks := IndyStrToInt(LBuffer, 0); LI.BlockSize := VMS_BLOCK_SIZE; LI.Size := IndyStrToInt64(LBuffer, 0) * VMS_BLOCK_SIZE; //512 is the size of a VMS block end else begin //on the UCX VMS server, the file size might not be reported. Probably the file owner if not TextStartsWith(LCols[0], '[') then {do not localize} begin if not IsNumeric(LCols[0], 1, 1) then begin //the server probably reported an error in the FTP list such as no permission //we need to stop right there. LVMSError := True; AItem.SizeAvail := False; AItem.ModifiedAvail := False; end; end else begin LOwnerIdx := 0; end; end; if not LVMSError then begin if LOwnerIdx > 0 then begin //Date if LCols.Count > 1 then begin LBuffer := LCols[1]; LDay := IndyStrToInt(Fetch(LBuffer, '-'), 1); {do not localize} LMonth := StrToMonth(Fetch(LBuffer, '-')); {do not localize} LYear := IndyStrToInt(Fetch(LBuffer), 1989); LI.ModifiedDate := EncodeDate(LYear, LMonth, LDay); end; //Time if LCols.Count > 2 then begin //Modified Time of Day //Some dir listings might be missing the time //such as this: // //vms_dir_2.DIR;1 1 19-NOV-2001 [root,root] (RWE,RWE,RE,RE) if IndyPos(':', LCols[2]) = 0 then begin {do not localize} Dec(LOwnerIdx); end else begin LI.ModifiedDate := AItem.ModifiedDate + TimeHHMMSS(LCols[2]); end; end; end; //Owner/Group //This is in the order of Group/Owner //See: // http://seqaxp.bio.caltech.edu/www/vms_beginners_faq.html#FILE00 if LCols.Count > LOwnerIdx then begin LBuffer := LCols[LOwnerIdx]; Fetch(LBuffer, '['); {do not localize} LBuffer := Fetch(LBuffer,']'); LI.GroupName := Trim(Fetch(LBuffer, ',')); {do not localize} LI.OwnerName := Trim(LBuffer); {do not localize} end; //Protections if LCols.Count > (LOwnerIdx+1) then begin LBuffer := LCols[LOwnerIdx+1]; Fetch(LBuffer, '('); {do not localize} LBuffer := Fetch(LBuffer, ')'); {do not localize} LI.PermissionDisplay := '(' + LBuffer + ')'; {do not localize} LI.VMSSystemPermissions := Trim(Fetch(LBuffer, ',')); {do not localize} LI.VMSOwnerPermissions := Trim(Fetch(LBuffer, ',')); {do not localize} LI.VMSGroupPermissions := Trim(Fetch(LBuffer, ',')); {do not localize} LI.VMSWorldPermissions := Trim(LBuffer); end; end; end; finally FreeAndNil(LCols); end; Result := True; end; class function TIdFTPLPVMS.ParseListing(AListing: TStrings; ADir: TIdFTPListItems): Boolean; var i : Integer; LItem : TIdFTPListItem; LStartLine, LEndLine : Integer; LRootPath : String; //needed for recursive dir listings "DIR [...]" LRelPath : String; begin { VMS is really a ball because the listing can start and end with blank lines as well as a begging and ending banner } LStartLine := 0; LRelPath := ''; LEndLine := AListing.Count-1; for i := 0 to LEndLine do begin if IsWhiteString(AListing[i]) then begin Inc(LStartLine); end else begin if IsVMSHeader(AListing[i]) then begin LRootPath := AListing[i]; //to make things easy, we will only use entire banner for deteriming a subdir //such as this: // //Directory ANONYMOUS_ROOT:[000000.VMS-FREEWARE.NARNIA] // if //Directory ANONYMOUS_ROOT:[000000.VMS-FREEWARE.NARNIA.COM] // then result = [.COM] LRootPath := Fetch(LRootPath, PATH_FILENAME_SEP_VMS) + '.'; {do not localize} Inc(LStartLine); end; Break; end; end; //find the end of our parsing for i := LEndLine downto LStartLine do begin if IsWhiteString(AListing[i]) or IsVMSFooter(AListing[i]) then begin Dec(LEndLine); end else begin Break; end; end; for i := LStartLine to LEndLine do begin if not IsWhiteString(AListing[i]) then begin if IsVMSHeader(AListing[i]) then begin //+1 is used because there's a period that we are dropping and then adding back LRelPath := Copy(AListing[i], Length(LRootPath)+1, MaxInt); LRelPath := VMS_RELPATH_PREFIX + LRelPath; end else if not IsContinuedLine(AListing[i]) then //needed because some VMS computers return entries with multiple lines begin LItem := MakeNewItem(ADir); LItem.Data := UnfoldLines(AListing[i], i, AListing); Result := ParseLine(LItem, LRelPath); if not Result then begin FreeAndNil(LItem); Exit; end; end; end; end; Result := True; end; initialization RegisterFTPListParser(TIdFTPLPVMS); finalization UnRegisterFTPListParser(TIdFTPLPVMS); end.