View Issue Details

IDProjectCategoryView StatusLast Update
0023533FPCPackagespublic2015-09-29 14:26
ReporterBigChimpAssigned ToMarco van de Voort 
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionfixed 
Platformx64 (with x86 compiler)OSWindowsOS VersionVista
Product Version2.7.1Product Build 
Target VersionFixed in Version3.0.0 
Summary0023533: No support for zip64 zip format
DescriptionBuilt in T(Un)Zipper has no support for the zip64 zip format, which supports e.g. >4 gb files/zip files and >65535 files per archive.

Zip64 has become widely supported by zip tools and e.g. Vista+ Windows explorer; adding support would seem logical
Steps To ReproduceRun test program in 23482 TUnzipper cannot list and unpack archives > 2 GB
http://bugs.freepascal.org/file_download.php?file_id=17492&type=bug
on attached test file
testzip64.zip

If zip64 is supported, it should show that the zip contains the file readme.txt. Instead, it throws an unhandled exception: EZipError: Corrupt ZIP file testzip64.zip
Additional InformationAbout zip64:
http://en.wikipedia.org/wiki/Zip_%28file_format%29#ZIP64

zip64 extends some file length fields inside the zip file itself to allow bigger limits.

Zip specification including zip64:
http://www.pkware.com/documents/casestudies/APPNOTE.TXT

Abbrevia apparently supports win64:
https://sourceforge.net/projects/tpabbrevia/

Generated test file by this method:
Downloaded the DotNetZip devkit from
http://dotnetzip.codeplex.com/
Extracted the and ran DotNetZip-WinFormsTool.exe (with associated dlls)
Set zip64 setting to always
add readme.txt and compress
TagsNo tags attached.
Fixed in Revision25567
FPCOldBugId0
FPCTarget
Attached Files
  • newfiles.zip (2,241 bytes)
  • zip64.diff (77,403 bytes)
    Index: packages/paszlib/readme.txt
    ===================================================================
    --- packages/paszlib/readme.txt	(revision 25488)
    +++ packages/paszlib/readme.txt	(working copy)
    @@ -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
    Index: packages/paszlib/src/zinflate.pas
    ===================================================================
    --- packages/paszlib/src/zinflate.pas	(revision 25488)
    +++ packages/paszlib/src/zinflate.pas	(working copy)
    @@ -3,7 +3,7 @@
     {  inflate.c -- zlib interface to inflate modules
        Copyright (C) 1995-1998 Mark Adler
     
    -  Pascal tranlastion
    +  Pascal translation
       Copyright (C) 1998 by Jacques Nomssi Nzali
       For conditions of distribution and use, see copyright notice in readme.txt
     }
    Index: packages/paszlib/src/zipper.pp
    ===================================================================
    --- packages/paszlib/src/zipper.pp	(revision 25488)
    +++ packages/paszlib/src/zipper.pp	(working copy)
    @@ -1,7 +1,7 @@
     {
    -    $Id: header,v 1.1 2000/07/13 06:33:45 michael Exp $
    +    $Id: header,v 1.3 2013/05/26 06:33:45 michael Exp $
         This file is part of the Free Component Library (FCL)
    -    Copyright (c) 1999-2000 by the Free Pascal development team
    +    Copyright (c) 1999-2013 by the Free Pascal development team
     
         See the file COPYING.FPC, included in this distribution,
         for details about the copyright.
    @@ -26,15 +26,19 @@
     
     Const
       { Signatures }
    -  END_OF_CENTRAL_DIR_SIGNATURE  = $06054B50;
    -  LOCAL_FILE_HEADER_SIGNATURE   = $04034B50;
    -  CENTRAL_FILE_HEADER_SIGNATURE = $02014B50;
    +  END_OF_CENTRAL_DIR_SIGNATURE               = $06054B50;
    +  ZIP64_END_OF_CENTRAL_DIR_SIGNATURE         = $06064B50;
    +  ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE = $07064B50;
    +  LOCAL_FILE_HEADER_SIGNATURE                = $04034B50;
    +  CENTRAL_FILE_HEADER_SIGNATURE              = $02014B50;
    +  ZIP64_HEADER_ID                            = $0001;
     
     Type
    -   Local_File_Header_Type = Packed Record
    +   Local_File_Header_Type = Packed Record //1 per zipped file
          Signature              :  LongInt; //4 bytes
    -     Extract_Version_Reqd   :  Word;
    -     Bit_Flag               :  Word;
    +     Extract_Version_Reqd   :  Word; //if zip64: >= 45
    +     {$warning TODO implement EFS/language enooding using UTF-8}
    +     Bit_Flag               :  Word; //"General purpose bit flag in PKZip appnote
          Compress_Method        :  Word;
          Last_Mod_Time          :  Word;
          Last_Mod_Date          :  Word;
    @@ -42,16 +46,38 @@
          Compressed_Size        :  LongWord;
          Uncompressed_Size      :  LongWord;
          Filename_Length        :  Word;
    -     Extra_Field_Length     :  Word;
    +     Extra_Field_Length     :  Word; //refers to Extensible data field size
        end;
     
    +   Extensible_Data_Field_Header_Type = Packed Record
    +     // Beginning of extra field
    +     // after local file header
    +     // after central directory header
    +     Header_ID              :  Word;
    +     //e.g. $0001 (ZIP64_HEADER_ID) Zip64 extended information extra field
    +     //$0009 OS/2: extended attributes
    +     //$000a NTFS: (Win32 really)
    +     //$000d UNIX: uid, gid etc
    +     Data_Size              :  Word; //size of following field data
    +     //... field data should follow...
    +   end;
    +
    +   Zip64_Extended_Info_Field_Type = Packed Record //goes after Extensible_Data_Field_Header_Type
    +     // overrides Local and Central Directory data
    +     // stored in extra field
    +     Original_Size          :  QWord; //Uncompressed file
    +     Compressed_Size        :  QWord; //Compressed data
    +     Relative_Hdr_Offset    :  QWord; //Offset that leads to local header record
    +     Disk_Start_Number      :  LongWord; //on which disk this file starts
    +   end;
    +
       { Define the Central Directory record types }
     
       Central_File_Header_Type = Packed Record
         Signature            :  LongInt; //4 bytes
    -    MadeBy_Version       :  Word;
    -    Extract_Version_Reqd :  Word;
    -    Bit_Flag             :  Word;
    +    MadeBy_Version       :  Word; //if zip64: lower byte >= 45
    +    Extract_Version_Reqd :  Word; //if zip64: >=45
    +    Bit_Flag             :  Word; //General purpose bit flag in PKZip appnote
         Compress_Method      :  Word;
         Last_Mod_Time        :  Word;
         Last_Mod_Date        :  Word;
    @@ -64,10 +90,11 @@
         Starting_Disk_Num    :  Word;
         Internal_Attributes  :  Word;
         External_Attributes  :  LongWord;
    -    Local_Header_Offset  :  LongWord;
    +    Local_Header_Offset  :  LongWord; //todo: use zip64 and set to 0xFFFFFFFF if needed
       End;
     
    -  End_of_Central_Dir_Type =  Packed Record
    +  End_of_Central_Dir_Type =  Packed Record //End of central directory record
    +    //1 per zip file, near end, before comment
         Signature               :  LongInt; //4 bytes
         Disk_Number             :  Word;
         Central_Dir_Start_Disk  :  Word;
    @@ -78,6 +105,26 @@
         ZipFile_Comment_Length  :  Word;
       end;
     
    +  Zip64_End_of_Central_Dir_type = Packed Record
    +    Signature                 : LongInt;
    +    Record_Size               : QWord;
    +    Version_Made_By           : Word; //lower byte >= 45
    +    Extract_Version_Reqd      : Word; //version >= 45
    +    Disk_Number               : LongWord;
    +    Central_Dir_Start_Disk    : LongWord;
    +    Entries_This_Disk         : QWord;
    +    Total_Entries             : QWord;
    +    Central_Dir_Size          : QWord;
    +    Start_Disk_Offset         : QWord;
    +  end;
    +
    +  Zip64_End_of_Central_Dir_Locator_type = Packed Record //comes after Zip64_End_of_Central_Dir_type
    +    Signature                           : LongInt;
    +    Zip64_EOCD_Start_Disk               : LongWord; //Starting disk for Zip64 End of Central Directory record
    +    Central_Dir_Zip64_EOCD_Offset       : QWord; //offset of Zip64 End of Central Directory record
    +    Total_Disks                         : LongWord; //total number of disks (contained in zip)
    +  end;
    +
     Const
       Crc_32_Tab : Array[0..255] of LongWord = (
         $00000000, $77073096, $ee0e612c, $990951ba, $076dc419, $706af48f, $e963a535, $9e6495a3,
    @@ -264,13 +311,16 @@
         FDateTime: TDateTime;
         FDiskFileName: String;
         FHeaderPos: int64;
    +    FNeedsZip64: Boolean; //flags whether filesize is big enough so we need a zip64 entry
         FOS: Byte;
    -    FSize: Integer;
    +    FSize: Int64;
         FStream: TStream;
         FCompressionLevel: TCompressionlevel;
         function GetArchiveFileName: String;
       Protected
    +    // For multi-disk support, a disk number property could be added here.
         Property HdrPos : int64 Read FHeaderPos Write FheaderPos;
    +    Property NeedsZip64 : boolean Read FNeedsZip64 Write FNeedsZip64;
       Public
         constructor Create(ACollection: TCollection); override;
         function IsDirectory: Boolean;
    @@ -280,7 +330,7 @@
       Published
         Property ArchiveFileName : String Read GetArchiveFileName Write FArchiveFileName;
         Property DiskFileName : String Read FDiskFileName Write FDiskFileName;
    -    Property Size : Integer Read FSize Write FSize;
    +    Property Size : Int64 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;
    @@ -305,22 +355,25 @@
     
       TZipper = Class(TObject)
       Private
    -    FEntries: TZipFileEntries;
    -    FZipping    : Boolean;
    -    FBufSize    : LongWord;
    -    FFileName   : String;         { Name of resulting Zip file                 }
    -    FFileComment: String;
    -    FFiles      : TStrings;
    -    FInMemSize  : Integer;
    -    FOutStream  : TStream;
    -    FInFile     : TStream;     { I/O file variables                         }
    -    LocalHdr    : Local_File_Header_Type;
    -    CentralHdr  : Central_File_Header_Type;
    -    EndHdr      : End_of_Central_Dir_Type;
    -    FOnPercent  : LongInt;
    -    FOnProgress : TProgressEvent;
    -    FOnEndOfFile : TOnEndOfFileEvent;
    -    FOnStartFile : TOnStartFileEvent;
    +    FEntries        : TZipFileEntries;
    +    FZipping        : Boolean;
    +    FBufSize        : LongWord;
    +    FFileName       : String;         { Name of resulting Zip file                 }
    +    FFileComment    : String;
    +    FFiles          : TStrings;
    +    FInMemSize      : Int64;
    +    FZipFileNeedsZip64 : Boolean; //flags whether at least one file is big enough to require a zip64 record
    +    FOutStream      : TStream;
    +    FInFile         : TStream;     { I/O file variables                         }
    +    LocalHdr        : Local_File_Header_Type;
    +    LocalZip64ExtHdr: Extensible_Data_Field_Header_Type; //Extra field header fixed to zip64 (i.e. .ID=1)
    +    LocalZip64Fld   : Zip64_Extended_Info_Field_Type; //header is in LocalZip64ExtHdr
    +    CentralHdr      : Central_File_Header_Type;
    +    EndHdr          : End_of_Central_Dir_Type;
    +    FOnPercent      : LongInt;
    +    FOnProgress     : TProgressEvent;
    +    FOnEndOfFile    : TOnEndOfFileEvent;
    +    FOnStartFile    : TOnStartFileEvent;
         function CheckEntries: Integer;
         procedure SetEntries(const AValue: TZipFileEntries);
       Protected
    @@ -327,7 +380,7 @@
         Procedure CloseInput(Item : TZipFileEntry);
         Procedure StartZipFile(Item : TZipFileEntry);
         Function  UpdateZipHeader(Item : TZipFileEntry; FZip : TStream; ACRC : LongWord;AMethod : Word; AZipVersionReqd : Word; AZipBitFlag : Word) : Boolean;
    -    Procedure BuildZipDirectory;
    +    Procedure BuildZipDirectory; //Builds central directory based on local headers
         Procedure DoEndOfFile;
         Procedure ZipOneFile(Item : TZipFileEntry); virtual;
         Function  OpenInput(Item : TZipFileEntry) : Boolean;
    @@ -335,6 +388,7 @@
         Procedure SetBufSize(Value : LongWord);
         Procedure SetFileName(Value : String);
         Function CreateCompressor(Item : TZipFileEntry; AinFile,AZipStream : TStream) : TCompressor; virtual;
    +    Property NeedsZip64 : boolean Read FZipFileNeedsZip64 Write FZipFileNeedsZip64;
       Public
         Constructor Create;
         Destructor Destroy;override;
    @@ -356,7 +410,7 @@
         Property FileComment: String Read FFileComment Write FFileComment;
         // Deprecated. Use Entries.AddFileEntry(FileName) or Entries.AddFileEntries(List) instead.
         Property Files : TStrings Read FFiles; deprecated;
    -    Property InMemSize : Integer Read FInMemSize Write FInMemSize;
    +    Property InMemSize : Int64 Read FInMemSize Write FInMemSize;
         Property Entries : TZipFileEntries Read FEntries Write SetEntries;
       end;
     
    @@ -364,12 +418,12 @@
     
       TFullZipFileEntry = Class(TZipFileEntry)
       private
    -    FCompressedSize: LongWord;
    +    FCompressedSize: QWord;
         FCompressMethod: Word;
         FCRC32: LongWord;
       Public
         Property CompressMethod : Word Read FCompressMethod;
    -    Property CompressedSize : LongWord Read FCompressedSize;
    +    Property CompressedSize : QWord Read FCompressedSize;
         property CRC32: LongWord read FCRC32 write FCRC32;
       end;
     
    @@ -402,9 +456,9 @@
         FEntries    : TFullZipFileEntries;
         FFiles      : TStrings;
         FZipStream  : TStream;     { I/O file variables                         }
    -    LocalHdr    : Local_File_Header_Type;
    +    LocalHdr    : Local_File_Header_Type; //Local header, before compressed file data
    +    LocalZip64Fld   : Zip64_Extended_Info_Field_Type; //header is in LocalZip64ExtHdr
         CentralHdr  : Central_File_Header_Type;
    -    EndHdr      : End_of_Central_Dir_Type;
     
         FOnPercent  : LongInt;
         FOnProgress : TProgressEvent;
    @@ -414,6 +468,11 @@
         Procedure OpenInput;
         Procedure CloseOutput(Item : TFullZipFileEntry; var OutStream: TStream);
         Procedure CloseInput;
    +    Procedure FindEndHeaders(
    +      out AEndHdr: End_of_Central_Dir_Type;
    +      out AEndHdrPos: Int64;
    +      out AEndZip64Hdr: Zip64_End_of_Central_Dir_type;
    +      out AEndZip64HdrPos: Int64);
         Procedure ReadZipDirectory;
         Procedure ReadZipHeader(Item : TFullZipFileEntry; out AMethod : Word);
         Procedure DoEndOfFile;
    @@ -454,14 +513,18 @@
     Implementation
     
     ResourceString
    -  SErrBufsizeChange = 'Changing buffer size is not allowed while (un)zipping';
    -  SErrFileChange = 'Changing output file name is not allowed while (un)zipping';
    -  SErrInvalidCRC = 'Invalid CRC checksum while unzipping %s';
    -  SErrCorruptZIP = 'Corrupt ZIP file %s';
    +  SErrBufsizeChange = 'Changing buffer size is not allowed while (un)zipping.';
    +  SErrFileChange = 'Changing output file name is not allowed while (un)zipping.';
    +  SErrInvalidCRC = 'Invalid CRC checksum while unzipping %s.';
    +  SErrCorruptZIP = 'Corrupt ZIP file %s.';
       SErrUnsupportedCompressionFormat = 'Unsupported compression format %d';
    -  SErrMissingFileName = 'Missing filename in entry %d';
    -  SErrMissingArchiveName = 'Missing archive filename in streamed entry %d';
    +  SErrUnsupportedMultipleDisksCD = 'A central directory split over multiple disks is unsupported.';
    +  SErrMaxEntries = 'Encountered %d file entries; maximum supported is %d.';
    +  SErrMissingFileName = 'Missing filename in entry %d.';
    +  SErrMissingArchiveName = 'Missing archive filename in streamed entry %d.';
       SErrFileDoesNotExist = 'File "%s" does not exist.';
    +  SErrFileTooLarge = 'File size %d is larger than maximum supported size %d.';
    +  SErrPosTooLarge = 'Position/offset %d is larger than maximum supported %d.';
       SErrNoFileName = 'No archive filename for examine operation.';
       SErrNoStream = 'No stream is opened.';
     
    @@ -488,6 +551,26 @@
       end;
     end;
     
    +function SwapEDFH(const Values: Extensible_Data_Field_Header_Type): Extensible_Data_Field_Header_Type;
    +begin
    +  with Values do
    +  begin
    +    Result.Header_ID := SwapEndian(Header_ID);
    +    Result.Data_Size := SwapEndian(Data_Size);
    +  end;
    +end;
    +
    +function SwapZ64EIF(const Values: Zip64_Extended_Info_Field_Type): Zip64_Extended_Info_Field_Type;
    +begin
    +  with Values do
    +  begin
    +    Result.Original_Size := SwapEndian(Original_Size);
    +    Result.Compressed_Size := SwapEndian(Compressed_Size);
    +    Result.Relative_Hdr_Offset := SwapEndian(Relative_Hdr_Offset);
    +    Result.Disk_Start_Number := SwapEndian(Disk_Start_Number);
    +  end;
    +end;
    +
     function SwapCFH(const Values: Central_File_Header_Type): Central_File_Header_Type;
     begin
       with Values do
    @@ -526,6 +609,34 @@
         Result.ZipFile_Comment_Length := SwapEndian(ZipFile_Comment_Length);
       end;
     end;
    +
    +function SwapZ64ECD(const Values: Zip64_End_of_Central_Dir_Type): Zip64_End_of_Central_Dir_Type;
    +begin
    +  with Values do
    +  begin
    +    Result.Signature := SwapEndian(Signature);
    +    Result.Record_Size := SwapEndian(Record_Size);
    +    Result.Version_Made_By := SwapEndian(Version_Made_By);
    +    Result.Extract_Version_Reqd := SwapEndian(Extract_Version_Reqd);
    +    Result.Disk_Number := SwapEndian(Disk_Number);
    +    Result.Central_Dir_Start_Disk := SwapEndian(Central_Dir_Start_Disk);
    +    Result.Entries_This_Disk := SwapEndian(Entries_This_Disk);
    +    Result.Total_Entries := SwapEndian(Total_Entries);
    +    Result.Central_Dir_Size := SwapEndian(Central_Dir_Size);
    +    Result.Start_Disk_Offset := SwapEndian(Start_Disk_Offset);
    +  end;
    +end;
    +
    +function SwapZ64ECDL(const Values: Zip64_End_of_Central_Dir_Locator_type): Zip64_End_of_Central_Dir_Locator_type;
    +begin
    +  with Values do
    +  begin
    +    Result.Signature := SwapEndian(Signature);
    +    Result.Zip64_EOCD_Start_Disk := SwapEndian(Zip64_EOCD_Start_Disk);
    +    Result.Central_Dir_Zip64_EOCD_Offset := SwapEndian(Central_Dir_Zip64_EOCD_Offset);
    +    Result.Total_Disks := SwapEndian(Total_Disks);
    +  end;
    +end;
     {$ENDIF FPC_BIG_ENDIAN}
     
     Procedure DateTimeToZipDateTime(DT : TDateTime; out ZD,ZT : Word);
    @@ -536,7 +647,21 @@
     begin
       DecodeDate(DT,Y,M,D);
       DecodeTime(DT,H,N,S,MS);
    -  Y:=Y-1980;
    +  if Y<1980 then
    +  begin
    +    // Invalid date/time; set to earliest possible
    +    Y:=0;
    +    M:=1;
    +    D:=1;
    +    H:=0;
    +    N:=0;
    +    S:=0;
    +    MS:=0;
    +  end
    +  else
    +  begin
    +    Y:=Y-1980;
    +  end;
       ZD:=d+(32*M)+(512*Y);
       ZT:=(S div 2)+(32*N)+(2048*h);
     end;
    @@ -561,8 +686,12 @@
     end;
     
     const
    -  OS_FAT = 0;
    +  OS_FAT  = 0; //MS-DOS and OS/2 (FAT/VFAT/FAT32)
       OS_UNIX = 3;
    +  OS_OS2  = 6; //OS/2 HPFS
    +  OS_NTFS = 10;
    +  OS_VFAT = 14;
    +  OS_OSX  = 19;
     
       UNIX_MASK = $F000;
       UNIX_FIFO = $1000;
    @@ -673,15 +802,14 @@
     
     
     procedure TDeflater.Compress;
    -
     Var
       Buf : PByte;
    -  I,Count,NewCount : Integer;
    +  I,Count,NewCount : integer;
       C : TCompressionStream;
    -  BytesNow : Integer;
    -  NextMark : Integer;
    -  OnBytes : Integer;
    -  FSize    : Integer;
    +  BytesNow : Int64;
    +  NextMark : Int64;
    +  OnBytes : Int64;
    +  FSize : Int64;
     begin
       CRC32Val:=$FFFFFFFF;
       Buf:=GetMem(FBufferSize);
    @@ -688,7 +816,8 @@
       if FOnPercent = 0 then
         FOnPercent := 1;
       OnBytes:=Round((FInFile.Size * FOnPercent) / 100);
    -  BytesNow:=0; NextMark := OnBytes;
    +  BytesNow:=0;
    +  NextMark := OnBytes;
       FSize:=FInfile.Size;
       Try
         C:=TCompressionStream.Create(FCompressionLevel,FOutFile,True);
    @@ -700,7 +829,7 @@
             For I:=0 to Count-1 do
               UpdC32(Buf[i]);
             NewCount:=Count;
    -        While (NewCount>0) do
    +        while (NewCount>0) do
               NewCount:=NewCount-C.Write(Buf^,NewCount);
             inc(BytesNow,Count);
             if BytesNow>NextMark Then
    @@ -856,7 +985,7 @@
       FirstCh:= TRUE;
       Crc32Val:=$FFFFFFFF;
       FOnBytes:=Round((FInFile.Size * FOnPercent) / 100);
    -  While NOT InputEof do
    +  While Not InputEof do
         begin
         Remaining:=Succ(MaxInBufIdx - InBufIdx);
         If Remaining>255 then
    @@ -871,7 +1000,7 @@
           ProcessLine(OneString);
           end;
         end;
    -   Crc32Val := NOT Crc32Val;
    +   Crc32Val := Not Crc32Val;
        ProcessLine('');
     end;
     
    @@ -994,7 +1123,7 @@
     Begin
       CurrChild := CodeTable^[Parent].Child;
       { Find first Child that has descendants .. clear any that don't }
    -  While (CurrChild <> -1) AND (CodeTable^[CurrChild].Child = -1) do
    +  While (CurrChild <> -1) and (CodeTable^[CurrChild].Child = -1) do
         begin
         CodeTable^[Parent].Child := CodeTable^[CurrChild].Sibling;
         CodeTable^[CurrChild].Sibling := -1;
    @@ -1192,7 +1321,7 @@
     Var
       F    : TZipFileEntry;
       Info : TSearchRec;
    -  I    : LongWord;
    +  I    : integer; //zip spec allows QWord but FEntries.Count does not support it
     {$IFDEF UNIX}
       UnixInfo: Stat;
     {$ENDIF}
    @@ -1272,19 +1401,36 @@
     
     Begin
       FillChar(LocalHdr,SizeOf(LocalHdr),0);
    +  FillChar(LocalZip64Fld,SizeOf(LocalZip64Fld),0);
       With LocalHdr do
         begin
         Signature := LOCAL_FILE_HEADER_SIGNATURE;
    -    Extract_Version_Reqd := 10;
    +    Extract_Version_Reqd := 10; //default value, v1.0
         Bit_Flag := 0;
         Compress_Method := 1;
         DateTimeToZipDateTime(Item.DateTime,Last_Mod_Date,Last_Mod_Time);
         Crc32 := 0;
         Compressed_Size := 0;
    -    Uncompressed_Size := Item.Size;
    +    LocalZip64Fld.Compressed_Size := 0;
    +    if Item.Size >= $FFFFFFFF then
    +      begin
    +      Uncompressed_Size := $FFFFFFFF;
    +      LocalZip64Fld.Original_Size := Item.Size;
    +      end
    +    else
    +      begin
    +      Uncompressed_Size := Item.Size;
    +      LocalZip64Fld.Original_Size := 0;
    +      end;
         FileName_Length := 0;
    -    Extra_Field_Length := 0;
    -  end ;
    +    if (LocalZip64Fld.Original_Size>0) or
    +      (LocalZip64Fld.Compressed_Size>0) or
    +      (LocalZip64Fld.Disk_Start_Number>0) or
    +      (LocalZip64Fld.Relative_Hdr_Offset>0) then
    +      Extra_Field_Length := SizeOf(LocalZip64ExtHdr) + SizeOf(LocalZip64Fld)
    +    else
    +      Extra_Field_Length := 0;
    +  end;
     End;
     
     
    @@ -1291,104 +1437,274 @@
     function TZipper.UpdateZipHeader(Item: TZipFileEntry; FZip: TStream;
       ACRC: LongWord; AMethod: Word; AZipVersionReqd: Word; AZipBitFlag: Word
       ): Boolean;
    +  // Update header for a single zip file (local header)
     var
    -  ZFileName  : ShortString;
    +  IsZip64           : boolean; //Must the local header be in zip64 format?
    +  // Separate from zip64 status of entire zip file.
    +  ZFileName         : String;
     Begin
    -  ZFileName:=Item.ArchiveFileName;
    +  ZFileName := Item.ArchiveFileName;
    +  IsZip64 := false;
       With LocalHdr do
         begin
         FileName_Length := Length(ZFileName);
         Crc32 := ACRC;
    -    Result:=Not (Compressed_Size >= Uncompressed_Size);
    +    if LocalZip64Fld.Original_Size > 0 then
    +      Result := Not (FZip.Size >= LocalZip64Fld.Original_Size)
    +    else
    +      Result := Not (Compressed_Size >= Uncompressed_Size);
    +    if Item.CompressionLevel=clNone
    +      then Result:=false; //user wishes override or invalid compression
         If Not Result then
    -      begin                     { No...                          }
    -      Compress_Method := 0;                  { ...change stowage type      }
    -      Compressed_Size := Uncompressed_Size;  { ...update compressed size   }
    +      begin
    +      Compress_Method := 0; // No use for compression: change storage type & compression size...
    +      if LocalZip64Fld.Original_Size>0 then
    +        begin
    +        IsZip64 := true;
    +        Compressed_Size := $FFFFFFFF;
    +        LocalZip64Fld.Compressed_Size := LocalZip64Fld.Original_Size;
    +        end
    +      else
    +        begin
    +        Compressed_Size := Uncompressed_Size;
    +        LocalZip64Fld.Compressed_Size := 0;
    +        end;
           end
    -    else
    +    else { Using compression }
           begin
    -      Compress_method:=AMethod;
    -      Compressed_Size := FZip.Size;
    +      Compress_method := AMethod;
           Bit_Flag := Bit_Flag or AZipBitFlag;
    +      if FZip.Size >= $FFFFFFFF then
    +      begin
    +        IsZip64 := true;
    +        Compressed_Size := $FFFFFFFF;
    +        LocalZip64Fld.Compressed_Size := FZip.Size;
    +      end
    +      else
    +      begin
    +        Compressed_Size := FZip.Size;
    +        LocalZip64Fld.Compressed_Size := 0;
    +      end;
           if AZipVersionReqd > Extract_Version_Reqd then
             Extract_Version_Reqd := AZipVersionReqd;
           end;
    +    if (IsZip64) and (Extract_Version_Reqd<45) then
    +      Extract_Version_Reqd := 45;
         end;
    +  if IsZip64 then
    +    LocalHdr.Extra_Field_Length:=SizeOf(LocalZip64ExtHdr)+SizeOf(LocalZip64Fld);
       FOutStream.WriteBuffer({$IFDEF ENDIAN_BIG}SwapLFH{$ENDIF}(LocalHdr),SizeOf(LocalHdr));
    +  // Append extensible field header+zip64 extensible field if needed:
    +  if IsZip64 then
    +  begin
    +    FOutStream.WriteBuffer({$IFDEF ENDIAN_BIG}SwapEDFH{$ENDIF}(LocalZip64ExtHdr),SizeOf(LocalZip64ExtHdr));
    +    FOutStream.WriteBuffer({$IFDEF ENDIAN_BIG}SwapZ64EIF{$ENDIF}(LocalZip64Fld),SizeOf(LocalZip64Fld));
    +  end;
       FOutStream.WriteBuffer(ZFileName[1],Length(ZFileName));
     End;
     
     
     Procedure TZipper.BuildZipDirectory;
    -
    +// Write out all central file headers using info from local headers
     Var
    -   SavePos   : int64;
    -   HdrPos    : int64;
    -   CenDirPos : int64;
    -   ACount    : Word;
    -   ZFileName  : ShortString;
    -
    +  SavePos   : Int64;
    +  HdrPos    : Int64; //offset from disk where file begins to local header
    +  CenDirPos : Int64;
    +  ACount    : QWord; //entry counter
    +  ZFileName : string; //archive filename
    +  IsZip64   : boolean; //local header=zip64 format?
    +  MinReqdVersion: word; //minimum
    +  ExtInfoHeader : Extensible_Data_Field_Header_Type;
    +  Zip64ECD  : Zip64_End_of_Central_Dir_type;
    +  Zip64ECDL : Zip64_End_of_Central_Dir_Locator_type;
     Begin
    -   ACount := 0;
    -   CenDirPos := FOutStream.Position;
    -   FOutStream.Seek(0,soBeginning);             { Rewind output file }
    -   HdrPos := FOutStream.Position;
    -   FOutStream.ReadBuffer(LocalHdr, SizeOf(LocalHdr));
    +  ACount := 0;
    +  CenDirPos := FOutStream.Position;
    +  FOutStream.Seek(0,soBeginning);             { Rewind output file }
    +  HdrPos := FOutStream.Position;
    +  FOutStream.ReadBuffer(LocalHdr, SizeOf(LocalHdr));
     {$IFDEF FPC_BIG_ENDIAN}
    -   LocalHdr := SwapLFH(LocalHdr);
    +  LocalHdr := SwapLFH(LocalHdr);
     {$ENDIF}
    -   Repeat
    -     SetLength(ZFileName,LocalHdr.FileName_Length);
    -     FOutStream.ReadBuffer(ZFileName[1], LocalHdr.FileName_Length);
    -     SavePos := FOutStream.Position;
    -     FillChar(CentralHdr,SizeOf(CentralHdr),0);
    -     With CentralHdr do
    -       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;
    -     {$IFDEF UNIX}
    -       External_Attributes := Entries[ACount].Attributes shl 16;
    -     {$ELSE}
    -       External_Attributes := Entries[ACount].Attributes;
    -     {$ENDIF}
    -       Local_Header_Offset := HdrPos;
    -       end;
    -     FOutStream.Seek(0,soEnd);
    -     FOutStream.WriteBuffer({$IFDEF FPC_BIG_ENDIAN}SwapCFH{$ENDIF}(CentralHdr),SizeOf(CentralHdr));
    -     FOutStream.WriteBuffer(ZFileName[1],Length(ZFileName));
    -     Inc(ACount);
    -     FOutStream.Seek(SavePos + LocalHdr.Compressed_Size,soBeginning);
    -     HdrPos:=FOutStream.Position;
    -     FOutStream.ReadBuffer(LocalHdr, SizeOf(LocalHdr));
    -{$IFDEF FPC_BIG_ENDIAN}
    -     LocalHdr := SwapLFH(LocalHdr);
    -{$ENDIF}
    -   Until LocalHdr.Signature = CENTRAL_FILE_HEADER_SIGNATURE;
    -   FOutStream.Seek(0,soEnd);
    -   FillChar(EndHdr,SizeOf(EndHdr),0);
    -   With EndHdr do
    -     begin
    -     Signature := END_OF_CENTRAL_DIR_SIGNATURE;
    -     Disk_Number := 0;
    -     Central_Dir_Start_Disk := 0;
    -     Entries_This_Disk := ACount;
    -     Total_Entries := ACount;
    -     Central_Dir_Size := FOutStream.Size-CenDirPos;
    -     Start_Disk_Offset := CenDirPos;
    -     ZipFile_Comment_Length := Length(FFileComment);
    -     FOutStream.WriteBuffer({$IFDEF FPC_BIG_ENDIAN}SwapECD{$ENDIF}(EndHdr), SizeOf(EndHdr));
    -     if Length(FFileComment) > 0 then
    -       FOutStream.WriteBuffer(FFileComment[1],Length(FFileComment));
    -     end;
    +  Repeat
    +    SetLength(ZFileName,LocalHdr.FileName_Length);
    +    FOutStream.ReadBuffer(ZFileName[1], LocalHdr.FileName_Length);
    +    IsZip64:=(LocalHdr.Compressed_Size=$FFFFFFFF) or (LocalHdr.Uncompressed_Size=$FFFFFFFF) or (HdrPos>=$FFFFFFFF);
    +    FillChar(LocalZip64Fld,SizeOf(LocalZip64Fld),0); // easier to check compressed length
    +    if LocalHdr.Extra_Field_Length>0 then
    +      begin
    +      SavePos := FOutStream.Position;
    +      if (IsZip64 and (LocalHdr.Extra_Field_Length>=SizeOf(LocalZip64ExtHdr)+SizeOf(LocalZip64Fld))) then
    +        while FOutStream.Position<SavePos+LocalHdr.Extra_Field_Length do
    +          begin
    +          FOutStream.ReadBuffer(ExtInfoHeader, SizeOf(ExtInfoHeader));
    +        {$IFDEF FPC_BIG_ENDIAN}
    +          ExtInfoHeader := SwapEDFH(ExtInfoHeader);
    +        {$ENDIF}
    +          if ExtInfoHeader.Header_ID=ZIP64_HEADER_ID then
    +            begin
    +            FOutStream.ReadBuffer(LocalZip64Fld, SizeOf(LocalZip64Fld));
    +          {$IFDEF FPC_BIG_ENDIAN}
    +            LocalZip64Fld := SwapZ64EIF(LocalZip64Fld);
    +          {$ENDIF}
    +            end
    +          else
    +            begin
    +            // Read past non-zip64 extra field
    +            FOutStream.Seek(ExtInfoHeader.Data_Size,soFromCurrent);
    +            end;
    +          end;
    +      // Move past extra fields
    +      FOutStream.Seek(SavePos+LocalHdr.Extra_Field_Length,soFromBeginning);
    +      end;
    +      SavePos := FOutStream.Position;
    +
    +    FillChar(CentralHdr,SizeOf(CentralHdr),0);
    +    With CentralHdr do
    +      begin
    +      Signature := CENTRAL_FILE_HEADER_SIGNATURE;
    +      MadeBy_Version := LocalHdr.Extract_Version_Reqd;
    +      if (IsZip64) and (MadeBy_Version<45) then
    +        MadeBy_Version := 45;
    +    {$IFDEF UNIX}
    +      {$IFDEF DARWIN} //OSX
    +      MadeBy_Version := MadeBy_Version or (OS_OSX shl 8);
    +      {$ELSE}
    +      MadeBy_Version := MadeBy_Version or (OS_UNIX shl 8);
    +      {$ENDIF}
    +    {$ENDIF}
    +    {$IFDEF OS2}
    +      MadeBy_Version := MadeBy_Version or (OS_OS2 shl 8);
    +    {$ENDIF}
    +      {$warning TODO: find a way to recognize VFAT and NTFS}
    +      // Copy over extract_version_reqd..extra_field_length
    +      Move(LocalHdr.Extract_Version_Reqd, Extract_Version_Reqd, 26);
    +      if (IsZip64) and (Extract_Version_Reqd<45) then
    +        Extract_Version_Reqd := 45;
    +      // Keep track of the minimum version required to extract
    +      // zip file as a whole
    +      if Extract_Version_Reqd>MinReqdVersion then
    +        MinReqdVersion:=Extract_Version_Reqd;
    +      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;
    +    {$IFDEF UNIX}
    +      External_Attributes := Entries[ACount].Attributes shl 16;
    +    {$ELSE}
    +      External_Attributes := Entries[ACount].Attributes;
    +    {$ENDIF}
    +      if HdrPos>=$FFFFFFFF then
    +      begin
    +        FZipFileNeedsZip64:=true;
    +        IsZip64:=true;
    +        Local_Header_offset := $FFFFFFFF;
    +        // LocalZip64Fld will be written out as central dir extra field later
    +        LocalZip64Fld.Relative_Hdr_Offset := HdrPos;
    +      end
    +      else
    +        Local_Header_Offset := HdrPos;
    +      end;
    +    FOutStream.Seek(0,soEnd);
    +    FOutStream.WriteBuffer({$IFDEF FPC_BIG_ENDIAN}SwapCFH{$ENDIF}(CentralHdr),SizeOf(CentralHdr));
    +    FOutStream.WriteBuffer(ZFileName[1],Length(ZFileName));
    +    if IsZip64 then
    +      begin
    +      FOutStream.Seek(0,soEnd);
    +      FOutStream.WriteBuffer({$IFDEF FPC_BIG_ENDIAN}SwapEDFH{$ENDIF}(LocalZip64ExtHdr),SizeOf(LocalZip64ExtHdr));
    +      FOutStream.WriteBuffer({$IFDEF FPC_BIG_ENDIAN}SwapZ64EIF{$ENDIF}(LocalZip64Fld),SizeOf(LocalZip64Fld));
    +      end;
    +
    +    Inc(ACount);
    +    // Move past compressed file data to next header:
    +    if LocalHdr.Compressed_Size=$FFFFFFFF then
    +      FOutStream.Seek(SavePos + LocalZip64Fld.Compressed_Size,soBeginning)
    +    else
    +      FOutStream.Seek(SavePos + LocalHdr.Compressed_Size,soBeginning);
    +    HdrPos:=FOutStream.Position;
    +    FOutStream.ReadBuffer(LocalHdr, SizeOf(LocalHdr));
    +  {$IFDEF FPC_BIG_ENDIAN}
    +    LocalHdr := SwapLFH(LocalHdr);
    +  {$ENDIF}
    +  Until LocalHdr.Signature = CENTRAL_FILE_HEADER_SIGNATURE;
    +  FOutStream.Seek(0,soEnd);
    +  FillChar(EndHdr,SizeOf(EndHdr),0);
    +
    +  // Write end of central directory record
    +  // We'll use the zip64 variants to store counts etc
    +  // and copy to the old record variables if possible
    +  // This seems to match expected behaviour of unzippers like
    +  // unrar that only look at the zip64 record
    +  FillChar(Zip64ECD, SizeOf(Zip64ECD), 0);
    +  Zip64ECD.Signature:=ZIP64_END_OF_CENTRAL_DIR_SIGNATURE;
    +  FillChar(Zip64ECDL, SizeOf(Zip64ECDL), 0);
    +  Zip64ECDL.Signature:=ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE;
    +  Zip64ECDL.Total_Disks:=1; //default and no support for multi disks yet anyway
    +  With EndHdr do
    +    begin
    +    Signature := END_OF_CENTRAL_DIR_SIGNATURE;
    +    Disk_Number := 0;
    +    Central_Dir_Start_Disk := 0;
    +
    +    Zip64ECD.Entries_This_Disk:=ACount;
    +    Zip64ECD.Total_Entries:=Acount;
    +    if ACount>$FFFF then
    +      begin
    +      FZipFileNeedsZip64 := true;
    +      Entries_This_Disk := $FFFF;
    +      Total_Entries := $FFFF;
    +      end
    +    else
    +      begin
    +      Entries_This_Disk := Zip64ECD.Entries_This_Disk;
    +      Total_Entries := Zip64ECD.Total_Entries;
    +      end;
    +
    +    Zip64ECD.Central_Dir_Size := FOutStream.Size-CenDirPos;
    +    if (Zip64ECD.Central_Dir_Size)>$FFFFFFFF then
    +      begin
    +      FZipFileNeedsZip64 := true;
    +      Central_Dir_Size := $FFFFFFFF;
    +      end
    +    else
    +      begin
    +      Central_Dir_Size := Zip64ECD.Central_Dir_Size;
    +      end;
    +
    +    Zip64ECD.Start_Disk_Offset := CenDirPos;
    +    if Zip64ECD.Start_Disk_Offset>$FFFFFFFF then
    +      begin
    +      FZipFileNeedsZip64 := true;
    +      Start_Disk_Offset := $FFFFFFFF;
    +      end
    +    else
    +      begin
    +      Start_Disk_Offset := Zip64ECD.Start_Disk_Offset;
    +      end;
    +
    +    ZipFile_Comment_Length := Length(FFileComment);
    +
    +    if FZipFileNeedsZip64 then
    +    begin
    +      //Write zip64 end of central directory record if needed
    +      if MinReqdVersion<45 then
    +        MinReqdVersion := 45;
    +      Zip64ECD.Extract_Version_Reqd := MinReqdVersion;
    +      Zip64ECD.Version_Made_By := MinReqdVersion;
    +      Zip64ECD.Record_Size := SizeOf(Zip64ECD)-12; //Assumes no variable length field following
    +      Zip64ECDL.Central_Dir_Zip64_EOCD_Offset := FOutStream.Position;
    +      Zip64ECDL.Zip64_EOCD_Start_Disk := 0;
    +      FOutStream.WriteBuffer({$IFDEF FPC_BIG_ENDIAN}SwapZ64ECD{$ENDIF}(Zip64ECD), SizeOf(Zip64ECD));
    +
    +      //Write zip64 end of central directory locator if needed
    +      FOutStream.WriteBuffer({$IFDEF FPC_BIG_ENDIAN}SwapZ64ECDL{$ENDIF}(Zip64ECDL), SizeOf(Zip64ECDL));
    +    end;
    +
    +    FOutStream.WriteBuffer({$IFDEF FPC_BIG_ENDIAN}SwapECD{$ENDIF}(EndHdr), SizeOf(EndHdr));
    +    if Length(FFileComment) > 0 then
    +      FOutStream.WriteBuffer(FFileComment[1],Length(FFileComment));
    +    end;
     end;
     
     Function TZipper.CreateCompressor(Item : TZipFileEntry; AInFile,AZipStream : TStream) : TCompressor;
    @@ -1417,6 +1733,8 @@
         else
           begin
           TmpFileName:=ChangeFileExt(FFileName,'.tmp');
    +      if TmpFileName=FFileName then
    +        TmpFileName:=TmpFileName+'.tmp';
           ZipStream:=TFileStream.Create(TmpFileName,fmCreate);
           end;
         Try
    @@ -1453,8 +1771,7 @@
     
     // Just like SaveToFile, but uses the FileName property
     Procedure TZipper.ZipAllFiles;
    -
    -Begin
    +begin
       SaveToFile(FileName);
     end;
     
    @@ -1472,29 +1789,25 @@
     
     procedure TZipper.SaveToStream(AStream: TStream);
     Var
    -   I : Integer;
    -   filecnt : integer;
    +  I : integer; //could be qword but limited by FEntries.Count
     begin
       FOutStream := AStream;
     
       If CheckEntries=0 then
         Exit;
    +
       FZipping:=True;
       Try
    -    GetFileInfo;
    +    GetFileInfo; //get info on file entries in zip
     
    -    filecnt:=0;
         for I:=0 to FEntries.Count-1 do
    -    begin
           ZipOneFile(FEntries[i]);
    -      inc(filecnt);
    -    end;
    -    if filecnt>0 then
    +    if FEntries.Count>0 then
           BuildZipDirectory;
       finally
         FZipping:=False;
         // Remove entries that have been added by CheckEntries from Files.
    -    For I:=0 to FFiles.Count-1 do
    +    for I:=0 to FFiles.Count-1 do
           FEntries.Delete(FEntries.Count-1);
       end;
     end;
    @@ -1548,7 +1861,9 @@
       ComprPct : Double;
     
     begin
    -  If (LocalHdr.Uncompressed_Size>0) then
    +  if (FZipFileNeedsZip64) and (LocalZip64Fld.Original_Size>0) then
    +    ComprPct := (100.0 * (LocalZip64Fld.Original_size - LocalZip64Fld.Compressed_Size)) / LocalZip64Fld.Original_Size
    +  else if (LocalHdr.Uncompressed_Size>0) then
         ComprPct := (100.0 * (LocalHdr.Uncompressed_Size - LocalHdr.Compressed_Size)) / LocalHdr.Uncompressed_Size
       else
         ComprPct := 0;
    @@ -1564,16 +1879,38 @@
       FFiles:=TStringList.Create;
       FEntries:=TZipFileEntries.Create(TZipFileEntry);
       FOnPercent:=1;
    +  FZipFileNeedsZip64:=false;
    +  LocalZip64ExtHdr.Header_ID:=ZIP64_HEADER_ID;
    +  LocalZip64ExtHdr.Data_Size:=SizeOf(Zip64_Extended_Info_Field_Type);
     end;
     
     Function TZipper.CheckEntries : Integer;
     
     Var
    -  I : Integer;
    +  I : integer; //Could be QWord but limited by FFiles.Count
     
     begin
    -  For I:=0 to FFiles.Count-1 do
    +  for I:=0 to FFiles.Count-1 do
         FEntries.AddFileEntry(FFiles[i]);
    +
    +  // Use zip64 when number of file entries
    +  // or individual (un)compressed sizes
    +  // require it.
    +  if FEntries.Count >= $FFFF then
    +    FZipFileNeedsZip64:=true;
    +
    +  if not(FZipFileNeedsZip64) then
    +    begin
    +    for I:=0 to FFiles.Count-1 do
    +      begin
    +      if FEntries[i].FNeedsZip64 then
    +        begin
    +        FZipFileNeedsZip64:=true;
    +        break;
    +        end;
    +      end;
    +    end;
    +
       Result:=FEntries.Count;
     end;
     
    @@ -1614,14 +1951,14 @@
       Path: String;
       OldDirectorySeparators: set of char;
     Begin
    -  { the default RTL behaviour is broken on Unix platforms
    +  { the default RTL behavior is broken on Unix platforms
         for Windows compatibility: it allows both '/' and '\'
    -    as directory separator. We don't want that behaviour
    +    as directory separator. We don't want that behavior
         here, since 'abc\' is a valid file name under Unix.
     	
    -	(mantis 15836) On the other hand, many archives on
    -	 windows have '/' as pathseparator, even Windows
    -	 generated .odt files. So we disable this for windows.
    +    (mantis 15836) On the other hand, many archives on
    +    Windows have '/' as pathseparator, even Windows
    +    generated .odt files. So we disable this for Windows.
       }
       OldDirectorySeparators:=AllowDirectorySeparators;
       {$ifndef Windows}
    @@ -1633,7 +1970,7 @@
         FOnCreateStream(Self, OutStream, Item);
       // If FOnCreateStream didn't create one, we create one now.
       If (OutStream=Nil) then
    -    Begin
    +    begin
         if (Path<>'') then
           ForceDirectories(Path);
         AllowDirectorySeparators:=OldDirectorySeparators;
    @@ -1644,7 +1981,6 @@
       Result:=True;
       If Assigned(FOnStartFile) then
         FOnStartFile(Self,OutFileName);
    -	
     End;
     
     
    @@ -1675,6 +2011,8 @@
     Var
       S : String;
       D : TDateTime;
    +  ExtraFieldHdr: Extensible_Data_Field_Header_Type;
    +  SavePos: int64; //could be qword but limited by stream
     Begin
       FZipStream.Seek(Item.HdrPos,soBeginning);
       FZipStream.ReadBuffer(LocalHdr,SizeOf(LocalHdr));
    @@ -1681,15 +2019,35 @@
     {$IFDEF FPC_BIG_ENDIAN}
       LocalHdr := SwapLFH(LocalHdr);
     {$ENDIF}
    +  FillChar(LocalZip64Fld,SizeOf(LocalZip64Fld),0); //ensure no erroneous info
       With LocalHdr do
         begin
           SetLength(S,Filename_Length);
           FZipStream.ReadBuffer(S[1],Filename_Length);
    -      //SetLength(E,Extra_Field_Length);
    -      //FZipStream.ReadBuffer(E[1],Extra_Field_Length);
    -      FZipStream.Seek(Extra_Field_Length,soCurrent);
           Item.ArchiveFileName:=S;
           Item.DiskFileName:=S;
    +      SavePos:=FZipStream.Position; //after filename, before extra fields
    +      if Extra_Field_Length>0 then
    +        begin
    +        SavePos := FZipStream.Position;
    +        if (LocalHdr.Extra_Field_Length>=SizeOf(ExtraFieldHdr)+SizeOf(LocalZip64Fld)) then
    +          while FZipStream.Position<SavePos+LocalHdr.Extra_Field_Length do
    +            begin
    +            FZipStream.ReadBuffer(ExtraFieldHdr, SizeOf(ExtraFieldHdr));
    +          {$IFDEF FPC_BIG_ENDIAN}
    +            ExtraFieldHdr := SwapEDFH(ExtraFieldHdr);
    +          {$ENDIF}
    +            if ExtraFieldHdr.Header_ID=ZIP64_HEADER_ID then
    +              begin
    +              FZipStream.ReadBuffer(LocalZip64Fld, SizeOf(LocalZip64Fld));
    +            {$IFDEF FPC_BIG_ENDIAN}
    +              LocalZip64Fld := SwapZ64EIF(LocalZip64Fld);
    +            {$ENDIF}
    +              end;
    +            end;
    +        // Move past extra fields
    +        FZipStream.Seek(SavePos+Extra_Field_Length,soFromBeginning);
    +        end;
           Item.Size:=Uncompressed_Size;
           ZipDateTimeToDateTime(Last_Mod_Date,Last_Mod_Time,D);
           Item.DateTime:=D;
    @@ -1699,64 +2057,141 @@
         end;
     End;
     
    -procedure FindEndHeader(AZip: TStream; out AEndHdr: End_of_Central_Dir_Type; out AEndHdrPos: Int64; out AZipFileComment: string);
    +procedure TUnZipper.FindEndHeaders(
    +  out AEndHdr: End_of_Central_Dir_Type;
    +  out AEndHdrPos: Int64;
    +  out AEndZip64Hdr: Zip64_End_of_Central_Dir_type;
    +  out AEndZip64HdrPos: Int64);
    +// Reads backwords from the end of the zip file,
    +// following end of central directory, and, if present
    +// zip64 end of central directory locator and
    +// zip64 end of central directory record
    +
    +// If valid regular end of directory found, AEndHdrPos>0
    +// If valid zip64 end of directory found, AEndZip64HdrPos>0
     var
    -  Buf: PByte;
    -  BufSize: Integer;
    -  I: Integer;
    +  EndZip64Locator: Zip64_End_of_Central_Dir_Locator_type;
    +  procedure SearchForSignature;
    +  // Search for end of central directory record signature
    +  // If failed, set AEndHdrPos to 0
    +  var
    +    I: Integer;
    +    Buf: PByte;
    +    BufSize: Integer;
    +    result: boolean;
    +  begin
    +    result:=false;
    +    // scan the last (64k + something) bytes for the END_OF_CENTRAL_DIR_SIGNATURE
    +    // (zip file comments are 64k max).
    +    BufSize := 65536 + SizeOf(AEndHdr) + 128;
    +    if FZipStream.Size < BufSize then
    +      BufSize := FZipStream.Size;
    +
    +    Buf := GetMem(BufSize);
    +    try
    +      FZipStream.Seek(FZipStream.Size - BufSize, soBeginning);
    +      FZipStream.ReadBuffer(Buf^, BufSize);
    +
    +      for I := BufSize - SizeOf(AEndHdr) downto 0 do
    +      begin
    +        if (Buf[I] or (Buf[I + 1] shl 8) or (Buf[I + 2] shl 16) or (Buf[I + 3] shl 24)) = END_OF_CENTRAL_DIR_SIGNATURE then
    +        begin
    +          Move(Buf[I], AEndHdr, SizeOf(AEndHdr));
    +          {$IFDEF FPC_BIG_ENDIAN}
    +          AEndHdr := SwapECD(AEndHdr);
    +          {$ENDIF}
    +          if (AEndHdr.Signature = END_OF_CENTRAL_DIR_SIGNATURE) and
    +             (I + SizeOf(AEndHdr) + AEndHdr.ZipFile_Comment_Length = BufSize) then
    +          begin
    +            AEndHdrPos := FZipStream.Size - BufSize + I;
    +            FZipStream.Seek(AEndHdrPos + SizeOf(AEndHdr), soBeginning);
    +            SetLength(FFileComment, AEndHdr.ZipFile_Comment_Length);
    +            FZipStream.ReadBuffer(FFileComment[1], Length(FFileComment));
    +            result:=true; //found it
    +            break;
    +          end;
    +        end;
    +      end;
    +    finally
    +      FreeMem(Buf);
    +    end;
    +    if not(result) then
    +    begin
    +      AEndHdrPos := 0;
    +      FillChar(AEndHdr, SizeOf(AEndHdr), 0);
    +    end;
    +  end;
    +
    +  procedure ZeroData;
    +  begin
    +    AEndHdrPos := 0;
    +    FillChar(AEndHdr, SizeOf(AEndHdr), 0);
    +    AEndZip64HdrPos:=0;
    +    FillChar(AEndZip64Hdr, SizeOf(AEndZip64Hdr), 0);
    +  end;
    +
     begin
    -  AZipFileComment := '';
    -  AEndHdrPos := AZip.Size - SizeOf(AEndHdr);
    -  if AEndHdrPos < 0 then
    +  // Zip64 records may not exist, so fill out default values
    +  FillChar(AEndZip64Hdr,SizeOf(AEndZip64Hdr), 0);
    +  AEndZip64HdrPos:=0;
    +  // Look for end of central directory record from
    +  // back of file based on signature (only way due to
    +  // variable length zip comment etc)
    +  FFileComment := '';
    +  // Zip file requires end of central dir header so
    +  // is corrupt if it is smaller than that
    +  if FZipStream.Size < SizeOf(AEndHdr) then
       begin
    -    AEndHdrPos := -1;
    -    FillChar(AEndHdr, SizeOf(AEndHdr), 0);
    +    ZeroData;
         exit;
       end;
    -  AZip.Seek(AEndHdrPos, soBeginning);
    -  AZip.ReadBuffer(AEndHdr, SizeOf(AEndHdr));
    +
    +  AEndHdrPos := FZipStream.Size - SizeOf(AEndHdr);
    +  FZipStream.Seek(AEndHdrPos, soBeginning);
    +  FZipStream.ReadBuffer(AEndHdr, SizeOf(AEndHdr));
       {$IFDEF FPC_BIG_ENDIAN}
       AEndHdr := SwapECD(AEndHdr);
       {$ENDIF}
    -  if (AEndHdr.Signature = END_OF_CENTRAL_DIR_SIGNATURE) and
    -     (AEndHdr.ZipFile_Comment_Length = 0) then
    +  // Search unless record is right at the end of the file:
    +  if (AEndHdr.Signature <> END_OF_CENTRAL_DIR_SIGNATURE) or
    +     (AEndHdr.ZipFile_Comment_Length <> 0) then
    +    SearchForSignature;
    +  if AEndHdrPos=0 then
    +  begin
    +    ZeroData;
         exit;
    +  end;
     
    -  // scan the last (64k + something) bytes for the END_OF_CENTRAL_DIR_SIGNATURE
    -  // (zip file comments are 64k max)
    -  BufSize := 65536 + SizeOf(AEndHdr) + 128;
    -  if AZip.Size < BufSize then
    -    BufSize := AZip.Size;
    -
    -  Buf := GetMem(BufSize);
    -  try
    -    AZip.Seek(AZip.Size - BufSize, soBeginning);
    -    AZip.ReadBuffer(Buf^, BufSize);
    -
    -    for I := BufSize - SizeOf(AEndHdr) downto 0 do
    +  // With a valid end of dir record, see if there's zip64
    +  // fields:
    +  FZipStream.Seek(AEndHdrPos-SizeOf(Zip64_End_of_Central_Dir_Locator_type),soBeginning);
    +  FZipStream.ReadBuffer(EndZip64Locator, SizeOf(EndZip64Locator));
    +  {$IFDEF FPC_BIG_ENDIAN}
    +  EndZip64Locator := SwapZ64ECDL(EndZip64Locator);
    +  {$ENDIF}
    +  if EndZip64Locator.Signature=ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE then
    +  begin
    +    //Read EndZip64Locator.Total_Disks when implementing multiple disks support
    +    if EndZip64Locator.Central_Dir_Zip64_EOCD_Offset>High(Int64) then
    +      raise EZipError.CreateFmt(SErrPosTooLarge,[EndZip64Locator.Central_Dir_Zip64_EOCD_Offset,High(Int64)]);
    +    AEndZip64HdrPos:=EndZip64Locator.Central_Dir_Zip64_EOCD_Offset;
    +    FZipStream.Seek(AEndZip64HdrPos, soBeginning);
    +    FZipStream.ReadBuffer(AEndZip64Hdr, SizeOf(AEndZip64Hdr));
    +    {$IFDEF FPC_BIG_ENDIAN}
    +    AEndZip64Hdr := SwapZ64ECD(AEndZip64Hdr);
    +    {$ENDIF}
    +    if AEndZip64Hdr.Signature<>ZIP64_END_OF_CENTRAL_DIR_SIGNATURE then
         begin
    -      if (Buf[I] or (Buf[I + 1] shl 8) or (Buf[I + 2] shl 16) or (Buf[I + 3] shl 24)) = END_OF_CENTRAL_DIR_SIGNATURE then
    -      begin
    -        Move(Buf[I], AEndHdr, SizeOf(AEndHdr));
    -        {$IFDEF FPC_BIG_ENDIAN}
    -        AEndHdr := SwapECD(AEndHdr);
    -        {$ENDIF}
    -        if (AEndHdr.Signature = END_OF_CENTRAL_DIR_SIGNATURE) and
    -           (I + SizeOf(AEndHdr) + AEndHdr.ZipFile_Comment_Length = BufSize) then
    -        begin
    -          AEndHdrPos := AZip.Size - BufSize + I;
    -          AZip.Seek(AEndHdrPos + SizeOf(AEndHdr), soBeginning);
    -          SetLength(AZipFileComment, AEndHdr.ZipFile_Comment_Length);
    -          AZip.ReadBuffer(AZipFileComment[1], Length(AZipFileComment));
    -          exit;
    -        end;
    -      end;
    +      //Corrupt header
    +      ZeroData;
    +      Exit;
         end;
    -
    -    AEndHdrPos := -1;
    -    FillChar(AEndHdr, SizeOf(AEndHdr), 0);
    -  finally
    -    FreeMem(Buf);
    +  end
    +  else
    +  begin
    +    // No zip64 data, so follow the offset in the end of central directory record
    +    AEndZip64HdrPos:=0;
    +    FillChar(AEndZip64Hdr, SizeOf(AEndZip64Hdr), 0);
       end;
     end;
     
    @@ -1763,20 +2198,54 @@
     Procedure TUnZipper.ReadZipDirectory;
     
     Var
    -  i : LongWord; //todo: expand to 8 bytes when introducing zip64 format
    +  EndHdr      : End_of_Central_Dir_Type;
    +  EndZip64Hdr : Zip64_End_of_Central_Dir_type;
    +  i : integer; //could be Qword but limited to number of items in collection
       EndHdrPos,
    -  CenDirPos : Int64;
    +  EndZip64HdrPos,
    +  CenDirPos,
    +  SavePos   : Int64; //could be QWord but limited to stream maximums
    +  ExtraFieldHeader : Extensible_Data_Field_Header_Type;
    +  EntriesThisDisk : QWord;
    +  Zip64Field: Zip64_Extended_Info_Field_Type;
       NewNode   : TFullZipFileEntry;
       D : TDateTime;
       S : String;
     Begin
    -  FindEndHeader(FZipStream, EndHdr, EndHdrPos, FFileComment);
    -  if EndHdrPos < 0 then
    +  FindEndHeaders(EndHdr, EndHdrPos,
    +    EndZip64Hdr, EndZip64HdrPos);
    +  if EndHdrPos=0 then
         raise EZipError.CreateFmt(SErrCorruptZIP,[FileName]);
    -  CenDirPos := EndHdr.Start_Disk_Offset;
    +  if (EndZip64HdrPos>0) and (EndZip64Hdr.Start_Disk_Offset>0) then
    +    begin
    +    if EndZip64Hdr.Start_Disk_Offset>High(Int64) then
    +      raise EZipError.CreateFmt(SErrPosTooLarge,[EndZip64Hdr.Start_Disk_Offset,High(Int64)]);
    +    CenDirPos := EndZip64Hdr.Start_Disk_Offset;
    +    end
    +  else
    +    CenDirPos := EndHdr.Start_Disk_Offset;
       FZipStream.Seek(CenDirPos,soBeginning);
       FEntries.Clear;
    -  for i:=0 to EndHdr.Entries_This_Disk-1 do
    +  if (EndZip64HdrPos>0) and (EndZip64Hdr.Entries_This_Disk>0) then
    +  begin
    +    EntriesThisDisk := EndZip64Hdr.Entries_This_Disk;
    +    if EntriesThisDisk<>EndZip64Hdr.Total_Entries then
    +      raise EZipError.Create(SErrUnsupportedMultipleDisksCD);
    +  end
    +  else
    +  begin
    +    EntriesThisDisk :=EndHdr.Entries_This_Disk;
    +    if EntriesThisDisk<>EndHdr.Total_Entries then
    +      raise EZipError.Create(SErrUnsupportedMultipleDisksCD);
    +  end;
    +
    +  // Entries are added to a collection. The max number of items
    +  // in a collection limits the entries we can process.
    +  if EntriesThisDisk>MaxInt then
    +    raise EZipError.CreateFmt(SErrMaxEntries,[EntriesThisDisk,MaxInt]);
    +
    +  // Using while instead of for loop so qword can be used on 32 bit as well.
    +  for i:=0 to EntriesThisDisk-1 do
         begin
         FZipStream.ReadBuffer(CentralHdr, SizeOf(CentralHdr));
     {$IFDEF FPC_BIG_ENDIAN}
    @@ -1787,15 +2256,18 @@
           if Signature<>CENTRAL_FILE_HEADER_SIGNATURE then
             raise EZipError.CreateFmt(SErrCorruptZIP,[FileName]);
           NewNode:=FEntries.Add as TFullZipFileEntry;
    +      // Header position will be corrected later with zip64 version, if needed..
           NewNode.HdrPos := Local_Header_Offset;
           SetLength(S,Filename_Length);
           FZipStream.ReadBuffer(S[1],Filename_Length);
    +      SavePos:=FZipStream.Position; //After fixed part of central directory...
    +      // and the filename; before any extra field(s)
           NewNode.ArchiveFileName:=S;
    +      // Size/compressed size will be adjusted by zip64 entries if needed...
           NewNode.Size:=Uncompressed_Size;
           NewNode.FCompressedSize:=Compressed_Size;
           NewNode.CRC32:=CRC32;
           NewNode.OS := MadeBy_Version shr 8;
    -
           if NewNode.OS = OS_UNIX then
             NewNode.Attributes := External_Attributes shr 16
           else
    @@ -1802,9 +2274,44 @@
             NewNode.Attributes := External_Attributes;
           ZipDateTimeToDateTime(Last_Mod_Date,Last_Mod_Time,D);
           NewNode.DateTime:=D;
    -      FZipStream.Seek(Extra_Field_Length+File_Comment_Length,soCurrent);
    +
    +      // Go through any extra fields and extract any zip64 info
    +      if Extra_Field_Length>0 then
    +        begin
    +        while (FZipStream.Position<SavePos+Extra_Field_Length) do
    +          begin
    +          FZipStream.ReadBuffer(ExtraFieldHeader, SizeOf(ExtraFieldHeader));
    +        {$IFDEF FPC_BIG_ENDIAN}
    +          ExtraFieldHeader := SwapEDFH(ExtraFieldHeader);
    +        {$ENDIF}
    +          if ExtraFieldHeader.Header_ID = ZIP64_HEADER_ID then
    +            begin
    +            FZipStream.ReadBuffer(Zip64Field, SizeOf(Zip64Field));
    +          {$IFDEF FPC_BIG_ENDIAN}
    +            Zip64Field := SwapZ64EIF(Zip64Field);
    +          {$ENDIF}
    +            if Zip64Field.Compressed_Size > 0 then
    +              NewNode.FCompressedSize := Zip64Field.Compressed_Size;
    +            if Zip64Field.Original_Size>0 then
    +              NewNode.Size := Zip64Field.Original_Size;
    +            if Zip64Field.Relative_Hdr_Offset<>0 then
    +              begin
    +              if Zip64Field.Relative_Hdr_Offset>High(Int64) then
    +                raise EZipError.CreateFmt(SErrPosTooLarge,[Zip64Field.Relative_Hdr_Offset,High(Int64)]);
    +              NewNode.HdrPos := Zip64Field.Relative_Hdr_Offset;
    +              end;
    +            end
    +          else
    +            begin
    +              // Read past non-Zip64 extra field
    +              FZipStream.Seek(ExtraFieldHeader.Data_Size,soFromCurrent);
    +            end;
    +          end;
    +        end;
    +      // Move past extra fields and file comment to next header
    +      FZipStream.Seek(SavePos+Extra_Field_Length+File_Comment_Length,soFromBeginning);
           end;
    -   end;
    +    end;
     end;
     
     Function TUnZipper.CreateDeCompressor(Item : TZipFileEntry; AMethod : Word;AZipFile,AOutFile : TStream) : TDeCompressor;
    @@ -1829,7 +2336,6 @@
       IsLink: Boolean;
       IsCustomStream: Boolean;
     
    -
       procedure DoUnzip(const Dest: TStream);
       begin
         if ZMethod=0 then
    @@ -1836,7 +2342,10 @@
         begin
           if (LocalHdr.Compressed_Size<>0) then
             begin
    -          Count:=Dest.CopyFrom(FZipStream,LocalHdr.Compressed_Size)
    +          if LocalZip64Fld.Compressed_Size>0 then
    +            Count:=Dest.CopyFrom(FZipStream,LocalZip64Fld.Compressed_Size)
    +          else
    +            Count:=Dest.CopyFrom(FZipStream,LocalHdr.Compressed_Size);
              {$warning TODO: Implement CRC Check}
             end
           else
    @@ -1860,7 +2369,6 @@
     
       IsCustomStream := Assigned(FOnCreateStream);
     
    -
       if (IsCustomStream = False) and (FOutputPath<>'') then
         OutputFileName:=IncludeTrailingPathDelimiter(FOutputPath)+OutputFileName;
     
    @@ -1874,7 +2382,6 @@
       end;
     {$ENDIF}
     
    -
       if IsCustomStream then
       begin
         try
    @@ -1915,7 +2422,6 @@
         end;
       end;
     
    -
       if Not IsCustomStream then
       begin
         // set attributes
    @@ -1925,12 +2431,12 @@
         begin
           Attrs := 0;
         {$IFDEF UNIX}
    -      if Item.OS = OS_UNIX then Attrs := Item.Attributes;
    -      if Item.OS = OS_FAT then
    +      if (Item.OS in [OS_UNIX,OS_OSX]) then Attrs := Item.Attributes;
    +      if (Item.OS in [OS_FAT,OS_NTFS,OS_OS2,OS_VFAT]) then
             Attrs := ZipFatAttrsToUnixAttrs(Item.Attributes);
         {$ELSE}
    -      if Item.OS = OS_FAT then Attrs := Item.Attributes;
    -      if Item.OS = OS_UNIX then
    +      if (Item.OS in [OS_FAT,OS_NTFS,OS_OS2,OS_VFAT]) then Attrs := Item.Attributes;
    +      if (Item.OS in [OS_UNIX,OS_OSX]) then
             Attrs := ZipUnixAttrsToFatAttrs(ExtractFileName(Item.ArchiveFileName), Item.Attributes);
         {$ENDIF}
     
    @@ -1949,9 +2455,9 @@
     
     Procedure TUnZipper.UnZipAllFiles;
     Var
    -   Item : TFullZipFileEntry;
    -   I : Integer;
    -   AllFiles : Boolean;
    +  Item : TFullZipFileEntry;
    +  I : integer; //Really QWord but limited to FEntries.Count
    +  AllFiles : Boolean;
     
     Begin
       FUnZipping:=True;
    @@ -1960,7 +2466,7 @@
         OpenInput;
         Try
           ReadZipDirectory;
    -      For I:=0 to FEntries.Count-1 do
    +      for i:=0 to FEntries.Count-1 do
             begin
             Item:=FEntries[i];
             if AllFiles or (FFiles.IndexOf(Item.ArchiveFileName)<>-1) then
    @@ -2023,11 +2529,25 @@
     
     Var
       ComprPct : Double;
    -
    +  Uncompressed: QWord;
    +  Compressed: QWord;
     begin
    -  If (LocalHdr.Uncompressed_Size>0) then
    -    ComprPct := (100.0 * (LocalHdr.Uncompressed_Size - LocalHdr.Compressed_Size)) / LocalHdr.Uncompressed_Size
    +  If LocalZip64Fld.Original_Size > 0 then
    +    Uncompressed := LocalZip64Fld.Original_Size
       else
    +    Uncompressed := LocalHdr.Uncompressed_Size;
    +
    +  If LocalZip64Fld.Compressed_Size > 0 then
    +    Compressed := LocalZip64Fld.Compressed_Size
    +  else
    +    Compressed := LocalHdr.Compressed_Size;
    +
    +  If (Compressed>0) and (Uncompressed>0) then
    +    if (Compressed>Uncompressed) then
    +      ComprPct := (-100.0 * (Compressed - Uncompressed)) / Uncompressed
    +    else
    +      ComprPct := (100.0 * (Uncompressed - Compressed)) / Uncompressed
    +  else
         ComprPct := 0;
       If Assigned(FOnEndOfFile) then
         FOnEndOfFile(Self,ComprPct);
    @@ -2091,6 +2611,8 @@
       FOS := OS_FAT;
     {$ENDIF}
       FCompressionLevel:=cldefault;
    +  FDateTime:=now;
    +  FNeedsZip64:=false;
       inherited create(ACollection);
     end;
     
    @@ -2181,6 +2703,7 @@
       For I:=0 to List.Count-1 do
         AddFileEntry(List[i]);
     end;
    +
     { TFullZipFileEntries }
     
     function TFullZipFileEntries.GetFZ(AIndex : Integer): TFullZipFileEntry;
    Index: packages/paszlib/tests/tczipper.pp
    ===================================================================
    --- packages/paszlib/tests/tczipper.pp	(revision 25488)
    +++ packages/paszlib/tests/tczipper.pp	(working copy)
    @@ -1,50 +1,114 @@
     program tczipper;
     {
         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.
    +    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,
    -    for details about the copyright.
    +    for details about the license.
     
    -    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.
    -
      **********************************************************************}
     {$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
    -  TCallBackHandler = class(TObject)
    +
    +  { TCallBackHandler }
    +
    +  TCallBackHandler = class(TObject) //Callbacks used in zip/unzip processing
    +  private
    +    FPerformChecks: boolean;
    +    FOriginalContent: string;
    +    FShowContent: boolean;
    +    FStreamResult: boolean;
       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 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;
     
    -
    -procedure TCallBackHandler.EndOfFile(Sender : TObject; Const Ratio : Double);
    +procedure TCallBackHandler.EndOfFile(Sender: TObject; const Ratio: double);
     begin
    -  if (Ratio<0) then
    +  writeln('End of file handler hit; ratio: '+floattostr(ratio));
    +  if (FPerformChecks) and (Ratio<0) then
       begin
         writeln('Found compression ratio '+floattostr(Ratio)+', which should never be lower than 0.');
    -    halt(3);
    +    halt(1);
       end;
     end;
     
    -procedure TCallBackHandler.StartOfFile(Sender : TObject; Const AFileName : String);
    +procedure TCallBackHandler.StartOfFile(Sender: TObject; const AFileName: string);
     begin
    -  if AFileName='' then
    +  writeln('Start of file handler hit; filename: '+AFileName);
    +  if (FPerformChecks) and (AFileName='') then
       begin
         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
    -  code: cardinal;
    +  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;
    +  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;
    +
    +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
       CallBackHandler: TCallBackHandler;
       CompressedFile: string;
       FileContents: TStringList;
    @@ -55,10 +119,10 @@
       OurZipper: TZipper;
       UnZipper: TUnZipper;
     begin
    -  code := 0;
    +  result:=true;
       UncompressedFile1:=SysUtils.GetTempFileName('', 'UNC');
       UncompressedFile2:=SysUtils.GetTempFileName('', 'UNC');
    -  CompressedFile:=SysUtils.GetTempFileName('', 'ZP');
    +  CompressedFile:=SysUtils.GetTempFileName('', 'CC');
     
       FileContents:=TStringList.Create;
       OurZipper:=TZipper.Create;
    @@ -93,8 +157,10 @@
         end;
     
         // Delete original files
    +    {$IFNDEF KEEPTESTFILES}
         DeleteFile(UncompressedFile1);
         DeleteFile(UncompressedFile2);
    +    {$ENDIF}
     
         // Now unzip
         Unzipper.FileName:=CompressedFile;
    @@ -109,7 +175,7 @@
           (not FileExists(UncompressedFile2)) then
         begin
           writeln('Unzip failed: could not find decompressed files.');
    -      halt(6);
    +      exit(false);
         end;
     
         // Compare hashes
    @@ -120,25 +186,510 @@
         then
         begin
           writeln('Unzip failed: uncompressed files are not the same as the originals.');
    -      halt(7);
    +      exit(false);
         end;
     
    -    if code = 0 then
    -      writeln('Basic zip/unzip tests passed')
    -    else
    -      writeln('Basic zip/unzip test failed: ', code);
       finally
         FileContents.Free;
         CallBackHandler.Free;
         OurZipper.Free;
         UnZipper.Free;
    +    {$IFNDEF KEEPTESTFILES}
         try
           if FileExists(CompressedFile) then DeleteFile(CompressedFile);
           if FileExists(UncompressedFile1) then DeleteFile(UncompressedFile1);
           if FileExists(UncompressedFile2) then DeleteFile(UncompressedFile2);
         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:=@CallBackHandler.DoCreateZipOutputStream;
    +    Unzipper.OnDoneStream:=@CallBackHandler.DoDoneOutZipStream;
    +    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:=@CallBackHandler.DoCreateZipOutputStream;
    +    Unzipper.OnDoneStream:=@CallBackHandler.DoDoneOutZipStream;
    +    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:=@CallBackHandler.DoCreateZipOutputStream;
    +    Unzipper.OnDoneStream:=@CallBackHandler.DoDoneOutZipStream;
    +    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;
    +  {$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);
     end.
    
    zip64.diff (77,403 bytes)

Relationships

related to 0024897 closedMarco van de Voort TZipper: Corrupted Compression 
related to 0028311 resolvedMichael Van Canneyt TZipper does not support files larger than 4GB 

Activities

Reinier Olislagers

2013-09-15 15:19

developer   ~0070081

Removed testzip64.zip to minimize confusion as it will be included in the upcoming patch.

Reinier Olislagers

2013-09-15 15:49

developer  

newfiles.zip (2,241 bytes)

Reinier Olislagers

2013-09-15 15:51

developer  

zip64.diff (77,403 bytes)
Index: packages/paszlib/readme.txt
===================================================================
--- packages/paszlib/readme.txt	(revision 25488)
+++ packages/paszlib/readme.txt	(working copy)
@@ -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
Index: packages/paszlib/src/zinflate.pas
===================================================================
--- packages/paszlib/src/zinflate.pas	(revision 25488)
+++ packages/paszlib/src/zinflate.pas	(working copy)
@@ -3,7 +3,7 @@
 {  inflate.c -- zlib interface to inflate modules
    Copyright (C) 1995-1998 Mark Adler
 
-  Pascal tranlastion
+  Pascal translation
   Copyright (C) 1998 by Jacques Nomssi Nzali
   For conditions of distribution and use, see copyright notice in readme.txt
 }
Index: packages/paszlib/src/zipper.pp
===================================================================
--- packages/paszlib/src/zipper.pp	(revision 25488)
+++ packages/paszlib/src/zipper.pp	(working copy)
@@ -1,7 +1,7 @@
 {
-    $Id: header,v 1.1 2000/07/13 06:33:45 michael Exp $
+    $Id: header,v 1.3 2013/05/26 06:33:45 michael Exp $
     This file is part of the Free Component Library (FCL)
-    Copyright (c) 1999-2000 by the Free Pascal development team
+    Copyright (c) 1999-2013 by the Free Pascal development team
 
     See the file COPYING.FPC, included in this distribution,
     for details about the copyright.
@@ -26,15 +26,19 @@
 
 Const
   { Signatures }
-  END_OF_CENTRAL_DIR_SIGNATURE  = $06054B50;
-  LOCAL_FILE_HEADER_SIGNATURE   = $04034B50;
-  CENTRAL_FILE_HEADER_SIGNATURE = $02014B50;
+  END_OF_CENTRAL_DIR_SIGNATURE               = $06054B50;
+  ZIP64_END_OF_CENTRAL_DIR_SIGNATURE         = $06064B50;
+  ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE = $07064B50;
+  LOCAL_FILE_HEADER_SIGNATURE                = $04034B50;
+  CENTRAL_FILE_HEADER_SIGNATURE              = $02014B50;
+  ZIP64_HEADER_ID                            = $0001;
 
 Type
-   Local_File_Header_Type = Packed Record
+   Local_File_Header_Type = Packed Record //1 per zipped file
      Signature              :  LongInt; //4 bytes
-     Extract_Version_Reqd   :  Word;
-     Bit_Flag               :  Word;
+     Extract_Version_Reqd   :  Word; //if zip64: >= 45
+     {$warning TODO implement EFS/language enooding using UTF-8}
+     Bit_Flag               :  Word; //"General purpose bit flag in PKZip appnote
      Compress_Method        :  Word;
      Last_Mod_Time          :  Word;
      Last_Mod_Date          :  Word;
@@ -42,16 +46,38 @@
      Compressed_Size        :  LongWord;
      Uncompressed_Size      :  LongWord;
      Filename_Length        :  Word;
-     Extra_Field_Length     :  Word;
+     Extra_Field_Length     :  Word; //refers to Extensible data field size
    end;
 
+   Extensible_Data_Field_Header_Type = Packed Record
+     // Beginning of extra field
+     // after local file header
+     // after central directory header
+     Header_ID              :  Word;
+     //e.g. $0001 (ZIP64_HEADER_ID) Zip64 extended information extra field
+     //$0009 OS/2: extended attributes
+     //$000a NTFS: (Win32 really)
+     //$000d UNIX: uid, gid etc
+     Data_Size              :  Word; //size of following field data
+     //... field data should follow...
+   end;
+
+   Zip64_Extended_Info_Field_Type = Packed Record //goes after Extensible_Data_Field_Header_Type
+     // overrides Local and Central Directory data
+     // stored in extra field
+     Original_Size          :  QWord; //Uncompressed file
+     Compressed_Size        :  QWord; //Compressed data
+     Relative_Hdr_Offset    :  QWord; //Offset that leads to local header record
+     Disk_Start_Number      :  LongWord; //on which disk this file starts
+   end;
+
   { Define the Central Directory record types }
 
   Central_File_Header_Type = Packed Record
     Signature            :  LongInt; //4 bytes
-    MadeBy_Version       :  Word;
-    Extract_Version_Reqd :  Word;
-    Bit_Flag             :  Word;
+    MadeBy_Version       :  Word; //if zip64: lower byte >= 45
+    Extract_Version_Reqd :  Word; //if zip64: >=45
+    Bit_Flag             :  Word; //General purpose bit flag in PKZip appnote
     Compress_Method      :  Word;
     Last_Mod_Time        :  Word;
     Last_Mod_Date        :  Word;
@@ -64,10 +90,11 @@
     Starting_Disk_Num    :  Word;
     Internal_Attributes  :  Word;
     External_Attributes  :  LongWord;
-    Local_Header_Offset  :  LongWord;
+    Local_Header_Offset  :  LongWord; //todo: use zip64 and set to 0xFFFFFFFF if needed
   End;
 
-  End_of_Central_Dir_Type =  Packed Record
+  End_of_Central_Dir_Type =  Packed Record //End of central directory record
+    //1 per zip file, near end, before comment
     Signature               :  LongInt; //4 bytes
     Disk_Number             :  Word;
     Central_Dir_Start_Disk  :  Word;
@@ -78,6 +105,26 @@
     ZipFile_Comment_Length  :  Word;
   end;
 
+  Zip64_End_of_Central_Dir_type = Packed Record
+    Signature                 : LongInt;
+    Record_Size               : QWord;
+    Version_Made_By           : Word; //lower byte >= 45
+    Extract_Version_Reqd      : Word; //version >= 45
+    Disk_Number               : LongWord;
+    Central_Dir_Start_Disk    : LongWord;
+    Entries_This_Disk         : QWord;
+    Total_Entries             : QWord;
+    Central_Dir_Size          : QWord;
+    Start_Disk_Offset         : QWord;
+  end;
+
+  Zip64_End_of_Central_Dir_Locator_type = Packed Record //comes after Zip64_End_of_Central_Dir_type
+    Signature                           : LongInt;
+    Zip64_EOCD_Start_Disk               : LongWord; //Starting disk for Zip64 End of Central Directory record
+    Central_Dir_Zip64_EOCD_Offset       : QWord; //offset of Zip64 End of Central Directory record
+    Total_Disks                         : LongWord; //total number of disks (contained in zip)
+  end;
+
 Const
   Crc_32_Tab : Array[0..255] of LongWord = (
     $00000000, $77073096, $ee0e612c, $990951ba, $076dc419, $706af48f, $e963a535, $9e6495a3,
@@ -264,13 +311,16 @@
     FDateTime: TDateTime;
     FDiskFileName: String;
     FHeaderPos: int64;
+    FNeedsZip64: Boolean; //flags whether filesize is big enough so we need a zip64 entry
     FOS: Byte;
-    FSize: Integer;
+    FSize: Int64;
     FStream: TStream;
     FCompressionLevel: TCompressionlevel;
     function GetArchiveFileName: String;
   Protected
+    // For multi-disk support, a disk number property could be added here.
     Property HdrPos : int64 Read FHeaderPos Write FheaderPos;
+    Property NeedsZip64 : boolean Read FNeedsZip64 Write FNeedsZip64;
   Public
     constructor Create(ACollection: TCollection); override;
     function IsDirectory: Boolean;
@@ -280,7 +330,7 @@
   Published
     Property ArchiveFileName : String Read GetArchiveFileName Write FArchiveFileName;
     Property DiskFileName : String Read FDiskFileName Write FDiskFileName;
-    Property Size : Integer Read FSize Write FSize;
+    Property Size : Int64 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;
@@ -305,22 +355,25 @@
 
   TZipper = Class(TObject)
   Private
-    FEntries: TZipFileEntries;
-    FZipping    : Boolean;
-    FBufSize    : LongWord;
-    FFileName   : String;         { Name of resulting Zip file                 }
-    FFileComment: String;
-    FFiles      : TStrings;
-    FInMemSize  : Integer;
-    FOutStream  : TStream;
-    FInFile     : TStream;     { I/O file variables                         }
-    LocalHdr    : Local_File_Header_Type;
-    CentralHdr  : Central_File_Header_Type;
-    EndHdr      : End_of_Central_Dir_Type;
-    FOnPercent  : LongInt;
-    FOnProgress : TProgressEvent;
-    FOnEndOfFile : TOnEndOfFileEvent;
-    FOnStartFile : TOnStartFileEvent;
+    FEntries        : TZipFileEntries;
+    FZipping        : Boolean;
+    FBufSize        : LongWord;
+    FFileName       : String;         { Name of resulting Zip file                 }
+    FFileComment    : String;
+    FFiles          : TStrings;
+    FInMemSize      : Int64;
+    FZipFileNeedsZip64 : Boolean; //flags whether at least one file is big enough to require a zip64 record
+    FOutStream      : TStream;
+    FInFile         : TStream;     { I/O file variables                         }
+    LocalHdr        : Local_File_Header_Type;
+    LocalZip64ExtHdr: Extensible_Data_Field_Header_Type; //Extra field header fixed to zip64 (i.e. .ID=1)
+    LocalZip64Fld   : Zip64_Extended_Info_Field_Type; //header is in LocalZip64ExtHdr
+    CentralHdr      : Central_File_Header_Type;
+    EndHdr          : End_of_Central_Dir_Type;
+    FOnPercent      : LongInt;
+    FOnProgress     : TProgressEvent;
+    FOnEndOfFile    : TOnEndOfFileEvent;
+    FOnStartFile    : TOnStartFileEvent;
     function CheckEntries: Integer;
     procedure SetEntries(const AValue: TZipFileEntries);
   Protected
@@ -327,7 +380,7 @@
     Procedure CloseInput(Item : TZipFileEntry);
     Procedure StartZipFile(Item : TZipFileEntry);
     Function  UpdateZipHeader(Item : TZipFileEntry; FZip : TStream; ACRC : LongWord;AMethod : Word; AZipVersionReqd : Word; AZipBitFlag : Word) : Boolean;
-    Procedure BuildZipDirectory;
+    Procedure BuildZipDirectory; //Builds central directory based on local headers
     Procedure DoEndOfFile;
     Procedure ZipOneFile(Item : TZipFileEntry); virtual;
     Function  OpenInput(Item : TZipFileEntry) : Boolean;
@@ -335,6 +388,7 @@
     Procedure SetBufSize(Value : LongWord);
     Procedure SetFileName(Value : String);
     Function CreateCompressor(Item : TZipFileEntry; AinFile,AZipStream : TStream) : TCompressor; virtual;
+    Property NeedsZip64 : boolean Read FZipFileNeedsZip64 Write FZipFileNeedsZip64;
   Public
     Constructor Create;
     Destructor Destroy;override;
@@ -356,7 +410,7 @@
     Property FileComment: String Read FFileComment Write FFileComment;
     // Deprecated. Use Entries.AddFileEntry(FileName) or Entries.AddFileEntries(List) instead.
     Property Files : TStrings Read FFiles; deprecated;
-    Property InMemSize : Integer Read FInMemSize Write FInMemSize;
+    Property InMemSize : Int64 Read FInMemSize Write FInMemSize;
     Property Entries : TZipFileEntries Read FEntries Write SetEntries;
   end;
 
@@ -364,12 +418,12 @@
 
   TFullZipFileEntry = Class(TZipFileEntry)
   private
-    FCompressedSize: LongWord;
+    FCompressedSize: QWord;
     FCompressMethod: Word;
     FCRC32: LongWord;
   Public
     Property CompressMethod : Word Read FCompressMethod;
-    Property CompressedSize : LongWord Read FCompressedSize;
+    Property CompressedSize : QWord Read FCompressedSize;
     property CRC32: LongWord read FCRC32 write FCRC32;
   end;
 
@@ -402,9 +456,9 @@
     FEntries    : TFullZipFileEntries;
     FFiles      : TStrings;
     FZipStream  : TStream;     { I/O file variables                         }
-    LocalHdr    : Local_File_Header_Type;
+    LocalHdr    : Local_File_Header_Type; //Local header, before compressed file data
+    LocalZip64Fld   : Zip64_Extended_Info_Field_Type; //header is in LocalZip64ExtHdr
     CentralHdr  : Central_File_Header_Type;
-    EndHdr      : End_of_Central_Dir_Type;
 
     FOnPercent  : LongInt;
     FOnProgress : TProgressEvent;
@@ -414,6 +468,11 @@
     Procedure OpenInput;
     Procedure CloseOutput(Item : TFullZipFileEntry; var OutStream: TStream);
     Procedure CloseInput;
+    Procedure FindEndHeaders(
+      out AEndHdr: End_of_Central_Dir_Type;
+      out AEndHdrPos: Int64;
+      out AEndZip64Hdr: Zip64_End_of_Central_Dir_type;
+      out AEndZip64HdrPos: Int64);
     Procedure ReadZipDirectory;
     Procedure ReadZipHeader(Item : TFullZipFileEntry; out AMethod : Word);
     Procedure DoEndOfFile;
@@ -454,14 +513,18 @@
 Implementation
 
 ResourceString
-  SErrBufsizeChange = 'Changing buffer size is not allowed while (un)zipping';
-  SErrFileChange = 'Changing output file name is not allowed while (un)zipping';
-  SErrInvalidCRC = 'Invalid CRC checksum while unzipping %s';
-  SErrCorruptZIP = 'Corrupt ZIP file %s';
+  SErrBufsizeChange = 'Changing buffer size is not allowed while (un)zipping.';
+  SErrFileChange = 'Changing output file name is not allowed while (un)zipping.';
+  SErrInvalidCRC = 'Invalid CRC checksum while unzipping %s.';
+  SErrCorruptZIP = 'Corrupt ZIP file %s.';
   SErrUnsupportedCompressionFormat = 'Unsupported compression format %d';
-  SErrMissingFileName = 'Missing filename in entry %d';
-  SErrMissingArchiveName = 'Missing archive filename in streamed entry %d';
+  SErrUnsupportedMultipleDisksCD = 'A central directory split over multiple disks is unsupported.';
+  SErrMaxEntries = 'Encountered %d file entries; maximum supported is %d.';
+  SErrMissingFileName = 'Missing filename in entry %d.';
+  SErrMissingArchiveName = 'Missing archive filename in streamed entry %d.';
   SErrFileDoesNotExist = 'File "%s" does not exist.';
+  SErrFileTooLarge = 'File size %d is larger than maximum supported size %d.';
+  SErrPosTooLarge = 'Position/offset %d is larger than maximum supported %d.';
   SErrNoFileName = 'No archive filename for examine operation.';
   SErrNoStream = 'No stream is opened.';
 
@@ -488,6 +551,26 @@
   end;
 end;
 
+function SwapEDFH(const Values: Extensible_Data_Field_Header_Type): Extensible_Data_Field_Header_Type;
+begin
+  with Values do
+  begin
+    Result.Header_ID := SwapEndian(Header_ID);
+    Result.Data_Size := SwapEndian(Data_Size);
+  end;
+end;
+
+function SwapZ64EIF(const Values: Zip64_Extended_Info_Field_Type): Zip64_Extended_Info_Field_Type;
+begin
+  with Values do
+  begin
+    Result.Original_Size := SwapEndian(Original_Size);
+    Result.Compressed_Size := SwapEndian(Compressed_Size);
+    Result.Relative_Hdr_Offset := SwapEndian(Relative_Hdr_Offset);
+    Result.Disk_Start_Number := SwapEndian(Disk_Start_Number);
+  end;
+end;
+
 function SwapCFH(const Values: Central_File_Header_Type): Central_File_Header_Type;
 begin
   with Values do
@@ -526,6 +609,34 @@
     Result.ZipFile_Comment_Length := SwapEndian(ZipFile_Comment_Length);
   end;
 end;
+
+function SwapZ64ECD(const Values: Zip64_End_of_Central_Dir_Type): Zip64_End_of_Central_Dir_Type;
+begin
+  with Values do
+  begin
+    Result.Signature := SwapEndian(Signature);
+    Result.Record_Size := SwapEndian(Record_Size);
+    Result.Version_Made_By := SwapEndian(Version_Made_By);
+    Result.Extract_Version_Reqd := SwapEndian(Extract_Version_Reqd);
+    Result.Disk_Number := SwapEndian(Disk_Number);
+    Result.Central_Dir_Start_Disk := SwapEndian(Central_Dir_Start_Disk);
+    Result.Entries_This_Disk := SwapEndian(Entries_This_Disk);
+    Result.Total_Entries := SwapEndian(Total_Entries);
+    Result.Central_Dir_Size := SwapEndian(Central_Dir_Size);
+    Result.Start_Disk_Offset := SwapEndian(Start_Disk_Offset);
+  end;
+end;
+
+function SwapZ64ECDL(const Values: Zip64_End_of_Central_Dir_Locator_type): Zip64_End_of_Central_Dir_Locator_type;
+begin
+  with Values do
+  begin
+    Result.Signature := SwapEndian(Signature);
+    Result.Zip64_EOCD_Start_Disk := SwapEndian(Zip64_EOCD_Start_Disk);
+    Result.Central_Dir_Zip64_EOCD_Offset := SwapEndian(Central_Dir_Zip64_EOCD_Offset);
+    Result.Total_Disks := SwapEndian(Total_Disks);
+  end;
+end;
 {$ENDIF FPC_BIG_ENDIAN}
 
 Procedure DateTimeToZipDateTime(DT : TDateTime; out ZD,ZT : Word);
@@ -536,7 +647,21 @@
 begin
   DecodeDate(DT,Y,M,D);
   DecodeTime(DT,H,N,S,MS);
-  Y:=Y-1980;
+  if Y<1980 then
+  begin
+    // Invalid date/time; set to earliest possible
+    Y:=0;
+    M:=1;
+    D:=1;
+    H:=0;
+    N:=0;
+    S:=0;
+    MS:=0;
+  end
+  else
+  begin
+    Y:=Y-1980;
+  end;
   ZD:=d+(32*M)+(512*Y);
   ZT:=(S div 2)+(32*N)+(2048*h);
 end;
@@ -561,8 +686,12 @@
 end;
 
 const
-  OS_FAT = 0;
+  OS_FAT  = 0; //MS-DOS and OS/2 (FAT/VFAT/FAT32)
   OS_UNIX = 3;
+  OS_OS2  = 6; //OS/2 HPFS
+  OS_NTFS = 10;
+  OS_VFAT = 14;
+  OS_OSX  = 19;
 
   UNIX_MASK = $F000;
   UNIX_FIFO = $1000;
@@ -673,15 +802,14 @@
 
 
 procedure TDeflater.Compress;
-
 Var
   Buf : PByte;
-  I,Count,NewCount : Integer;
+  I,Count,NewCount : integer;
   C : TCompressionStream;
-  BytesNow : Integer;
-  NextMark : Integer;
-  OnBytes : Integer;
-  FSize    : Integer;
+  BytesNow : Int64;
+  NextMark : Int64;
+  OnBytes : Int64;
+  FSize : Int64;
 begin
   CRC32Val:=$FFFFFFFF;
   Buf:=GetMem(FBufferSize);
@@ -688,7 +816,8 @@
   if FOnPercent = 0 then
     FOnPercent := 1;
   OnBytes:=Round((FInFile.Size * FOnPercent) / 100);
-  BytesNow:=0; NextMark := OnBytes;
+  BytesNow:=0;
+  NextMark := OnBytes;
   FSize:=FInfile.Size;
   Try
     C:=TCompressionStream.Create(FCompressionLevel,FOutFile,True);
@@ -700,7 +829,7 @@
         For I:=0 to Count-1 do
           UpdC32(Buf[i]);
         NewCount:=Count;
-        While (NewCount>0) do
+        while (NewCount>0) do
           NewCount:=NewCount-C.Write(Buf^,NewCount);
         inc(BytesNow,Count);
         if BytesNow>NextMark Then
@@ -856,7 +985,7 @@
   FirstCh:= TRUE;
   Crc32Val:=$FFFFFFFF;
   FOnBytes:=Round((FInFile.Size * FOnPercent) / 100);
-  While NOT InputEof do
+  While Not InputEof do
     begin
     Remaining:=Succ(MaxInBufIdx - InBufIdx);
     If Remaining>255 then
@@ -871,7 +1000,7 @@
       ProcessLine(OneString);
       end;
     end;
-   Crc32Val := NOT Crc32Val;
+   Crc32Val := Not Crc32Val;
    ProcessLine('');
 end;
 
@@ -994,7 +1123,7 @@
 Begin
   CurrChild := CodeTable^[Parent].Child;
   { Find first Child that has descendants .. clear any that don't }
-  While (CurrChild <> -1) AND (CodeTable^[CurrChild].Child = -1) do
+  While (CurrChild <> -1) and (CodeTable^[CurrChild].Child = -1) do
     begin
     CodeTable^[Parent].Child := CodeTable^[CurrChild].Sibling;
     CodeTable^[CurrChild].Sibling := -1;
@@ -1192,7 +1321,7 @@
 Var
   F    : TZipFileEntry;
   Info : TSearchRec;
-  I    : LongWord;
+  I    : integer; //zip spec allows QWord but FEntries.Count does not support it
 {$IFDEF UNIX}
   UnixInfo: Stat;
 {$ENDIF}
@@ -1272,19 +1401,36 @@
 
 Begin
   FillChar(LocalHdr,SizeOf(LocalHdr),0);
+  FillChar(LocalZip64Fld,SizeOf(LocalZip64Fld),0);
   With LocalHdr do
     begin
     Signature := LOCAL_FILE_HEADER_SIGNATURE;
-    Extract_Version_Reqd := 10;
+    Extract_Version_Reqd := 10; //default value, v1.0
     Bit_Flag := 0;
     Compress_Method := 1;
     DateTimeToZipDateTime(Item.DateTime,Last_Mod_Date,Last_Mod_Time);
     Crc32 := 0;
     Compressed_Size := 0;
-    Uncompressed_Size := Item.Size;
+    LocalZip64Fld.Compressed_Size := 0;
+    if Item.Size >= $FFFFFFFF then
+      begin
+      Uncompressed_Size := $FFFFFFFF;
+      LocalZip64Fld.Original_Size := Item.Size;
+      end
+    else
+      begin
+      Uncompressed_Size := Item.Size;
+      LocalZip64Fld.Original_Size := 0;
+      end;
     FileName_Length := 0;
-    Extra_Field_Length := 0;
-  end ;
+    if (LocalZip64Fld.Original_Size>0) or
+      (LocalZip64Fld.Compressed_Size>0) or
+      (LocalZip64Fld.Disk_Start_Number>0) or
+      (LocalZip64Fld.Relative_Hdr_Offset>0) then
+      Extra_Field_Length := SizeOf(LocalZip64ExtHdr) + SizeOf(LocalZip64Fld)
+    else
+      Extra_Field_Length := 0;
+  end;
 End;
 
 
@@ -1291,104 +1437,274 @@
 function TZipper.UpdateZipHeader(Item: TZipFileEntry; FZip: TStream;
   ACRC: LongWord; AMethod: Word; AZipVersionReqd: Word; AZipBitFlag: Word
   ): Boolean;
+  // Update header for a single zip file (local header)
 var
-  ZFileName  : ShortString;
+  IsZip64           : boolean; //Must the local header be in zip64 format?
+  // Separate from zip64 status of entire zip file.
+  ZFileName         : String;
 Begin
-  ZFileName:=Item.ArchiveFileName;
+  ZFileName := Item.ArchiveFileName;
+  IsZip64 := false;
   With LocalHdr do
     begin
     FileName_Length := Length(ZFileName);
     Crc32 := ACRC;
-    Result:=Not (Compressed_Size >= Uncompressed_Size);
+    if LocalZip64Fld.Original_Size > 0 then
+      Result := Not (FZip.Size >= LocalZip64Fld.Original_Size)
+    else
+      Result := Not (Compressed_Size >= Uncompressed_Size);
+    if Item.CompressionLevel=clNone
+      then Result:=false; //user wishes override or invalid compression
     If Not Result then
-      begin                     { No...                          }
-      Compress_Method := 0;                  { ...change stowage type      }
-      Compressed_Size := Uncompressed_Size;  { ...update compressed size   }
+      begin
+      Compress_Method := 0; // No use for compression: change storage type & compression size...
+      if LocalZip64Fld.Original_Size>0 then
+        begin
+        IsZip64 := true;
+        Compressed_Size := $FFFFFFFF;
+        LocalZip64Fld.Compressed_Size := LocalZip64Fld.Original_Size;
+        end
+      else
+        begin
+        Compressed_Size := Uncompressed_Size;
+        LocalZip64Fld.Compressed_Size := 0;
+        end;
       end
-    else
+    else { Using compression }
       begin
-      Compress_method:=AMethod;
-      Compressed_Size := FZip.Size;
+      Compress_method := AMethod;
       Bit_Flag := Bit_Flag or AZipBitFlag;
+      if FZip.Size >= $FFFFFFFF then
+      begin
+        IsZip64 := true;
+        Compressed_Size := $FFFFFFFF;
+        LocalZip64Fld.Compressed_Size := FZip.Size;
+      end
+      else
+      begin
+        Compressed_Size := FZip.Size;
+        LocalZip64Fld.Compressed_Size := 0;
+      end;
       if AZipVersionReqd > Extract_Version_Reqd then
         Extract_Version_Reqd := AZipVersionReqd;
       end;
+    if (IsZip64) and (Extract_Version_Reqd<45) then
+      Extract_Version_Reqd := 45;
     end;
+  if IsZip64 then
+    LocalHdr.Extra_Field_Length:=SizeOf(LocalZip64ExtHdr)+SizeOf(LocalZip64Fld);
   FOutStream.WriteBuffer({$IFDEF ENDIAN_BIG}SwapLFH{$ENDIF}(LocalHdr),SizeOf(LocalHdr));
+  // Append extensible field header+zip64 extensible field if needed:
+  if IsZip64 then
+  begin
+    FOutStream.WriteBuffer({$IFDEF ENDIAN_BIG}SwapEDFH{$ENDIF}(LocalZip64ExtHdr),SizeOf(LocalZip64ExtHdr));
+    FOutStream.WriteBuffer({$IFDEF ENDIAN_BIG}SwapZ64EIF{$ENDIF}(LocalZip64Fld),SizeOf(LocalZip64Fld));
+  end;
   FOutStream.WriteBuffer(ZFileName[1],Length(ZFileName));
 End;
 
 
 Procedure TZipper.BuildZipDirectory;
-
+// Write out all central file headers using info from local headers
 Var
-   SavePos   : int64;
-   HdrPos    : int64;
-   CenDirPos : int64;
-   ACount    : Word;
-   ZFileName  : ShortString;
-
+  SavePos   : Int64;
+  HdrPos    : Int64; //offset from disk where file begins to local header
+  CenDirPos : Int64;
+  ACount    : QWord; //entry counter
+  ZFileName : string; //archive filename
+  IsZip64   : boolean; //local header=zip64 format?
+  MinReqdVersion: word; //minimum
+  ExtInfoHeader : Extensible_Data_Field_Header_Type;
+  Zip64ECD  : Zip64_End_of_Central_Dir_type;
+  Zip64ECDL : Zip64_End_of_Central_Dir_Locator_type;
 Begin
-   ACount := 0;
-   CenDirPos := FOutStream.Position;
-   FOutStream.Seek(0,soBeginning);             { Rewind output file }
-   HdrPos := FOutStream.Position;
-   FOutStream.ReadBuffer(LocalHdr, SizeOf(LocalHdr));
+  ACount := 0;
+  CenDirPos := FOutStream.Position;
+  FOutStream.Seek(0,soBeginning);             { Rewind output file }
+  HdrPos := FOutStream.Position;
+  FOutStream.ReadBuffer(LocalHdr, SizeOf(LocalHdr));
 {$IFDEF FPC_BIG_ENDIAN}
-   LocalHdr := SwapLFH(LocalHdr);
+  LocalHdr := SwapLFH(LocalHdr);
 {$ENDIF}
-   Repeat
-     SetLength(ZFileName,LocalHdr.FileName_Length);
-     FOutStream.ReadBuffer(ZFileName[1], LocalHdr.FileName_Length);
-     SavePos := FOutStream.Position;
-     FillChar(CentralHdr,SizeOf(CentralHdr),0);
-     With CentralHdr do
-       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;
-     {$IFDEF UNIX}
-       External_Attributes := Entries[ACount].Attributes shl 16;
-     {$ELSE}
-       External_Attributes := Entries[ACount].Attributes;
-     {$ENDIF}
-       Local_Header_Offset := HdrPos;
-       end;
-     FOutStream.Seek(0,soEnd);
-     FOutStream.WriteBuffer({$IFDEF FPC_BIG_ENDIAN}SwapCFH{$ENDIF}(CentralHdr),SizeOf(CentralHdr));
-     FOutStream.WriteBuffer(ZFileName[1],Length(ZFileName));
-     Inc(ACount);
-     FOutStream.Seek(SavePos + LocalHdr.Compressed_Size,soBeginning);
-     HdrPos:=FOutStream.Position;
-     FOutStream.ReadBuffer(LocalHdr, SizeOf(LocalHdr));
-{$IFDEF FPC_BIG_ENDIAN}
-     LocalHdr := SwapLFH(LocalHdr);
-{$ENDIF}
-   Until LocalHdr.Signature = CENTRAL_FILE_HEADER_SIGNATURE;
-   FOutStream.Seek(0,soEnd);
-   FillChar(EndHdr,SizeOf(EndHdr),0);
-   With EndHdr do
-     begin
-     Signature := END_OF_CENTRAL_DIR_SIGNATURE;
-     Disk_Number := 0;
-     Central_Dir_Start_Disk := 0;
-     Entries_This_Disk := ACount;
-     Total_Entries := ACount;
-     Central_Dir_Size := FOutStream.Size-CenDirPos;
-     Start_Disk_Offset := CenDirPos;
-     ZipFile_Comment_Length := Length(FFileComment);
-     FOutStream.WriteBuffer({$IFDEF FPC_BIG_ENDIAN}SwapECD{$ENDIF}(EndHdr), SizeOf(EndHdr));
-     if Length(FFileComment) > 0 then
-       FOutStream.WriteBuffer(FFileComment[1],Length(FFileComment));
-     end;
+  Repeat
+    SetLength(ZFileName,LocalHdr.FileName_Length);
+    FOutStream.ReadBuffer(ZFileName[1], LocalHdr.FileName_Length);
+    IsZip64:=(LocalHdr.Compressed_Size=$FFFFFFFF) or (LocalHdr.Uncompressed_Size=$FFFFFFFF) or (HdrPos>=$FFFFFFFF);
+    FillChar(LocalZip64Fld,SizeOf(LocalZip64Fld),0); // easier to check compressed length
+    if LocalHdr.Extra_Field_Length>0 then
+      begin
+      SavePos := FOutStream.Position;
+      if (IsZip64 and (LocalHdr.Extra_Field_Length>=SizeOf(LocalZip64ExtHdr)+SizeOf(LocalZip64Fld))) then
+        while FOutStream.Position<SavePos+LocalHdr.Extra_Field_Length do
+          begin
+          FOutStream.ReadBuffer(ExtInfoHeader, SizeOf(ExtInfoHeader));
+        {$IFDEF FPC_BIG_ENDIAN}
+          ExtInfoHeader := SwapEDFH(ExtInfoHeader);
+        {$ENDIF}
+          if ExtInfoHeader.Header_ID=ZIP64_HEADER_ID then
+            begin
+            FOutStream.ReadBuffer(LocalZip64Fld, SizeOf(LocalZip64Fld));
+          {$IFDEF FPC_BIG_ENDIAN}
+            LocalZip64Fld := SwapZ64EIF(LocalZip64Fld);
+          {$ENDIF}
+            end
+          else
+            begin
+            // Read past non-zip64 extra field
+            FOutStream.Seek(ExtInfoHeader.Data_Size,soFromCurrent);
+            end;
+          end;
+      // Move past extra fields
+      FOutStream.Seek(SavePos+LocalHdr.Extra_Field_Length,soFromBeginning);
+      end;
+      SavePos := FOutStream.Position;
+
+    FillChar(CentralHdr,SizeOf(CentralHdr),0);
+    With CentralHdr do
+      begin
+      Signature := CENTRAL_FILE_HEADER_SIGNATURE;
+      MadeBy_Version := LocalHdr.Extract_Version_Reqd;
+      if (IsZip64) and (MadeBy_Version<45) then
+        MadeBy_Version := 45;
+    {$IFDEF UNIX}
+      {$IFDEF DARWIN} //OSX
+      MadeBy_Version := MadeBy_Version or (OS_OSX shl 8);
+      {$ELSE}
+      MadeBy_Version := MadeBy_Version or (OS_UNIX shl 8);
+      {$ENDIF}
+    {$ENDIF}
+    {$IFDEF OS2}
+      MadeBy_Version := MadeBy_Version or (OS_OS2 shl 8);
+    {$ENDIF}
+      {$warning TODO: find a way to recognize VFAT and NTFS}
+      // Copy over extract_version_reqd..extra_field_length
+      Move(LocalHdr.Extract_Version_Reqd, Extract_Version_Reqd, 26);
+      if (IsZip64) and (Extract_Version_Reqd<45) then
+        Extract_Version_Reqd := 45;
+      // Keep track of the minimum version required to extract
+      // zip file as a whole
+      if Extract_Version_Reqd>MinReqdVersion then
+        MinReqdVersion:=Extract_Version_Reqd;
+      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;
+    {$IFDEF UNIX}
+      External_Attributes := Entries[ACount].Attributes shl 16;
+    {$ELSE}
+      External_Attributes := Entries[ACount].Attributes;
+    {$ENDIF}
+      if HdrPos>=$FFFFFFFF then
+      begin
+        FZipFileNeedsZip64:=true;
+        IsZip64:=true;
+        Local_Header_offset := $FFFFFFFF;
+        // LocalZip64Fld will be written out as central dir extra field later
+        LocalZip64Fld.Relative_Hdr_Offset := HdrPos;
+      end
+      else
+        Local_Header_Offset := HdrPos;
+      end;
+    FOutStream.Seek(0,soEnd);
+    FOutStream.WriteBuffer({$IFDEF FPC_BIG_ENDIAN}SwapCFH{$ENDIF}(CentralHdr),SizeOf(CentralHdr));
+    FOutStream.WriteBuffer(ZFileName[1],Length(ZFileName));
+    if IsZip64 then
+      begin
+      FOutStream.Seek(0,soEnd);
+      FOutStream.WriteBuffer({$IFDEF FPC_BIG_ENDIAN}SwapEDFH{$ENDIF}(LocalZip64ExtHdr),SizeOf(LocalZip64ExtHdr));
+      FOutStream.WriteBuffer({$IFDEF FPC_BIG_ENDIAN}SwapZ64EIF{$ENDIF}(LocalZip64Fld),SizeOf(LocalZip64Fld));
+      end;
+
+    Inc(ACount);
+    // Move past compressed file data to next header:
+    if LocalHdr.Compressed_Size=$FFFFFFFF then
+      FOutStream.Seek(SavePos + LocalZip64Fld.Compressed_Size,soBeginning)
+    else
+      FOutStream.Seek(SavePos + LocalHdr.Compressed_Size,soBeginning);
+    HdrPos:=FOutStream.Position;
+    FOutStream.ReadBuffer(LocalHdr, SizeOf(LocalHdr));
+  {$IFDEF FPC_BIG_ENDIAN}
+    LocalHdr := SwapLFH(LocalHdr);
+  {$ENDIF}
+  Until LocalHdr.Signature = CENTRAL_FILE_HEADER_SIGNATURE;
+  FOutStream.Seek(0,soEnd);
+  FillChar(EndHdr,SizeOf(EndHdr),0);
+
+  // Write end of central directory record
+  // We'll use the zip64 variants to store counts etc
+  // and copy to the old record variables if possible
+  // This seems to match expected behaviour of unzippers like
+  // unrar that only look at the zip64 record
+  FillChar(Zip64ECD, SizeOf(Zip64ECD), 0);
+  Zip64ECD.Signature:=ZIP64_END_OF_CENTRAL_DIR_SIGNATURE;
+  FillChar(Zip64ECDL, SizeOf(Zip64ECDL), 0);
+  Zip64ECDL.Signature:=ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE;
+  Zip64ECDL.Total_Disks:=1; //default and no support for multi disks yet anyway
+  With EndHdr do
+    begin
+    Signature := END_OF_CENTRAL_DIR_SIGNATURE;
+    Disk_Number := 0;
+    Central_Dir_Start_Disk := 0;
+
+    Zip64ECD.Entries_This_Disk:=ACount;
+    Zip64ECD.Total_Entries:=Acount;
+    if ACount>$FFFF then
+      begin
+      FZipFileNeedsZip64 := true;
+      Entries_This_Disk := $FFFF;
+      Total_Entries := $FFFF;
+      end
+    else
+      begin
+      Entries_This_Disk := Zip64ECD.Entries_This_Disk;
+      Total_Entries := Zip64ECD.Total_Entries;
+      end;
+
+    Zip64ECD.Central_Dir_Size := FOutStream.Size-CenDirPos;
+    if (Zip64ECD.Central_Dir_Size)>$FFFFFFFF then
+      begin
+      FZipFileNeedsZip64 := true;
+      Central_Dir_Size := $FFFFFFFF;
+      end
+    else
+      begin
+      Central_Dir_Size := Zip64ECD.Central_Dir_Size;
+      end;
+
+    Zip64ECD.Start_Disk_Offset := CenDirPos;
+    if Zip64ECD.Start_Disk_Offset>$FFFFFFFF then
+      begin
+      FZipFileNeedsZip64 := true;
+      Start_Disk_Offset := $FFFFFFFF;
+      end
+    else
+      begin
+      Start_Disk_Offset := Zip64ECD.Start_Disk_Offset;
+      end;
+
+    ZipFile_Comment_Length := Length(FFileComment);
+
+    if FZipFileNeedsZip64 then
+    begin
+      //Write zip64 end of central directory record if needed
+      if MinReqdVersion<45 then
+        MinReqdVersion := 45;
+      Zip64ECD.Extract_Version_Reqd := MinReqdVersion;
+      Zip64ECD.Version_Made_By := MinReqdVersion;
+      Zip64ECD.Record_Size := SizeOf(Zip64ECD)-12; //Assumes no variable length field following
+      Zip64ECDL.Central_Dir_Zip64_EOCD_Offset := FOutStream.Position;
+      Zip64ECDL.Zip64_EOCD_Start_Disk := 0;
+      FOutStream.WriteBuffer({$IFDEF FPC_BIG_ENDIAN}SwapZ64ECD{$ENDIF}(Zip64ECD), SizeOf(Zip64ECD));
+
+      //Write zip64 end of central directory locator if needed
+      FOutStream.WriteBuffer({$IFDEF FPC_BIG_ENDIAN}SwapZ64ECDL{$ENDIF}(Zip64ECDL), SizeOf(Zip64ECDL));
+    end;
+
+    FOutStream.WriteBuffer({$IFDEF FPC_BIG_ENDIAN}SwapECD{$ENDIF}(EndHdr), SizeOf(EndHdr));
+    if Length(FFileComment) > 0 then
+      FOutStream.WriteBuffer(FFileComment[1],Length(FFileComment));
+    end;
 end;
 
 Function TZipper.CreateCompressor(Item : TZipFileEntry; AInFile,AZipStream : TStream) : TCompressor;
@@ -1417,6 +1733,8 @@
     else
       begin
       TmpFileName:=ChangeFileExt(FFileName,'.tmp');
+      if TmpFileName=FFileName then
+        TmpFileName:=TmpFileName+'.tmp';
       ZipStream:=TFileStream.Create(TmpFileName,fmCreate);
       end;
     Try
@@ -1453,8 +1771,7 @@
 
 // Just like SaveToFile, but uses the FileName property
 Procedure TZipper.ZipAllFiles;
-
-Begin
+begin
   SaveToFile(FileName);
 end;
 
@@ -1472,29 +1789,25 @@
 
 procedure TZipper.SaveToStream(AStream: TStream);
 Var
-   I : Integer;
-   filecnt : integer;
+  I : integer; //could be qword but limited by FEntries.Count
 begin
   FOutStream := AStream;
 
   If CheckEntries=0 then
     Exit;
+
   FZipping:=True;
   Try
-    GetFileInfo;
+    GetFileInfo; //get info on file entries in zip
 
-    filecnt:=0;
     for I:=0 to FEntries.Count-1 do
-    begin
       ZipOneFile(FEntries[i]);
-      inc(filecnt);
-    end;
-    if filecnt>0 then
+    if FEntries.Count>0 then
       BuildZipDirectory;
   finally
     FZipping:=False;
     // Remove entries that have been added by CheckEntries from Files.
-    For I:=0 to FFiles.Count-1 do
+    for I:=0 to FFiles.Count-1 do
       FEntries.Delete(FEntries.Count-1);
   end;
 end;
@@ -1548,7 +1861,9 @@
   ComprPct : Double;
 
 begin
-  If (LocalHdr.Uncompressed_Size>0) then
+  if (FZipFileNeedsZip64) and (LocalZip64Fld.Original_Size>0) then
+    ComprPct := (100.0 * (LocalZip64Fld.Original_size - LocalZip64Fld.Compressed_Size)) / LocalZip64Fld.Original_Size
+  else if (LocalHdr.Uncompressed_Size>0) then
     ComprPct := (100.0 * (LocalHdr.Uncompressed_Size - LocalHdr.Compressed_Size)) / LocalHdr.Uncompressed_Size
   else
     ComprPct := 0;
@@ -1564,16 +1879,38 @@
   FFiles:=TStringList.Create;
   FEntries:=TZipFileEntries.Create(TZipFileEntry);
   FOnPercent:=1;
+  FZipFileNeedsZip64:=false;
+  LocalZip64ExtHdr.Header_ID:=ZIP64_HEADER_ID;
+  LocalZip64ExtHdr.Data_Size:=SizeOf(Zip64_Extended_Info_Field_Type);
 end;
 
 Function TZipper.CheckEntries : Integer;
 
 Var
-  I : Integer;
+  I : integer; //Could be QWord but limited by FFiles.Count
 
 begin
-  For I:=0 to FFiles.Count-1 do
+  for I:=0 to FFiles.Count-1 do
     FEntries.AddFileEntry(FFiles[i]);
+
+  // Use zip64 when number of file entries
+  // or individual (un)compressed sizes
+  // require it.
+  if FEntries.Count >= $FFFF then
+    FZipFileNeedsZip64:=true;
+
+  if not(FZipFileNeedsZip64) then
+    begin
+    for I:=0 to FFiles.Count-1 do
+      begin
+      if FEntries[i].FNeedsZip64 then
+        begin
+        FZipFileNeedsZip64:=true;
+        break;
+        end;
+      end;
+    end;
+
   Result:=FEntries.Count;
 end;
 
@@ -1614,14 +1951,14 @@
   Path: String;
   OldDirectorySeparators: set of char;
 Begin
-  { the default RTL behaviour is broken on Unix platforms
+  { the default RTL behavior is broken on Unix platforms
     for Windows compatibility: it allows both '/' and '\'
-    as directory separator. We don't want that behaviour
+    as directory separator. We don't want that behavior
     here, since 'abc\' is a valid file name under Unix.
 	
-	(mantis 15836) On the other hand, many archives on
-	 windows have '/' as pathseparator, even Windows
-	 generated .odt files. So we disable this for windows.
+    (mantis 15836) On the other hand, many archives on
+    Windows have '/' as pathseparator, even Windows
+    generated .odt files. So we disable this for Windows.
   }
   OldDirectorySeparators:=AllowDirectorySeparators;
   {$ifndef Windows}
@@ -1633,7 +1970,7 @@
     FOnCreateStream(Self, OutStream, Item);
   // If FOnCreateStream didn't create one, we create one now.
   If (OutStream=Nil) then
-    Begin
+    begin
     if (Path<>'') then
       ForceDirectories(Path);
     AllowDirectorySeparators:=OldDirectorySeparators;
@@ -1644,7 +1981,6 @@
   Result:=True;
   If Assigned(FOnStartFile) then
     FOnStartFile(Self,OutFileName);
-	
 End;
 
 
@@ -1675,6 +2011,8 @@
 Var
   S : String;
   D : TDateTime;
+  ExtraFieldHdr: Extensible_Data_Field_Header_Type;
+  SavePos: int64; //could be qword but limited by stream
 Begin
   FZipStream.Seek(Item.HdrPos,soBeginning);
   FZipStream.ReadBuffer(LocalHdr,SizeOf(LocalHdr));
@@ -1681,15 +2019,35 @@
 {$IFDEF FPC_BIG_ENDIAN}
   LocalHdr := SwapLFH(LocalHdr);
 {$ENDIF}
+  FillChar(LocalZip64Fld,SizeOf(LocalZip64Fld),0); //ensure no erroneous info
   With LocalHdr do
     begin
       SetLength(S,Filename_Length);
       FZipStream.ReadBuffer(S[1],Filename_Length);
-      //SetLength(E,Extra_Field_Length);
-      //FZipStream.ReadBuffer(E[1],Extra_Field_Length);
-      FZipStream.Seek(Extra_Field_Length,soCurrent);
       Item.ArchiveFileName:=S;
       Item.DiskFileName:=S;
+      SavePos:=FZipStream.Position; //after filename, before extra fields
+      if Extra_Field_Length>0 then
+        begin
+        SavePos := FZipStream.Position;
+        if (LocalHdr.Extra_Field_Length>=SizeOf(ExtraFieldHdr)+SizeOf(LocalZip64Fld)) then
+          while FZipStream.Position<SavePos+LocalHdr.Extra_Field_Length do
+            begin
+            FZipStream.ReadBuffer(ExtraFieldHdr, SizeOf(ExtraFieldHdr));
+          {$IFDEF FPC_BIG_ENDIAN}
+            ExtraFieldHdr := SwapEDFH(ExtraFieldHdr);
+          {$ENDIF}
+            if ExtraFieldHdr.Header_ID=ZIP64_HEADER_ID then
+              begin
+              FZipStream.ReadBuffer(LocalZip64Fld, SizeOf(LocalZip64Fld));
+            {$IFDEF FPC_BIG_ENDIAN}
+              LocalZip64Fld := SwapZ64EIF(LocalZip64Fld);
+            {$ENDIF}
+              end;
+            end;
+        // Move past extra fields
+        FZipStream.Seek(SavePos+Extra_Field_Length,soFromBeginning);
+        end;
       Item.Size:=Uncompressed_Size;
       ZipDateTimeToDateTime(Last_Mod_Date,Last_Mod_Time,D);
       Item.DateTime:=D;
@@ -1699,64 +2057,141 @@
     end;
 End;
 
-procedure FindEndHeader(AZip: TStream; out AEndHdr: End_of_Central_Dir_Type; out AEndHdrPos: Int64; out AZipFileComment: string);
+procedure TUnZipper.FindEndHeaders(
+  out AEndHdr: End_of_Central_Dir_Type;
+  out AEndHdrPos: Int64;
+  out AEndZip64Hdr: Zip64_End_of_Central_Dir_type;
+  out AEndZip64HdrPos: Int64);
+// Reads backwords from the end of the zip file,
+// following end of central directory, and, if present
+// zip64 end of central directory locator and
+// zip64 end of central directory record
+
+// If valid regular end of directory found, AEndHdrPos>0
+// If valid zip64 end of directory found, AEndZip64HdrPos>0
 var
-  Buf: PByte;
-  BufSize: Integer;
-  I: Integer;
+  EndZip64Locator: Zip64_End_of_Central_Dir_Locator_type;
+  procedure SearchForSignature;
+  // Search for end of central directory record signature
+  // If failed, set AEndHdrPos to 0
+  var
+    I: Integer;
+    Buf: PByte;
+    BufSize: Integer;
+    result: boolean;
+  begin
+    result:=false;
+    // scan the last (64k + something) bytes for the END_OF_CENTRAL_DIR_SIGNATURE
+    // (zip file comments are 64k max).
+    BufSize := 65536 + SizeOf(AEndHdr) + 128;
+    if FZipStream.Size < BufSize then
+      BufSize := FZipStream.Size;
+
+    Buf := GetMem(BufSize);
+    try
+      FZipStream.Seek(FZipStream.Size - BufSize, soBeginning);
+      FZipStream.ReadBuffer(Buf^, BufSize);
+
+      for I := BufSize - SizeOf(AEndHdr) downto 0 do
+      begin
+        if (Buf[I] or (Buf[I + 1] shl 8) or (Buf[I + 2] shl 16) or (Buf[I + 3] shl 24)) = END_OF_CENTRAL_DIR_SIGNATURE then
+        begin
+          Move(Buf[I], AEndHdr, SizeOf(AEndHdr));
+          {$IFDEF FPC_BIG_ENDIAN}
+          AEndHdr := SwapECD(AEndHdr);
+          {$ENDIF}
+          if (AEndHdr.Signature = END_OF_CENTRAL_DIR_SIGNATURE) and
+             (I + SizeOf(AEndHdr) + AEndHdr.ZipFile_Comment_Length = BufSize) then
+          begin
+            AEndHdrPos := FZipStream.Size - BufSize + I;
+            FZipStream.Seek(AEndHdrPos + SizeOf(AEndHdr), soBeginning);
+            SetLength(FFileComment, AEndHdr.ZipFile_Comment_Length);
+            FZipStream.ReadBuffer(FFileComment[1], Length(FFileComment));
+            result:=true; //found it
+            break;
+          end;
+        end;
+      end;
+    finally
+      FreeMem(Buf);
+    end;
+    if not(result) then
+    begin
+      AEndHdrPos := 0;
+      FillChar(AEndHdr, SizeOf(AEndHdr), 0);
+    end;
+  end;
+
+  procedure ZeroData;
+  begin
+    AEndHdrPos := 0;
+    FillChar(AEndHdr, SizeOf(AEndHdr), 0);
+    AEndZip64HdrPos:=0;
+    FillChar(AEndZip64Hdr, SizeOf(AEndZip64Hdr), 0);
+  end;
+
 begin
-  AZipFileComment := '';
-  AEndHdrPos := AZip.Size - SizeOf(AEndHdr);
-  if AEndHdrPos < 0 then
+  // Zip64 records may not exist, so fill out default values
+  FillChar(AEndZip64Hdr,SizeOf(AEndZip64Hdr), 0);
+  AEndZip64HdrPos:=0;
+  // Look for end of central directory record from
+  // back of file based on signature (only way due to
+  // variable length zip comment etc)
+  FFileComment := '';
+  // Zip file requires end of central dir header so
+  // is corrupt if it is smaller than that
+  if FZipStream.Size < SizeOf(AEndHdr) then
   begin
-    AEndHdrPos := -1;
-    FillChar(AEndHdr, SizeOf(AEndHdr), 0);
+    ZeroData;
     exit;
   end;
-  AZip.Seek(AEndHdrPos, soBeginning);
-  AZip.ReadBuffer(AEndHdr, SizeOf(AEndHdr));
+
+  AEndHdrPos := FZipStream.Size - SizeOf(AEndHdr);
+  FZipStream.Seek(AEndHdrPos, soBeginning);
+  FZipStream.ReadBuffer(AEndHdr, SizeOf(AEndHdr));
   {$IFDEF FPC_BIG_ENDIAN}
   AEndHdr := SwapECD(AEndHdr);
   {$ENDIF}
-  if (AEndHdr.Signature = END_OF_CENTRAL_DIR_SIGNATURE) and
-     (AEndHdr.ZipFile_Comment_Length = 0) then
+  // Search unless record is right at the end of the file:
+  if (AEndHdr.Signature <> END_OF_CENTRAL_DIR_SIGNATURE) or
+     (AEndHdr.ZipFile_Comment_Length <> 0) then
+    SearchForSignature;
+  if AEndHdrPos=0 then
+  begin
+    ZeroData;
     exit;
+  end;
 
-  // scan the last (64k + something) bytes for the END_OF_CENTRAL_DIR_SIGNATURE
-  // (zip file comments are 64k max)
-  BufSize := 65536 + SizeOf(AEndHdr) + 128;
-  if AZip.Size < BufSize then
-    BufSize := AZip.Size;
-
-  Buf := GetMem(BufSize);
-  try
-    AZip.Seek(AZip.Size - BufSize, soBeginning);
-    AZip.ReadBuffer(Buf^, BufSize);
-
-    for I := BufSize - SizeOf(AEndHdr) downto 0 do
+  // With a valid end of dir record, see if there's zip64
+  // fields:
+  FZipStream.Seek(AEndHdrPos-SizeOf(Zip64_End_of_Central_Dir_Locator_type),soBeginning);
+  FZipStream.ReadBuffer(EndZip64Locator, SizeOf(EndZip64Locator));
+  {$IFDEF FPC_BIG_ENDIAN}
+  EndZip64Locator := SwapZ64ECDL(EndZip64Locator);
+  {$ENDIF}
+  if EndZip64Locator.Signature=ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE then
+  begin
+    //Read EndZip64Locator.Total_Disks when implementing multiple disks support
+    if EndZip64Locator.Central_Dir_Zip64_EOCD_Offset>High(Int64) then
+      raise EZipError.CreateFmt(SErrPosTooLarge,[EndZip64Locator.Central_Dir_Zip64_EOCD_Offset,High(Int64)]);
+    AEndZip64HdrPos:=EndZip64Locator.Central_Dir_Zip64_EOCD_Offset;
+    FZipStream.Seek(AEndZip64HdrPos, soBeginning);
+    FZipStream.ReadBuffer(AEndZip64Hdr, SizeOf(AEndZip64Hdr));
+    {$IFDEF FPC_BIG_ENDIAN}
+    AEndZip64Hdr := SwapZ64ECD(AEndZip64Hdr);
+    {$ENDIF}
+    if AEndZip64Hdr.Signature<>ZIP64_END_OF_CENTRAL_DIR_SIGNATURE then
     begin
-      if (Buf[I] or (Buf[I + 1] shl 8) or (Buf[I + 2] shl 16) or (Buf[I + 3] shl 24)) = END_OF_CENTRAL_DIR_SIGNATURE then
-      begin
-        Move(Buf[I], AEndHdr, SizeOf(AEndHdr));
-        {$IFDEF FPC_BIG_ENDIAN}
-        AEndHdr := SwapECD(AEndHdr);
-        {$ENDIF}
-        if (AEndHdr.Signature = END_OF_CENTRAL_DIR_SIGNATURE) and
-           (I + SizeOf(AEndHdr) + AEndHdr.ZipFile_Comment_Length = BufSize) then
-        begin
-          AEndHdrPos := AZip.Size - BufSize + I;
-          AZip.Seek(AEndHdrPos + SizeOf(AEndHdr), soBeginning);
-          SetLength(AZipFileComment, AEndHdr.ZipFile_Comment_Length);
-          AZip.ReadBuffer(AZipFileComment[1], Length(AZipFileComment));
-          exit;
-        end;
-      end;
+      //Corrupt header
+      ZeroData;
+      Exit;
     end;
-
-    AEndHdrPos := -1;
-    FillChar(AEndHdr, SizeOf(AEndHdr), 0);
-  finally
-    FreeMem(Buf);
+  end
+  else
+  begin
+    // No zip64 data, so follow the offset in the end of central directory record
+    AEndZip64HdrPos:=0;
+    FillChar(AEndZip64Hdr, SizeOf(AEndZip64Hdr), 0);
   end;
 end;
 
@@ -1763,20 +2198,54 @@
 Procedure TUnZipper.ReadZipDirectory;
 
 Var
-  i : LongWord; //todo: expand to 8 bytes when introducing zip64 format
+  EndHdr      : End_of_Central_Dir_Type;
+  EndZip64Hdr : Zip64_End_of_Central_Dir_type;
+  i : integer; //could be Qword but limited to number of items in collection
   EndHdrPos,
-  CenDirPos : Int64;
+  EndZip64HdrPos,
+  CenDirPos,
+  SavePos   : Int64; //could be QWord but limited to stream maximums
+  ExtraFieldHeader : Extensible_Data_Field_Header_Type;
+  EntriesThisDisk : QWord;
+  Zip64Field: Zip64_Extended_Info_Field_Type;
   NewNode   : TFullZipFileEntry;
   D : TDateTime;
   S : String;
 Begin
-  FindEndHeader(FZipStream, EndHdr, EndHdrPos, FFileComment);
-  if EndHdrPos < 0 then
+  FindEndHeaders(EndHdr, EndHdrPos,
+    EndZip64Hdr, EndZip64HdrPos);
+  if EndHdrPos=0 then
     raise EZipError.CreateFmt(SErrCorruptZIP,[FileName]);
-  CenDirPos := EndHdr.Start_Disk_Offset;
+  if (EndZip64HdrPos>0) and (EndZip64Hdr.Start_Disk_Offset>0) then
+    begin
+    if EndZip64Hdr.Start_Disk_Offset>High(Int64) then
+      raise EZipError.CreateFmt(SErrPosTooLarge,[EndZip64Hdr.Start_Disk_Offset,High(Int64)]);
+    CenDirPos := EndZip64Hdr.Start_Disk_Offset;
+    end
+  else
+    CenDirPos := EndHdr.Start_Disk_Offset;
   FZipStream.Seek(CenDirPos,soBeginning);
   FEntries.Clear;
-  for i:=0 to EndHdr.Entries_This_Disk-1 do
+  if (EndZip64HdrPos>0) and (EndZip64Hdr.Entries_This_Disk>0) then
+  begin
+    EntriesThisDisk := EndZip64Hdr.Entries_This_Disk;
+    if EntriesThisDisk<>EndZip64Hdr.Total_Entries then
+      raise EZipError.Create(SErrUnsupportedMultipleDisksCD);
+  end
+  else
+  begin
+    EntriesThisDisk :=EndHdr.Entries_This_Disk;
+    if EntriesThisDisk<>EndHdr.Total_Entries then
+      raise EZipError.Create(SErrUnsupportedMultipleDisksCD);
+  end;
+
+  // Entries are added to a collection. The max number of items
+  // in a collection limits the entries we can process.
+  if EntriesThisDisk>MaxInt then
+    raise EZipError.CreateFmt(SErrMaxEntries,[EntriesThisDisk,MaxInt]);
+
+  // Using while instead of for loop so qword can be used on 32 bit as well.
+  for i:=0 to EntriesThisDisk-1 do
     begin
     FZipStream.ReadBuffer(CentralHdr, SizeOf(CentralHdr));
 {$IFDEF FPC_BIG_ENDIAN}
@@ -1787,15 +2256,18 @@
       if Signature<>CENTRAL_FILE_HEADER_SIGNATURE then
         raise EZipError.CreateFmt(SErrCorruptZIP,[FileName]);
       NewNode:=FEntries.Add as TFullZipFileEntry;
+      // Header position will be corrected later with zip64 version, if needed..
       NewNode.HdrPos := Local_Header_Offset;
       SetLength(S,Filename_Length);
       FZipStream.ReadBuffer(S[1],Filename_Length);
+      SavePos:=FZipStream.Position; //After fixed part of central directory...
+      // and the filename; before any extra field(s)
       NewNode.ArchiveFileName:=S;
+      // Size/compressed size will be adjusted by zip64 entries if needed...
       NewNode.Size:=Uncompressed_Size;
       NewNode.FCompressedSize:=Compressed_Size;
       NewNode.CRC32:=CRC32;
       NewNode.OS := MadeBy_Version shr 8;
-
       if NewNode.OS = OS_UNIX then
         NewNode.Attributes := External_Attributes shr 16
       else
@@ -1802,9 +2274,44 @@
         NewNode.Attributes := External_Attributes;
       ZipDateTimeToDateTime(Last_Mod_Date,Last_Mod_Time,D);
       NewNode.DateTime:=D;
-      FZipStream.Seek(Extra_Field_Length+File_Comment_Length,soCurrent);
+
+      // Go through any extra fields and extract any zip64 info
+      if Extra_Field_Length>0 then
+        begin
+        while (FZipStream.Position<SavePos+Extra_Field_Length) do
+          begin
+          FZipStream.ReadBuffer(ExtraFieldHeader, SizeOf(ExtraFieldHeader));
+        {$IFDEF FPC_BIG_ENDIAN}
+          ExtraFieldHeader := SwapEDFH(ExtraFieldHeader);
+        {$ENDIF}
+          if ExtraFieldHeader.Header_ID = ZIP64_HEADER_ID then
+            begin
+            FZipStream.ReadBuffer(Zip64Field, SizeOf(Zip64Field));
+          {$IFDEF FPC_BIG_ENDIAN}
+            Zip64Field := SwapZ64EIF(Zip64Field);
+          {$ENDIF}
+            if Zip64Field.Compressed_Size > 0 then
+              NewNode.FCompressedSize := Zip64Field.Compressed_Size;
+            if Zip64Field.Original_Size>0 then
+              NewNode.Size := Zip64Field.Original_Size;
+            if Zip64Field.Relative_Hdr_Offset<>0 then
+              begin
+              if Zip64Field.Relative_Hdr_Offset>High(Int64) then
+                raise EZipError.CreateFmt(SErrPosTooLarge,[Zip64Field.Relative_Hdr_Offset,High(Int64)]);
+              NewNode.HdrPos := Zip64Field.Relative_Hdr_Offset;
+              end;
+            end
+          else
+            begin
+              // Read past non-Zip64 extra field
+              FZipStream.Seek(ExtraFieldHeader.Data_Size,soFromCurrent);
+            end;
+          end;
+        end;
+      // Move past extra fields and file comment to next header
+      FZipStream.Seek(SavePos+Extra_Field_Length+File_Comment_Length,soFromBeginning);
       end;
-   end;
+    end;
 end;
 
 Function TUnZipper.CreateDeCompressor(Item : TZipFileEntry; AMethod : Word;AZipFile,AOutFile : TStream) : TDeCompressor;
@@ -1829,7 +2336,6 @@
   IsLink: Boolean;
   IsCustomStream: Boolean;
 
-
   procedure DoUnzip(const Dest: TStream);
   begin
     if ZMethod=0 then
@@ -1836,7 +2342,10 @@
     begin
       if (LocalHdr.Compressed_Size<>0) then
         begin
-          Count:=Dest.CopyFrom(FZipStream,LocalHdr.Compressed_Size)
+          if LocalZip64Fld.Compressed_Size>0 then
+            Count:=Dest.CopyFrom(FZipStream,LocalZip64Fld.Compressed_Size)
+          else
+            Count:=Dest.CopyFrom(FZipStream,LocalHdr.Compressed_Size);
          {$warning TODO: Implement CRC Check}
         end
       else
@@ -1860,7 +2369,6 @@
 
   IsCustomStream := Assigned(FOnCreateStream);
 
-
   if (IsCustomStream = False) and (FOutputPath<>'') then
     OutputFileName:=IncludeTrailingPathDelimiter(FOutputPath)+OutputFileName;
 
@@ -1874,7 +2382,6 @@
   end;
 {$ENDIF}
 
-
   if IsCustomStream then
   begin
     try
@@ -1915,7 +2422,6 @@
     end;
   end;
 
-
   if Not IsCustomStream then
   begin
     // set attributes
@@ -1925,12 +2431,12 @@
     begin
       Attrs := 0;
     {$IFDEF UNIX}
-      if Item.OS = OS_UNIX then Attrs := Item.Attributes;
-      if Item.OS = OS_FAT then
+      if (Item.OS in [OS_UNIX,OS_OSX]) then Attrs := Item.Attributes;
+      if (Item.OS in [OS_FAT,OS_NTFS,OS_OS2,OS_VFAT]) then
         Attrs := ZipFatAttrsToUnixAttrs(Item.Attributes);
     {$ELSE}
-      if Item.OS = OS_FAT then Attrs := Item.Attributes;
-      if Item.OS = OS_UNIX then
+      if (Item.OS in [OS_FAT,OS_NTFS,OS_OS2,OS_VFAT]) then Attrs := Item.Attributes;
+      if (Item.OS in [OS_UNIX,OS_OSX]) then
         Attrs := ZipUnixAttrsToFatAttrs(ExtractFileName(Item.ArchiveFileName), Item.Attributes);
     {$ENDIF}
 
@@ -1949,9 +2455,9 @@
 
 Procedure TUnZipper.UnZipAllFiles;
 Var
-   Item : TFullZipFileEntry;
-   I : Integer;
-   AllFiles : Boolean;
+  Item : TFullZipFileEntry;
+  I : integer; //Really QWord but limited to FEntries.Count
+  AllFiles : Boolean;
 
 Begin
   FUnZipping:=True;
@@ -1960,7 +2466,7 @@
     OpenInput;
     Try
       ReadZipDirectory;
-      For I:=0 to FEntries.Count-1 do
+      for i:=0 to FEntries.Count-1 do
         begin
         Item:=FEntries[i];
         if AllFiles or (FFiles.IndexOf(Item.ArchiveFileName)<>-1) then
@@ -2023,11 +2529,25 @@
 
 Var
   ComprPct : Double;
-
+  Uncompressed: QWord;
+  Compressed: QWord;
 begin
-  If (LocalHdr.Uncompressed_Size>0) then
-    ComprPct := (100.0 * (LocalHdr.Uncompressed_Size - LocalHdr.Compressed_Size)) / LocalHdr.Uncompressed_Size
+  If LocalZip64Fld.Original_Size > 0 then
+    Uncompressed := LocalZip64Fld.Original_Size
   else
+    Uncompressed := LocalHdr.Uncompressed_Size;
+
+  If LocalZip64Fld.Compressed_Size > 0 then
+    Compressed := LocalZip64Fld.Compressed_Size
+  else
+    Compressed := LocalHdr.Compressed_Size;
+
+  If (Compressed>0) and (Uncompressed>0) then
+    if (Compressed>Uncompressed) then
+      ComprPct := (-100.0 * (Compressed - Uncompressed)) / Uncompressed
+    else
+      ComprPct := (100.0 * (Uncompressed - Compressed)) / Uncompressed
+  else
     ComprPct := 0;
   If Assigned(FOnEndOfFile) then
     FOnEndOfFile(Self,ComprPct);
@@ -2091,6 +2611,8 @@
   FOS := OS_FAT;
 {$ENDIF}
   FCompressionLevel:=cldefault;
+  FDateTime:=now;
+  FNeedsZip64:=false;
   inherited create(ACollection);
 end;
 
@@ -2181,6 +2703,7 @@
   For I:=0 to List.Count-1 do
     AddFileEntry(List[i]);
 end;
+
 { TFullZipFileEntries }
 
 function TFullZipFileEntries.GetFZ(AIndex : Integer): TFullZipFileEntry;
Index: packages/paszlib/tests/tczipper.pp
===================================================================
--- packages/paszlib/tests/tczipper.pp	(revision 25488)
+++ packages/paszlib/tests/tczipper.pp	(working copy)
@@ -1,50 +1,114 @@
 program tczipper;
 {
     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.
+    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,
-    for details about the copyright.
+    for details about the license.
 
-    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.
-
  **********************************************************************}
 {$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
-  TCallBackHandler = class(TObject)
+
+  { TCallBackHandler }
+
+  TCallBackHandler = class(TObject) //Callbacks used in zip/unzip processing
+  private
+    FPerformChecks: boolean;
+    FOriginalContent: string;
+    FShowContent: boolean;
+    FStreamResult: boolean;
   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 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;
 
-
-procedure TCallBackHandler.EndOfFile(Sender : TObject; Const Ratio : Double);
+procedure TCallBackHandler.EndOfFile(Sender: TObject; const Ratio: double);
 begin
-  if (Ratio<0) then
+  writeln('End of file handler hit; ratio: '+floattostr(ratio));
+  if (FPerformChecks) and (Ratio<0) then
   begin
     writeln('Found compression ratio '+floattostr(Ratio)+', which should never be lower than 0.');
-    halt(3);
+    halt(1);
   end;
 end;
 
-procedure TCallBackHandler.StartOfFile(Sender : TObject; Const AFileName : String);
+procedure TCallBackHandler.StartOfFile(Sender: TObject; const AFileName: string);
 begin
-  if AFileName='' then
+  writeln('Start of file handler hit; filename: '+AFileName);
+  if (FPerformChecks) and (AFileName='') then
   begin
     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
-  code: cardinal;
+  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;
+  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;
+
+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
   CallBackHandler: TCallBackHandler;
   CompressedFile: string;
   FileContents: TStringList;
@@ -55,10 +119,10 @@
   OurZipper: TZipper;
   UnZipper: TUnZipper;
 begin
-  code := 0;
+  result:=true;
   UncompressedFile1:=SysUtils.GetTempFileName('', 'UNC');
   UncompressedFile2:=SysUtils.GetTempFileName('', 'UNC');
-  CompressedFile:=SysUtils.GetTempFileName('', 'ZP');
+  CompressedFile:=SysUtils.GetTempFileName('', 'CC');
 
   FileContents:=TStringList.Create;
   OurZipper:=TZipper.Create;
@@ -93,8 +157,10 @@
     end;
 
     // Delete original files
+    {$IFNDEF KEEPTESTFILES}
     DeleteFile(UncompressedFile1);
     DeleteFile(UncompressedFile2);
+    {$ENDIF}
 
     // Now unzip
     Unzipper.FileName:=CompressedFile;
@@ -109,7 +175,7 @@
       (not FileExists(UncompressedFile2)) then
     begin
       writeln('Unzip failed: could not find decompressed files.');
-      halt(6);
+      exit(false);
     end;
 
     // Compare hashes
@@ -120,25 +186,510 @@
     then
     begin
       writeln('Unzip failed: uncompressed files are not the same as the originals.');
-      halt(7);
+      exit(false);
     end;
 
-    if code = 0 then
-      writeln('Basic zip/unzip tests passed')
-    else
-      writeln('Basic zip/unzip test failed: ', code);
   finally
     FileContents.Free;
     CallBackHandler.Free;
     OurZipper.Free;
     UnZipper.Free;
+    {$IFNDEF KEEPTESTFILES}
     try
       if FileExists(CompressedFile) then DeleteFile(CompressedFile);
       if FileExists(UncompressedFile1) then DeleteFile(UncompressedFile1);
       if FileExists(UncompressedFile2) then DeleteFile(UncompressedFile2);
     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:=@CallBackHandler.DoCreateZipOutputStream;
+    Unzipper.OnDoneStream:=@CallBackHandler.DoDoneOutZipStream;
+    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:=@CallBackHandler.DoCreateZipOutputStream;
+    Unzipper.OnDoneStream:=@CallBackHandler.DoDoneOutZipStream;
+    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:=@CallBackHandler.DoCreateZipOutputStream;
+    Unzipper.OnDoneStream:=@CallBackHandler.DoDoneOutZipStream;
+    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;
+  {$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);
 end.
zip64.diff (77,403 bytes)

Reinier Olislagers

2013-09-15 15:51

developer   ~0070083

Please apply patch

Please remove
$(fpcdir)\packages\paszlib\tests\tconend.pp
=> functionality has been added to tczipper.pp in patch above
Please add contents of newfiles.zip to
$(fpcdir)\packages\paszlib\tests\nullstream.pas
$(fpcdir)\packages\paszlib\tests\testzip64.zip
$(fpcdir)\packages\paszlib\tests\readme.txt

Modifications:
- support for filenames > 255 characters
- support for zip64 format (mantis 0023533) allowing
-- large files (>4Gb) (up to FPC's limit of int64 size for streams)
-- large number of files per zip archive (>65535)(up to FPC's limit of integer due to collection.count)
including raising exception on decompressing/reading zip directory if limits above are exceeded
- several cosmetic changes:
-- Move findendheaders to tunzipper class as it is only used in that class and some unnecessary variables could be cut
-- Harden date/time handling routine against invalid input
-- harden handling of extra fields in local file headers (encryption, any other vendor-specific fields)
-- various cosmetic fixes applied while going through the code (mostly capitalization/spacing in code that I touched anyway)
- extended tests to
-- verify compressed and decompressed content
-- test zip64 format files
-- allow read/decompression testing of arbitrary zip files
-- verifies that small files are compressed or stored, whichever is the smallest mantis 0024897)
- updated readme.txt with tzipper development info

The code has been compiled and the tests run under x64, Windows FPC trunk.


Detailed changes (including diffs etc) that could ease review available via
https://bitbucket.org/reiniero/fpc_laz_patch_playground/commits/all
commit log:
changeset: 562:23dc09becfa0
date: Sun Sep 15 15:38:38 2013 +0200
summary: Cosmetic: remove superfluous tests/variables

changeset: 561:9e457634eaca
date: Sun Sep 15 14:08:29 2013 +0200
summary: Use High(int64) instead of constant value; more elegant/future proof

changeset: 560:5527d6c5961d
date: Sun Sep 15 13:55:57 2013 +0200
summary: Correction for f39930b1c41a: reading directory used old small entry count not zip64 version. Tests: remove excessively long test; exit on first error

changeset: 559:2be2ffe23ae3
date: Sun Sep 15 13:27:38 2013 +0200
summary: Cosmetic: FPC coding standards

changeset: 558:f39930b1c41a
date: Sun Sep 15 13:24:36 2013 +0200
summary: Correction for i386; FInFile.Read returns LongInt which is <> int64 there. Cosmetic (capitalization)

changeset: 557:c1cfdfd13ca4
date: Sun Sep 15 12:56:13 2013 +0200
summary: Scaled back qword to relevant int64 (offsets) or integer (collection count) varieables with exceptions on reading excessive values

changeset: 556:842c9c12e17f
date: Sun Sep 15 11:47:31 2013 +0200
summary: cosmetic: updated test results

changeset: 555:d44b3bc1af6f
date: Sun Sep 15 11:38:57 2013 +0200
summary: Cosmetic

changeset: 554:ecf03cd5691b
date: Sun Sep 15 11:30:54 2013 +0200
summary: Cleanup TUnZipper.FindEndHeaders

changeset: 553:a2d64ae54c1e
date: Sun Sep 15 11:04:48 2013 +0200
summary: When writing zip64 end of dir records, always fill content as unzippers like unrar depend on it

changeset: 552:2689e2ed1a35
date: Sun Sep 15 10:46:48 2013 +0200
summary: Cosmetic

changeset: 551:e3dcfe709adb
date: Sat Sep 14 13:50:02 2013 +0200
summary: Testing remarks

changeset: 550:516a2ce211c3
date: Sat Sep 14 13:20:29 2013 +0200
summary: Write number of disks in zip64 central directory

changeset: 549:889abbc0b304
date: Sat Sep 14 12:19:51 2013 +0200
summary: Fix previous commit

changeset: 548:c5c848083354
date: Sat Sep 14 12:18:31 2013 +0200
summary: Tests: fix nullstream read past end of stream detection

changeset: 547:114532ed7224
date: Sat Sep 14 11:35:38 2013 +0200
summary: test stream fix

changeset: 546:da235dcfe30a
date: Sat Sep 14 11:17:40 2013 +0200
summary: Zip: added a max file size detection; needs more though. Use null stream to create huge stream for test

changeset: 545:d49c9a7b8c0a
date: Fri Sep 13 11:57:14 2013 +0200
summary: Test large files

changeset: 544:1f3404961f63
date: Fri Sep 13 11:05:43 2013 +0200
summary: Adding large file test

changeset: 543:c737450f5a89
date: Tue Sep 10 20:51:06 2013 +0200
summary: Use qword instead of integer for unzipallfiles

changeset: 542:bfcb3bc1f028
date: Tue Sep 10 17:23:07 2013 +0200
summary: Harden tests

changeset: 541:bb2eac87d6fc
date: Tue Sep 10 17:10:32 2013 +0200
summary: Zip64 end header content may be empty if the normal equivalents are valid. Cater for that when reading.

changeset: 540:b512c534a6b3
date: Tue Sep 10 17:01:11 2013 +0200
summary: Fix zip64 zip minimum required version

changeset: 539:ee3b33b10984
date: Tue Sep 10 16:57:03 2013 +0200
summary: Zip64 support for large central directories

changeset: 538:c4c639c4506f
date: Tue Sep 10 16:38:37 2013 +0200
summary: Implement minimum version for zip64; don't always write number of entries in zip64, only if needed

changeset: 537:556df6276df0
date: Tue Sep 10 16:15:23 2013 +0200
summary: Fix for writing local zip64 headers only if local header is zip64 not when entire archive needs zip64 support

changeset: 536:74967d91669c
date: Tue Sep 10 15:59:18 2013 +0200
summary: Write out zip64 end of central directory record and ... locator.

changeset: 535:890571f2425c
date: Tue Sep 10 15:24:47 2013 +0200
summary: Cosmetic: align layout with other procedures

changeset: 534:92e814f99ebf
date: Tue Sep 10 15:08:22 2013 +0200
summary: Move findendheaders to tunzipper class

changeset: 533:0c61bfbdb6b0
date: Tue Sep 10 15:02:15 2013 +0200
summary: Remove debug code

changeset: 532:2f417054d1ed
date: Tue Sep 10 14:20:08 2013 +0200
summary: Fix range issues for compression ratio

changeset: 531:775c748bdfef
date: Tue Sep 10 14:10:32 2013 +0200
summary: Fix (zip64) end of central directory detection

changeset: 530:899ae08b4369
date: Mon Sep 09 15:00:47 2013 +0200
summary: Harden date/time handling routine

changeset: 529:78e283814007
date: Sat Sep 07 15:34:02 2013 +0200
summary: Continue trying to read end headers; needs more debugging

changeset: 528:1c14b0b557cf
date: Sat Sep 07 14:27:06 2013 +0200
summary: Working on reading zip64 end of central directory info

changeset: 527:8ff92ba22647
date: Tue Sep 03 12:40:23 2013 +0200
summary: cosmetic

changeset: 526:5eda5a3471a9
date: Tue Sep 03 12:10:48 2013 +0200
summary: Cosmetic

changeset: 525:16dd13625e46
date: Tue Sep 03 12:06:46 2013 +0200
summary: Properly skip non-zip64 extra fields

changeset: 524:ec48b1afb87d
date: Tue Sep 03 11:58:50 2013 +0200
summary: Fix typo

changeset: 523:f072672580a8
date: Tue Sep 03 11:56:18 2013 +0200
summary: Unzip zip64 supoprt

changeset: 522:b0e30897b0b5
date: Tue Sep 03 10:21:20 2013 +0200
summary: Try and fix zip64 zip

changeset: 521:cc74435520c2
date: Mon Sep 02 16:36:20 2013 +0200
summary: WIP zip64 fixes

changeset: 520:1dcc1433ecca
date: Mon Sep 02 13:36:34 2013 +0200
summary: Zip64 fixes

changeset: 519:e721afcdbe6e
date: Mon Sep 02 12:20:52 2013 +0200
summary: Added zip file test; amended copyright warning to Lazarus standard

changeset: 518:fa6203e8989c
date: Mon Sep 02 11:59:22 2013 +0200
summary: Also test zip decompressions (comparing with original content).

changeset: 517:f7d6f7232d87
date: Sun Sep 01 21:34:54 2013 +0200
summary: Hardened local header zip64 handling

changeset: 516:83d5bf407a9f
date: Sun Sep 01 21:12:04 2013 +0200
summary: cosmetic: clarified definitions

changeset: 515:71a6eeba0911
date: Sun Sep 01 17:03:49 2013 +0200
summary: Filename > 255 character supported

changeset: 514:98d4c26003c3
date: Sun Sep 01 16:51:01 2013 +0200
summary: WIP: implementing large filename/zip64 zipping for central directory

changeset: 513:8ac8920e9790
date: Sat Aug 31 14:44:31 2013 +0200
summary: Use zip64 if too many zip entries

changeset: 512:c8c86f722a51
date: Sat Aug 31 12:12:30 2013 +0200
summary: Fix some bigendiean swaps

changeset: 511:c3ab0039bb7f
date: Sat Aug 31 12:01:51 2013 +0200
summary: Implemented endian swap functions for headers

changeset: 510:ff0b769a922b
date: Sat Aug 31 11:47:03 2013 +0200
summary: In some places, use 8 bytes to keep track of file entries instead of 4

changeset: 509:73f12fbd952e
date: Sat Aug 31 11:00:12 2013 +0200
summary: cosmetic

changeset: 508:3934b3c71400
date: Sat Aug 31 10:48:28 2013 +0200
summary: fix for entry count

changeset: 507:7e779179a8b8
date: Sat Aug 31 10:45:21 2013 +0200
summary: cosmetic

changeset: 506:d462e22a0957
date: Fri Aug 30 17:01:19 2013 +0200
summary: Fixed zip64 test

changeset: 505:b5733fcb2e1a
date: Fri Aug 30 12:43:36 2013 +0200
summary: test zip64 format

changeset: 504:82c8465310b1
date: Fri Aug 30 12:07:59 2013 +0200
summary: updated zip tests with small file compression (mantis 0024897)

changeset: 503:22fa7f20f8db
date: Wed Aug 28 17:41:53 2013 +0200
summary: add test program

changeset: 502:4e5b8641af11
date: Wed Aug 28 17:28:09 2013 +0200
summary: moved zip64 development to separate dir. Is already in progress.

Reinier Olislagers

2013-09-22 17:04

developer   ~0070251

FYI, a generated zip64 file was tested with various 3rd party unzip tools on Windows, Linux and OSX and it works.

See http://forum.lazarus.freepascal.org/index.php/topic,22025.msg129459.html#msg129459 and further.

Marco van de Voort

2013-09-25 13:04

manager   ~0070314

Committed, thanks

Reinier Olislagers

2013-09-26 20:25

developer   ~0070361

Thanks, Marco.

Issue History

Date Modified Username Field Change
2012-12-24 11:20 Reinier Olislagers New Issue
2012-12-24 11:20 Reinier Olislagers File Added: testzip64.zip
2012-12-24 11:20 Reinier Olislagers FPCOldBugId => 0
2012-12-29 16:40 Michael Van Canneyt Status new => assigned
2012-12-29 16:40 Michael Van Canneyt Assigned To => Michael Van Canneyt
2013-06-05 08:59 Reinier Olislagers File Added: zip64.diff
2013-08-21 16:30 Marco van de Voort Relationship added related to 0024897
2013-08-22 09:17 Reinier Olislagers Relationship deleted related to 0024897
2013-09-01 17:13 Reinier Olislagers Relationship added related to 0024897
2013-09-15 15:18 Reinier Olislagers File Deleted: testzip64.zip
2013-09-15 15:18 Reinier Olislagers File Deleted: zip64.diff
2013-09-15 15:19 Reinier Olislagers Note Added: 0070081
2013-09-15 15:49 Reinier Olislagers File Added: newfiles.zip
2013-09-15 15:51 Reinier Olislagers File Added: zip64.diff
2013-09-15 15:51 Reinier Olislagers Note Added: 0070083
2013-09-22 17:04 Reinier Olislagers Note Added: 0070251
2013-09-25 13:04 Marco van de Voort Fixed in Revision => 25567
2013-09-25 13:04 Marco van de Voort Note Added: 0070314
2013-09-25 13:04 Marco van de Voort Status assigned => resolved
2013-09-25 13:04 Marco van de Voort Fixed in Version => 2.7.1
2013-09-25 13:04 Marco van de Voort Resolution open => fixed
2013-09-26 20:25 Reinier Olislagers Note Added: 0070361
2013-09-26 20:25 Reinier Olislagers Status resolved => closed
2013-09-26 20:25 Reinier Olislagers Assigned To Michael Van Canneyt => Marco van de Voort
2015-09-29 14:26 Jonas Maebe Relationship added related to 0028311