Browse Source

* zip64 support by Reinier Olislagers, mantis #23533

git-svn-id: trunk@25567 -
marco 12 years ago
parent
commit
c34760677b

+ 96 - 0
packages/paszlib/readme.txt

@@ -1,3 +1,99 @@
+Contents:
+zipper.pp/TZipper
+- Introduction
+- Zip standards compliance
+- Zip file format
+- Zip64 support notes
+paszlib
+- Introduction
+- Change Log
+- File list
+- Legal issues
+- Archive Locations
+
+=================
+zipper.pp/TZipper
+=================
+
+Introduction
+============
+Zipper.pp contains TZipper, an object-oriented wrapper for the paszlib units 
+that allows
+- compressing/adding files/streams
+- decompressing files/streams
+- listing files
+contained in a zip file.
+
+Zip standards compliance
+========================
+TZipper is meant to help implement the most widely used and useful aspects of 
+the zip format, while following the official specifications 
+http://www.pkware.com/documents/casestudies/APPNOTE.TXT
+(latest version reviewed for this readme: 6.3.3, September 1, 2012)
+as much as possible.
+
+Not all (de)compression methods specified in the zip standard [1] are supported.
+Encryption (either zip 2.0 or AES) is not supported, nor are multiple disk sets (spanning/splitting).
+Please see the fpdoc help and the zipper.pp for details on using the class.
+
+Zip file format
+===============
+The standard mentioned above documents the zip file format authoratively 
+and in detail. However, a brief summary can be useful:
+A zip file consists of
+
+For each file:
+local file header 
+(filename, uncompressed,compressed size etc)
+optional extended file header 
+(e.g. zip64 extended info which overrides size above)
+compressed file data
+
+Central directory:
+- for each file:
+central directory header 
+(much the same data as local file header+position of local file header)
+optional extended file header (e.g. zip64 extended info which overrides the 
+above)
+
+if zip64 is used: one
+zip64 end of central directory record 
+(mainly used to point to beginning of central directory)     
+zip64 end of central directory locator
+(mainly used to point to zip64 end of central directory record)
+
+in any case: one
+end of central directory record
+(contains position of central directory, zip file comment etc)
+
+Zip64 support notes
+===================
+The zip64 extensions that allow large files are supported:
+- total zip file size and uncompressed sizes of >4Gb (up to FPC's limit of int64
+  size for streams)
+- > 65535 files per zip archive (up to FPC's limit of integer due to 
+  collection.count)
+
+Write support:
+zip64 headers are added after local file headers only if the uncompressed or 
+compressed sizes overflow the local file header space. This avoids wasting space.
+
+Each local zip64 file header variable overrides its corresponding variable in
+the local file header only if it is not 0. If it is, the local version is used.
+
+Each central directory zip64 file header variable overrides its corresponding 
+variable in the central directory file header only if it is not 0. If it is, the
+central directory file header version is used.
+
+If zip64 support is needed due to zip64 local/central file headers and/or the
+number of files in the zip file, the zip64 alternatives to the end of central 
+diretory variables are always written. Although the zip standard doesn't seem to
+require this explicitly, it doesn't forbid it either and other utilities such as
+rar and Windows 7 built in zip support seem to require it.
+
+=======
+paszlib
+=======
 _____________________________________________________________________________
 _____________________________________________________________________________
 
 
 PASZLIB 1.0                                                   May 11th, 1998
 PASZLIB 1.0                                                   May 11th, 1998

+ 1 - 1
packages/paszlib/src/zinflate.pas

@@ -3,7 +3,7 @@ unit  zinflate;
 {  inflate.c -- zlib interface to inflate modules
 {  inflate.c -- zlib interface to inflate modules
    Copyright (C) 1995-1998 Mark Adler
    Copyright (C) 1995-1998 Mark Adler
 
 
-  Pascal tranlastion
+  Pascal translation
   Copyright (C) 1998 by Jacques Nomssi Nzali
   Copyright (C) 1998 by Jacques Nomssi Nzali
   For conditions of distribution and use, see copyright notice in readme.txt
   For conditions of distribution and use, see copyright notice in readme.txt
 }
 }

File diff suppressed because it is too large
+ 518 - 160
packages/paszlib/src/zipper.pp


+ 577 - 26
packages/paszlib/tests/tczipper.pp

@@ -1,50 +1,114 @@
 program tczipper;
 program tczipper;
 {
 {
     This file is part of the Free Pascal packages.
     This file is part of the Free Pascal packages.
-    Copyright (c) 1999-2012 by the Free Pascal development team
+    Copyright (c) 2012-2013 by the Free Pascal Development Team
+    Created by Reinier Olislagers
 
 
     Tests zip/unzip functionality provided by the FPC zipper.pp unit.
     Tests zip/unzip functionality provided by the FPC zipper.pp unit.
+    If passed a zip file name as first argument, it will try and decompress
+    and list the contents of the zip file.
 
 
     See the file COPYING.FPC, included in this distribution,
     See the file COPYING.FPC, included in this distribution,
-    for details about the copyright.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+    for details about the license.
 
 
  **********************************************************************}
  **********************************************************************}
 {$mode objfpc}{$h+}
 {$mode objfpc}{$h+}
 
 
-uses SysUtils, classes, zipper, md5;
+//Define this if you want to inspect the generated zips etc
+{$define KEEPTESTFILES}
+
+uses SysUtils, classes, zipper, unzip, zdeflate, zinflate, zip, md5, zstream, nullstream;
 
 
 type
 type
-  TCallBackHandler = class(TObject)
+
+  { TCallBackHandler }
+
+  TCallBackHandler = class(TObject) //Callbacks used in zip/unzip processing
+  private
+    FPerformChecks: boolean;
+    FOriginalContent: string;
+    FShowContent: boolean;
+    FStreamResult: boolean;
   public
   public
+    property PerformChecks: boolean read FPerformChecks write FPerformChecks; //If false, do not perform any consistency checks
+    property OriginalContent: string read FOriginalContent write FOriginalContent; //Zip entry uncompressed content used in TestZipEntries
+    property ShowContent: boolean read FShowContent write FShowContent; //Show contents of zip when extracting?
+    property StreamResult: boolean read FStreamResult; //For handler to report success/failure
     procedure EndOfFile(Sender:TObject; const Ratio:double);
     procedure EndOfFile(Sender:TObject; const Ratio:double);
     procedure StartOfFile(Sender:TObject; const AFileName:string);
     procedure StartOfFile(Sender:TObject; const AFileName:string);
+    procedure DoCreateZipOutputStream(Sender: TObject; var AStream: TStream;
+      AItem: TFullZipFileEntry);
+    procedure DoDoneOutZipStream(Sender: TObject; var AStream: TStream;
+      AItem: TFullZipFileEntry); //Used to verify zip entry decompressed contents
+    constructor Create;
   end;
   end;
 
 
-
-procedure TCallBackHandler.EndOfFile(Sender : TObject; Const Ratio : Double);
+procedure TCallBackHandler.EndOfFile(Sender: TObject; const Ratio: double);
 begin
 begin
-  if (Ratio<0) then
+  writeln('End of file handler hit; ratio: '+floattostr(ratio));
+  if (FPerformChecks) and (Ratio<0) then
   begin
   begin
     writeln('Found compression ratio '+floattostr(Ratio)+', which should never be lower than 0.');
     writeln('Found compression ratio '+floattostr(Ratio)+', which should never be lower than 0.');
-    halt(3);
+    halt(1);
   end;
   end;
 end;
 end;
 
 
-procedure TCallBackHandler.StartOfFile(Sender : TObject; Const AFileName : String);
+procedure TCallBackHandler.StartOfFile(Sender: TObject; const AFileName: string);
 begin
 begin
-  if AFileName='' then
+  writeln('Start of file handler hit; filename: '+AFileName);
+  if (FPerformChecks) and (AFileName='') then
   begin
   begin
     writeln('Archive filename should not be empty.');
     writeln('Archive filename should not be empty.');
-    halt(4);
+    halt(1);
+  end;
+end;
+
+procedure TCallBackHandler.DoCreateZipOutputStream(Sender: TObject; var AStream: TStream;
+  AItem: TFullZipFileEntry);
+begin
+  AStream:=TMemoryStream.Create;
+end;
+
+procedure TCallBackHandler.DoDoneOutZipStream(Sender: TObject; var AStream: TStream;
+  AItem: TFullZipFileEntry);
+var
+  DecompressedContent: string;
+begin
+  //writeln('At end of '+AItem.ArchiveFileName);
+  AStream.Position:=0;
+  SetLength(DecompressedContent,Astream.Size);
+  if AStream.Size>0 then
+    (AStream as TMemoryStream).Read(DecompressedContent[1], AStream.Size);
+  if (FPerformChecks) and (DecompressedContent<>OriginalContent) then
+  begin
+    FStreamResult:=false;
+    writeln('TestZipEntries failed: found entry '+AItem.ArchiveFileName+
+      ' has value ');
+    writeln('*'+DecompressedContent+'*');
+    writeln('expected ');
+    writeln('*'+OriginalContent+'*');
   end;
   end;
+  if (FPerformChecks=false) and (ShowContent=true) then
+  begin
+    //display only
+    writeln('TestZipEntries info: found entry '+AItem.ArchiveFileName+
+      ' has value ');
+    writeln('*'+DecompressedContent+'*');
+  end;
+  Astream.Free;
 end;
 end;
 
 
+constructor TCallBackHandler.Create;
+begin
+  FOriginalContent:='A'; //nice short demo content
+  FStreamResult:=true;
+  FPerformChecks:=true; //perform verification by default
+  FShowContent:=true;
+end;
+
+
+function CompareCompressDecompress: boolean;
 var
 var
-  code: cardinal;
   CallBackHandler: TCallBackHandler;
   CallBackHandler: TCallBackHandler;
   CompressedFile: string;
   CompressedFile: string;
   FileContents: TStringList;
   FileContents: TStringList;
@@ -55,10 +119,10 @@ var
   OurZipper: TZipper;
   OurZipper: TZipper;
   UnZipper: TUnZipper;
   UnZipper: TUnZipper;
 begin
 begin
-  code := 0;
+  result:=true;
   UncompressedFile1:=SysUtils.GetTempFileName('', 'UNC');
   UncompressedFile1:=SysUtils.GetTempFileName('', 'UNC');
   UncompressedFile2:=SysUtils.GetTempFileName('', 'UNC');
   UncompressedFile2:=SysUtils.GetTempFileName('', 'UNC');
-  CompressedFile:=SysUtils.GetTempFileName('', 'ZP');
+  CompressedFile:=SysUtils.GetTempFileName('', 'CC');
 
 
   FileContents:=TStringList.Create;
   FileContents:=TStringList.Create;
   OurZipper:=TZipper.Create;
   OurZipper:=TZipper.Create;
@@ -93,8 +157,10 @@ begin
     end;
     end;
 
 
     // Delete original files
     // Delete original files
+    {$IFNDEF KEEPTESTFILES}
     DeleteFile(UncompressedFile1);
     DeleteFile(UncompressedFile1);
     DeleteFile(UncompressedFile2);
     DeleteFile(UncompressedFile2);
+    {$ENDIF}
 
 
     // Now unzip
     // Now unzip
     Unzipper.FileName:=CompressedFile;
     Unzipper.FileName:=CompressedFile;
@@ -109,7 +175,7 @@ begin
       (not FileExists(UncompressedFile2)) then
       (not FileExists(UncompressedFile2)) then
     begin
     begin
       writeln('Unzip failed: could not find decompressed files.');
       writeln('Unzip failed: could not find decompressed files.');
-      halt(6);
+      exit(false);
     end;
     end;
 
 
     // Compare hashes
     // Compare hashes
@@ -120,25 +186,510 @@ begin
     then
     then
     begin
     begin
       writeln('Unzip failed: uncompressed files are not the same as the originals.');
       writeln('Unzip failed: uncompressed files are not the same as the originals.');
-      halt(7);
+      exit(false);
     end;
     end;
 
 
-    if code = 0 then
-      writeln('Basic zip/unzip tests passed')
-    else
-      writeln('Basic zip/unzip test failed: ', code);
   finally
   finally
     FileContents.Free;
     FileContents.Free;
     CallBackHandler.Free;
     CallBackHandler.Free;
     OurZipper.Free;
     OurZipper.Free;
     UnZipper.Free;
     UnZipper.Free;
+    {$IFNDEF KEEPTESTFILES}
     try
     try
       if FileExists(CompressedFile) then DeleteFile(CompressedFile);
       if FileExists(CompressedFile) then DeleteFile(CompressedFile);
       if FileExists(UncompressedFile1) then DeleteFile(UncompressedFile1);
       if FileExists(UncompressedFile1) then DeleteFile(UncompressedFile1);
       if FileExists(UncompressedFile2) then DeleteFile(UncompressedFile2);
       if FileExists(UncompressedFile2) then DeleteFile(UncompressedFile2);
     finally
     finally
-      // Ignore errors; operating system should clean out temp files
-    end; 
+      // Ignore errors: OS should eventually clean out temp files anyway
+    end;
+    {$ENDIF}
+  end;
+end;
+
+function CompressSmallStreams: boolean;
+// Compresses some small streams using default compression and
+// no compression (storage)
+// Just storing is the best option; compression will enlarge the zip.
+// Test verifies that the entries in the zip are not bigger than
+// the originals.
+var
+  DestFile: string;
+  z: TZipper;
+  zfe: TZipFileEntry;
+  s: string = 'abcd';
+  DefaultStream, StoreStream: TStringStream;
+begin
+  result:=true;
+  DestFile:=SysUtils.GetTempFileName('', 'CS1');
+  z:=TZipper.Create;
+  z.FileName:=DestFile;
+  try
+    DefaultStream:=TStringStream.Create(s);
+    StoreStream:=TStringStream.Create(s);
+
+    //DefaultStream - compression  level = Default
+    zfe:=z.Entries.AddFileEntry(DefaultStream, 'Compressed');
+    z.ZipAllFiles;
+
+    if (z.Entries[0].Size>zfe.Size) then
+    begin
+      result:=false;
+      writeln('Small stream test default compression failed: compressed size '+
+        inttostr(z.Entries[0].Size) + ' > original size '+inttostr(zfe.Size));
+      exit;
+    end;
+
+  finally
+    DefaultStream.Free;
+    StoreStream.Free;
+    z.Free;
+  end;
+
+  {$IFNDEF KEEPTESTFILES}
+  try
+    DeleteFile(DestFile);
+  except
+    // ignore mess
+  end;
+  {$ENDIF}
+
+  DestFile:=SysUtils.GetTempFileName('', 'CS2');
+  z:=TZipper.Create;
+  z.FileName:=DestFile;
+  try
+    DefaultStream:=TStringStream.Create(s);
+    StoreStream:=TStringStream.Create(s);
+
+    //StoreStream - compression  level = Store
+    zfe:=z.Entries.AddFileEntry(StoreStream, 'Uncompressed');
+    zfe.CompressionLevel:=clnone;
+    z.ZipAllFiles;
+
+    if (z.Entries[0].Size>zfe.Size) then
+    begin
+      result:=false;
+      writeln('Small stream test uncompressed failed: compressed size '+
+        inttostr(z.Entries[0].Size) + ' > original size '+inttostr(zfe.Size));
+      exit;
+    end;
+  finally
+    DefaultStream.Free;
+    StoreStream.Free;
+    z.Free;
+  end;
+
+  {$IFNDEF KEEPTESTFILES}
+  try
+    DeleteFile(DestFile);
+  except
+    // ignore mess
+  end;
+  {$ENDIF}
+
+  //The result can be checked with the command (on Linux):
+  //unzip -v <DestFile>
+  //The column Size Shows that compressed files are bigger than source files
+end;
+
+function ShowZipFile(ZipFile: string): boolean;
+// Reads zip file and lists entries
+var
+  CallBackHandler: TCallBackHandler;
+  i: integer;
+  UnZipper: TUnZipper;
+  UnzipArchiveFiles: TStringList;
+begin
+  result:=true;
+  UnZipper:=TUnZipper.Create;
+  CallBackHandler:=TCallBackHandler.Create;
+  UnzipArchiveFiles:=TStringList.Create;
+  try
+    CallBackHandler.PerformChecks:=false; //only display output
+    UnZipper.FileName:=ZipFile;
+    Unzipper.Examine;
+    writeln('ShowZipFile: zip file has '+inttostr(UnZipper.Entries.Count)+' entries');
+
+    i:=0;
+    Unzipper.OnCreateStream:[email protected];
+    Unzipper.OnDoneStream:[email protected];
+    while i<Unzipper.Entries.Count do
+    begin
+      if CallBackHandler.StreamResult then
+      begin
+        UnzipArchiveFiles.Clear;
+        UnzipArchiveFiles.Add(Unzipper.Entries[i].ArchiveFileName);
+        Unzipper.UnZipFiles(UnzipArchiveFiles);
+        // This will kick off the DoCreateOutZipStream/DoDoneOutZipStream handlers
+        inc(i);
+      end
+      else
+      begin
+        break; // Handler has reported error; stop loop
+      end;
+    end;
+  finally
+    Unzipper.Free;
+    CallBackHandler.Free;
+    UnzipArchiveFiles.Free;
+  end;
+end;
+
+function TestZipEntries(Entries: qword): boolean;
+// Adds Entries amount of zip file entries and reads them
+// Starting from 65535 entries, the zip needs to be in zip64 format
+var
+  CallBackHandler: TCallBackHandler;
+  DestFile: string;
+  i: qword;
+  OriginalContent: string = 'A'; //Uncompressed content for zip file entry
+  ContentStreams: TFPList;
+  ContentStream: TStringStream;
+  UnZipper: TUnZipper;
+  UnzipArchiveFiles: TStringList;
+  Zipper: TZipper;
+begin
+  result:=true;
+  DestFile:=SysUtils.GetTempFileName('', 'E'+inttostr(Entries)+'_');
+  Zipper:=TZipper.Create;
+  Zipper.FileName:=DestFile;
+  ContentStreams:=TFPList.Create;
+  try
+    i:=0;
+    while i<Entries do
+    begin
+      ContentStream:=TStringStream.Create(OriginalContent);
+      ContentStreams.Add(ContentStream);
+      // Start filenames at 1
+      Zipper.Entries.AddFileEntry(TStringStream(ContentStreams.Items[i]), format('%U',[i+1]));
+      inc(i);
+    end;
+    Zipper.ZipAllFiles;
+    {
+    i:=0;
+    while i<Entries do
+    begin
+      ContentStreams.Delete(i);
+    end;
+    }
+  finally
+    ContentStreams.Free;
+    Zipper.Free;
+  end;
+
+  UnZipper:=TUnZipper.Create;
+  CallBackHandler:=TCallBackHandler.Create;
+  UnzipArchiveFiles:=TStringList.Create;
+  try
+    CallBackHandler.OriginalContent:=OriginalContent;
+    UnZipper.FileName:=DestFile;
+    Unzipper.Examine;
+    if (UnZipper.Entries.Count<>Entries) then
+    begin
+      result:=false;
+      writeln('TestZipEntries failed: found '+
+        inttostr(UnZipper.Entries.Count) + ' entries; expected '+inttostr(Entries));
+      exit;
+    end;
+    i:=0;
+    Unzipper.OnCreateStream:[email protected];
+    Unzipper.OnDoneStream:[email protected];
+    while i<Entries do
+    begin
+      if CallBackHandler.StreamResult then
+      begin
+        UnzipArchiveFiles.Clear;
+        UnzipArchiveFiles.Add(Unzipper.Entries[i].ArchiveFileName);
+        Unzipper.UnZipFiles(UnzipArchiveFiles);
+        // This will kick off the DoCreateOutZipStream/DoDoneOutZipStream handlers
+        inc(i);
+      end
+      else
+      begin
+        break; // Handler has reported error; stop loop
+      end;
+    end;
+  finally
+    Unzipper.Free;
+    CallBackHandler.Free;
+    UnzipArchiveFiles.Free;
+  end;
+
+  {$IFNDEF KEEPTESTFILES}
+  try
+    DeleteFile(DestFile);
+  except
+    // ignore mess
+  end;
+  {$ENDIF}
+end;
+
+function TestEmptyZipEntries(Entries: qword): boolean;
+// Same as TestZipEntries, except uses empty data:
+// useful for testing large number of files
+var
+  CallBackHandler: TCallBackHandler;
+  DestFile: string;
+  i: qword;
+  ContentStreams: TFPList;
+  ContentStream: TNullStream;
+  UnZipper: TUnZipper;
+  UnzipArchiveFiles: TStringList;
+  Zipper: TZipper;
+begin
+  result:=true;
+  DestFile:=SysUtils.GetTempFileName('', 'EZ'+inttostr(Entries)+'_');
+  Zipper:=TZipper.Create;
+  Zipper.FileName:=DestFile;
+  ContentStreams:=TFPList.Create;
+  try
+    i:=0;
+    while i<Entries do
+    begin
+      ContentStream:=TNullStream.Create;
+      ContentStreams.Add(ContentStream);
+      // Start filenames at 1
+      Zipper.Entries.AddFileEntry(TStringStream(ContentStreams.Items[i]), format('%U',[i+1]));
+      inc(i);
+    end;
+    Zipper.ZipAllFiles;
+    {
+    i:=0;
+    while i<Entries do
+    begin
+      ContentStreams.Delete(i);
+    end;
+    }
+  finally
+    ContentStreams.Free;
+    Zipper.Free;
+  end;
+
+  UnZipper:=TUnZipper.Create;
+  UnzipArchiveFiles:=TStringList.Create;
+  CallBackHandler:=TCallBackHandler.Create;
+  try
+    // Use callbacks to dump zip output into the bit bucket
+    CallBackHandler.PerformChecks:=false;
+    CallBackHandler.ShowContent:=false;
+    Unzipper.OnCreateStream:[email protected];
+    Unzipper.OnDoneStream:[email protected];
+    UnZipper.FileName:=DestFile;
+    Unzipper.Examine;
+    if (UnZipper.Entries.Count<>Entries) then
+    begin
+      result:=false;
+      writeln('TestEmptyZipEntries failed: found '+
+        inttostr(UnZipper.Entries.Count) + ' entries; expected '+inttostr(Entries));
+      exit;
+    end;
+    i:=0;
+    while i<Entries do
+    begin
+      UnzipArchiveFiles.Clear;
+      UnzipArchiveFiles.Add(Unzipper.Entries[i].ArchiveFileName);
+      Unzipper.UnZipFiles(UnzipArchiveFiles);
+      inc(i);
+    end;
+  finally
+    CallBackHandler.Free;
+    Unzipper.Free;
+    UnzipArchiveFiles.Free;
+  end;
+
+  {$IFNDEF KEEPTESTFILES}
+  try
+    DeleteFile(DestFile);
+  except
+    // ignore mess
   end;
   end;
+  {$ENDIF}
+end;
+
+
+function TestLargeFileName: boolean;
+// Zips/unzips 259-character filename
+var
+  ArchiveFile: string;
+  DestFile: string;
+  s: string = 'a';
+  DefaultStream: TStringStream;
+  UnZipper: TUnZipper;
+  Zipper: TZipper;
+begin
+  result:=true;
+  ArchiveFile:=StringOfChar('A',259);
+  DestFile:=SysUtils.GetTempFileName('', 'TL');
+  Zipper:=TZipper.Create;
+  Zipper.FileName:=DestFile;
+  try
+    DefaultStream:=TStringStream.Create(s);
+    Zipper.Entries.AddFileEntry(DefaultStream, ArchiveFile);
+    Zipper.ZipAllFiles;
+  finally
+    DefaultStream.Free;
+    Zipper.Free;
+  end;
+
+  UnZipper:=TUnZipper.Create;
+  try
+    UnZipper.FileName:=DestFile;
+    Unzipper.Examine;
+    if (Unzipper.Entries[0].ArchiveFileName<>ArchiveFile) then
+    begin
+      result:=false;
+      writeln('TestLargeFileName failed: found filename length '+
+        inttostr(Length(Unzipper.Entries[0].ArchiveFileName)));
+      writeln('*'+Unzipper.Entries[0].ArchiveFileName + '*');
+      writeln('Expected length '+inttostr(Length(ArchiveFile)));
+      writeln('*'+ArchiveFile+'*');
+      exit;
+    end;
+  finally
+    Unzipper.Free;
+  end;
+
+  {$IFNDEF KEEPTESTFILES}
+  try
+    DeleteFile(DestFile);
+  except
+    // ignore mess
+  end;
+  {$ENDIF}
+end;
+
+function TestLargeZip64: boolean;
+// Tests single zip file with large uncompressed content
+// which forces it to zip64 format
+var
+  ArchiveFile: string;
+  Buffer: PChar;
+  DestFile: string;
+  ContentStream: TNullStream; //empty contents
+  UnZipper: TUnZipper;
+  Zipper: TZipper;
+  i: int64;
+begin
+  result:=true;
+  DestFile:=SysUtils.GetTempFileName('', 'LZ');
+  Zipper:=TZipper.Create;
+  Zipper.FileName:=DestFile;
+  ArchiveFile:='HugeString.txt';
+
+  ContentStream:=TNullStream.Create;
+  // About 4Gb; content of 4 bytes+1 added
+  ContentStream.Size:=(1+$FFFFFFFF);
+  ContentStream.Position:=0;
+  writeln('Buffer created');
+  try
+    Zipper.Entries.AddFileEntry(ContentStream, ArchiveFile);
+    writeln('entry added');
+    Zipper.ZipAllFiles;
+  finally
+    ContentStream.Free;
+    Zipper.Free;
+  end;
+
+  UnZipper:=TUnZipper.Create;
+  try
+    UnZipper.FileName:=DestFile;
+    Unzipper.Examine;
+    if (UnZipper.Entries.Count<>1) then
+    begin
+      result:=false;
+      writeln('TestLargeZip64 failed: found '+
+        inttostr(UnZipper.Entries.Count) + ' entries; expected 1');
+      exit;
+    end;
+    if (Unzipper.Entries[0].ArchiveFileName<>ArchiveFile) then
+    begin
+      result:=false;
+      writeln('TestLargeZip64 failed: found filename length '+
+        inttostr(Length(Unzipper.Entries[0].ArchiveFileName)));
+      writeln('*'+Unzipper.Entries[0].ArchiveFileName + '*');
+      writeln('Expected length '+inttostr(Length(ArchiveFile)));
+      writeln('*'+ArchiveFile+'*');
+      exit;
+    end;
+  finally
+    Unzipper.Free;
+  end;
+
+  {$IFNDEF KEEPTESTFILES}
+  try
+    DeleteFile(DestFile);
+  except
+    // ignore mess
+  end;
+  {$ENDIF}
+end;
+
+var
+  code: cardinal; //test result code: 0 for success
+begin
+  code:=0;
+  try
+    if FileExists(ParamStr(1)) then
+    begin
+      writeln('');
+      writeln('Started investigating file '+ParamStr(1));
+      ShowZipFile(ParamStr(1));
+      writeln('Finished investigating file '+ParamStr(1));
+      writeln('');
+    end;
+
+    writeln('CompareCompressDecompress started');
+    if not(CompareCompressDecompress) then code:=code+2; //1 already taken by callback handler
+    writeln('CompareCompressDecompress finished');
+    writeln('');
+    writeln('CompressSmallStreams started');
+    if not(CompressSmallStreams) then code:=code+4;
+    writeln('CompressSmallStreams finished');
+    writeln('');
+    writeln('TestZipEntries(2) started');
+    if not(TestZipEntries(2)) then code:=code+8;
+    writeln('TestZipEntries(2) finished');
+    writeln('');
+    writeln('TestLargeFileName started');
+    if not(TestLargeFileName) then code:=code+16;
+    writeln('TestLargeFileName finished');
+    writeln('');
+    writeln('TestEmptyZipEntries(10) started');
+    // Run testemptyzipentries with a small number to test the test itself... as
+    // well as zip structure generated with empty files.
+    if not(TestEmptyZipEntries(10)) then code:=code+32;
+    writeln('TestEmptyZipEntries(10) finished');
+    writeln('');
+    writeln('TestEmptyZipEntries(65537) started');
+    writeln('(note: this will take a long time)');
+    {Note: tested tools with this file:
+    - info-zip unzip 6.0
+    - Ionic's DotNetZip library unzip.exe utility verison 1.9.1.8 works
+    - 7zip's 7za 9.22 beta works.
+    }
+    if not(TestEmptyZipEntries(65537)) then code:=code+32;
+    writeln('TestEmptyZipEntries(65537) finished');
+    writeln('');
+    { This test will take a very long time as it tries to zip a 4Gb memory block.
+    It is therefore commented out by default }
+    {
+    writeln('TestLargeZip64 - started');
+    if not(TestLargeZip64) then code:=code+thefollowingstatuscode;
+    writeln('TestLargeZip64 format - finished');
+    writeln('');
+    }
+  except
+    on E: Exception do
+    begin
+      writeln('');
+      writeln('Exception: ');
+      writeln(E.Message);
+      writeln('');
+    end;
+  end;
+
+  if code=0 then
+    writeln('Basic zip/unzip tests passed: code '+inttostr(code))
+  else
+    writeln('Basic zip/unzip tests failed: code '+inttostr(code));
   Halt(code);
   Halt(code);
 end.
 end.

Some files were not shown because too many files changed in this diff