Advertisement
filhotecmail

System.Zip

Jul 30th, 2018
930
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Delphi 55.33 KB | None | 0 0
  1. {*******************************************************}
  2. {                                                       }
  3. {           CodeGear Delphi Runtime Library             }
  4. {                                                       }
  5. { Copyright(c) 2016 Embarcadero Technologies, Inc.      }
  6. {              All rights reserved                      }
  7. {                                                       }
  8. {   Copyright and license exceptions noted in source    }
  9. {                                                       }
  10. {*******************************************************}
  11.  
  12. {*******************************************************}
  13. { Utility for creating and extracting Zip Files         }
  14. {                                                       }
  15. {See .ZIP File Format Specification at                  }
  16. {http://www.pkware.com/documents/casestudies/APPNOTE.TXT}
  17. {for more information on the .ZIP File Format.          }
  18. {                                                       }
  19. { Support for Compression modes 0(store) and 8(deflate) }
  20. { Are implemented in this unit.                         }
  21. {*******************************************************}
  22.  
  23.                        
  24.  
  25. unit System.Zip;
  26.  
  27. interface
  28.  
  29. uses
  30.   System.SysUtils,
  31.   System.IOUtils,
  32.   System.Generics.Collections,
  33.   System.Classes;
  34.  
  35. type
  36.   /// <summary> Zip Compression Method Enumeration </summary>
  37.   TZipCompression = (
  38.     zcStored    = 0,
  39.     zcShrunk,
  40.     zcReduce1,
  41.     zcReduce2,
  42.     zcReduce3,
  43.     zcReduce4,
  44.     zcImplode,
  45.     zcTokenize,
  46.     zcDeflate,
  47.     zcDeflate64,
  48.     zcPKImplode,
  49.     {11 RESERVED}
  50.     zcBZIP2    = 12,
  51.     {13 RESERVED}
  52.     zcLZMA     = 14,
  53.     {15-17 RESERVED}
  54.     zcTERSE    = 18,
  55.     zcLZ77,
  56.     zcWavePack = 97,
  57.     zcPPMdI1
  58.   );
  59.  
  60. /// <summary> Converts ZIP compression method value to string </summary>
  61. function TZipCompressionToString(Compression: TZipCompression): string;
  62.  
  63. const
  64.   SIGNATURE_ZIPENDOFHEADER: UInt32 = $06054B50;
  65.   SIGNATURE_CENTRALHEADER:  UInt32 = $02014B50;
  66.   SIGNATURE_LOCALHEADER:    UInt32 = $04034B50;
  67.  
  68.   LOCALHEADERSIZE = 26;
  69.   CENTRALHEADERSIZE = 42;
  70.  
  71.   MADEBY_MSDOS = 0;
  72.   MADEBY_UNIX = 3;
  73.  
  74. type
  75.   /// <summary> Final block written to zip file</summary>
  76.   TZipEndOfCentralHeader = packed record
  77.     DiskNumber:          UInt16;
  78.     CentralDirStartDisk: UInt16;
  79.     NumEntriesThisDisk:  UInt16;
  80.     CentralDirEntries:   UInt16;
  81.     CentralDirSize:      UInt32;
  82.     CentralDirOffset:    UInt32;
  83.     CommentLength:       UInt16;
  84.     {Comment: RawByteString}
  85.   end;
  86.   /// <summary> TZipHeader contains information about a file in a zip archive.
  87.   /// </summary>
  88.   /// <remarks>
  89.   /// <para>
  90.   /// This record is overloaded for use in reading/writing ZIP
  91.   /// [Local file header] and the Central Directory's [file header].
  92.   /// </para>
  93.   /// <para> See PKZIP Application Note section V. General Format of a .ZIP file
  94.   ///  sub section J. Explanation of fields for more detailed description
  95.   //   of each field's usage.
  96.   /// </para>
  97.   /// </remarks>
  98.   TZipHeader = packed record
  99.     MadeByVersion:      UInt16; // Start of Central Header
  100.     RequiredVersion:    UInt16; // Start of Local Header
  101.     Flag:               UInt16;
  102.     CompressionMethod:  UInt16;
  103.     ModifiedDateTime:   UInt32;
  104.     CRC32:              UInt32;
  105.     CompressedSize:     UInt32;
  106.     UncompressedSize:   UInt32;
  107.     FileNameLength:     UInt16;
  108.     ExtraFieldLength:   UInt16; // End of Local Header
  109.     FileCommentLength:  UInt16;
  110.     DiskNumberStart:    UInt16;
  111.     InternalAttributes: UInt16;
  112.     ExternalAttributes: UInt32;
  113.     LocalHeaderOffset:  UInt32; // End of Central Header
  114.     FileName: TBytes;
  115.     ExtraField: TBytes;
  116.     FileComment: TBytes;
  117.   end;
  118.  
  119.   PZipHeader = ^TZipHeader;
  120.  
  121.   /// <summary> Exception type for all Zip errors. </summary>
  122.   EZipException = class( Exception );
  123.  
  124.   TZipMode = (zmClosed, zmRead, zmReadWrite, zmWrite);
  125.  
  126.   /// <summary> On progress event</summary>
  127.   TZipProgressEvent = procedure(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64) of object;
  128.  
  129.   TZipFile = class;
  130.   /// <summary> Function to Create a Compression/Decompression stream </summary>
  131.   /// <remarks>
  132.   ///  Call <c>RegisterCompressionHandler</c> to register a compression type that
  133.   ///  can Compress/Decompress a stream. The output stream reads/write from/to InStream.
  134.   /// </remarks>
  135.   TStreamConstructor = reference to function(InStream: TStream; const ZipFile: TZipFile; const Item: TZipHeader): TStream;
  136.  
  137.   /// <summary>   Callback to create a custom stream  based on the original</summary>
  138.   TCreateCustomStreamCallBack = reference to function(const InStream: TStream; const ZipFile: TZipFile; const Item: TZipHeader; IsEncrypted: Boolean): TStream;
  139.   TOnCreateCustomStream = function(const InStream: TStream; const ZipFile: TZipFile; const Item: TZipHeader; IsEncrypted: Boolean): TStream of object;
  140.   /// <summary> Class for creating and reading .ZIP files.
  141.   /// </summary>
  142.   TZipFile = class
  143.   private type
  144.     TCompressionDict = TDictionary< TZipCompression , TPair<TStreamConstructor, TStreamConstructor > >;
  145.   private class var
  146.     FCompressionHandler: TCompressionDict;
  147.     FOnCreateDecompressStream: TOnCreateCustomStream;
  148.     FCreateDecompressStreamCallBack: TCreateCustomStreamCallBack;
  149.   private
  150.     FMode: TZipMode;
  151.     FStream: TStream;
  152.     FFileStream: TFileStream;
  153.     FStartFileData: Int64;
  154.     FEndFileData: Int64;
  155.     FFiles: TList<TZipHeader>;
  156.     FComment: TBytes;
  157.     FUTF8Support: Boolean;
  158.     FOnProgress: TZipProgressEvent;
  159.     FCurrentFile: string;
  160.     FCurrentHeader: TZipHeader;
  161.     function TBytesToString(B: TBytes): string;
  162.     function StringToTBytes(S: string): TBytes;
  163.     function GetFileComment(Index: Integer): string;
  164.     function GetFileCount: Integer;
  165.     function GetFileInfo(Index: Integer): TZipHeader;
  166.     function GetFileInfos: TArray<TZipHeader>;
  167.     function GetFileName(Index: Integer): string;
  168.     function GetFileNames: TArray<string>;
  169.     function GetComment: string;
  170.     procedure ReadCentralHeader;
  171.     procedure SetFileComment(Index: Integer; Value: string);
  172.     procedure SetComment(Value: string);
  173.     procedure SetUTF8Support(const Value: Boolean);
  174.     function LocateEndOfCentralHeader(var Header: TZipEndOfCentralHeader): Boolean;
  175.     procedure CheckFileName(const ArchiveFileName: string);
  176.     procedure DoZLibProgress(Sender: TObject);
  177.   public
  178.     class constructor Create;
  179.     class destructor Destroy;
  180.  
  181.     /// <remarks>
  182.     ///  Call <c>RegisterCompressionHandler</c> to register a compression type that
  183.     ///  can Compress/Decompress a stream. The output stream reads/write from/to InStream.
  184.     /// </remarks>
  185.     class procedure RegisterCompressionHandler(Compression: TZipCompression;
  186.       CompressStream, DecompressStream: TStreamConstructor);
  187.  
  188.     /// <param name="ZipFileName">Path to Zip File</param>
  189.     /// <returns>Is the .ZIP file valid</returns>
  190.     class function IsValid(const ZipFileName: string): Boolean; static;
  191.  
  192.     /// <summary> Extract a ZipFile</summary>
  193.     /// <param name="ZipFileName">File name of the ZIP file</param>
  194.     /// <param name="Path">Path to extract to disk</param>
  195.     /// <param name="ZipProgress">On progress callback.</param>
  196.     class procedure ExtractZipFile(const ZipFileName: string; const Path: string; ZipProgress: TZipProgressEvent = nil); static;
  197.  
  198.     /// <summary> Zip the contents of a directory </summary>
  199.     /// <param name="ZipFileName">File name of the ZIP file</param>
  200.     /// <param name="Path">Path of directory to zip</param>
  201.     /// <param name="Compression">Compression mode.</param>
  202.     /// <param name="ZipProgress">On progress callback.</param>
  203.     class procedure ZipDirectoryContents(const ZipFileName: string; const Path: string; Compression: TZipCompression = zcDeflate; ZipProgress: TZipProgressEvent = nil); static;
  204.  
  205.     /// <summary> Checks if header extra field contains unicode path, if true AFilename contains the unicode path</summary>
  206.     class function GetUTF8PathFromExtraField(const AHeader: TZipHeader; out AFileName: string): Boolean;
  207.  
  208.     /// <summary> Create a TZipFile</summary>
  209.     constructor Create;
  210.  
  211.     /// <remarks> Destroy will close an open zipfile before disposing of it</remarks>
  212.     destructor Destroy; override;
  213.  
  214.     /// <summary> Opens a ZIP file for reading or writing.</summary>
  215.     /// <param name="ZipFileName">Path to ZipFile</param>
  216.     /// <param name="OpenMode"> File Mode to open file.
  217.     ///   <c>zmWrite</c> Creates a new ZIP file for writing.
  218.     ///   <c>zmReadWrite</c> Opens the file for reading and allows adding
  219.     ///      additional new files.
  220.     ///   <c>zmRead</c> Opens the file for reading.
  221.     ///</param>
  222.     procedure Open(const ZipFileName: string; OpenMode: TZipMode); overload;
  223.     procedure Open(ZipFileStream: TStream; OpenMode: TZipMode); overload;
  224.  
  225.     /// <remarks>
  226.     ///   Closing is required to write the ZipFile's
  227.     ///   Central Directory to disk. Closing a file that is open for writing
  228.     ///   writes additonal metadata that is required for reading the file.
  229.     /// </remarks>
  230.     procedure Close;
  231.  
  232.     /// <summary> Extract a single file </summary>
  233.     /// <remarks>
  234.     ///  <c>FileName</c> specifies a file in the ZIP file. All slashes
  235.     ///  in ZIP file names should be '/'.
  236.     ///   The overload that takes an Integer may be useful when a ZIP file
  237.     ///   has duplicate filenames.
  238.     /// </remarks>
  239.     /// <param name="FileName">File name in the archive</param>
  240.     /// <param name="Path">Path to extract to disk</param>
  241.     /// <param name="CreateSubdirs">The output should create sub directories specified in the ZIP file</param>
  242.     procedure Extract(const FileName: string; const Path: string = ''; CreateSubdirs: Boolean = True); overload;
  243.     procedure Extract(Index: Integer; const Path: string = ''; CreateSubdirs: Boolean = True); overload;
  244.     /// <summary> Extract All files </summary>
  245.     /// <param name="Path">Path to extract to.</param>
  246.     procedure ExtractAll(const Path: string = '');
  247.  
  248.     /// <summary> Read a file from arcive to an array of Bytes </summary>
  249.     /// <remarks>
  250.     ///   The overload that takes an Integer may be useful when a ZIP file
  251.     ///   has duplicate filenames.
  252.     /// </remarks>
  253.     /// <param name="FileName">ZIP file FileName</param>
  254.     /// <param name="Bytes">Output bytes</param>
  255.     ///
  256.     procedure Read(const FileName: string; out Bytes: TBytes); overload;
  257.     procedure Read(Index: Integer; out Bytes: TBytes); overload;
  258.     /// <summary> Get a stream to read a file from disk </summary>
  259.     /// <remarks>
  260.     ///   The Stream returned by this function is a decomression stream
  261.     ///   wrapper around the interal Stream reading the zip file. You must
  262.     ///   Free this stream before using other TZipFile methods that change the
  263.     ///   contents of the ZipFile, such as Read or Add.
  264.     ///   The overload that takes an Integer may be useful when a ZIP file
  265.     ///   has duplicate filenames.
  266.     /// </remarks>
  267.     /// <param name="FileName">ZIP file FileName</param>
  268.     /// <param name="Stream">Output Stream</param>
  269.     /// <param name="LocalHeader">Local File header</param>
  270.     procedure Read(const FileName: string; out Stream: TStream; out LocalHeader: TZipHeader); overload;
  271.     procedure Read(Index: Integer; out Stream: TStream; out LocalHeader: TZipHeader); overload;
  272.  
  273.     /// <summary> Add a file to the ZIP file </summary>
  274.     /// <param name="FileName">FileName to be added</param>
  275.     /// <param name="ArchiveFileName">Path + Name of file in the arcive.
  276.     ///   If Ommitted, <C>ExtractFileName(FileName)</C> will be used.</param>
  277.     /// <param name="Compression">Compression mode.</param>
  278.     procedure Add(const FileName: string; const ArchiveFileName: string = '';
  279.       Compression: TZipCompression = zcDeflate); overload;
  280.     /// <summary> Add a memory file to the ZIP file </summary>
  281.     /// <param name="Data">Bytes to be added</param>
  282.     /// <param name="ArchiveFileName">Path + Name of file in the arcive.</param>
  283.     /// <param name="Compression">Compression mode.</param>
  284.     ///
  285.     procedure Add(Data: TBytes; const ArchiveFileName: string; Compression: TZipCompression = zcDeflate); overload;
  286.     /// <summary> Add a memory file to the ZIP file </summary>
  287.     /// <param name="Data">Stream of file to be added</param>
  288.     /// <param name="ArchiveFileName">Path + Name of file in the arcive.</param>
  289.     /// <param name="Compression">Compression mode.</param>
  290.     /// <param name="AExternalAttributes">External attributes for this file.</param>
  291.     procedure Add(Data: TStream; const ArchiveFileName: string; Compression: TZipCompression = zcDeflate;
  292.       AExternalAttributes: TFileAttributes = []); overload;
  293.     /// <summary> Add a memory file to the ZIP file. Allows programmer to specify
  294.     ///  the Local and Central Header data for more flexibility on what gets written.
  295.     ///  Minimal vailidation is done on the Header parameters; speficying bad options
  296.     ///  could result in a corrupted zip file. </summary>
  297.     /// <param name="Data">Stream of file to be added</param>
  298.     /// <param name="LocalHeader">The local header data</param>
  299.     /// <param name="CentralHeader">A Pointer to an optional central header. If no
  300.     /// central Header is provided, the Local Header information is used. </param>
  301.     procedure Add(Data: TStream; LocalHeader: TZipHeader; CentralHeader: PZipHeader = nil); overload;
  302.                                                          
  303.                                                        
  304.     /// <summary>
  305.     /// Event fired before a file inside a zip file is decompressed, allows access to the raw stream for decrypt purposes
  306.     /// </summary>
  307.     class property OnCreateDecompressStream: TOnCreateCustomStream read FOnCreateDecompressStream write FOnCreateDecompressStream;
  308.     /// <summary>
  309.     /// Callback called before a file inside a zip file is decompressed, allows access to the raw stream for decrypt purposes
  310.     /// </summary>
  311.     class property CreateDecompressStreamCallBack: TCreateCustomStreamCallBack read FCreateDecompressStreamCallBack write FCreateDecompressStreamCallBack;
  312.  
  313.     /// <summary> Translate from FileName to index in ZIP Central Header
  314.     /// </summary>
  315.     /// <remarks>
  316.     ///  A ZIP file may have dupicate entries with the same name. This
  317.     ///  function will return the index of the first.
  318.     /// </remarks>
  319.     /// <param name="FileName">Path + Name of file in the arcive.</param>
  320.     /// <returns>The index of the file in the archive, or -1 on failure.
  321.     /// </returns>
  322.     function IndexOf(const FileName: string): Integer;
  323.  
  324.     /// <returns> The mode the TZipFile is opened to</returns>
  325.     property Mode: TZipMode read FMode;
  326.  
  327.     /// <returns>Total files in ZIP File</returns>
  328.     property FileCount: Integer read GetFileCount;
  329.  
  330.     /// <returns>An array of FileNames in the ZIP file</returns>
  331.     property FileNames: TArray<string> read GetFileNames;
  332.     /// <returns>An array of the TZipHeader of the files in the ZIP file</returns>
  333.     property FileInfos: TArray<TZipHeader> read GetFileInfos;
  334.  
  335.     /// <returns>FileName of a File in the ZipFile</returns>
  336.     property FileName[Index: Integer]: string read GetFileName;
  337.     /// <returns>TZipHeader of a File in the ZipFile</returns>
  338.     property FileInfo[Index: Integer]: TZipHeader read GetFileInfo;
  339.     /// <remarks>
  340.     ///  File Comments can be changed for files opened in write mode at any point.
  341.     ///  The comment is written when the Central Directory is written to disk.
  342.     ///  Comments can be a maximum of 65535 bytes long. If a longer comment is supplied,
  343.     ///  It is truncated before writing to the ZIP File.
  344.     /// </remarks>
  345.     property FileComment[Index: Integer]: string read GetFileComment write SetFileComment;
  346.     /// <remarks>
  347.     ///  Comments can be a maximum of 65535 bytes long. If a longer comment is supplied,
  348.     ///  It is truncated before writing to the ZIP File.
  349.     /// </remarks>
  350.     property Comment: string read GetComment write SetComment;
  351.     property UTF8Support: Boolean read FUTF8Support write SetUTF8Support default True;
  352.     /// <summary> On progress event. </summary>
  353.     property OnProgress: TZipProgressEvent read FOnProgress write FOnProgress;
  354.   end;
  355.  
  356. implementation
  357.  
  358. uses
  359.   System.RTLConsts,
  360.   System.ZLib,
  361.   System.Types;
  362.  
  363. function DateTimeToWinFileDate(DateTime: TDateTime): UInt32;
  364. var
  365.   Year, Month, Day, Hour, Min, Sec, MSec: Word;
  366. begin
  367.   DecodeDate(DateTime, Year, Month, Day);
  368.   if (Year < 1980) or (Year > 2107)
  369.     then Result := 0
  370.   else
  371.   begin
  372.     DecodeTime(DateTime, Hour, Min, Sec, MSec);
  373.     LongRec(Result).Lo := (Sec shr 1) or (Min shl 5) or (Hour shl 11);
  374.     LongRec(Result).Hi := Day or (Month shl 5) or ((Year - 1980) shl 9);
  375.   end;
  376. end;
  377.  
  378. function WinFileDateToDateTime(FileDate: UInt32; out DateTime: TDateTime): Boolean;
  379. var
  380.   LDate: TDateTime;
  381.   LTime: TDateTime;
  382. begin
  383.   Result := TryEncodeDate(
  384.     LongRec(FileDate).Hi shr 9 + 1980,
  385.     LongRec(FileDate).Hi shr 5 and 15,
  386.     LongRec(FileDate).Hi and 31,
  387.     LDate);
  388.  
  389.   if Result then
  390.   begin
  391.     Result := TryEncodeTime(
  392.       LongRec(FileDate).Lo shr 11,
  393.       LongRec(FileDate).Lo shr 5 and 63,
  394.       LongRec(FileDate).Lo and 31 shl 1, 0, LTime);
  395.  
  396.     if Result then
  397.       DateTime := LDate + LTime;
  398.   end;
  399. end;
  400.  
  401. procedure VerifyRead(Stream: TStream; Buffer: TBytes; Count: Integer); overload;
  402. begin
  403.   if Stream.Read(Buffer, Count) <> Count then
  404.   raise EZipException.CreateRes(@SZipErrorRead) at ReturnAddress;
  405. end;
  406.  
  407. procedure VerifyRead(Stream: TStream; var Buffer: UInt8; Count: Integer); overload;
  408. begin
  409.   if Stream.Read(Buffer, Count) <> Count then
  410.   raise EZipException.CreateRes(@SZipErrorRead) at ReturnAddress;
  411. end;
  412.  
  413. procedure VerifyRead(Stream: TStream; var Buffer: UInt16; Count: Integer); overload;
  414. begin
  415.   if Stream.Read(Buffer, Count) <> Count then
  416.   raise EZipException.CreateRes(@SZipErrorRead) at ReturnAddress;
  417. end;
  418.  
  419. procedure VerifyRead(Stream: TStream; var Buffer: UInt32; Count: Integer); overload;
  420. begin
  421.   if Stream.Read(Buffer, Count) <> Count then
  422.   raise EZipException.CreateRes(@SZipErrorRead) at ReturnAddress;
  423. end;
  424.  
  425. procedure VerifyWrite(Stream: TStream; Buffer: TBytes; Count: Integer); overload;
  426. begin
  427.   if Stream.Write(Buffer, 0, Count) <> Count then
  428.     raise EZipException.CreateRes(@SZipErrorWrite) at ReturnAddress;
  429. end;
  430.  
  431. procedure VerifyWrite(Stream: TStream; Buffer: UInt8; Count: Integer); overload;
  432. begin
  433.   if Stream.Write(Buffer, Count) <> Count then
  434.     raise EZipException.CreateRes(@SZipErrorWrite) at ReturnAddress;
  435. end;
  436.  
  437. procedure VerifyWrite(Stream: TStream; Buffer: UInt16; Count: Integer); overload;
  438. begin
  439.   if Stream.Write(Buffer, Count) <> Count then
  440.     raise EZipException.CreateRes(@SZipErrorWrite) at ReturnAddress;
  441. end;
  442.  
  443. procedure VerifyWrite(Stream: TStream; Buffer: UInt32; Count: Integer); overload;
  444. begin
  445.   if Stream.Write(Buffer, Count) <> Count then
  446.     raise EZipException.CreateRes(@SZipErrorWrite) at ReturnAddress;
  447. end;
  448.  
  449. type
  450.   /// <summary> Helper class for reading a segment of another stream.</summary>
  451.   TStoredStream = class(TStream)
  452.   private
  453.     FStream: TStream;
  454.     FPos: Int64;
  455.   protected
  456.     function GetSize: Int64; override;
  457.   public
  458.     constructor Create(Stream: TStream);
  459.  
  460.     function Read(var Buffer; Count: Longint): Longint; overload; override;
  461.     function Write(const Buffer; Count: Longint): Longint; overload; override;
  462.     function Read(Buffer: TBytes; Offset, Count: Longint): Longint; overload; override;
  463.     function Write(const Buffer: TBytes; Offset, Count: Longint): Longint; overload; override;
  464.     function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
  465.   end;
  466.  
  467. { TStoredStream }
  468.  
  469. constructor TStoredStream.Create(Stream: TStream);
  470. begin
  471.   FStream := Stream;
  472.   FPos := FStream.Position;
  473. end;
  474.  
  475. function TStoredStream.GetSize: Int64;
  476. begin
  477.   Result := FStream.Size;
  478. end;
  479.  
  480. function TStoredStream.Read(var Buffer; Count: Longint): Longint;
  481. begin
  482.   Result := FStream.Read(Buffer, Count);
  483. end;
  484.  
  485. function TStoredStream.Read(Buffer: TBytes; Offset, Count: Longint): Longint;
  486. begin
  487.   Result := FStream.Read(Buffer, Offset, Count);
  488. end;
  489.  
  490. function TStoredStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
  491. begin
  492.   Result := FStream.Seek(Offset, Origin)
  493. end;
  494.  
  495. function TStoredStream.Write(const Buffer; Count: Longint): Longint;
  496. begin
  497.   Result := FStream.Write(Buffer, Count);
  498. end;
  499.  
  500. function TStoredStream.Write(const Buffer: TBytes; Offset, Count: Longint): Longint;
  501. begin
  502.   Result := FStream.Write(Buffer, Offset, Count);
  503. end;
  504.  
  505. function TZipCompressionToString(Compression: TZipCompression): string;
  506. begin
  507.   case Compression of
  508.     zcStored:    Result := 'Stored';                // do not localize
  509.     zcShrunk:    Result := 'Shrunk';                // do not localize
  510.     zcReduce1:   Result := 'Reduced1';              // do not localize
  511.     zcReduce2:   Result := 'Reduced2';              // do not localize
  512.     zcReduce3:   Result := 'Reduced3';              // do not localize
  513.     zcReduce4:   Result := 'Reduced4';              // do not localize
  514.     zcImplode:   Result := 'Imploded';              // do not localize
  515.     zcTokenize:  Result := 'Tokenized';             // do not localize
  516.     zcDeflate:   Result := 'Deflated';              // do not localize
  517.     zcDeflate64: Result := 'Deflated64';            // do not localize
  518.     zcPKImplode: Result := 'Imploded(TERSE)';       // do not localize
  519.     zcBZIP2:     Result := 'BZIP2';                 // do not localize
  520.     zcLZMA:      Result := 'LZMA';                  // do not localize
  521.     zcTERSE:     Result := 'TERSE';                 // do not localize
  522.     zcLZ77:      Result := 'LZ77';                  // do not localize
  523.     zcWavePack:  Result := 'WavPack';               // do not localize
  524.     zcPPMdI1:    Result := 'PPMd version I, Rev 1'; // do not localize
  525.     else
  526.       Result := 'Unknown';
  527.   end;
  528. end;
  529.  
  530. { TZipFile }
  531.  
  532. function TZipFile.TBytesToString(B: TBytes): string;
  533. var
  534.   E: TEncoding;
  535. begin
  536.   if FUTF8Support then
  537.     E := TEncoding.GetEncoding(65001)
  538.   else
  539.     E := TEncoding.GetEncoding(437);
  540.   try
  541.     Result := E.GetString(B);
  542.   finally
  543.     E.Free;
  544.   end;
  545. end;
  546.  
  547. function TZipFile.StringToTBytes(S: string): TBytes;
  548. var
  549.   E: TEncoding;
  550. begin
  551.   if FUTF8Support then
  552.     E := TEncoding.GetEncoding(65001)
  553.   else
  554.     E := TEncoding.GetEncoding(437);
  555.   try
  556.     Result := E.GetBytes(S);
  557.   finally
  558.     E.Free;
  559.   end;
  560. end;
  561.  
  562. function TZipFile.GetComment: string;
  563. begin
  564.   if FMode = zmClosed then
  565.     raise EZipException.CreateRes(@SZipNotOpen);
  566.   Result := TBytesToString(FComment);
  567. end;
  568.  
  569. function TZipFile.GetFileComment(Index: Integer): string;
  570. begin
  571.   if FMode = zmClosed then
  572.     raise EZipException.CreateRes(@SZipNotOpen);
  573.   Result := TBytesToString(FFiles[Index].FileComment);
  574. end;
  575.  
  576. function TZipFile.GetFileCount: Integer;
  577. begin
  578.   if FMode = zmClosed then
  579.     raise EZipException.CreateRes(@SZipNotOpen);
  580.   Result := FFiles.Count;
  581. end;
  582.  
  583. function TZipFile.GetFileInfo(Index: Integer): TZipHeader;
  584. begin
  585.   if FMode = zmClosed then
  586.     raise EZipException.CreateRes(@SZipNotOpen);
  587.   Result := FFiles[Index];
  588. end;
  589.  
  590. function TZipFile.GetFileInfos: TArray<TZipHeader>;
  591. begin
  592.   if FMode = zmClosed then
  593.     raise EZipException.CreateRes(@SZipNotOpen);
  594.   Result := FFiles.ToArray;
  595. end;
  596.  
  597. function TZipFile.GetFileName(Index: Integer): string;
  598. begin
  599.   if FMode = zmClosed then
  600.     raise EZipException.CreateRes(@SZipNotOpen);
  601.   Result := TBytesToString(FFiles[Index].FileName);
  602. end;
  603.  
  604. function TZipFile.GetFileNames: TArray<string>;
  605. var
  606.   I: Integer;
  607. begin
  608.   if FMode = zmClosed then
  609.     raise EZipException.CreateRes(@SZipNotOpen);
  610.   SetLength(Result, FFiles.Count);
  611.   for I := 0 to High(Result) do
  612.     Result[I] := TBytesToString(FFiles[I].FileName);
  613. end;
  614.  
  615. procedure TZipFile.ReadCentralHeader;
  616. var
  617.   I: Integer;
  618.   Signature: UInt32;
  619.   LEndHeader: TZipEndOfCentralHeader;
  620.   LHeader: TZipHeader;
  621. begin
  622.   FFiles.Clear;
  623.   if FStream.Size = 0 then
  624.     Exit;
  625.   // Read End Of Centeral Direcotry Header
  626.   if not LocateEndOfCentralHeader(LEndHeader) then
  627.     raise EZipException.CreateRes(@SZipErrorRead);
  628.   // Move to the beginning of the CentralDirectory
  629.   FStream.Position := LEndHeader.CentralDirOffset;
  630.   // Save Begginning of Central Directory. This is where new files
  631.   // get written to, and where the new central directory gets written when
  632.   // closing.
  633.   FEndFileData := LEndHeader.CentralDirOffset;
  634.   // Read File Headers
  635.   for I := 0 to LEndHeader.CentralDirEntries - 1 do
  636.   begin
  637.     // Verify Central Header signature
  638.     FStream.Read(Signature, Sizeof(Signature));
  639.     if Signature <> SIGNATURE_CENTRALHEADER then
  640.       raise EZipException.CreateRes(@SZipInvalidCentralHeader);
  641.     // Read Central Header
  642.     VerifyRead(FStream, LHeader.MadeByVersion,      Sizeof(UInt16));
  643.     VerifyRead(FStream, LHeader.RequiredVersion,    Sizeof(UInt16));
  644.     VerifyRead(FStream, LHeader.Flag,               Sizeof(UInt16));
  645.     VerifyRead(FStream, LHeader.CompressionMethod,  Sizeof(UInt16));
  646.     VerifyRead(FStream, LHeader.ModifiedDateTime,   Sizeof(UInt32));
  647.     VerifyRead(FStream, LHeader.CRC32,              Sizeof(UInt32));
  648.     VerifyRead(FStream, LHeader.CompressedSize,     Sizeof(UInt32));
  649.     VerifyRead(FStream, LHeader.UncompressedSize,   Sizeof(UInt32));
  650.     VerifyRead(FStream, LHeader.FileNameLength,     Sizeof(UInt16));
  651.     VerifyRead(FStream, LHeader.ExtraFieldLength,   Sizeof(UInt16));
  652.     VerifyRead(FStream, LHeader.FileCommentLength,  Sizeof(UInt16));
  653.     VerifyRead(FStream, LHeader.DiskNumberStart,    Sizeof(UInt16));
  654.     VerifyRead(FStream, LHeader.InternalAttributes, Sizeof(UInt16));
  655.     VerifyRead(FStream, LHeader.ExternalAttributes, Sizeof(UInt32));
  656.     VerifyRead(FStream, LHeader.LocalHeaderOffset,  Sizeof(UInt32));
  657.  
  658.     // Read Dynamic length fields (FileName, ExtraField, FileComment)
  659.     if LHeader.FileNameLength > 0 then
  660.     begin
  661.       SetLength(LHeader.FileName, LHeader.FileNameLength);
  662.       VerifyRead(FStream, LHeader.FileName, LHeader.FileNameLength);
  663.     end;
  664.     if LHeader.ExtraFieldLength > 0 then
  665.     begin
  666.       SetLength(LHeader.ExtraField, LHeader.ExtraFieldLength);
  667.       VerifyRead(FStream, LHeader.ExtraField, LHeader.ExtraFieldLength);
  668.     end;
  669.     if LHeader.FileCommentLength > 0 then
  670.     begin
  671.       SetLength(LHeader.FileComment, LHeader.FileCommentLength);
  672.       VerifyRead(FStream, LHeader.FileComment, LHeader.FileCommentLength);
  673.     end;
  674.     if (LHeader.Flag and (1 shl 11)) = 0 then
  675.       FUTF8Support := False;
  676.  
  677.     // Save File Header in interal list
  678.     FFiles.Add(LHeader);
  679.   end;
  680. end;
  681.  
  682. procedure TZipFile.SetComment(Value: string);
  683. begin
  684.   FComment := StringToTBytes(Value);
  685.   if not (FMode in [zmReadWrite, zmWrite]) then
  686.     raise EZipException.CreateRes(@SZipNoWrite);
  687.   if Length(FComment) > $FFFF then
  688.     SetLength(FComment, $FFFF);
  689. end;
  690.  
  691. procedure TZipFile.SetFileComment(Index: Integer; Value: string);
  692. var
  693.   LFile: TZipHeader;
  694. begin
  695.   if not (FMode in [zmReadWrite, zmWrite]) then
  696.     raise EZipException.CreateRes(@SZipNoWrite);
  697.   LFile := FFiles[Index];
  698.  
  699.   LFile.FileComment := StringToTBytes(Value);
  700.   if Length(LFile.FileComment) > $FFFF then
  701.     SetLength(LFile.FileComment, $FFFF);
  702.   LFile.FileCommentLength := Length(LFile.FileComment);
  703.   FFiles[Index] := LFile;
  704. end;
  705.  
  706. procedure TZipFile.SetUTF8Support(const Value: Boolean);
  707. begin
  708.   if Value = FUTF8Support then Exit;
  709.   if not (FMode in [zmReadWrite, zmWrite]) then
  710.     raise EZipException.CreateRes(@SZipNoWrite);
  711.   // Resetting this flag would require re-writing all the local headers with the
  712.   // new strings and flag, and adjusting the offsets.
  713.   if FFiles.Count <> 0 then
  714.     raise EZipException.CreateRes(@SZipNotEmpty);
  715.  
  716.                                          
  717.   FUTF8Support := Value;
  718. end;
  719.  
  720. class constructor TZipFile.Create;
  721. begin
  722.   FCompressionHandler := TCompressionDict.Create;
  723.  
  724.   RegisterCompressionHandler(zcStored,
  725.     function(InStream: TStream; const ZipFile: TZipFile; const Item: TZipHeader): TStream
  726.     begin
  727.       Result := TStoredStream.Create(InStream);
  728.     end,
  729.     function(InStream: TStream; const ZipFile: TZipFile; const Item: TZipHeader): TStream
  730.     begin
  731.       Result := TStoredStream.Create(InStream);
  732.     end);
  733.  
  734.   RegisterCompressionHandler(zcDeflate,
  735.     function(InStream: TStream; const ZipFile: TZipFile; const Item: TZipHeader): TStream
  736.     begin
  737.       Result := TZCompressionStream.Create(InStream, zcDefault, -15);
  738.     end,
  739.     function(InStream: TStream; const ZipFile: TZipFile; const Item: TZipHeader): TStream
  740.     var
  741.       LStream : TStream;
  742.       LIsEncrypted: Boolean;
  743.     begin
  744.       // From https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
  745.       // Section 4.4.4 general purpose bit flag: (2 bytes)
  746.       // Bit 0: If set, indicates that the file is encrypted.
  747.       LIsEncrypted := (Item.Flag and 1) = 1;
  748.  
  749.       if Assigned(TZipFile.FOnCreateDecompressStream) then
  750.         LStream := TZipFile.FOnCreateDecompressStream(InStream, ZipFile, Item, LIsEncrypted)
  751.       else if Assigned(TZipFile.FCreateDecompressStreamCallBack) then
  752.         LStream := TZipFile.FCreateDecompressStreamCallBack(InStream, ZipFile, Item, LIsEncrypted)
  753.       else
  754.         LStream := InStream;
  755.       Result := TZDecompressionStream.Create(LStream, -15, LStream <> InStream);
  756.     end);
  757. end;
  758.  
  759. class destructor TZipFile.Destroy;
  760. begin
  761.   FCompressionHandler.Free;
  762. end;
  763.  
  764. class procedure TZipFile.RegisterCompressionHandler(
  765.   Compression: TZipCompression; CompressStream, DecompressStream: TStreamConstructor);
  766. begin
  767.   FCompressionHandler.AddOrSetValue(Compression,
  768.     TPair<TStreamConstructor, TStreamConstructor>.Create(CompressStream, DecompressStream));
  769. end;
  770.  
  771. class function TZipFile.IsValid(const ZipFileName: string): Boolean;
  772. var
  773.   Z: TZipFile;
  774.   Header: TZipEndOfCentralHeader;
  775. begin
  776.   Result := False;
  777.   try
  778.     Z := TZipFile.Create;
  779.     try
  780.       Z.FStream := TFileStream.Create(ZipFileName, fmOpenRead);
  781.       try
  782.         Result := Z.LocateEndOfCentralHeader(Header);
  783.       finally
  784.         Z.FStream.Free;
  785.       end;
  786.     finally
  787.       Z.Free;
  788.     end;
  789.   except on E: EStreamError do
  790.     // Swallow only Stream exceptions and return False
  791.   end;
  792. end;
  793.  
  794. function TZipFile.LocateEndOfCentralHeader(var Header: TZipEndOfCentralHeader): Boolean;
  795. var
  796.   I: Integer;
  797.   LBackRead, LReadSize, LMaxBack: UInt32;
  798.   LBackBuf: TBytes;
  799. begin
  800.   if FStream.Size < $FFFF then
  801.     LMaxBack := FStream.Size
  802.   else
  803.     LMaxBack := $FFFF;
  804.   LBackRead := 4;
  805.   SetLength(LBackBuf, $404 - 1);
  806.   while LBackRead < LMaxBack do
  807.   begin
  808.     if LBackRead + Cardinal(Length(LBackBuf) - 4) > LMaxBack then
  809.       LBackRead := LMaxBack
  810.     else
  811.       Inc(LBackRead, Length(LBackBuf) -4);
  812.     FStream.Position := FStream.Size - LBackRead;
  813.     if Length(LBackBuf) < (FStream.Size - FStream.Position) then
  814.       LReadSize := Length(LBackBuf)
  815.     else
  816.       LReadSize := FStream.Size - FStream.Position;
  817.     VerifyRead(FStream, LBackBuf, LReadSize);
  818.  
  819.     for I := LReadSize - 4 downto 0 do
  820.     begin
  821.       if (LBackBuf[I]   = ((SIGNATURE_ZIPENDOFHEADER       ) and $FF)) and
  822.          (LBackBuf[I+1] = ((SIGNATURE_ZIPENDOFHEADER shr  8) and $FF)) and
  823.          (LBackBuf[I+2] = ((SIGNATURE_ZIPENDOFHEADER shr 16) and $FF)) and
  824.          (LBackBuf[I+3] = ((SIGNATURE_ZIPENDOFHEADER shr 24) and $FF)) then
  825.       begin
  826.         Move(LBackBuf[I+4], Header, SizeOf(Header));
  827.         if Header.CommentLength > 0 then
  828.         begin
  829.           FStream.Position := FStream.Size - LBackRead + I + 4 + SizeOf(Header);
  830.           SetLength(FComment, Header.CommentLength);
  831.           FStream.Read(FComment, Header.CommentLength);
  832.         end
  833.         else
  834.           SetLength(FComment, 0);
  835.         Exit(True);
  836.       end;
  837.     end;
  838.   end;
  839.   Result := False;
  840. end;
  841.  
  842. class procedure TZipFile.ExtractZipFile(const ZipFileName: string; const Path: string; ZipProgress: TZipProgressEvent);
  843. var
  844.   LZip: TZipFile;
  845. begin
  846.   LZip := TZipFile.Create;
  847.   try
  848.     if Assigned(ZipProgress) then
  849.       LZip.OnProgress := ZipProgress;
  850.     LZip.Open(ZipFileName, zmRead);
  851.     LZip.ExtractAll(Path);
  852.     LZip.Close;
  853.   finally
  854.     LZip.Free;
  855.   end;
  856. end;
  857.  
  858. class procedure TZipFile.ZipDirectoryContents(const ZipFileName: string; const Path: string;
  859.   Compression: TZipCompression; ZipProgress: TZipProgressEvent);
  860. var
  861.   LZipFile: TZipFile;
  862.   LFile: string;
  863.   LZFile: string;
  864.   LPath: string;
  865.   LFiles: TStringDynArray;
  866. begin
  867.   LZipFile := TZipFile.Create;
  868.   try
  869.     if Assigned(ZipProgress) then
  870.       LZipFile.OnProgress := ZipProgress;
  871.     if TFile.Exists(ZipFileName) then
  872.       TFile.Delete(ZipFileName);
  873.     LFiles := TDirectory.GetFiles(Path, '*', TSearchOption.soAllDirectories);
  874.     LZipFile.Open(ZipFileName, zmWrite);
  875.     LPath := System.SysUtils.IncludeTrailingPathDelimiter(Path);
  876.     for LFile in LFiles do
  877.     begin
  878.       // Strip off root path
  879. {$IFDEF MSWINDOWS}
  880.       LZFile := StringReplace(Copy(LFile, Length(LPath) + 1, Length(LFile)), '\', '/', [rfReplaceAll]);
  881. {$ELSE}
  882.       LZFile := Copy(LFile, Length(LPath) + 1, Length(LFile));
  883. {$ENDIF MSWINDOWS}
  884.       LZipFile.Add(LFile, LZFile, Compression);
  885.     end;
  886.   finally
  887.     LZipFile.Free;
  888.   end;
  889. end;
  890.  
  891. // Extract Unicode Path
  892. // Based on section 4.6.9 -Info-ZIP Unicode Path Extra Field (0x7075) from
  893. // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
  894. // Stores the UTF-8 version of the file name field as stored in the
  895. //       local header and central directory header. (Last Revision 20070912)
  896. //
  897. //         Value         Size        Description
  898. //         -----         ----        -----------
  899. // (UPath) 0x7075        Short       tag for this extra block type ("up")
  900. //         TSize         Short       total data size for this block
  901. //         Version       1 byte      version of this extra field, currently 1
  902. //         NameCRC32     4 bytes     File Name Field CRC32 Checksum
  903. //         UnicodeName   Variable    UTF-8 version of the entry File Name
  904. class function TZipFile.GetUTF8PathFromExtraField(const AHeader: TZipHeader; out AFileName: string): Boolean;
  905. const
  906.   UPATH = $7075;
  907.   SIZEPOS = 2;
  908.   CRCPOS = 5;
  909.   PATHPOS = 9;
  910.   PATHSIZESUB = 5;
  911. var
  912.   I: Integer;
  913.   LTotalSize: Word;
  914.   LCRC: Cardinal;
  915.   LPathCRC: Cardinal;
  916. begin
  917.   Result := False;
  918.   for I := 0 to AHeader.ExtraFieldLength - 2 do
  919.   begin
  920.     if PWord(@AHeader.ExtraField[I])^ = UPATH then
  921.     begin
  922.       LTotalSize := PWord(@AHeader.ExtraField[I + SIZEPOS])^;
  923.       LCRC := PCardinal(@AHeader.ExtraField[I + CRCPOS])^;
  924.       LPathCRC := crc32(0, nil, 0);
  925.       LPathCRC := crc32(LPathCRC, @AHeader.FileName[0], Length(AHeader.FileName));
  926.       if LPathCRC = LCRC then
  927.       begin
  928.         AFileName := TEncoding.UTF8.GetString(AHeader.ExtraField, I + PATHPOS, LTotalSize - PATHSIZESUB);
  929.         Result := True;
  930.       end;
  931.       Break;
  932.     end;
  933.   end;
  934. end;
  935.  
  936. constructor TZipFile.Create;
  937. begin
  938.   inherited Create;
  939.   FFiles := TList<TZipHeader>.Create;
  940.   FMode := zmClosed;
  941.   FUTF8Support := True;
  942. end;
  943.  
  944. destructor TZipFile.Destroy;
  945. begin
  946.   Close; // In case a file is open for writing currently
  947.  
  948.   FFiles.Free;
  949.   inherited;
  950. end;
  951.  
  952. procedure TZipFile.DoZLibProgress(Sender: TObject);
  953. begin
  954.   if Assigned(FOnProgress) then
  955.     FOnProgress(Self, FCurrentFile, FCurrentHeader, (Sender as TStream).Position);
  956. end;
  957.  
  958. procedure TZipFile.Open(const ZipFileName: string; OpenMode: TZipMode);
  959. var
  960.   LMode: LongInt;
  961.   LFileStream: TFileStream;
  962. begin
  963.   Close; // In case the user had a file open
  964.   case OpenMode of
  965.     zmRead:      LMode := fmOpenRead;
  966.     zmReadWrite: LMode := fmOpenReadWrite;
  967.     zmWrite:     LMode := fmCreate;
  968.     else
  969.       raise EZipException.CreateRes(@sArgumentInvalid);
  970.   end;
  971.   LFileStream := TFileStream.Create(ZipFileName, LMode);
  972.   try
  973.     Open(LFileStream, OpenMode);
  974.     FFileStream := LFileStream;
  975.   except
  976.     FreeAndNil(LFileStream);
  977.     raise;
  978.   end;
  979. end;
  980.  
  981. procedure TZipFile.Open(ZipFileStream: TStream; OpenMode: TZipMode);
  982. begin
  983.   Close; // In case the user had a file open
  984.   if OpenMode = zmClosed then
  985.     raise EZipException.CreateRes(@sArgumentInvalid);
  986.   if (OpenMode = zmRead) and (ZipFileStream.Size = 0) then
  987.     raise EZipException.CreateRes(@SReadError);
  988.  
  989.   FStream := ZipFileStream;
  990.   FStartFileData := FStream.Position;
  991.   if OpenMode in [zmRead, zmReadWrite] then
  992.   try
  993.     // Read the Central Header to verify it's a valid zipfile
  994.     ReadCentralHeader;
  995.   except
  996.     // If it's an invalid zipfile, cleanup
  997.     FStream := nil;
  998.     raise;
  999.   end;
  1000.   FMode := OpenMode;
  1001. end;
  1002.  
  1003. procedure TZipFile.Close;
  1004. var
  1005.   LHeader: TZipHeader;
  1006.   LEndOfHeader: TZipEndOfCentralHeader;
  1007.   I: Integer;
  1008.   Signature: UInt32;
  1009. begin
  1010.   try
  1011.     // Only need to write Central Directory and End Of Central Directory if writing
  1012.     if (FMode = zmReadWrite) or (FMode = zmWrite) then
  1013.     begin
  1014.       FStream.Position := FEndFileData;
  1015.       Signature := SIGNATURE_CENTRALHEADER;
  1016.       // Write File Signatures
  1017.       for I := 0 to FFiles.Count - 1 do
  1018.       begin
  1019.         LHeader := FFiles[I];
  1020.         VerifyWrite(FStream, Signature, SizeOf(Signature));
  1021. //        VerifyWrite(FStream, LHeader.MadeByVersion,  CENTRALHEADERSIZE);
  1022.         VerifyWrite(FStream, LHeader.MadeByVersion,      Sizeof(UInt16));
  1023.         VerifyWrite(FStream, LHeader.RequiredVersion,    Sizeof(UInt16));
  1024.         VerifyWrite(FStream, LHeader.Flag,               Sizeof(UInt16));
  1025.         VerifyWrite(FStream, LHeader.CompressionMethod,  Sizeof(UInt16));
  1026.         VerifyWrite(FStream, LHeader.ModifiedDateTime,   Sizeof(UInt32));
  1027.         VerifyWrite(FStream, LHeader.CRC32,              Sizeof(UInt32));
  1028.         VerifyWrite(FStream, LHeader.CompressedSize,     Sizeof(UInt32));
  1029.         VerifyWrite(FStream, LHeader.UncompressedSize,   Sizeof(UInt32));
  1030.         VerifyWrite(FStream, LHeader.FileNameLength,     Sizeof(UInt16));
  1031.         VerifyWrite(FStream, LHeader.ExtraFieldLength,   Sizeof(UInt16));
  1032.         VerifyWrite(FStream, LHeader.FileCommentLength,  Sizeof(UInt16));
  1033.         VerifyWrite(FStream, LHeader.DiskNumberStart,    Sizeof(UInt16));
  1034.         VerifyWrite(FStream, LHeader.InternalAttributes, Sizeof(UInt16));
  1035.         VerifyWrite(FStream, LHeader.ExternalAttributes, Sizeof(UInt32));
  1036.         VerifyWrite(FStream, LHeader.LocalHeaderOffset,  Sizeof(UInt32));
  1037.  
  1038.         if LHeader.FileNameLength <> 0 then
  1039.           VerifyWrite(FStream, LHeader.FileName, LHeader.FileNameLength);
  1040.         if LHeader.ExtraFieldLength <> 0 then
  1041.           VerifyWrite(FStream, LHeader.ExtraField, LHeader.ExtraFieldLength);
  1042.         if LHeader.FileCommentLength <> 0 then
  1043.           VerifyWrite(FStream, LHeader.FileComment, LHeader.FileCommentLength);
  1044.       end;
  1045.       // Only support writing single disk .ZIP files
  1046.       FillChar(LEndOfHeader, Sizeof(LEndOfHeader), 0);
  1047.       LEndOfHeader.CentralDirEntries := FFiles.Count;
  1048.       LEndOfHeader.NumEntriesThisDisk := FFiles.Count;
  1049.       LEndOfHeader.CentralDirSize := FStream.Position - FEndFileData;
  1050.       LEndOfHeader.CentralDirOffset := FEndFileData;
  1051.       // Truncate comment if it's too long
  1052.       if Length(FComment) > $FFFF then
  1053.         SetLength(FComment, $FFFF);
  1054.       LEndofHeader.CommentLength := Length(FComment);
  1055.       // Write End Of Centeral Directory
  1056.       Signature := SIGNATURE_ZIPENDOFHEADER;
  1057.       VerifyWrite(FStream, Signature, SizeOf(Signature));
  1058. //      VerifyWrite(FStream, LEndOfHeader, SizeOf(LEndOfHeader));
  1059.       VerifyWrite(FStream, LEndOfHeader.DiskNumber,          SizeOf(UInt16));
  1060.       VerifyWrite(FStream, LEndOfHeader.CentralDirStartDisk, SizeOf(UInt16));
  1061.       VerifyWrite(FStream, LEndOfHeader.NumEntriesThisDisk,  SizeOf(UInt16));
  1062.       VerifyWrite(FStream, LEndOfHeader.CentralDirEntries,   SizeOf(UInt16));
  1063.       VerifyWrite(FStream, LEndOfHeader.CentralDirSize,      SizeOf(UInt32));
  1064.       VerifyWrite(FStream, LEndOfHeader.CentralDirOffset,    SizeOf(UInt32));
  1065.       VerifyWrite(FStream, LEndOfHeader.CommentLength,       SizeOf(UInt16));
  1066.  
  1067.       if LEndOfHeader.CommentLength > 0 then
  1068.         VerifyWrite(FStream, FComment, LEndOfHeader.CommentLength);
  1069.     end;
  1070.   finally
  1071.     FMode := zmClosed;
  1072.     FFiles.Clear;
  1073.     FStream := nil;
  1074.     if Assigned(FFileStream) then
  1075.       FreeAndNil(FFileStream);
  1076.   end;
  1077. end;
  1078.  
  1079. procedure TZipFile.Extract(const FileName: string; const Path: string; CreateSubDirs: Boolean);
  1080. begin
  1081.   Extract(IndexOf(FileName), Path, CreateSubdirs);
  1082. end;
  1083.  
  1084. procedure TZipFile.Extract(Index: Integer; const Path: string; CreateSubdirs: Boolean);
  1085. var
  1086.   LInStream, LOutStream: TStream;
  1087.   LHeader: TZipHeader;
  1088.   LDir, LFileName: string;
  1089.   LModifiedDateTime: TDateTime;
  1090. begin
  1091.   // Get decompression stream for file
  1092.   Read(Index, LInStream, LHeader);
  1093.   FCurrentHeader := LHeader;
  1094.   try
  1095.     if not GetUTF8PathFromExtraField(LHeader, LFileName) then
  1096.       LFileName := TBytesToString(FFiles[Index].FileName);
  1097. {$IFDEF MSWINDOWS} // ZIP stores files with '/', so translate to a relative Windows path.
  1098.     LFileName := StringReplace(LFileName, '/', '\', [rfReplaceAll]);
  1099. {$ENDIF}
  1100.     // CreateSubDirs = False assumes the user passed in the path where they want the file to end up
  1101.     if CreateSubdirs then
  1102.       LFileName := TPath.Combine(Path, LFileName)
  1103.     else
  1104.       LFileName := TPath.Combine(Path, ExtractFileName(LFileName));
  1105.     // Force directory creation
  1106.     LDir := ExtractFileDir(LFileName);
  1107.     if CreateSubdirs and (LDir <> '') then
  1108.       TDirectory.CreateDirectory(ExtractFileDir(LFileName));
  1109.     // Open the File For output
  1110.     if LFileName.Chars[LFileName.Length-1] = PathDelim then
  1111.       Exit; // Central Directory Entry points at a directory, not a file.
  1112.     LOutStream := TFileStream.Create(LFileName, fmCreate);
  1113.     try // And Copy from the decompression stream.
  1114.       FCurrentFile := LFileName;
  1115.       // See Bit 3 at http://www.pkware.com/documents/casestudies/APPNOTE.TXT
  1116.       if (LHeader.Flag and (1 shl 3)) = 0 then
  1117.       begin
  1118.         // Empty files should not be read
  1119.         if FFiles[Index].UncompressedSize > 0 then
  1120.           LOutStream.CopyFrom(LInStream, FFiles[Index].UncompressedSize);
  1121.       end
  1122.       else
  1123.       begin
  1124.         LOutStream.CopyFrom(LInStream, FFiles[Index].UncompressedSize);
  1125.       end;
  1126.       if Assigned(FOnProgress) then
  1127.         FOnProgress(Self, FCurrentFile, FCurrentHeader, LOutStream.Position);
  1128.     finally
  1129.       LOutStream.Free;
  1130.       FCurrentFile := '';
  1131.     end;
  1132.     if FileExists(LFileName) then
  1133.     begin
  1134.       if WinFileDateToDateTime(LHeader.ModifiedDateTime, LModifiedDateTime) then
  1135.       begin
  1136.         TFile.SetCreationTime(LFileName, LModifiedDateTime);
  1137.         TFile.SetLastWriteTime(LFileName, LModifiedDateTime);
  1138.       end;
  1139. {$IFDEF MSWINDOWS}
  1140.       if (Hi(FFiles[Index].MadeByVersion) = MADEBY_MSDOS) then
  1141.         TFile.SetAttributes(LFileName, TFile.IntegerToFileAttributes(FFiles[Index].ExternalAttributes and $000000FF));
  1142. {$ENDIF}
  1143. {$IFDEF POSIX}
  1144.       if (Hi(FFiles[Index].MadeByVersion) = MADEBY_UNIX) and (FFiles[Index].ExternalAttributes shr 16 <> 0) then
  1145.         TFile.SetAttributes(LFileName, TFile.IntegerToFileAttributes(FFiles[Index].ExternalAttributes shr 16));
  1146. {$ENDIF}
  1147.     end;
  1148.   finally
  1149.     FCurrentHeader := Default(TZipHeader);
  1150.     LInStream.Free;
  1151.   end;
  1152. end;
  1153.  
  1154. procedure TZipFile.ExtractAll(const Path: string);
  1155. var
  1156.   I: Integer;
  1157. begin
  1158.   if not (FMode in [zmReadWrite, zmRead]) then
  1159.     raise EZipException.CreateRes(@SZipNoRead);
  1160.   for I := 0 to FFiles.Count - 1 do
  1161.     Extract(I, Path);
  1162. end;
  1163.  
  1164. procedure TZipFile.Read(const FileName: string; out Bytes: TBytes);
  1165. begin
  1166.   Read(IndexOf(FileName), Bytes);
  1167. end;
  1168.  
  1169. procedure TZipFile.Read(Index: Integer; out Bytes: TBytes);
  1170. var
  1171.   LStream: TStream;
  1172.   LHeader: TZipHeader;
  1173.   ReadStart, ReadBytes: Int64;
  1174. begin
  1175.   Read(Index, LStream, LHeader);
  1176.   try
  1177.     if (LHeader.Flag and (1 shl 3)) = 0 then
  1178.     begin
  1179.       SetLength(Bytes, FFiles[Index].UncompressedSize);
  1180.       if FFiles[Index].UncompressedSize > 0 then // Special case for empty files.
  1181.         VerifyRead(LStream, Bytes, LHeader.UncompressedSize);
  1182.     end
  1183.     else
  1184.     begin
  1185.       //CRC, Uncompressed, and Compressed Size follow the compressed data.
  1186.       SetLength(Bytes, 4096);
  1187.       ReadStart := 0;
  1188.       ReadBytes := 0; // Supress warning
  1189.       while True do
  1190.       begin
  1191.         ReadBytes := LStream.Read(Bytes[ReadStart], Length(Bytes)-ReadStart);
  1192.         if ReadBytes < (Length(Bytes) - ReadStart) then
  1193.           break;
  1194.         ReadStart := ReadStart + ReadBytes;
  1195.         SetLength(Bytes, Length(Bytes)*2);
  1196.       end;
  1197.       SetLength(Bytes, ReadStart + ReadBytes);
  1198.     end;
  1199.   finally
  1200.     LStream.Free;
  1201.   end;
  1202. end;
  1203. //{$ENDIF}
  1204.  
  1205. procedure TZipFile.Read(const FileName: string; out Stream: TStream; out LocalHeader: TZipHeader);
  1206. begin
  1207.   Read(IndexOf(FileName), Stream, LocalHeader);
  1208. end;
  1209.  
  1210. procedure TZipFile.Read(Index: Integer; out Stream: TStream; out LocalHeader: TZipHeader);
  1211. var
  1212.   Signature: UInt32;
  1213. begin
  1214.   if not (FMode in [zmReadWrite, zmRead]) then
  1215.     raise EZipException.CreateRes(@SZipNoRead);
  1216.  
  1217.   if (Index < 0) or (Index > FFiles.Count) then
  1218.     raise EZipException.CreateRes(@SFileNotFound);
  1219.  
  1220.   // Local Header doesn't have thse fields
  1221.   LocalHeader.MadeByVersion := 0;
  1222.   SetLength(LocalHeader.FileComment, 0);
  1223.   LocalHeader.FileCommentLength  := 0;
  1224.   LocalHeader.DiskNumberStart    := 0;
  1225.   LocalHeader.InternalAttributes := 0;
  1226.   LocalHeader.ExternalAttributes := 0;
  1227.   LocalHeader.LocalHeaderOffset  := 0;
  1228.  
  1229.   // Move to beginning of Local Header
  1230.   FStream.Position := FFiles[Index].LocalHeaderOffset + FStartFileData;
  1231.   // Verify local header signature
  1232.   FStream.Read(Signature, Sizeof(Signature));
  1233.   if Signature <> SIGNATURE_LOCALHEADER then
  1234.     raise EZipException.CreateRes(@SZipInvalidLocalHeader);
  1235.   // Read local header
  1236. //  FStream.Read(LocalHeader.RequiredVersion, LOCALHEADERSIZE);
  1237.     FStream.Read(LocalHeader.RequiredVersion,    Sizeof(UInt16));
  1238.     FStream.Read(LocalHeader.Flag,               Sizeof(UInt16));
  1239.     FStream.Read(LocalHeader.CompressionMethod,  Sizeof(UInt16));
  1240.     FStream.Read(LocalHeader.ModifiedDateTime,   Sizeof(UInt32));
  1241.     FStream.Read(LocalHeader.CRC32,              Sizeof(UInt32));
  1242.     FStream.Read(LocalHeader.CompressedSize,     Sizeof(UInt32));
  1243.     FStream.Read(LocalHeader.UncompressedSize,   Sizeof(UInt32));
  1244.     FStream.Read(LocalHeader.FileNameLength,     Sizeof(UInt16));
  1245.     FStream.Read(LocalHeader.ExtraFieldLength,   Sizeof(UInt16));
  1246.   // Read Name and extra fields
  1247.   SetLength(LocalHeader.FileName, LocalHeader.FileNameLength);
  1248.   FStream.Read(LocalHeader.FileName, LocalHeader.FileNameLength);
  1249.   if LocalHeader.ExtraFieldLength > 0 then
  1250.   begin
  1251.     SetLength(LocalHeader.ExtraField, LocalHeader.ExtraFieldLength);
  1252.     FStream.Read(LocalHeader.ExtraField, LocalHeader.ExtraFieldLength);
  1253.   end;
  1254.   // Create Decompression stream.
  1255.   Stream := FCompressionHandler[TZipCompression(FFiles[Index].CompressionMethod)].Value(FStream, Self, LocalHeader);
  1256.   if Stream is TZDecompressionStream then
  1257.     (Stream as TZDecompressionStream).OnProgress := DoZLibProgress;
  1258. end;
  1259.  
  1260. procedure TZipFile.Add(Data: TStream; LocalHeader: TZipHeader; CentralHeader: PZipHeader);
  1261. var
  1262.   DataStart: Int64;
  1263.   LCompressStream: TStream;
  1264.   Signature: UInt32;
  1265.   LStartPos: Int64;
  1266.   LBuffer: TBytes;
  1267. begin
  1268.   // Seek to End of zipped data
  1269.   FStream.Position := FEndFileData;
  1270.   LocalHeader.LocalHeaderOffset := FEndFileData;
  1271.   // Require at least version 2.0
  1272.   if Lo(LocalHeader.MadeByVersion) < 20 then
  1273.     LocalHeader.MadeByVersion := Word(LocalHeader.MadeByVersion and $FF00) + 20;
  1274.   if LocalHeader.RequiredVersion < 20 then
  1275.     LocalHeader.RequiredVersion := 20;
  1276.  
  1277.   // Trust the length of the strings over the Length members
  1278.   LocalHeader.FileNameLength   := Length(LocalHeader.FileName);
  1279.   LocalHeader.ExtraFieldLength := Length(LocalHeader.ExtraField);
  1280.   if CentralHeader = nil then
  1281.     CentralHeader := @LocalHeader
  1282.   else
  1283.   begin // Trust the length of the strings over the Length members
  1284.     CentralHeader^.FileNameLength   := Length(CentralHeader^.FileName);
  1285.     CentralHeader^.ExtraFieldLength := Length(CentralHeader^.ExtraField);
  1286.   end;
  1287.   CentralHeader^.FileCommentLength  := Length(CentralHeader^.FileComment);
  1288.  
  1289.   // Write Signature, Header, and FileName
  1290.   Signature := SIGNATURE_LOCALHEADER;
  1291.   VerifyWrite(FStream, Signature, SizeOf(Signature));
  1292. //  VerifyWrite(FStream, LocalHeader.RequiredVersion, LOCALHEADERSIZE);
  1293.     VerifyWrite(FStream, LocalHeader.RequiredVersion,    Sizeof(UInt16));
  1294.     VerifyWrite(FStream, LocalHeader.Flag,               Sizeof(UInt16));
  1295.     VerifyWrite(FStream, LocalHeader.CompressionMethod,  Sizeof(UInt16));
  1296.     VerifyWrite(FStream, LocalHeader.ModifiedDateTime,   Sizeof(UInt32));
  1297.     VerifyWrite(FStream, LocalHeader.CRC32,              Sizeof(UInt32));
  1298.     VerifyWrite(FStream, LocalHeader.CompressedSize,     Sizeof(UInt32));
  1299.     VerifyWrite(FStream, LocalHeader.UncompressedSize,   Sizeof(UInt32));
  1300.     VerifyWrite(FStream, LocalHeader.FileNameLength,     Sizeof(UInt16));
  1301.     VerifyWrite(FStream, LocalHeader.ExtraFieldLength,   Sizeof(UInt16));
  1302.  
  1303.   VerifyWrite(FStream, LocalHeader.FileName, LocalHeader.FileNameLength);
  1304.   if LocalHeader.ExtraFieldLength > 0 then
  1305.     VerifyWrite(FStream, LocalHeader.ExtraField, LocalHeader.ExtraFieldLength);
  1306.   // Save position to calcuate Compressed Size
  1307.   LStartPos := FStream.Position;
  1308.   DataStart := Data.Position;
  1309.   LocalHeader.UncompressedSize := Data.Size - DataStart;
  1310.   // Write Compressed data
  1311.   FCurrentHeader := LocalHeader;
  1312.   LCompressStream := FCompressionHandler[TZipCompression(LocalHeader.CompressionMethod)].Key(FStream, self, LocalHeader);
  1313.   if LCompressStream is TZCompressionStream then
  1314.     (LCompressStream as TZCompressionStream).OnProgress := DoZLibProgress;
  1315.   try
  1316.     LCompressStream.CopyFrom(Data, LocalHeader.UncompressedSize);
  1317.     if Assigned(FOnProgress) then
  1318.       FOnProgress(Self, FCurrentFile, FCurrentHeader, LCompressStream.Position);
  1319.   finally
  1320.     LCompressStream.Free;
  1321.     FCurrentHeader := Default(TZipHeader);
  1322.   end;
  1323.  
  1324.   // Calcuate CompressedSize
  1325.   LocalHeader.CompressedSize := FStream.Position - LStartPos;
  1326.   Data.Position := DataStart;
  1327.   SetLength(LBuffer, $4000);
  1328.   // Calcuate Uncompressed data's CRC
  1329.   while Data.Position < LocalHeader.UncompressedSize do
  1330.     LocalHeader.CRC32 := crc32(LocalHeader.CRC32, @LBuffer[0],
  1331.       Data.Read(LBuffer, Length(LBuffer)));
  1332.   CentralHeader.UnCompressedSize := LocalHeader.UnCompressedSize;
  1333.   CentralHeader.CompressedSize := LocalHeader.CompressedSize;
  1334.   CentralHeader.CRC32 := LocalHeader.CRC32;
  1335.   // Save new End of zipped data mark
  1336.   FEndFileData := FStream.Position;
  1337.   // Move to beginning of Local Header offset and rewrite header
  1338.   // with correct CompressedSize and CRC32
  1339.   FStream.Position := LocalHeader.LocalHeaderOffset + SizeOf(UInt32);
  1340. //  FStream.Write(LocalHeader.RequiredVersion, LOCALHEADERSIZE);
  1341.   FStream.Write(LocalHeader.RequiredVersion,    Sizeof(UInt16));
  1342.   FStream.Write(LocalHeader.Flag,               Sizeof(UInt16));
  1343.   FStream.Write(LocalHeader.CompressionMethod,  Sizeof(UInt16));
  1344.   FStream.Write(LocalHeader.ModifiedDateTime,   Sizeof(UInt32));
  1345.   FStream.Write(LocalHeader.CRC32,              Sizeof(UInt32));
  1346.   FStream.Write(LocalHeader.CompressedSize,     Sizeof(UInt32));
  1347.   FStream.Write(LocalHeader.UncompressedSize,   Sizeof(UInt32));
  1348.   FStream.Write(LocalHeader.FileNameLength,     Sizeof(UInt16));
  1349.   FStream.Write(LocalHeader.ExtraFieldLength,   Sizeof(UInt16));
  1350.  
  1351.   FFiles.Add(CentralHeader^);
  1352. end;
  1353.  
  1354. procedure TZipFile.CheckFileName(const ArchiveFileName: string);
  1355. begin
  1356.   if ArchiveFileName = '' then
  1357.     raise EZipException.CreateRes(@SZipFileNameEmpty);
  1358. end;
  1359.  
  1360. procedure TZipFile.Add(const FileName: string; const ArchiveFileName: string;
  1361.   Compression: TZipCompression);
  1362. var
  1363.   LInStream: TStream;
  1364.   LHeader: TZipHeader;
  1365.   LArchiveFileName: string;
  1366. begin
  1367.   CheckFileName(FileName);
  1368.   if not (FMode in [zmReadWrite, zmWrite]) then
  1369.     raise EZipException.CreateRes(@SZipNoWrite);
  1370.  
  1371.   if not FCompressionHandler.ContainsKey(Compression) then
  1372.     raise EZipException.CreateResFmt(@SZipNotSupported, [
  1373.       TZipCompressionToString(Compression) ]);
  1374.  
  1375.   // Setup Header
  1376.   FillChar(LHeader, sizeof(LHeader), 0);
  1377.   LHeader.Flag := 0;
  1378.   FCurrentFile := FileName;
  1379.   LInStream := TFileStream.Create(FileName, fmOpenRead);
  1380.   try
  1381.     {$IFDEF MSWINDOWS}
  1382.     LHeader.MadeByVersion := Word(MADEBY_MSDOS shl 8);
  1383.     {$ENDIF}
  1384.     {$IFDEF POSIX}
  1385.     LHeader.MadeByVersion := Word(MADEBY_UNIX shl 8);
  1386.     {$ENDIF}
  1387.     LHeader.Flag := 0;
  1388.     LHeader.CompressionMethod := UInt16(Compression);
  1389.     LHeader.ModifiedDateTime := DateTimeToWinFileDate(TFile.GetLastWriteTime(FileName));
  1390.     LHeader.UncompressedSize := LInStream.Size;
  1391.     LHeader.InternalAttributes := 0;
  1392.     LHeader.ExternalAttributes := TFile.FileAttributesToInteger(TFile.GetAttributes(FileName));
  1393.     if Hi(LHeader.MadeByVersion) = MADEBY_UNIX then
  1394.       LHeader.ExternalAttributes := LHeader.ExternalAttributes shl 16;
  1395.     if ArchiveFileName <> '' then
  1396.         LArchiveFileName := ArchiveFileName
  1397.       else
  1398.       LArchiveFileName := ExtractFileName(FileName);
  1399.     if FUTF8Support then
  1400.       LHeader.Flag := LHeader.Flag or (1 shl 11); // Language encoding flag, UTF8
  1401.     LHeader.FileName := StringToTBytes(LArchiveFileName);
  1402.     LHeader.FileNameLength := Length(LHeader.FileName);
  1403.  
  1404.     LHeader.ExtraFieldLength := 0;
  1405.     Add(LInStream, LHeader);
  1406.   finally
  1407.     LInStream.Free;
  1408.     FCurrentFile := '';
  1409.   end;
  1410. end;
  1411.  
  1412. procedure TZipFile.Add(Data: TBytes; const ArchiveFileName: string;
  1413.   Compression: TZipCompression);
  1414. var
  1415.   LInStream: TStream;
  1416. begin
  1417.   CheckFileName(ArchiveFileName);
  1418.   if not (FMode in [zmReadWrite, zmWrite]) then
  1419.     raise EZipException.CreateRes(@SZipNoWrite);
  1420.  
  1421.   if not FCompressionHandler.ContainsKey(Compression) then
  1422.     raise EZipException.CreateResFmt(@SZipNotSupported, [
  1423.       TZipCompressionToString(Compression) ]);
  1424.  
  1425.   LInStream := TBytesStream.Create(Data);
  1426.   try
  1427.     Add(LInStream, ArchiveFileName, Compression);
  1428.   finally
  1429.     LInStream.Free;
  1430.   end;
  1431. end;
  1432.  
  1433. procedure TZipFile.Add(Data: TStream; const ArchiveFileName: string;
  1434.   Compression: TZipCompression; AExternalAttributes: TFileAttributes);
  1435. var
  1436.   LHeader: TZipHeader;
  1437. begin
  1438.   CheckFileName(ArchiveFileName);
  1439.   if not (FMode in [zmReadWrite, zmWrite]) then
  1440.     raise EZipException.CreateRes(@SZipNoWrite);
  1441.  
  1442.   if not FCompressionHandler.ContainsKey(Compression) then
  1443.     raise EZipException.CreateResFmt(@SZipNotSupported, [
  1444.       TZipCompressionToString(Compression) ]);
  1445.  
  1446.   // Setup Header
  1447.   FillChar(LHeader, sizeof(LHeader), 0);
  1448.   {$IFDEF MSWINDOWS}
  1449.   LHeader.MadeByVersion := Word(MADEBY_MSDOS shl 8);
  1450.   {$ENDIF}
  1451.   {$IFDEF POSIX}
  1452.   LHeader.MadeByVersion := Word(MADEBY_UNIX shl 8);
  1453.   {$ENDIF}
  1454.   LHeader.Flag := 0;
  1455.   LHeader.CompressionMethod := UInt16(Compression);
  1456.   LHeader.ModifiedDateTime := DateTimeToWinFileDate(Now);
  1457.   LHeader.InternalAttributes := 0;
  1458.   LHeader.ExternalAttributes := TFile.FileAttributesToInteger(AExternalAttributes);
  1459.   if Hi(LHeader.MadeByVersion) = MADEBY_UNIX then
  1460.     LHeader.ExternalAttributes := LHeader.ExternalAttributes shl 16;
  1461.  
  1462.   if FUTF8Support then
  1463.     LHeader.Flag := LHeader.Flag or (1 shl 11); // Language encoding flag, UTF8
  1464.   LHeader.FileName := StringToTBytes(ArchiveFileName);
  1465.   LHeader.FileNameLength := Length(LHeader.FileName);
  1466.  
  1467.   LHeader.ExtraFieldLength := 0;
  1468.   Add(Data, LHeader);
  1469. end;
  1470.  
  1471.  
  1472. function TZipFile.IndexOf(const FileName: string): Integer;
  1473. var
  1474.   I: Integer;
  1475. begin
  1476.   Result := -1;
  1477.   for I := 0 to FFiles.Count - 1 do
  1478.     if SameText(TBytesToString(FFiles[I].FileName), FileName) then
  1479.       Exit(I);
  1480. end;
  1481.  
  1482. end.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement