diff --git a/System.Zip.TestCase.pas b/System.Zip.TestCase.pas index 084873f..227360d 100644 --- a/System.Zip.TestCase.pas +++ b/System.Zip.TestCase.pas @@ -2,14 +2,24 @@ interface -uses TestFramework, System.Zip2; +uses System.Classes, System.SysUtils, TestFramework, System.Zip2; type TTestCase_Zip = class(TTestCase) + private + class function ReadFile (const APath : string) : TBytes; static; + class procedure ReadBuffer (AStream : TStream; + var Buffer : TBytes; + Offset, Count : Int64); static; + function CreateFile(const AExtension: string): string; protected function GetCompression: TZipCompression; virtual; abstract; published procedure TestCase_Zip_File; +{$IFDEF WIN64} + procedure TestCase_BigZip_File; +{$ENDIF} + procedure TestCase_MultipleFile; end; TTestCase_Zip_zcStored = class(TTestCase_Zip) @@ -29,7 +39,176 @@ TTestCase_Zip_zcLZMA = class(TTestCase_Zip) implementation -uses Classes, System.SysUtils, System.IOUtils, System.Zip.LZMA; +uses System.IOUtils, System.RTLConsts, System.Types, System.Zip.LZMA; + +function TTestCase_Zip.CreateFile(const AExtension: string): string; + var + LCheminTemp : string; +BEGIN + LCheminTemp := TPath.GetTempFileName; + try + Result := TPath.ChangeExtension(LCheminTemp, AExtension); + finally + TFile.Delete(LCheminTemp); + end; +end; + +class procedure TTestCase_Zip.ReadBuffer(AStream : TStream; + var Buffer : TBytes; + Offset, Count : Int64); + var + LTotalCount, + LReadCount : NativeInt; +begin + { Perform a read directly. Most of the time this will succeed + without the need to go into the WHILE loop. } + LTotalCount := AStream.Read64(Buffer, Offset, Count); + { Check if there was an error } + if LTotalCount < 0 then + raise EReadError.CreateRes(@SReadError); + + while (LTotalCount < Count) DO + begin + { Try to read a contiguous block of size } + LReadCount := AStream.Read64(Buffer, Offset + LTotalCount, (Count - LTotalCount)); + { Check if we read something and decrease the number of bytes left to read } + if LReadCount <= 0 then + raise EReadError.CreateRes(@SReadError) + else + Inc(LTotalCount, LReadCount); + end; +end; + +class function TTestCase_Zip.ReadFile(const APath: string): TBytes; + var + LFileStream : TFileStream; +begin + LFileStream := NIL; + try + LFileStream := TFileStream.Create(APath, fmOpenRead or fmShareDenyWrite); + SetLength(Result, LFileStream.Size); + + ReadBuffer(LFileStream, Result, 0, Length(Result)); + finally + LFileStream.Free; + end; +end; + +{$IFDEF WIN64} +procedure TTestCase_Zip.TestCase_BigZip_File; + var + LCheminZip, + LCheminFichier : string; + LZipFile : TZipFile; + LOutput : TBytes; + LFileStream : TFileStream; + LLongueurVoulue, + LLongueur : Int64; +BEGIN + LCheminZip := CreateFile ('.zip'); + LCheminFichier := CreateFile ('.bin'); + + LFileStream := NIL; + try + LFileStream := TFile.Create (LCheminFichier); + LLongueurVoulue := $100000000; + LFileStream.Size := LLongueurVoulue; + finally + LFileStream.Free; + end; + + // Compress File + LZipFile := TZipFile.Create; + LZipFile.Open(LCheminZip, zmWrite); + try + try + LZipFile.Add(LCheminFichier, '', GetCompression); + finally + LZipFile.Close; + LZipFile.Free; + TFile.Delete(LCheminFichier); + end; + + // Decompress File + TZipFile.ExtractZipFile(LCheminZip, TPath.GetTempPath); + finally + TFile.Delete(LCheminZip); + end; + + CheckTrue(TFile.Exists(LCheminFichier)); + + try + LOutput := ReadFile(LCheminFichier); + LLongueur := Length(LOutput); + CheckEquals(LLongueurVoulue, LLongueur); + finally + TFile.Delete(LCheminFichier); + end; +end; +{$ENDIF} + +procedure TTestCase_Zip.TestCase_MultipleFile; + const + C_NbFichier : Integer = 65536; + var + LCheminZip, + LCheminTemp, + LNomFichier, + LCheminFichier : string; + LI : Integer; + LZipFile : TZipFile; + LInput, LOutput : TBytes; + LFiles : TStringDynArray; +BEGIN + LCheminZip := CreateFile ('.zip'); + LCheminFichier := CreateFile ('.bin'); + + Randomize; + SetLength(LInput, 10); + for LI := Low(LInput) to High(LInput) do + LInput[LI] := Random(128); + + TFile.WriteAllBytes(LCheminFichier, LInput); + + // Compress File + LZipFile := TZipFile.Create; + LZipFile.Open(LCheminZip, zmWrite); + try + try + LNomFichier := TPath.GetFileName(LCheminFichier); + for LI := 1 to C_NbFichier do + LZipFile.Add (LCheminFichier, + string.Format('%0:s.%1:d', [LNomFichier, LI]), + GetCompression); + finally + LZipFile.Close; + LZipFile.Free; + TFile.Delete(LCheminFichier); + end; + + // Decompress File + LCheminTemp := LCheminFichier.Substring(0, length (LCheminFichier) - 4); + TDirectory.CreateDirectory(LCheminTemp); + + try + TZipFile.ExtractZipFile(LCheminZip, LCheminTemp); + + LFiles := TDirectory.GetFiles(LCheminTemp); + CheckEquals(C_NbFichier, Length (LFiles)); + + for LI:= 0 to C_NbFichier - 1 do + begin + LOutput := TFile.ReadAllBytes(LFiles[LI]); + CheckEquals(Length(LInput), Length(LOutput)); + CheckTrue(CompareMem(LInput, LOutput, Length(LInput))); + end; + finally + TDirectory.Delete(LCheminTemp, True); + end; + finally + TFile.Delete(LCheminZip); + end; +end; procedure TTestCase_Zip.TestCase_Zip_File; var fZip, fData: string; diff --git a/System.Zip2.pas b/System.Zip2.pas index 5fa058c..6eaf210 100644 --- a/System.Zip2.pas +++ b/System.Zip2.pas @@ -79,6 +79,7 @@ function TZipCompressionToString(Compression: TZipCompression): string; EXTRAFIELD_ID_NTFS: UInt16 = $000A; ZIP64 = $FFFFFFFF; + C_MaxFileCount = $FFFF; type /// Final block written to zip file @@ -1015,6 +1016,8 @@ procedure TZipFile.ReadCentralHeader; LEndHeader: TZipEndOfCentralHeader; LHeader: TZipHeader; Z64: TZip64_EndOfCentralDirectory; + LFileCount : UInt64; + LCentralDirOffset : Int64; begin FFiles.Clear; if FStream.Size = 0 then @@ -1022,18 +1025,30 @@ procedure TZipFile.ReadCentralHeader; // Read End Of Centeral Direcotry Header if not LocateEndOfCentralHeader(LEndHeader) then raise EZipException.CreateRes(@SZipErrorRead); - // Move to the beginning of the CentralDirectory - FStream.Position := LEndHeader.CentralDirOffset; - if LEndHeader.CentralDirOffset = ZIP64 then begin + + // Read from End Of Central Directory Header + LCentralDirOffset := LEndHeader.CentralDirOffset; + LFileCount := LEndHeader.CentralDirEntries; + + if (LCentralDirOffset = ZIP64) OR + (LFileCount = C_MaxFileCount) then + begin + // On essaye de trouver le EOCD64 if ZIP64_LocateEndOfCentralHeader(Z64) then - FStream.Position := Z64.DirectoryOffset; + begin + // Lecture du EOCD64 + LCentralDirOffset := Z64.DirectoryOffset; + LFileCount := Z64.EntriesOnDisk; + end; end; + // Move to the beginning of the CentralDirectory + FStream.Position := LCentralDirOffset; // Save Begginning of Central Directory. This is where new files // get written to, and where the new central directory gets written when // closing. - FEndFileData := LEndHeader.CentralDirOffset; + FEndFileData := LCentralDirOffset; // Read File Headers - for I := 0 to LEndHeader.CentralDirEntries - 1 do + for I := 0 to LFileCount - 1 do begin // Verify Central Header signature FStream.Read(Signature, Sizeof(Signature)); @@ -1432,7 +1447,7 @@ procedure TZipFile.Close; LEndOfHeader: TZipEndOfCentralHeader; I: Integer; Signature: UInt32; - iCentralDirSize: UInt32; + iCentralDirSize: UInt64; Z64_End: TZip64_EndOfCentralDirectory; Z64_EndLocator: TZip64_EndOfCentralDirectoryLocator; bIsZIP64: Boolean; @@ -1441,7 +1456,7 @@ procedure TZipFile.Close; // Only need to write Central Directory and End Of Central Directory if writing if (FMode = zmReadWrite) or (FMode = zmWrite) then begin - bIsZIP64 := False; + bIsZIP64 := (FFiles.Count > C_MaxFileCount); FStream.Position := FEndFileData; Signature := SIGNATURE_CENTRALHEADER; // Write File Signatures @@ -1501,12 +1516,22 @@ procedure TZipFile.Close; // Only support writing single disk .ZIP files FillChar(LEndOfHeader, Sizeof(LEndOfHeader), 0); - LEndOfHeader.CentralDirEntries := FFiles.Count; - LEndOfHeader.NumEntriesThisDisk := FFiles.Count; - LEndOfHeader.CentralDirSize := iCentralDirSize; - LEndOfHeader.CentralDirOffset := FEndFileData; + if FFiles.Count > C_MaxFileCount then + LEndOfHeader.CentralDirEntries := C_MaxFileCount + else + LEndOfHeader.CentralDirEntries := FFiles.Count; + if FFiles.Count > C_MaxFileCount then + LEndOfHeader.NumEntriesThisDisk := C_MaxFileCount + else + LEndOfHeader.NumEntriesThisDisk := FFiles.Count; + IF iCentralDirSize > $FFFFFFFF then + LEndOfHeader.CentralDirSize := $FFFFFFFF + else + LEndOfHeader.CentralDirSize := iCentralDirSize; if FEndFileData > ZIP64 then - LEndOfHeader.CentralDirOffset := ZIP64; + LEndOfHeader.CentralDirOffset := ZIP64 + else + LEndOfHeader.CentralDirOffset := FEndFileData; // Truncate comment if it's too long if Length(FComment) > $FFFF then SetLength(FComment, $FFFF);