diff options
| author | jonas <jonas@3ad0048d-3df7-0310-abae-a5850022a9f2> | 2009-07-11 09:40:22 +0000 |
|---|---|---|
| committer | jonas <jonas@3ad0048d-3df7-0310-abae-a5850022a9f2> | 2009-07-11 09:40:22 +0000 |
| commit | 606202d69c3929cf508ff2f278195a6bd5c123bf (patch) | |
| tree | 8984c227adfcd07c44c65344a2a4e9f387cd926c /packages/paszlib | |
| parent | e85c328f1c77bef73ba758ac5fc48be4cef63a70 (diff) | |
| download | fpc-606202d69c3929cf508ff2f278195a6bd5c123bf.tar.gz | |
* patch by Tom Gregorovic (with some fixes by Alexander Grau) to
* support creating directories and symbolic links while unzipping
* preserve access rights and date of unzipped files
* fix the CRC32 control when the local header field is zero
-> fixes mantis #14106
* fixed unzipping stored files of zero bytes
git-svn-id: http://svn.freepascal.org/svn/fpc/trunk@13378 3ad0048d-3df7-0310-abae-a5850022a9f2
Diffstat (limited to 'packages/paszlib')
| -rw-r--r-- | packages/paszlib/src/zipper.pp | 334 |
1 files changed, 275 insertions, 59 deletions
diff --git a/packages/paszlib/src/zipper.pp b/packages/paszlib/src/zipper.pp index a5d0af2034..f38431a2dc 100644 --- a/packages/paszlib/src/zipper.pp +++ b/packages/paszlib/src/zipper.pp @@ -18,6 +18,9 @@ unit zipper; Interface Uses + {$IFDEF UNIX} + BaseUnix, + {$ENDIF} SysUtils,Classes,ZStream; @@ -251,15 +254,21 @@ Type TZipFileEntry = Class(TCollectionItem) private FArchiveFileName: String; + FAttributes: LongInt; + FCRC32: LongWord; FDateTime: TDateTime; FDiskFileName: String; FHeaderPos: Longint; + FOS: Byte; FSize: Integer; FStream: TStream; function GetArchiveFileName: String; Protected Property HdrPos : Longint Read FHeaderPos Write FheaderPos; Public + constructor Create; + function IsDirectory: Boolean; + function IsLink: Boolean; Procedure Assign(Source : TPersistent); override; Property Stream : TStream Read FStream Write FStream; Published @@ -267,6 +276,9 @@ Type Property DiskFileName : String Read FDiskFileName Write FDiskFileName; Property Size : Integer Read FSize Write FSize; Property DateTime : TDateTime Read FDateTime Write FDateTime; + property OS: Byte read FOS write FOS; + property Attributes: LongInt read FAttributes write FAttributes; + property CRC32: LongWord read FCRC32 write FCRC32; end; { TZipFileEntries } @@ -337,8 +349,6 @@ Type Property Entries : TZipFileEntries Read FEntries Write SetEntries; end; - { TYbZipper } - { TUnZipper } TUnZipper = Class(TObject) @@ -363,7 +373,7 @@ Type Procedure OpenInput; Procedure CloseOutput; Procedure CloseInput; - Procedure ReadZipHeader(Item : TZipFileEntry; out ACRC : LongWord;out AMethod : Word); + Procedure ReadZipHeader(Item : TZipFileEntry; out AMethod : Word); Procedure ReadZipDirectory; Procedure DoEndOfFile; Procedure UnZipOneFile(Item : TZipFileEntry); virtual; @@ -497,6 +507,67 @@ begin DT:=ComposeDateTime(EncodeDate(Y,M,D),EncodeTime(H,N,S,MS)); end; +const + OS_FAT = 0; + OS_UNIX = 3; + + UNIX_MASK = $F000; + UNIX_FIFO = $1000; + UNIX_CHAR = $2000; + UNIX_DIR = $4000; + UNIX_BLK = $6000; + UNIX_FILE = $8000; + UNIX_LINK = $A000; + UNIX_SOCK = $C000; + + + UNIX_RUSR = $0100; + UNIX_WUSR = $0080; + UNIX_XUSR = $0040; + + UNIX_RGRP = $0020; + UNIX_WGRP = $0010; + UNIX_XGRP = $0008; + + UNIX_ROTH = $0004; + UNIX_WOTH = $0002; + UNIX_XOTH = $0001; + + UNIX_DEFAULT = UNIX_RUSR or UNIX_WUSR or UNIX_XUSR or UNIX_RGRP or UNIX_ROTH; + + +function ZipUnixAttrsToFatAttrs(const Name: String; Attrs: Longint): Longint; +begin + Result := faArchive; + + if (Pos('.', Name) = 1) and (Name <> '.') and (Name <> '..') then + Result := Result + faHidden; + case (Attrs and UNIX_MASK) of + UNIX_DIR: Result := Result + faDirectory; + UNIX_LINK: Result := Result + faSymLink; + UNIX_FIFO, UNIX_CHAR, UNIX_BLK, UNIX_SOCK: + Result := Result + faSysFile; + end; + + if (Attrs and UNIX_WUSR) = 0 then + Result := Result + faReadOnly; +end; + +function ZipFatAttrsToUnixAttrs(Attrs: Longint): Longint; +begin + Result := UNIX_DEFAULT; + if (faReadOnly and Attrs) > 0 then + Result := Result and not (UNIX_WUSR); + + if (faSymLink and Attrs) > 0 then + Result := Result or UNIX_LINK + else + if (faDirectory and Attrs) > 0 then + Result := Result or UNIX_DIR + else + Result := Result or UNIX_FILE; +end; + { --------------------------------------------------------------------- TDeCompressor ---------------------------------------------------------------------} @@ -639,7 +710,7 @@ Const SPECIAL = 256; { Special function code } INCSIZE = 1; { Code indicating a jump in code size } CLEARCODE = 2; { Code indicating code table has been cleared } - STDATTR = $23; { Standard file attribute for DOS Find First/Next } + STDATTR = faAnyFile; { Standard file attribute for DOS Find First/Next } constructor TShrinker.Create(AInFile, AOutFile : TStream; ABufSize : LongWord); begin @@ -1001,7 +1072,9 @@ Var F : TZipFileEntry; Info : TSearchRec; I : Longint; - +{$IFDEF UNIX} + UnixInfo: Stat; +{$ENDIF} Begin For I := 0 to FEntries.Count-1 do begin @@ -1014,6 +1087,12 @@ Begin try F.Size:=Info.Size; F.DateTime:=FileDateToDateTime(Info.Time); + {$IFDEF UNIX} + if fplstat(F.DiskFileName, @UnixInfo) = 0 then + F.Attributes := UnixInfo.st_mode; + {$ELSE} + F.Attributes := Info.Attr; + {$ENDIF} finally FindClose(Info); end @@ -1025,6 +1104,11 @@ Begin If (F.ArchiveFileName='') then Raise EZipError.CreateFmt(SErrMissingArchiveName,[I]); F.Size:=F.Stream.Size; + {$IFDEF UNIX} + F.Attributes := UNIX_FILE or UNIX_DEFAULT; + {$ELSE} + F.Attributes := faArchive; + {$ENDIF} end; end; end; @@ -1049,7 +1133,10 @@ Begin If (Item.Stream<>nil) then FInFile:=Item.Stream else - FInFile:=TFileStream.Create(Item.DiskFileName,fmOpenRead); + if Item.IsDirectory then + FInFile := TStringStream.Create('') + else + FInFile:=TFileStream.Create(Item.DiskFileName,fmOpenRead); Result:=True; If Assigned(FOnStartFile) then FOnStartFile(Self,Item.ArchiveFileName); @@ -1143,13 +1230,20 @@ Begin begin Signature := CENTRAL_FILE_HEADER_SIGNATURE; MadeBy_Version := LocalHdr.Extract_Version_Reqd; + {$IFDEF UNIX} + MadeBy_Version := MadeBy_Version or (OS_UNIX shl 8); + {$ENDIF} Move(LocalHdr.Extract_Version_Reqd, Extract_Version_Reqd, 26); Last_Mod_Time:=localHdr.Last_Mod_Time; Last_Mod_Date:=localHdr.Last_Mod_Date; File_Comment_Length := 0; Starting_Disk_Num := 0; Internal_Attributes := 0; - External_Attributes := faARCHIVE; + {$IFDEF UNIX} + External_Attributes := Entries[ACount].Attributes shl 16; + {$ELSE} + External_Attributes := Entries[ACount].Attributes; + {$ENDIF} Local_Header_Offset := HdrPos; end; FOutFile.Seek(0,soFromEnd); @@ -1367,9 +1461,21 @@ End; Function TUnZipper.OpenOutput(OutFileName : String) : Boolean; - +Var + Path: String; + OldDirectorySeparators: set of char; Begin - ForceDirectories(ExtractFilePath(OutFileName)); + { the default RTL behaviour is broken on Unix platforms + for Windows compatibility: it allows both '/' and '\' + as directory separator. We don't want that behaviour + here, since 'abc\' is a valid file name under Unix. + } + OldDirectorySeparators:=AllowDirectorySeparators; + AllowDirectorySeparators:=[DirectorySeparator]; + Path:=ExtractFilePath(OutFileName); + if (Path<>'') then + ForceDirectories(Path); + AllowDirectorySeparators:=OldDirectorySeparators; FOutFile:=TFileStream.Create(OutFileName,fmCreate); Result:=True; If Assigned(FOnStartFile) then @@ -1391,12 +1497,10 @@ Begin end; -Procedure TUnZipper.ReadZipHeader(Item : TZipFileEntry; out ACRC : LongWord; out AMethod : Word); - +Procedure TUnZipper.ReadZipHeader(Item : TZipFileEntry; out AMethod : Word); Var S : String; D : TDateTime; - Begin FZipFile.Seek(Item.HdrPos,soFromBeginning); FZipFile.ReadBuffer(LocalHdr,SizeOf(LocalHdr)); @@ -1405,16 +1509,19 @@ Begin {$ENDIF} With LocalHdr do begin - SetLength(S,Filename_Length); - FZipFile.ReadBuffer(S[1],Filename_Length); - FZipFile.Seek(Extra_Field_Length,soCurrent); - Item.ArchiveFileName:=S; - Item.DiskFileName:=S; - Item.Size:=Uncompressed_Size; - ZipDateTimeToDateTime(Last_Mod_Date,Last_Mod_Time,D); - Item.DateTime:=D; - ACrc:=Crc32; - AMethod:=Compress_method; + SetLength(S,Filename_Length); + FZipFile.ReadBuffer(S[1],Filename_Length); + //SetLength(E,Extra_Field_Length); + //FZipFile.ReadBuffer(E[1],Extra_Field_Length); + FZipFile.Seek(Extra_Field_Length,soCurrent); + Item.ArchiveFileName:=S; + Item.DiskFileName:=S; + Item.Size:=Uncompressed_Size; + ZipDateTimeToDateTime(Last_Mod_Date,Last_Mod_Time,D); + Item.DateTime:=D; + if Crc32 <> 0 then + Item.CRC32 := Crc32; + AMethod:=Compress_method; end; End; @@ -1427,7 +1534,6 @@ Var CenDirPos : LongInt; NewNode : TZipFileEntry; S : String; - Begin EndHdrPos:=FZipFile.Size-SizeOf(EndHdr); if EndHdrPos < 0 then @@ -1452,14 +1558,23 @@ Begin {$ENDIF} With CentralHdr do begin - if Signature<>CENTRAL_FILE_HEADER_SIGNATURE then - raise EZipError.CreateFmt(SErrCorruptZIP,[FZipFile.FileName]); - NewNode:=FEntries.Add as TZipFileEntry; - NewNode.HdrPos := Local_Header_Offset; - SetLength(S,Filename_Length); - FZipFile.ReadBuffer(S[1],Filename_Length); - NewNode.ArchiveFileName:=S; - FZipFile.Seek(Extra_Field_Length+File_Comment_Length,soCurrent); + if Signature<>CENTRAL_FILE_HEADER_SIGNATURE then + raise EZipError.CreateFmt(SErrCorruptZIP,[FZipFile.FileName]); + NewNode:=FEntries.Add as TZipFileEntry; + NewNode.HdrPos := Local_Header_Offset; + SetLength(S,Filename_Length); + FZipFile.ReadBuffer(S[1],Filename_Length); + NewNode.ArchiveFileName:=S; + NewNode.OS := MadeBy_Version shr 8; + + if NewNode.OS = OS_UNIX then + NewNode.Attributes := External_Attributes shr 16 + else + NewNode.Attributes := External_Attributes; + + NewNode.CRC32 := Crc32; + + FZipFile.Seek(Extra_Field_Length + File_Comment_Length,soCurrent); end; end; end; @@ -1477,35 +1592,104 @@ end; Procedure TUnZipper.UnZipOneFile(Item : TZipFileEntry); Var - Count : Longint; - CRC : LongWord; + Count, Attrs: Longint; ZMethod : Word; - OutputFileName : string; -Begin - Try - ReadZipHeader(Item,CRC,ZMethod); - OutputFileName:=Item.DiskFileName; - if FOutputPath<>'' then - OutputFileName:=IncludeTrailingPathDelimiter(FOutputPath)+OutputFileName; - OpenOutput(OutputFileName); + LinkTargetStream: TStringStream; + OutputFileName: string; + IsLink: Boolean; + + procedure DoUnzip(const Dest: TStream); + begin if ZMethod=0 then - begin - Count:=FOutFile.CopyFrom(FZipFile,LocalHdr.Compressed_Size); -{$warning TODO: Implement CRC Check} - end + begin + if (LocalHdr.Compressed_Size<>0) then + begin + Count:=Dest.CopyFrom(FZipFile,LocalHdr.Compressed_Size) + {$warning TODO: Implement CRC Check} + end + else + Count:=0; + end else - With CreateDecompressor(Item, ZMethod, FZipFile, FOutFile) do - Try - OnProgress:=Self.OnProgress; - OnPercent:=Self.OnPercent; - DeCompress; - if CRC<>Crc32Val then - raise EZipError.CreateFmt(SErrInvalidCRC,[Item.ArchiveFileName]); - Finally - Free; - end; - Finally - CloseOutput; + With CreateDecompressor(Item, ZMethod, FZipFile, Dest) do + Try + OnProgress:=Self.OnProgress; + OnPercent:=Self.OnPercent; + DeCompress; + if Item.CRC32 <> Crc32Val then + raise EZipError.CreateFmt(SErrInvalidCRC,[Item.ArchiveFileName]); + Finally + Free; + end; + end; +Begin + ReadZipHeader(Item, ZMethod); + OutputFileName:=Item.DiskFileName; + if FOutputPath<>'' then + OutputFileName:=IncludeTrailingPathDelimiter(FOutputPath)+OutputFileName; + + IsLink := Item.IsLink; + +{$IFNDEF UNIX} + if IsLink then + begin + {$warning TODO: Implement symbolic link creation for non-unix} + IsLink := False; + end; +{$ENDIF} + + + if IsLink then + begin + {$IFDEF UNIX} + LinkTargetStream := TStringStream.Create(''); + try + DoUnzip(LinkTargetStream); + fpSymlink(PChar(LinkTargetStream.DataString), PChar(OutputFileName)); + finally + LinkTargetStream.Free; + end; + {$ENDIF} + end + else + begin + if Item.IsDirectory then + CreateDir(OutputFileName) + else + begin + try + OpenOutput(OutputFileName); + DoUnzip(FOutFile); + Finally + CloseOutput; + end; + end; + end; + + // set attributes + FileSetDate(OutputFileName, DateTimeToFileDate(Item.DateTime)); + + if (Item.Attributes <> 0) then + begin + Attrs := 0; + {$IFDEF UNIX} + if Item.OS = OS_UNIX then Attrs := Item.Attributes; + if Item.OS = OS_FAT then + Attrs := ZipFatAttrsToUnixAttrs(Item.Attributes); + {$ELSE} + if Item.OS = OS_FAT then Attrs := Item.Attributes; + if Item.OS = OS_UNIX then + Attrs := ZipUnixAttrsToFatAttrs(ExtractFileName(Item.ArchiveFileName), Item.Attributes); + {$ENDIF} + + if Attrs <> 0 then + begin + {$IFDEF UNIX} + FpChmod(OutputFileName, Attrs); + {$ELSE} + FileSetAttr(OutputFileName, Attrs); + {$ENDIF} + end; end; end; @@ -1626,6 +1810,39 @@ begin Result:=FDiskFileName; end; +constructor TZipFileEntry.Create; +begin +{$IFDEF UNIX} + FOS := OS_UNIX; +{$ELSE} + FOS := OS_FAT; +{$ENDIF} +end; + +function TZipFileEntry.IsDirectory: Boolean; +begin + Result := (DiskFileName <> '') and (DiskFileName[Length(DiskFileName)] in ['/', '\']); + if Attributes <> 0 then + begin + case OS of + OS_FAT: Result := (faDirectory and Attributes) > 0; + OS_UNIX: Result := (Attributes and UNIX_MASK) = UNIX_DIR; + end; + end; +end; + +function TZipFileEntry.IsLink: Boolean; +begin + Result := False; + if Attributes <> 0 then + begin + case OS of + OS_FAT: Result := (faSymLink and Attributes) > 0; + OS_UNIX: Result := (Attributes and UNIX_MASK) = UNIX_LINK; + end; + end; +end; + procedure TZipFileEntry.Assign(Source: TPersistent); Var @@ -1657,8 +1874,7 @@ begin Items[AIndex]:=AValue; end; -function TZipFileEntries.AddFileEntry(const ADiskFileName: String - ): TZipFileEntry; +function TZipFileEntries.AddFileEntry(const ADiskFileName: String): TZipFileEntry; begin Result:=Add as TZipFileEntry; Result.DiskFileName:=ADiskFileName; |
