2027 lines
67 KiB
ObjectPascal
2027 lines
67 KiB
ObjectPascal
/// high-level access to .zip archive file compression
|
|
// - this unit is a part of the freeware Synopse framework,
|
|
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
|
|
unit SynZipFiles;
|
|
|
|
(*
|
|
This file is part of Synopse framework.
|
|
|
|
Synopse framework. Copyright (C) 2022 Arnaud Bouchez
|
|
Synopse Informatique - https://synopse.info
|
|
|
|
*** BEGIN LICENSE BLOCK *****
|
|
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
|
|
The contents of this file are subject to the Mozilla Public License Version
|
|
1.1 (the "License"); you may not use this file except in compliance with
|
|
the License. You may obtain a copy of the License at
|
|
http://www.mozilla.org/MPL
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
for the specific language governing rights and limitations under the License.
|
|
|
|
The Original Code is Synopse framework.
|
|
|
|
The Initial Developer of the Original Code is Arnaud Bouchez.
|
|
|
|
Portions created by the Initial Developer are Copyright (C) 2022
|
|
the Initial Developer. All Rights Reserved.
|
|
|
|
Contributor(s):
|
|
Alternatively, the contents of this file may be used under the terms of
|
|
either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
in which case the provisions of the GPL or the LGPL are applicable instead
|
|
of those above. If you wish to allow use of your version of this file only
|
|
under the terms of either the GPL or the LGPL, and not to allow others to
|
|
use your version of this file under the terms of the MPL, indicate your
|
|
decision by deleting the provisions above and replace them with the notice
|
|
and other provisions required by the GPL or the LGPL. If you do not delete
|
|
the provisions above, a recipient may use your version of this file under
|
|
the terms of any one of the MPL, the GPL or the LGPL.
|
|
|
|
***** END LICENSE BLOCK *****
|
|
|
|
*)
|
|
|
|
interface
|
|
|
|
{$I Synopse.inc} // define HASINLINE CPU32 CPU64
|
|
|
|
uses
|
|
{$ifdef MSWINDOWS}
|
|
Windows,
|
|
// USEPDF: under Windows: get ZLib 1.2.3 functions from libpdf.dll
|
|
{$ifdef USEPDF}pdf,{$endif}
|
|
{$else}
|
|
{$ifdef FPC}
|
|
SynFPCLinux,
|
|
{$endif}
|
|
{$ifdef KYLIX3}
|
|
Types,
|
|
{$endif}
|
|
{$endif}
|
|
SynCommons,
|
|
Classes,
|
|
SysUtils,
|
|
SynZip;
|
|
|
|
{
|
|
Proprietary compression/encryption adding to standard zip files
|
|
- the .zip file structure is 100% compatible with standard
|
|
- data is stored uncompressed, but passed via TSynCompressionAlgo class
|
|
- algorithms are ID-identified (from 1 to 15)
|
|
- algorithm registration is made with SynCompressionAlgos.AlgoRegister
|
|
- ID is stored in unused 7..10 bits of "flags" zip entry (cf. PKware appnote)
|
|
- TSynCompressionAlgo is called by 64KB chunks or once for whole data
|
|
- inherit from TSynCompressionAlgoBuf to simply handle 64KB chunks
|
|
- Synopse has registered several TSynCompressionAlgo IDs:
|
|
1=SynLZ-chunked 2=SynLZ-whole
|
|
3=LzoAsm-chunked 4=LzoAsm-whole
|
|
5=Bz2-chunked
|
|
6=AES-chunked 7=AES+Zip-chunked 8=AES+SynLz-chunked
|
|
so you can use 9..15 for your own purpose
|
|
- most of this unit functions are TSynCompressionAlgo aware
|
|
}
|
|
|
|
type
|
|
TZipException = class(Exception);
|
|
|
|
TSynCompressionAlgo = class
|
|
protected
|
|
fDestStream: TStream;
|
|
public
|
|
/// initialize compression into OutStream
|
|
procedure CompressInit(OutStream: TStream); virtual;
|
|
/// compress InP[InLen] into OutStream + update CRC, return compressed length
|
|
function Compress(InP: pointer; InLen: cardinal; CRC: PCardinal): cardinal; virtual; abstract;
|
|
/// called once at the end for compression flush, return compressed length
|
|
// (default implementation: just do nothing)
|
|
function CompressFinish: cardinal; virtual;
|
|
/// return uncompressed length of InP[InLen] for proper mem allocation
|
|
function UnCompressedLength(InP: pointer; InLen: cardinal): cardinal; virtual; abstract;
|
|
/// uncompress InP[InLen] into OutP, return uncompressed length (called once for decompression)
|
|
function UnCompress(InP: pointer; InLen: cardinal; OutP: pointer): cardinal; virtual; abstract;
|
|
end;
|
|
|
|
TSynCompressionAlgoClass = class of TSynCompressionAlgo;
|
|
|
|
{$ifdef USERECORDWITHMETHODS}TSynCompressionAlgos = record
|
|
{$else}TSynCompressionAlgos = object{$endif}
|
|
public
|
|
Values: array of record
|
|
ID, WholeID: integer;
|
|
func: TSynCompressionAlgoClass;
|
|
end;
|
|
procedure AlgoRegister(aAlgo: TSynCompressionAlgoClass; aID,aWholeID: integer);
|
|
function Algo(aID: integer): TSynCompressionAlgoClass;
|
|
function WholeAlgoID(aID: integer): integer;
|
|
end;
|
|
|
|
/// template class for 64KB chunked (not whole) algorithm (SynLZ, LZO...)
|
|
// which forces storing as uncompressed if compression ratio has no gain
|
|
TSynCompressionAlgoBuf = class(TSynCompressionAlgo)
|
|
protected
|
|
// fast tmp buffer (size=worse case with 64KB chunk)
|
|
fCompressBuf: PAnsiChar;
|
|
function AlgoCompress(src: PAnsiChar; size: integer; dst: PAnsiChar): integer; virtual; abstract;
|
|
function AlgoCompressLength(size: integer): integer; virtual; abstract;
|
|
function AlgoUnCompress(src: PAnsiChar; size: integer; dst: PAnsiChar): integer; virtual; abstract;
|
|
function AlgoUnCompressLength(src: PAnsiChar; size: integer): integer; virtual; abstract;
|
|
public
|
|
/// initialize compression into OutStream
|
|
procedure CompressInit(OutStream: TStream); override;
|
|
/// free fCompressBuf memory if allocated
|
|
destructor Destroy; override;
|
|
/// compress InP[InLen] into OutStream + update CRC, return compressed length
|
|
function Compress(InP: pointer; InLen: cardinal; CRC: PCardinal): cardinal; override;
|
|
/// return uncompressed length of InP[InLen] for proper mem allocation
|
|
function UnCompressedLength(InP: pointer; InLen: cardinal): cardinal; override;
|
|
/// uncompress InP[InLen] into OutP, return uncompressed length
|
|
function UnCompress(InP: pointer; InLen: cardinal; OutP: pointer): cardinal; override;
|
|
end;
|
|
|
|
/// template class for whole algorithm (SynLZ, LZO...)
|
|
// which forces storing as uncompressed if compression ratio has no gain
|
|
TSynCompressionAlgoWhole = class(TSynCompressionAlgo)
|
|
protected
|
|
function AlgoCompress(src: PAnsiChar; size: integer; dst: PAnsiChar): integer; virtual; abstract;
|
|
function AlgoCompressLength(size: integer): integer; virtual; abstract;
|
|
function AlgoUnCompress(src: PAnsiChar; size: integer; dst: PAnsiChar): integer; virtual; abstract;
|
|
function AlgoUnCompressLength(src: PAnsiChar; size: integer): integer; virtual; abstract;
|
|
public
|
|
/// compress InP[InLen] into OutStream + update CRC, return compressed length
|
|
function Compress(InP: pointer; InLen: cardinal; CRC: PCardinal): cardinal; override;
|
|
/// return uncompressed length of InP[InLen] for proper mem allocation
|
|
function UnCompressedLength(InP: pointer; InLen: cardinal): cardinal; override;
|
|
/// uncompress InP[InLen] into OutP, return uncompressed length
|
|
function UnCompress(InP: pointer; InLen: cardinal; OutP: pointer): cardinal; override;
|
|
end;
|
|
|
|
TZipCompressor = class(TStream)
|
|
private
|
|
fInitialized: Boolean;
|
|
fDestStream: TStream;
|
|
fStrm: TZStream;
|
|
fAlgorithm: TSynCompressionAlgo;
|
|
fAlgorithmStream: THeapMemoryStream;
|
|
fAlgorithmID: integer; // =0 if not Assigned(fAlgorithm)
|
|
fCRC: Cardinal;
|
|
fBlobDataHeaderPosition: Int64;
|
|
fBufferIn, fBufferOut: array[word] of byte; // two 64kb buffers
|
|
procedure Finish;
|
|
function FlushBufferOut: integer;
|
|
function InFlateDeflate: boolean; // return true if error
|
|
function GetSizeIn: cardinal;
|
|
function GetSizeOut: cardinal;
|
|
public
|
|
constructor Create(outStream: TStream; CompressionLevel: Integer;
|
|
Algorithm: integer=0);
|
|
constructor CreateAsBlobData(outStream: TStream; CompressionLevel: Integer;
|
|
Algorithm: integer=0);
|
|
destructor Destroy; override;
|
|
function Read(var Buffer; Count: Longint): Longint; override;
|
|
function Write(const Buffer; Count: Longint): Longint; override;
|
|
function WriteOnce(const Buffer; Count: Longint): Longint;
|
|
function Seek(Offset: Longint; Origin: Word): Longint; override;
|
|
property SizeIn: cardinal read GetSizeIn;
|
|
property SizeOut: cardinal read GetSizeOut;
|
|
property CRC: cardinal read fCRC;
|
|
end;
|
|
|
|
TGzWriter = class(TZipCompressor)
|
|
private
|
|
outFile: TStream;
|
|
outFileToBeFree: boolean;
|
|
public
|
|
constructor Create(const aFileName: TFileName); overload;
|
|
constructor Create(const aDestStream: TStream); overload;
|
|
// use Write() to add some data
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
PZipEntry = ^TZipEntry;
|
|
TZipEntry = {$ifdef UNICODE} record {$else} object {$endif}
|
|
ZipName: RawUTF8;
|
|
Header: TFileHeader; // as stored in standard .zip file
|
|
function SameAs(const aEntry: TZipEntry): boolean;
|
|
// test if algo is registered, perform crc32 check and create one instance
|
|
function AlgoCreate(data: pointer; const FileName: TFileName): TSynCompressionAlgo;
|
|
function LocalHeader(ZipStart: PAnsiChar): PLocalFileHeader;
|
|
function LocalDataPosition(ZipStart: PAnsiChar): PtrUInt;
|
|
end;
|
|
|
|
TZipCommon = class
|
|
private
|
|
fCount: integer;
|
|
fFileName: TFileName;
|
|
public
|
|
Entry: array of TZipEntry;
|
|
constructor Create(const aFileName: TFileName);
|
|
destructor Destroy; override;
|
|
function ZipNameIndexOf(const aZipName: RawUTF8): integer;
|
|
property Count: integer read fCount;
|
|
property FileName: TFileName read fFileName;
|
|
end;
|
|
|
|
// used to transfert Blob Data from/to Client without compress/uncompress:
|
|
{$A-}
|
|
PBlobData = ^TBlobData;
|
|
{$ifdef USERECORDWITHMETHODS}TBlobData = record
|
|
{$else}TBlobData = object{$endif}
|
|
private
|
|
// test if algo is registered, perform crc32 check and create one instance
|
|
function AlgoCreate(data: pointer): TSynCompressionAlgo;
|
|
public
|
|
dataSize, // always used
|
|
dataFullSize, dataCRC: cardinal; // used only if AlreadyCompressed
|
|
dataMethod: byte; // 0=stored 8=inflate >=16: AlgoID=dataMethod shr 4
|
|
databuf: AnsiChar;
|
|
function AlgoID: cardinal;
|
|
procedure SetFrom(const FileInfo: TFileInfo);
|
|
// uncompress if necessary:
|
|
function Expand: RawByteString;
|
|
// do freemem() if dataMethod<>0; no direct AES since may be mapped
|
|
function ExpandBuf(out destSize: cardinal): pointer;
|
|
procedure ExpandStream(Stream: TStream);
|
|
function Next: PAnsiChar; // points to next bloc
|
|
end;
|
|
{$A8}
|
|
|
|
const
|
|
BLOBDATA_HEADSIZE = sizeof(TBlobData)-sizeof(AnsiChar); // databuf: AnsiChar
|
|
|
|
type
|
|
TZipReader = class(TZipCommon)
|
|
protected
|
|
fMap: TMemoryMap;
|
|
public
|
|
constructor Create(const aFileName: TFileName);
|
|
destructor Destroy; override;
|
|
procedure Clear; // force Count=0
|
|
function GetData(aIndex: integer; aStream: TStream=nil; CheckCRC: boolean=false;
|
|
asBlobDataStored: boolean=false; withAlgoDataLen: boolean=false): PAnsiChar;
|
|
function GetString(aIndex: integer): RawByteString;
|
|
function GetBuffer(aIndex: integer; out Dest: PAnsiChar): integer;
|
|
function GetBlobData(aIndex: integer): RawByteString; overload; // PBlobData(result)
|
|
procedure GetBlobData(aIndex: integer; aStream: TStream); overload; // TBlobData->aStream
|
|
procedure DeleteLastEntry; // don't use inside TZipValues: already done in Create
|
|
procedure SaveToStream(aStream: TStream); // save uncompressed to stream
|
|
function SameAs(aReader: TZipReader): boolean;
|
|
property Map: TMemoryMap read fMap;
|
|
end;
|
|
|
|
TZipWriter = class(TZipCommon)
|
|
private
|
|
outFile: TFileStream;
|
|
fNow: integer;
|
|
fDestFileName: TFileName;
|
|
function AddEntry(const aZipName: RawUTF8; FileAge: integer = 0): PZipEntry;
|
|
public
|
|
Zip: TZipCompressor;
|
|
forceFileAge: integer; // <>0 -> will be used in Destroy to dest file
|
|
constructor Create(const aFileName: TFileName); overload;
|
|
constructor Create(AppendTo: TZipReader; ReCreate: boolean=false); overload;
|
|
constructor Create(fromStream: TStream; const DestFilename: TFileName=''); overload;
|
|
// restore after TZipReader.SaveToStream()
|
|
destructor Destroy; override;
|
|
|
|
/// this Creates a TZipCompressor -> user Zip.Write() to send data:
|
|
// if CompressionLevel<0: direct copy
|
|
procedure ZipCreate(const aZipName: RawUTF8; CompressionLevel: integer;
|
|
FileAge: integer = 0; Algorithm: integer=0);
|
|
/// compression finish, fileInfo update+save, Zip.Free;
|
|
// after ZipCreate: let aIndex=-1 will update Entry[Count]+inc(fCount)
|
|
procedure ZipClose(aIndex: integer=-1);
|
|
|
|
procedure Add(const aZipName: RawUTF8; data: PAnsiChar; dataSize: cardinal;
|
|
CompressionLevel: integer; dataCRC: pCardinal=nil;
|
|
FileAge: integer = 0; Algorithm: integer=0); overload;
|
|
procedure Add(const aZipName: RawUTF8; p: PBlobData); overload;
|
|
procedure Add(aReader: TZipReader; aReaderIndex: integer); overload;
|
|
procedure AddFile(const aFileName: TFileName; const aZipName: RawUTF8;
|
|
CompressionLevel: integer; Algorithm: integer=0);
|
|
function LastCRC32Added: cardinal;
|
|
end;
|
|
|
|
// TZip handles ZIP standard files on disk
|
|
TZip = class
|
|
private
|
|
FileQueue: TStringList;
|
|
SomeDeleted: boolean;
|
|
public
|
|
Reader: TZipReader;
|
|
Writer: TZipWriter;
|
|
constructor Create(const aFileName: TFileName);
|
|
destructor Destroy; override;
|
|
function FileName: TFileName;
|
|
function MarkDeleted(aReaderIndex: integer): boolean; virtual; // before any ZipCreate
|
|
function MarkDeletedBefore(aDate: TDateTime; aBackup: TZip=nil): boolean;
|
|
procedure BeginWriter; virtual;
|
|
function ZipCreate(const aZipName: RawUTF8; CompressionLevel: integer;
|
|
Algorithm: integer=0): TZipCompressor;
|
|
// use Zip.Write() to send data before ZipClose
|
|
procedure ZipClose;
|
|
function AddBuf(const aZipName: RawUTF8; CompressionLevel: integer;
|
|
data: pointer; dataSize: cardinal; Algorithm: integer=0): boolean;
|
|
function AddToFileQueue(const aFileName: TFileName;
|
|
const aZipName: RawUTF8): boolean; // flushed at Destroy
|
|
function SameAs(aZip: TZip): boolean;
|
|
function FileQueueCount: integer;
|
|
end;
|
|
|
|
TZipValues = class(TZip)
|
|
// store some Values[] in a .zip file (TBlob, TBlobDiff, TDC4...)
|
|
protected // all this must be overrided according to Values[]:
|
|
procedure LoadValues(data: PAnsiChar); virtual; abstract;
|
|
// SetLength(Values,Count+10); move(data,Values[0],Count*sizeof(Values[0]));
|
|
procedure SaveValues(Zip: TZipCompressor); virtual; abstract;
|
|
public
|
|
Count: integer;
|
|
modified: boolean;
|
|
constructor Create(const aFileName: TFileName); // make Reader.Create
|
|
destructor Destroy; override;
|
|
function GetValue(aReaderIndex: integer; aStream: TStream=nil): PAnsiChar;
|
|
procedure CopyValue(source, dest: integer); virtual; abstract;
|
|
// Values[dest] := Values[source];
|
|
procedure BeginWriter; override;
|
|
function MarkDeleted(aReaderIndex: integer): boolean; override; // before any AddValue
|
|
end;
|
|
|
|
var
|
|
BlobDataNull: TBlobData;
|
|
SynCompressionAlgos: TSynCompressionAlgos;
|
|
|
|
|
|
procedure CompressAsBlobData(const data; size: integer; aStream: TStream;
|
|
CompressionLevel: integer=6; Algorithm: integer=0);
|
|
// create a TBlobData in aStream - can use encryption with algo
|
|
// 7=AES+Zip-chunked and 8=AES+SynLz-chunked
|
|
|
|
function GZRead(const aFileName: TFileName): RawByteString; overload;
|
|
function GZRead(gz: PAnsiChar; gzLen: integer): RawByteString; overload;
|
|
procedure GZRead(const aFileName: TFileName; aStream: TStream; StoreLen: boolean); overload;
|
|
// direct uncompress .gz file into string or TStream
|
|
|
|
|
|
implementation
|
|
|
|
const // TZipException messages:
|
|
sZlibInternalError = 'zlib: Internal error';
|
|
sIncorrectZipFormatN = 'Incorrect zip format in file %s: %s';
|
|
sZipAlgoIDNUnknownN = 'Algo ID %d unknown for %s';
|
|
sZipCrcErrorNN = 'crc32 checksum error for %s in %s';
|
|
|
|
|
|
{ TZipCommon }
|
|
|
|
constructor TZipCommon.Create(const aFileName: TFileName);
|
|
begin
|
|
fFileName := aFileName;
|
|
end;
|
|
|
|
destructor TZipCommon.Destroy;
|
|
begin
|
|
Finalize(Entry);
|
|
inherited;
|
|
end;
|
|
|
|
function TZipCommon.ZipNameIndexOf(const aZipName: RawUTF8): integer;
|
|
begin
|
|
for result := 0 to Count-1 do
|
|
if SameTextU(Entry[result].ZipName,aZipName) then
|
|
exit;
|
|
result := -1;
|
|
end;
|
|
|
|
{ TZipReader }
|
|
|
|
procedure TZipReader.Clear;
|
|
begin
|
|
fCount := 0;
|
|
Map.UnMap;
|
|
end;
|
|
|
|
constructor TZipReader.Create(const aFileName: TFileName);
|
|
var i: integer;
|
|
lhr: ^TLastHeader;
|
|
H: ^TFileHeader;
|
|
tmp: WinAnsiString;
|
|
LastHeaderPosition: integer;
|
|
procedure Error(const msg: string);
|
|
begin
|
|
// MessageBox(0,pointer(fFileName),'Incorrect format',MB_ICONERROR);
|
|
Map.UnMap;
|
|
fCount := 0;
|
|
raise TZipException.CreateFmt(sIncorrectZipFormatN,[fFileName,msg]);
|
|
end;
|
|
begin
|
|
// 1. open aFileName
|
|
inherited; // fFileName := aFileName;
|
|
Map.Map(fFileName);
|
|
if Map.Buffer=nil then exit;
|
|
// 2. find last header, in order to reach the TFileHeader entries
|
|
if Map.Size<sizeof(lhr^) then begin
|
|
Error('file too small');
|
|
exit;
|
|
end;
|
|
lhr := @Map.Buffer[Map.Size-sizeof(lhr^)];
|
|
with lhr^ do begin
|
|
if signature<>$06054b50 then begin
|
|
Error('missing trailing signature');
|
|
exit;
|
|
end;
|
|
fCount := thisFiles;
|
|
SetLength(Entry,Count);
|
|
LastHeaderPosition := headerOffset;
|
|
end;
|
|
// 3. read all TFileHeader entries and fill Entry[] with its values
|
|
H := @Map.Buffer[LastHeaderPosition];
|
|
for i := 0 to Count-1 do
|
|
with H^ do begin
|
|
if signature<>$02014b50 then begin
|
|
Error('missing local signature');
|
|
break;
|
|
end;
|
|
if (fileInfo.flags and (1 shl 3)<>0) or // crc+sizes in "data descriptor"
|
|
(fileInfo.zzipSize=0) or (fileInfo.zfullSize=0) then begin
|
|
Error('unexpected "data descriptor"');
|
|
break; // not handled yet: use SynZip's TZipRead to access this archive
|
|
end;
|
|
with Entry[i] do begin
|
|
{$ifdef MSWINDOWS}
|
|
if FileInfo.GetUTF8FileName then
|
|
SetString(ZipName,PAnsiChar(H)+sizeof(H^),fileInfo.nameLen) else begin
|
|
SetLength(tmp,fileInfo.nameLen); // convert from DOS/OEM into WinAnsi
|
|
OemToCharBuffA(PAnsiChar(H)+sizeof(H^),pointer(tmp),fileInfo.nameLen);
|
|
ZipName := WinAnsiToUtf8(tmp);
|
|
end;
|
|
{$else}
|
|
SetString(ZipName,PAnsiChar(H)+sizeof(H^),fileInfo.nameLen);
|
|
{$endif}
|
|
ZipName := StringReplaceChars(ZipName,'/','\');
|
|
Header := H^;
|
|
end; // next entry is after the ZipNname and some extra/comment
|
|
inc(PByte(H),sizeof(H^)+fileInfo.nameLen+fileInfo.extraLen+commentLen);
|
|
end;
|
|
end;
|
|
|
|
procedure TZipReader.DeleteLastEntry;
|
|
// don't use inside TZipValues: already done in TZipValues.Create
|
|
begin
|
|
if Count<=0 then
|
|
exit;
|
|
dec(fCount);
|
|
end;
|
|
|
|
destructor TZipReader.Destroy;
|
|
begin
|
|
Map.UnMap;
|
|
inherited;
|
|
end;
|
|
|
|
function TZipReader.GetBlobData(aIndex: integer): RawByteString;
|
|
begin
|
|
result := '';
|
|
if (self=nil) or (cardinal(aIndex)>=cardinal(Count)) or (Map.Buffer=nil) then
|
|
exit;
|
|
with Entry[aIndex], LocalHeader(Map.Buffer)^ do
|
|
if not Header.IsFolder then begin
|
|
SetLength(result,fileInfo.zzipSize+BLOBDATA_HEADSIZE);
|
|
with PBlobData(result)^ do begin
|
|
SetFrom(fileInfo);
|
|
move(LocalData^,databuf,datasize);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TZipReader.GetBlobData(aIndex: integer; aStream: TStream);
|
|
// put TBlobData in aStream
|
|
var blob: TBlobData;
|
|
begin
|
|
if (self=nil) or (aIndex<0) or (aIndex>=Count) or (Map.Buffer=nil) then
|
|
aStream.WriteBuffer(BlobDataNull,BLOBDATA_HEADSIZE) else
|
|
with Entry[aIndex], LocalHeader(Map.Buffer)^ do begin
|
|
blob.SetFrom(fileInfo);
|
|
aStream.WriteBuffer(blob,BLOBDATA_HEADSIZE);
|
|
aStream.WriteBuffer(LocalData^,blob.dataSize);
|
|
end;
|
|
end;
|
|
|
|
function TZipReader.GetBuffer(aIndex: integer; out Dest: PAnsiChar): integer;
|
|
begin
|
|
result := 0;
|
|
Dest := nil;
|
|
if (self=nil) or (aIndex<0) or (aIndex>=Count) or (Map.Buffer=nil) then
|
|
exit;
|
|
with Entry[aIndex], LocalHeader(Map.Buffer)^, fileInfo do
|
|
if not Header.IsFolder then begin
|
|
result := zfullSize;
|
|
if AlgoID<>0 then begin
|
|
with AlgoCreate(LocalData,FileName) do // crc32+algo object create
|
|
try // algo registered (no TZipException raised) -> uncompress
|
|
result := UnCompressedLength(LocalData,zfullsize);
|
|
Getmem(Dest,result);
|
|
UnCompress(LocalData,zfullsize,Dest); // direct uncompress into Dest
|
|
finally
|
|
Free;
|
|
end;
|
|
exit;
|
|
end;
|
|
Getmem(Dest,zfullSize);
|
|
if zfullSize>0 then
|
|
case zzipMethod of
|
|
0: move(LocalData^,Dest^,result); // stored = direct copy
|
|
8: if (UnCompressMem(LocalData,Dest,zzipSize,zfullSize)<>integer(zfullSize)) or
|
|
(crc32(0,Dest,zfullSize)<>zcrc32) then begin
|
|
Freemem(Dest);
|
|
Dest := nil;
|
|
raise TZipException.CreateFmt(sZipCrcErrorNN,[Entry[aIndex].ZipName,FileName]);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TZipReader.GetData(aIndex: integer; aStream: TStream=nil;
|
|
CheckCRC: boolean=false; asBlobDataStored: boolean=false; withAlgoDataLen: boolean=false): PAnsiChar;
|
|
// aStream=nil -> return bulk memory data position in mapped file
|
|
// aStream<>nil -> uncompress and un-algo into aStream; CheckCRC=true -> force check CRC
|
|
// asBlobDataStored=true -> PBlobData stored format into aStream
|
|
// withAlgoDataLen=true -> unCompressed algo length stored into aStream
|
|
var CRC: cardinal;
|
|
CRCP: PCardinal;
|
|
Blob: TBlobData;
|
|
tmp: PAnsiChar;
|
|
L: cardinal;
|
|
begin
|
|
result := nil;
|
|
if (self=nil) or (aIndex<0) or (aIndex>=Count) or (Map.Buffer=nil) then
|
|
exit;
|
|
with Entry[aIndex], LocalHeader(Map.Buffer)^ do
|
|
if not Header.IsFolder then begin
|
|
result := LocalData;
|
|
if aStream=nil then
|
|
exit; // no decompress to stream: only get result PAnsiChar [and DataLen^]
|
|
if fileInfo.AlgoID<>0 then begin // bits 7..10 are used for algo
|
|
// un-algo specific uncompression
|
|
L := GetBuffer(aIndex,tmp); // uncompress with algo (always check crc)
|
|
if tmp<>nil then
|
|
try
|
|
if asBlobDataStored then begin // algo: add uncompressed data header
|
|
Blob.dataSize := L;
|
|
Blob.dataFullSize := L;
|
|
Blob.dataCRC := crc32(0,tmp,L);
|
|
Blob.dataMethod := fileInfo.AlgoID shl 4; // 0=stored + AlgoID
|
|
aStream.WriteBuffer(Blob,BLOBDATA_HEADSIZE);
|
|
end else
|
|
if withAlgoDataLen then
|
|
aStream.WriteBuffer(L,4);
|
|
aStream.WriteBuffer(tmp^,L); // write uncompressed
|
|
finally
|
|
freemem(tmp);
|
|
end;
|
|
end else begin // standard zip format
|
|
if asBlobDataStored then begin
|
|
Blob.dataSize := FileInfo.zfullSize;
|
|
Blob.dataFullSize := FileInfo.zfullSize;
|
|
Blob.dataCRC := FileInfo.zcrc32;
|
|
Blob.dataMethod := 0; // stored, since will be uncompressed below
|
|
aStream.WriteBuffer(Blob,BLOBDATA_HEADSIZE);
|
|
end;
|
|
case fileInfo.zzipMethod of
|
|
0: begin
|
|
aStream.WriteBuffer(result^,fileInfo.zfullSize); // stored = direct copy
|
|
if CheckCRC then
|
|
CRC := crc32(0,result,fileInfo.zfullSize);
|
|
end;
|
|
8: begin // deflate
|
|
if CheckCRC then
|
|
CRCP := @CRC else CRCP := nil;
|
|
if UnCompressStream(result,fileInfo.zzipSize,aStream,CRCP)
|
|
<>fileInfo.zfullSize then result := nil;
|
|
end;
|
|
end; // case fileInfo.zzipMethod of
|
|
if CheckCRC and (CRC<>fileInfo.zcrc32) then
|
|
result := nil;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TZipReader.GetString(aIndex: integer): RawByteString;
|
|
begin
|
|
result := '';
|
|
if (self=nil) or (aIndex<0) or (aIndex>=Count) or (Map.Buffer=nil) then
|
|
exit;
|
|
with Entry[aIndex], LocalHeader(Map.Buffer)^, fileInfo do
|
|
if not Header.IsFolder then begin
|
|
if AlgoID<>0 then begin // special algo
|
|
with AlgoCreate(LocalData,FileName) do // crc32+algo object create
|
|
try // algo registered (no TZipException raised) -> uncompress
|
|
SetLength(result,UnCompressedLength(LocalData,zfullsize));
|
|
if UnCompress(LocalData,zfullsize,pointer(result))<> // direct uncompress into string
|
|
cardinal(length(result)) then
|
|
raise TZipException.CreateFmt(sZipCrcErrorNN,[Entry[aIndex].ZipName,FileName]);
|
|
finally
|
|
Free;
|
|
end;
|
|
end else // no algo: normal .zip file
|
|
case zzipMethod of
|
|
0: SetString(result,LocalData,zfullSize); // stored = direct copy
|
|
8: begin // deflate:
|
|
SetLength(result,zfullSize);
|
|
if (UnCompressMem(LocalData,pointer(result),zzipSize,zfullSize)<>integer(zfullSize)) or
|
|
(crc32(0,pointer(result),zfullSize)<>zcrc32) then
|
|
raise TZipException.CreateFmt(sZipCrcErrorNN,[Entry[aIndex].ZipName,FileName]);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TZipReader.SameAs(aReader: TZipReader): boolean;
|
|
var i: integer;
|
|
begin
|
|
result := self=aReader; if result then exit;
|
|
if (self=nil) or (aReader=nil) or (Count<>aReader.Count) then
|
|
exit;
|
|
for i := 0 to Count-1 do
|
|
if not Entry[i].SameAs(aReader.Entry[i]) then
|
|
exit;
|
|
for i := 0 to Count-1 do
|
|
with Entry[i], LocalHeader(Map.Buffer)^ do
|
|
if not Header.IsFolder then
|
|
if not Comparemem(LocalData,
|
|
aReader.Entry[i].LocalHeader(aReader.Map.Buffer).LocalData,fileInfo.zzipSize) then
|
|
exit;
|
|
result := true;
|
|
end;
|
|
|
|
procedure TZipReader.SaveToStream(aStream: TStream);
|
|
var i: integer;
|
|
L: cardinal;
|
|
aName: RawUTF8;
|
|
begin
|
|
aName := StringToUTF8(ExtractFileName(fFileName));
|
|
// 1. write global params
|
|
L := length(aName);
|
|
aStream.WriteBuffer(L,1);
|
|
aStream.WriteBuffer(aName[1],L); // UTF-8 encoded file name
|
|
aStream.WriteBuffer(fCount,4);
|
|
// 2. write Entry[].ZipName
|
|
for i := 0 to Count-1 do
|
|
with Entry[i] do begin
|
|
assert(not Header.IsFolder,'empty folders streaming is not implemented');
|
|
aStream.WriteBuffer(Header.fileInfo,sizeof(Header.fileInfo));
|
|
aStream.WriteBuffer(pointer(ZipName)^,Header.fileInfo.NameLen);
|
|
end;
|
|
// 3. write all uncompressed data
|
|
for i := 0 to Count-1 do
|
|
with Entry[i] do // withAlgoDataLen=true: algo -> uncompressed length stored
|
|
GetData(i,aStream,false,false,true); // deflate and un-algo if necessary
|
|
end;
|
|
|
|
|
|
{ TZip }
|
|
|
|
function TZip.AddBuf(const aZipName: RawUTF8; CompressionLevel: integer;
|
|
data: pointer; dataSize: cardinal; Algorithm: integer=0): boolean;
|
|
var Z: TZipCompressor;
|
|
begin
|
|
if self=nil then begin
|
|
result := false;
|
|
exit;
|
|
end;
|
|
if dataSize<512 then
|
|
CompressionLevel := -1; // force store if too small
|
|
Z := ZipCreate(aZipName,CompressionLevel,Algorithm); // Z=Writer.Zip
|
|
result := Z<>nil;
|
|
if not result then exit;
|
|
Z.WriteOnce(data^,dataSize);
|
|
ZipClose;
|
|
end;
|
|
|
|
function TZip.AddToFileQueue(const aFileName: TFileName; const aZipName: RawUTF8): boolean;
|
|
// flushed at Destroy
|
|
var i: integer;
|
|
begin
|
|
result := false;
|
|
if self=nil then exit;
|
|
if Writer=nil then begin
|
|
i := Reader.ZipNameIndexOf(aZipName);
|
|
if i>=0 then
|
|
MarkDeleted(i);
|
|
end else
|
|
if Writer.ZipNameIndexOf(aZipName)>=0 then
|
|
exit;
|
|
if FileQueue=nil then
|
|
FileQueue := TStringList.Create;
|
|
FileQueue.Values[aFileName] := UTF8ToString(aZipName);
|
|
result := true;
|
|
end;
|
|
|
|
procedure TZip.BeginWriter;
|
|
begin
|
|
if (self<>nil) and (Writer=nil) then
|
|
Writer := TZipWriter.Create(Reader); // append Reader, recreate=false
|
|
end;
|
|
|
|
constructor TZip.Create(const aFileName: TFileName);
|
|
begin
|
|
Reader := TZipReader.Create(aFileName);
|
|
// Writer will be created as necessary
|
|
end;
|
|
|
|
function GetValueFromIndex(List: TStrings; Index: Integer): string;
|
|
begin // not defined before Delphi 7
|
|
if Index >= 0 then
|
|
Result := Copy(List[Index],Length(List.Names[Index])+2,MaxInt) else
|
|
Result := '';
|
|
end;
|
|
|
|
destructor TZip.Destroy;
|
|
var i, method: integer;
|
|
zipName: TFileName;
|
|
begin
|
|
ZipClose; // close pending Writer.Zip if any
|
|
if SomeDeleted and (Writer=nil) and
|
|
((FileQueue=nil) or (FileQueue.Count=0)) then
|
|
BeginWriter else
|
|
if FileQueue<>nil then
|
|
for i := 0 to FileQueue.Count-1 do begin
|
|
zipName := GetValueFromIndex(FileQueue,i);
|
|
if zipName='' then
|
|
continue;
|
|
if GetFileNameExtIndex(zipName,'zip,jpg,jpeg,gz,bz2,bZ,7z,gif,bj,bjt')>=0 then
|
|
method := -1 else // store already compressed file
|
|
method := 6; // normal deflate compression
|
|
if Writer=nil then
|
|
BeginWriter;
|
|
Writer.AddFile(FileQueue.Names[i],StringToUTF8(zipName),method);
|
|
end;
|
|
FreeAndNil(FileQueue);
|
|
FreeAndNil(Reader);
|
|
FreeAndNil(Writer);
|
|
inherited;
|
|
end;
|
|
|
|
function TZip.FileName: TFileName;
|
|
begin
|
|
if (self=nil) or (Reader=nil) then
|
|
result := '' else
|
|
result := Reader.fFileName;
|
|
end;
|
|
|
|
function TZip.FileQueueCount: integer;
|
|
begin
|
|
if FileQueue=nil then
|
|
result := 0 else
|
|
result := FileQueue.Count;
|
|
end;
|
|
|
|
function TZip.MarkDeleted(aReaderIndex: integer): boolean;
|
|
begin
|
|
result := (self<>nil)and(Writer=nil)and(aReaderIndex>=0)and(aReaderIndex<Reader.Count);
|
|
if not result then
|
|
exit;
|
|
SomeDeleted := true;
|
|
//if aReaderIndex=Reader.Count-1 then Reader.DeleteLastEntry else !TZipValues use signature!
|
|
Reader.Entry[aReaderIndex].Header.signature := 0; // just signature = 0 to delete
|
|
end;
|
|
|
|
{$ifndef MSWINDOWS}
|
|
function DateTimeToFileDateWindows(DateTime: TDateTime): Integer;
|
|
var
|
|
Year, Month, Day, Hour, Min, Sec, MSec: Word;
|
|
begin
|
|
DecodeDate(DateTime, Year, Month, Day);
|
|
if (Year < 1980) or (Year > 2107) then
|
|
Result := 0 else begin
|
|
DecodeTime(DateTime, Hour, Min, Sec, MSec);
|
|
LongRec(Result).Lo := (Sec shr 1) or (Min shl 5) or (Hour shl 11);
|
|
LongRec(Result).Hi := Day or (Month shl 5) or ((Year - 1980) shl 9);
|
|
end;
|
|
end;
|
|
|
|
function NowToFileDateWindows: Integer;
|
|
begin
|
|
result := DateTimeToFileDateWindows(Now);
|
|
end;
|
|
{$endif}
|
|
|
|
function TZip.MarkDeletedBefore(aDate: TDateTime; aBackup: TZip=nil): boolean;
|
|
var dt, i: integer;
|
|
begin
|
|
result := false;
|
|
if (self=nil) or (Writer<>nil) then
|
|
exit;
|
|
dt := {$ifdef MSWINDOWS}DateTimeToFileDate{$else}DateTimeToFileDateWindows{$endif}(aDate);
|
|
for i := 0 to Reader.Count-1 do
|
|
with Reader.Entry[i].Header do
|
|
if (signature<>0) and (fileInfo.zlastMod<dt) then begin
|
|
if aBackup<>nil then begin
|
|
aBackup.BeginWriter;
|
|
aBackup.Writer.Add(Reader,i);
|
|
end;
|
|
MarkDeleted(i);
|
|
result := true;
|
|
end;
|
|
end;
|
|
|
|
function TZip.SameAs(aZip: TZip): boolean;
|
|
begin
|
|
result := self=aZip;
|
|
if result then
|
|
exit;
|
|
if (self=nil) or (aZip=nil) then
|
|
exit;
|
|
assert(Writer=nil);
|
|
if Writer<>nil then
|
|
exit;
|
|
result := Reader.SameAs(aZip.Reader);
|
|
end;
|
|
|
|
procedure TZip.ZipClose;
|
|
begin
|
|
if (self=nil) or (Writer=nil) then exit;
|
|
Writer.ZipClose;
|
|
end;
|
|
|
|
function TZip.ZipCreate(const aZipName: RawUTF8; CompressionLevel: integer;
|
|
Algorithm: integer=0): TZipCompressor;
|
|
// use TZipCompressor.Write() to send data - CompressionLevel<0 -> force store
|
|
var i: integer;
|
|
ZipName: string;
|
|
begin
|
|
result := nil;
|
|
if self=nil then exit;
|
|
if Writer=nil then begin
|
|
i := Reader.ZipNameIndexOf(aZipName);
|
|
if (i>=0) then
|
|
MarkDeleted(i);
|
|
end else
|
|
if Writer.ZipNameIndexOf(aZipName)>=0 then
|
|
exit;
|
|
if FileQueue<>nil then begin
|
|
ZipName := UTF8ToString(aZipName);
|
|
for i := 0 to FileQueue.Count-1 do
|
|
if GetValueFromIndex(FileQueue,i)=ZipName then begin
|
|
FileQueue.Delete(i);
|
|
break;
|
|
end;
|
|
end;
|
|
BeginWriter;
|
|
if Writer.Zip<>nil then
|
|
exit;
|
|
Writer.ZipCreate(aZipName,CompressionLevel,0,Algorithm);
|
|
result := Writer.Zip;
|
|
end;
|
|
|
|
|
|
{ TCompressorDecompressor }
|
|
|
|
constructor TZipCompressor.Create(outStream: TStream; CompressionLevel, Algorithm: Integer);
|
|
var Algo: TSynCompressionAlgoClass;
|
|
begin
|
|
fDestStream := outStream;
|
|
fBlobDataHeaderPosition := -1; // not AsBlobData
|
|
StreamInit(FStrm);
|
|
FStrm.next_out := @FBufferOut;
|
|
FStrm.avail_out := SizeOf(FBufferOut);
|
|
FStrm.next_in := @FBufferIn;
|
|
if Algorithm<>0 then begin
|
|
Algo := SynCompressionAlgos.Algo(Algorithm);
|
|
if not Assigned(Algo) then // unknown algo -> error
|
|
raise TZipException.CreateFmt(sZipAlgoIDNUnknownN,[Algorithm,ClassName]);
|
|
fAlgorithm := Algo.Create;
|
|
fAlgorithmID := Algorithm;
|
|
fAlgorithm.CompressInit(fDestStream);
|
|
if SynCompressionAlgos.WholeAlgoID(Algorithm)=Algorithm then
|
|
// whole algo = not a 64KB chunked algo
|
|
fAlgorithmStream := THeapMemoryStream.Create; // create temp buffer
|
|
end else begin
|
|
if CompressionLevel>=0 then // FInitialized=false -> direct copy to FDestStream
|
|
fInitialized := Check(deflateInit2_(FStrm, CompressionLevel, Z_DEFLATED,
|
|
-MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,ZLIB_VERSION, sizeof(FStrm)),
|
|
[Z_OK])=Z_OK; // -MAX_WBITS -> no zLib header => .zip compatible !
|
|
end;
|
|
end;
|
|
|
|
constructor TZipCompressor.CreateAsBlobData(outStream: TStream;
|
|
CompressionLevel, Algorithm: Integer);
|
|
begin
|
|
Create(outStream,CompressionLevel,Algorithm);
|
|
fBlobDataHeaderPosition := outStream.Seek(0,soCurrent);
|
|
outStream.WriteBuffer(FBufferOut,BLOBDATA_HEADSIZE); // save Bulk header
|
|
end;
|
|
|
|
destructor TZipCompressor.Destroy;
|
|
var p: Int64;
|
|
blob: TBlobData;
|
|
begin
|
|
if FInitialized then begin
|
|
FStrm.next_out := nil;
|
|
FStrm.avail_out := 0;
|
|
deflateEnd(FStrm);
|
|
end else begin
|
|
FreeAndNil(fAlgorithmStream);
|
|
FreeAndNil(fAlgorithm);
|
|
end;
|
|
if fBlobDataHeaderPosition>=0 then begin // CreateAsBlobData() -> update header
|
|
p := fDestStream.Seek(0,soCurrent);
|
|
with blob do begin
|
|
dataFullSize := SizeIn;
|
|
dataSize := p-fBlobDataHeaderPosition-BLOBDATA_HEADSIZE;
|
|
assert(dataSize=SizeOut);
|
|
dataCRC := CRC;
|
|
// dataMethod: 0=stored 8=inflate >16: AlgoID=dataMethod shr 4
|
|
if FInitialized then
|
|
dataMethod := 8 else
|
|
dataMethod := fAlgorithmID shl 4; // stored + AlgoID
|
|
end;
|
|
fDestStream.Seek(fBlobDataHeaderPosition,soBeginning);
|
|
fDestStream.WriteBuffer(blob,BLOBDATA_HEADSIZE);
|
|
fDestStream.Seek(p,soBeginning);
|
|
end;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TZipCompressor.Finish;
|
|
begin
|
|
if (self=nil) then exit;
|
|
if assigned(fAlgorithm) then begin
|
|
if assigned(fAlgorithmStream) then begin
|
|
fAlgorithmStream.WriteBuffer(fBufferIn,fStrm.avail_in); // write pending data
|
|
fStrm.total_in := fAlgorithm.Compress( // compress whole data at once
|
|
fAlgorithmStream.Memory,fAlgorithmStream.Seek(0,soCurrent),@fCRC);
|
|
end else
|
|
if FStrm.avail_in>0 then
|
|
inc(fStrm.total_in,fAlgorithm.Compress(@fBufferIn,FStrm.avail_in,@fCRC));
|
|
inc(fStrm.total_in,fAlgorithm.CompressFinish); // finish compression
|
|
fStrm.total_out := fStrm.total_in; // .zip file compression mode = stored
|
|
exit;
|
|
end;
|
|
if not FInitialized then
|
|
exit;
|
|
while FStrm.avail_in > 0 do begin // compress pending data
|
|
if InFlateDeflate then
|
|
raise TZipException.Create(SZlibInternalError);
|
|
if FStrm.avail_out = 0 then
|
|
FlushBufferOut;
|
|
end;
|
|
FStrm.next_in := nil;
|
|
FStrm.avail_in := 0;
|
|
while (Check(deflate(FStrm, Z_FINISH), [Z_OK, Z_STREAM_END]) <> Z_STREAM_END) and
|
|
(FStrm.avail_out = 0) do
|
|
FlushBufferOut;
|
|
FlushBufferOut;
|
|
end;
|
|
|
|
function TZipCompressor.FlushBufferOut: integer;
|
|
begin
|
|
Result := 0;
|
|
if not FInitialized then
|
|
exit;
|
|
if FStrm.avail_out < SizeOf(FBufferOut) then begin
|
|
Result := SizeOf(FBufferOut) - FStrm.avail_out;
|
|
FDestStream.WriteBuffer(FBufferOut, Result);
|
|
FStrm.next_out := @FBufferOut;
|
|
FStrm.avail_out := SizeOf(FBufferOut);
|
|
end;
|
|
end;
|
|
|
|
function TZipCompressor.GetSizeIn: cardinal;
|
|
begin
|
|
result := FStrm.total_in;
|
|
end;
|
|
|
|
function TZipCompressor.GetSizeOut: cardinal;
|
|
begin
|
|
result := FStrm.total_out;
|
|
end;
|
|
|
|
function TZipCompressor.InFlateDeflate: boolean;
|
|
begin
|
|
Result := Check(deflate(FStrm, Z_NO_FLUSH), [Z_OK])<>Z_OK;
|
|
end;
|
|
|
|
function TZipCompressor.Read(var Buffer; Count: Longint): Longint;
|
|
begin
|
|
assert(false);
|
|
result := 0;
|
|
end;
|
|
|
|
function TZipCompressor.Seek(Offset: Longint; Origin: Word): Longint;
|
|
begin
|
|
if not FInitialized then // CompressionLevel<0: direct copy to
|
|
result := 0 else
|
|
if (Offset = 0) and (Origin = soFromCurrent) then // for TStream.Position
|
|
if assigned(fAlgorithmStream) then
|
|
Result := fAlgorithmStream.Seek(0,soFromCurrent) else
|
|
Result := FStrm.total_in else begin
|
|
Result := 0;
|
|
assert((Offset = 0) and (Origin = soFromBeginning) and (FStrm.total_in = 0));
|
|
end;
|
|
end;
|
|
|
|
function TZipCompressor.Write(const Buffer; Count: integer): integer;
|
|
begin
|
|
if self<>nil then begin
|
|
result := Count;
|
|
if FInitialized then begin
|
|
if Count=0 then exit;
|
|
fCRC := crc32(fCRC,@Buffer,Count);
|
|
if cardinal(Count)+FStrm.avail_in>sizeof(fBufferIn)-1 then begin
|
|
while FStrm.avail_in > 0 do begin
|
|
if InflateDeflate then
|
|
raise TZipException.Create(SZlibInternalError);
|
|
if FStrm.avail_out = 0 then
|
|
FlushBufferOut;
|
|
end;
|
|
FStrm.avail_in := 0;
|
|
FStrm.next_in := @fBufferIn;
|
|
end;
|
|
if Count<sizeof(fBufferIn) then begin
|
|
move(Buffer,fBufferIn[FStrm.avail_in],Count);
|
|
inc(FStrm.avail_in,Count);
|
|
end else begin
|
|
FStrm.avail_in := Count;
|
|
FStrm.next_in := @Buffer;
|
|
while FStrm.avail_in > 0 do begin
|
|
if InFlateDeflate then
|
|
raise TZipException.Create(SZlibInternalError);
|
|
if FStrm.avail_out = 0 then
|
|
FlushBufferOut;
|
|
end;
|
|
FStrm.avail_in := 0;
|
|
FStrm.next_in := @fBufferIn;
|
|
end;
|
|
end else begin // if not FIinitialized: CompressionLevel<0: direct copy to
|
|
if Count=0 then exit;
|
|
if Assigned(fAlgorithmStream) then begin
|
|
// algo -> copy into fAlgorithmStream by fBufferIn[] chunks
|
|
if cardinal(Count)+FStrm.avail_in>sizeof(fBufferIn)-1 then begin
|
|
fAlgorithmStream.WriteBuffer(fBufferIn,fStrm.avail_in); // flush buffer
|
|
FStrm.avail_in := 0;
|
|
FStrm.next_in := @fBufferIn;
|
|
end;
|
|
if Count<sizeof(fBufferIn) then begin // small block -> in fBufferIn[]
|
|
move(Buffer,fBufferIn[FStrm.avail_in],Count);
|
|
inc(FStrm.avail_in,Count);
|
|
end else
|
|
fAlgorithmStream.WriteBuffer(Buffer,Count); // big block -> direct store
|
|
end else
|
|
if Assigned(fAlgorithm) then begin
|
|
// algo without fAlgorithmStream -> direct compress in fBufferIn[] chunks
|
|
FStrm.next_out := @Buffer;
|
|
repeat
|
|
FStrm.avail_out := sizeof(fBufferIn)-FStrm.avail_in;
|
|
if cardinal(Count)<=FStrm.avail_out then begin // count fit in fBufferIn[]
|
|
move(FStrm.next_out^,fBufferIn[FStrm.avail_in],Count); // -> copy bytes
|
|
inc(FStrm.avail_in,Count);
|
|
break;
|
|
end else begin // Count too big for fBufferIn[] -> compress chunk
|
|
if FStrm.avail_in=0 then begin // direct compress from buffer
|
|
inc(fStrm.total_in,fAlgorithm.Compress(FStrm.next_out,sizeof(fBufferIn),@fCRC));
|
|
inc(FStrm.next_out,sizeof(fBufferIn));
|
|
dec(Count,sizeof(fBufferIn));
|
|
end else begin // compress with data already in fBufferIn[]
|
|
move(FStrm.next_out,fBufferIn[FStrm.avail_in],FStrm.avail_out);
|
|
inc(fStrm.total_in,fAlgorithm.Compress(@fBufferIn,sizeof(fBufferIn),@fCRC));
|
|
FStrm.avail_in := 0;
|
|
FStrm.next_in := @fBufferIn;
|
|
inc(FStrm.next_out,FStrm.avail_out);
|
|
dec(Count,FStrm.avail_out);
|
|
end;
|
|
end;
|
|
until Count=0;
|
|
end else begin
|
|
// normal store -> direct copy to fDestStream
|
|
inc(FStrm.total_in,Count);
|
|
inc(FStrm.total_out,Count);
|
|
fCRC := crc32(fCRC,@Buffer,Count);
|
|
fDestStream.WriteBuffer(Buffer,Count);
|
|
end;
|
|
end;
|
|
end else
|
|
result := 0; // self=nil
|
|
end;
|
|
|
|
function TZipCompressor.WriteOnce(const Buffer; Count: Integer): Longint;
|
|
// same as Write, but optimized for one call of Write()
|
|
begin
|
|
if Count=0 then
|
|
result := Count else
|
|
if Assigned(fAlgorithmStream) then begin // whole: avoid fAlgorithmStream use
|
|
FreeAndNil(fAlgorithmStream); // very fast, since memory already allocated=0
|
|
result := fAlgorithm.Compress(@Buffer,Count,@fCRC)+fAlgorithm.CompressFinish;
|
|
fStrm.total_in := result;
|
|
end else
|
|
result := Write(Buffer,Count);
|
|
end;
|
|
|
|
{ TGzWriter }
|
|
|
|
constructor TGzWriter.Create(const aFileName: TFileName);
|
|
begin
|
|
if FileExists(aFilename) then begin
|
|
Create(TFileStream.Create(aFileName,fmOpenWrite));
|
|
outFile.Size := outFile.Position;
|
|
end else
|
|
Create(TFileStream.Create(aFileName,fmCreate));
|
|
outFileToBeFree := true;
|
|
end;
|
|
|
|
constructor TGzWriter.Create(const aDestStream: TStream);
|
|
const gzheader : array [0..2] of cardinal = ($88B1F,0,0);
|
|
begin
|
|
outFile := aDestStream;
|
|
outFile.WriteBuffer(gzHeader,10);
|
|
inherited Create(outFile, 6);
|
|
end;
|
|
|
|
destructor TGzWriter.Destroy;
|
|
begin
|
|
Finish;
|
|
outFile.WriteBuffer(CRC,4);
|
|
outFile.WriteBuffer(FStrm.total_in,4);
|
|
if outFileToBeFree then
|
|
FreeAndNil(outFile);
|
|
inherited;
|
|
end;
|
|
|
|
function GZRead(const aFileName: TFileName): RawByteString; overload;
|
|
var Map: TMemoryMap;
|
|
begin
|
|
if not Map.Map(aFileName) then
|
|
result := '' else
|
|
try
|
|
result := SynZipFiles.GzRead(pointer(Map.Buffer),Map.Size);
|
|
finally
|
|
Map.UnMap;
|
|
end;
|
|
end;
|
|
|
|
function GZRead(gz: PAnsiChar; gzLen: integer): RawByteString; overload;
|
|
var Len: integer;
|
|
begin
|
|
if PCardinal(gz)^<>$88B1F then
|
|
SetString(result,gz,gzLen) else begin
|
|
Len := pInteger(@gz[gzLen-4])^;
|
|
assert(Len>=0);
|
|
SetString(result,nil,Len);
|
|
UnCompressMem(@gz[10],pointer(result),gzLen-18,Len);
|
|
end;
|
|
end;
|
|
|
|
procedure GZRead(const aFileName: TFileName; aStream: TStream; StoreLen: boolean); overload;
|
|
// just add an ungz file contents, storing len:Integer first if StoreLen=true
|
|
var Map: TMemoryMap;
|
|
Len: integer;
|
|
begin
|
|
if not Map.Map(aFileName) then begin
|
|
if StoreLen then
|
|
aStream.WriteBuffer(Map.Buffer,4); // no file -> store len=0
|
|
end else
|
|
try
|
|
if PCardinal(Map.Buffer)^<>$88B1F then begin
|
|
if StoreLen then
|
|
aStream.WriteBuffer(Map.Size,4);
|
|
aStream.WriteBuffer(Map.Buffer^,Map.Size); // not a .gz -> store as is
|
|
end else begin
|
|
Len := pInteger(@Map.Buffer[Map.Size-4])^; // .gz -> uncompress
|
|
assert(Len>=0);
|
|
if StoreLen then
|
|
aStream.WriteBuffer(Len,4);
|
|
UnCompressStream(@Map.Buffer[10],Map.Size-18,aStream,nil);
|
|
end;
|
|
finally
|
|
Map.UnMap;
|
|
end;
|
|
end;
|
|
|
|
{ TZipWriter }
|
|
|
|
constructor TZipWriter.Create(AppendTo: TZipReader; ReCreate: boolean=false);
|
|
procedure InitTmp;
|
|
begin
|
|
fDestFileName := AppendTo.fFileName;
|
|
fFileName := ChangeFileExt(fDestFileName,'.tmp');
|
|
outFile := TFileStream.Create(fFileName,fmCreate);
|
|
end;
|
|
var i, firstDeleted: integer;
|
|
posi: PtrUInt;
|
|
begin
|
|
fNow := {$ifdef MSWINDOWS}DateTimeToFileDate{$else}DateTimeToFileDateWindows{$endif}(Now);
|
|
if AppendTo=nil then
|
|
exit;
|
|
SetLength(Entry,AppendTo.Count+32);
|
|
if ReCreate then begin // force full file recreate from AppendTo data
|
|
InitTmp;
|
|
// don't call AppendTo.Map.UnMap since we will need to read the data!
|
|
exit; // all the data will be copied from AppendTo.Map manually by caller
|
|
end;
|
|
firstDeleted := -1;
|
|
for i := 0 to AppendTo.Count-1 do
|
|
if AppendTo.Entry[i].Header.signature<>0 then begin // file not deleted
|
|
Entry[Count] := AppendTo.Entry[i]; // -> add Entry[]
|
|
inc(fCount);
|
|
end else // file deleted
|
|
if firstDeleted<0 then // -> update first deleted index
|
|
firstDeleted := i;
|
|
if (Count=0) then begin
|
|
// nothing to read from old file -> just reopen
|
|
AppendTo.Map.UnMap;
|
|
fFileName := AppendTo.fFileName;
|
|
DeleteFile(fFileName); // avoid win32 bug if filesize=0
|
|
outFile := TFileStream.Create(fFileName,fmCreate);
|
|
end else
|
|
if (Count=AppendTo.Count) or (firstDeleted=AppendTo.Count-1) then begin
|
|
// no delete or only the last one: append to end of file
|
|
fFileName := AppendTo.fFileName;
|
|
if AppendTo.Map.Buffer=nil then // AppendTo file doesn't exists
|
|
outFile := TFileStream.Create(fFileName,fmCreate) // new void file
|
|
else begin // AppendTo file exists
|
|
with AppendTo.Entry[Count-1] do
|
|
posi := LocalDataPosition(AppendTo.Map.Buffer)+Header.fileInfo.zzipSize;
|
|
AppendTo.Map.UnMap; // outFile seek to end of AppendTo file data
|
|
outFile := TFileStream.Create(fFileName,fmOpenReadWrite);
|
|
outFile.Position := posi;
|
|
end;
|
|
end else begin
|
|
// some deleted: copy entries from mapped file to .tmp file
|
|
InitTmp;
|
|
fCount := 0; // recreate Entry[] in Add(AppendTo,i) below
|
|
for i := 0 to AppendTo.Count-1 do
|
|
if AppendTo.Entry[i].Header.signature<>0 then
|
|
Add(AppendTo,i); // add not deleted entries
|
|
AppendTo.Map.UnMap; // we won't use AppendTo any more
|
|
end;
|
|
end;
|
|
|
|
constructor TZipWriter.Create(fromStream: TStream; const DestFileName: TFileName='');
|
|
// used to restore data uncompressed+bz-compressed with TZipReader.SaveToStream()
|
|
var i, sign: integer;
|
|
L, srcLen: cardinal;
|
|
src: pointer; // temporary buffer for CompressMem
|
|
aAlgo, wAlgo: integer;
|
|
fromMemory: PAnsiChar;
|
|
begin
|
|
fNow := {$ifdef MSWINDOWS}DateTimeToFileDate{$else}DateTimeToFileDateWindows{$endif}(Now);
|
|
// 1. read global params
|
|
L := 0;
|
|
fromStream.Read(L,1);
|
|
if DestFileName<>'' then begin
|
|
fFileName := DestFileName;
|
|
fromStream.Seek(L,soCurrent); // ignore file name stored in fromStream
|
|
end else begin
|
|
SetLength(fFileName,L);
|
|
fromStream.Read(fFileName[1],L); // fromStream -> dest file name
|
|
end;
|
|
fromStream.Read(fCount,4);
|
|
// 2. read Entry[]
|
|
SetLength(Entry,fCount);
|
|
for i := 0 to Count-1 do
|
|
with Entry[i] do begin
|
|
Header.Init; // signature, madeBy, extFileAttr init
|
|
fromStream.Read(Header.FileInfo,sizeof(Header.fileInfo));
|
|
SetLength(ZipName,Header.FileInfo.nameLen);
|
|
fromStream.Read(ZipName[1],Header.FileInfo.nameLen);
|
|
end;
|
|
// 3. read and recompress all data
|
|
outFile := TFileStream.Create(fFileName,fmCreate);
|
|
if fromStream.InheritsFrom(TMemoryStream) then
|
|
fromMemory := PAnsiChar(TMemoryStream(fromStream).Memory)+
|
|
fromStream.Seek(0,soCurrent) else
|
|
fromMemory := nil;
|
|
srcLen := 0;
|
|
src := nil;
|
|
for i := 0 to Count-1 do
|
|
with Entry[i],Header.fileInfo do begin
|
|
Header.localHeadOff := outFile.Position; // position can change, as we recompress
|
|
sign := $04034b50; outFile.WriteBuffer(sign,4); // write .zip fileinfo signature
|
|
aAlgo := AlgoID;
|
|
if (aAlgo<>0) or (zzipMethod=8) then begin // reuse same compression/algo
|
|
// special ZipCreate(), without AddEntry():
|
|
if aAlgo<>0 then begin // reuse same algo
|
|
wAlgo := SynCompressionAlgos.WholeAlgoID(aAlgo); // whole algo is prefered here
|
|
if wAlgo<>0 then begin
|
|
aAlgo := wAlgo;
|
|
Header.fileInfo.SetAlgoID(aAlgo); // update Header.fileInfo.flags
|
|
end;
|
|
end;
|
|
Zip := TZipCompressor.Create(outFile, 6, aAlgo);
|
|
outFile.WriteBuffer(neededVersion,sizeof(Header.fileInfo)); // save bulk fileInfo
|
|
outFile.WriteBuffer(ZipName[1],nameLen);
|
|
if fromMemory<>nil then begin
|
|
Zip.WriteOnce(fromMemory[4],pInteger(fromMemory)^); // direct recompress using algo
|
|
L := pInteger(fromMemory)^+4;
|
|
inc(fromMemory,L); // jump uncompressed data
|
|
fromStream.Seek(L,soCurrent); // synchronize fromStream position
|
|
end else begin
|
|
fromStream.Read(L,4); // SaveStream(..,withAlgoDataLen=true)
|
|
if L>srcLen then begin
|
|
if srcLen<>0 then
|
|
Freemem(src); // Freemem+Getmem is better than Reallocmem (no move)
|
|
srcLen := succ(L shr 12) shl 12; // 4KB size boundary
|
|
Getmem(src,srcLen);
|
|
end;
|
|
fromStream.Read(src^,L); // read uncompressed data
|
|
Zip.WriteOnce(src^,L); // recompress using algo
|
|
end;
|
|
ZipClose(i); // fileInfo update+Zip.Free; aIndex>=0 -> no inc(fCount)
|
|
end else begin
|
|
assert(zzipMethod=0);
|
|
zzipSize := zfullSize;
|
|
outFile.WriteBuffer(neededVersion,sizeof(Header.fileInfo)); // save new fileInfo
|
|
outFile.WriteBuffer(ZipName[1],nameLen);
|
|
outFile.CopyFrom(fromStream,zzipSize);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
constructor TZipWriter.Create(const aFileName: TFileName);
|
|
begin
|
|
fNow := {$ifdef MSWINDOWS}DateTimeToFileDate{$else}DateTimeToFileDateWindows{$endif}(Now);
|
|
SetLength(Entry,100);
|
|
fFileName := aFileName;
|
|
outFile := TFileStream.Create(fFileName,fmCreate);
|
|
end;
|
|
|
|
destructor TZipWriter.Destroy;
|
|
var i: integer;
|
|
lhr: TLastHeader;
|
|
begin
|
|
if not Assigned(outFile) then begin // an error occured during outfile creation
|
|
inherited; // -> just free memory and leave
|
|
exit;
|
|
end;
|
|
// 1. prepare last header
|
|
with lhr do begin
|
|
signature := $06054b50;
|
|
thisDisk := 0;
|
|
headerDisk := 0;
|
|
thisFiles := Count;
|
|
totalFiles := Count;
|
|
headerSize := 0;
|
|
headerOffset := outFile.seek(0,soCurrent); // position of file entries
|
|
commentLen := 0;
|
|
end;
|
|
// 2. write file entries from Entry[]
|
|
for i := 0 to Count-1 do
|
|
with Entry[i] do begin
|
|
inc(lhr.headerSize, sizeOf(TFileHeader)+length(ZipName));
|
|
outFile.WriteBuffer(Header,sizeof(TFileHeader));
|
|
outFile.WriteBuffer(ZipName[1],length(ZipName));
|
|
end;
|
|
// 3. write last header
|
|
outFile.WriteBuffer(lhr,sizeof(lhr));
|
|
// 4. truncate and close file
|
|
{$ifdef KYLIX3}
|
|
ftruncate(outFile.Handle, outFile.seek(0,soFromCurrent));
|
|
{$else}
|
|
SetEndOfFile(outFile.Handle);
|
|
{$endif}
|
|
if forceFileAge<>0 then
|
|
FileSetDate(outFile.Handle,forceFileAge);
|
|
outFile.Free;
|
|
// 5. if we worked on a .tmp file (recreated from a TZipReader) -> make it new
|
|
if fDestFileName<>'' then begin
|
|
if not DeleteFile(fDestFileName) then begin
|
|
SleepHiRes(100);
|
|
if not DeleteFile(fDestFileName) then assert(false); end;
|
|
RenameFile(fFileName,fDestFileName); // '.tmp' -> '.bjt' ou '.zip'
|
|
end;
|
|
// 6. free memory: Finalize(Entry)
|
|
inherited;
|
|
end;
|
|
|
|
procedure TZipWriter.AddFile(const aFileName: TFileName; const aZipName: RawUTF8;
|
|
CompressionLevel: integer; Algorithm: integer=0);
|
|
// direct compress or store of a file content, using memory mapped file
|
|
var Map: TMemoryMap;
|
|
begin
|
|
if not Map.Map(aFileName) then exit;
|
|
if Map.Size<64 then
|
|
CompressionLevel := -1; // store if too small
|
|
ZipCreate(aZipName,CompressionLevel,0,Algorithm); // initialize Zip object
|
|
Entry[Count].Header.fileInfo.zlastMod :=
|
|
{$ifdef MSWINDOWS}FileGetDate(Map.FileHandle){$else}
|
|
DateTimeToFileDateWindows(FileDateToDateTime(FileGetDate(Map.FileHandle))){$endif};
|
|
Zip.WriteOnce(Map.Buffer^,Map.Size);
|
|
Map.UnMap;
|
|
ZipClose;
|
|
end;
|
|
|
|
procedure TZipWriter.Add(const aZipName: RawUTF8;
|
|
data: PAnsiChar; dataSize: cardinal; CompressionLevel: integer;
|
|
dataCRC: pCardinal=nil; FileAge: integer = 0; Algorithm: integer=0);
|
|
begin
|
|
if (self<>nil) and (aZipName<>'') then
|
|
if (CompressionLevel<0) and (Algorithm=0) then
|
|
with AddEntry(aZipName,FileAge)^.Header,FileInfo do begin
|
|
zzipSize := dataSize;
|
|
zfullSize := dataSize;
|
|
if dataCRC<>nil then
|
|
zcrc32 := dataCRC^ else
|
|
zcrc32 := crc32(0,data,dataSize);
|
|
outFile.WriteBuffer(fileInfo,sizeof(fileInfo));
|
|
outFile.WriteBuffer(aZipName[1],length(aZipName));
|
|
outFile.WriteBuffer(data^,dataSize);
|
|
inc(fCount);
|
|
end else begin
|
|
ZipCreate(aZipName,CompressionLevel,FileAge,Algorithm);
|
|
Zip.WriteOnce(data^,dataSize);
|
|
ZipClose; // fileInfo update+save, Zip.Free, inc(Count)
|
|
end;
|
|
end;
|
|
|
|
procedure TZipWriter.Add(const aZipName: RawUTF8; p: PBlobData);
|
|
begin
|
|
with AddEntry(aZipName)^.Header,FileInfo do begin
|
|
if p^.AlgoID=0 then begin
|
|
zzipMethod := p^.dataMethod;
|
|
zzipSize := p^.dataSize;
|
|
zfullSize := p^.dataFullSize;
|
|
zcrc32 := p^.dataCRC;
|
|
end else
|
|
if Assigned(SynCompressionAlgos.Algo(p^.AlgoID)) then
|
|
SetAlgoID(p^.AlgoID) else
|
|
raise TZipException.CreateFmt(sZipAlgoIDNUnknownN,[p^.AlgoID,aZipName]);
|
|
outFile.WriteBuffer(fileInfo,sizeof(fileInfo));
|
|
outFile.WriteBuffer(aZipName[1],length(aZipName));
|
|
outFile.WriteBuffer(p^.databuf,p^.dataSize);
|
|
inc(fCount);
|
|
end;
|
|
end;
|
|
|
|
procedure TZipWriter.Add(aReader: TZipReader; aReaderIndex: integer);
|
|
// this copy directly from a TZipReader
|
|
var sign: integer;
|
|
E: PZipEntry;
|
|
begin
|
|
if (aReader=nil) or (aReaderIndex<0) or (aReaderIndex>=aReader.Count) then
|
|
exit;
|
|
if Count=length(Entry) then
|
|
SetLength(Entry,Count+100);
|
|
with Entry[Count] do begin
|
|
E := @aReader.Entry[aReaderIndex];
|
|
ZipName := E^.ZipName; // may be different after delete or new
|
|
Header := E^.Header; // direct whole header copy
|
|
with Header do begin // update Entry[Count]:
|
|
fileInfo.nameLen := length(ZipName); // recalc length, as may be updated
|
|
localHeadOff := outFile.Position; // only changed data = position in file
|
|
sign := $04034b50; outFile.Write(sign,4);
|
|
outFile.WriteBuffer(fileInfo,sizeof(fileInfo)); // save new fileInfo
|
|
outFile.WriteBuffer(ZipName[1],fileInfo.nameLen); // append file name
|
|
outFile.WriteBuffer(E^.LocalHeader(aReader.Map.Buffer).LocalData^,fileInfo.zzipSize); // data copy
|
|
end;
|
|
end;
|
|
inc(fCount);
|
|
end;
|
|
|
|
procedure TZipWriter.ZipClose(aIndex: integer=-1);
|
|
// compression finish, fileInfo update+save, Zip.Free
|
|
// after ZipCreate: aIndex=-1 -> update Entry[Count] + inc(Count)
|
|
// in TZipWriter.Create(fromStream..): aIndex>=0 -> update Entry[aIndex] only
|
|
var p: cardinal;
|
|
i: integer;
|
|
begin
|
|
if Zip=nil then exit;
|
|
Zip.Finish;
|
|
if aIndex<0 then
|
|
i := Count else
|
|
i := aIndex;
|
|
with Entry[i] do begin
|
|
Header.fileInfo.zcrc32 := Zip.CRC;
|
|
Header.fileInfo.zfullSize := Zip.SizeIn; // if algo -> SizeIn=SizeOut
|
|
if Zip.FInitialized then
|
|
Header.fileInfo.zzipSize := Zip.SizeOut else
|
|
Header.fileInfo.zzipSize := Header.fileInfo.zfullSize;
|
|
p := outFile.Seek(0,soCurrent);
|
|
outFile.Seek(Header.localHeadOff+sizeof(dword),soBeginning);
|
|
outFile.WriteBuffer(Header.fileInfo,sizeof(Header.fileInfo)); // save updated fileInfo
|
|
outFile.Seek(p,soBeginning);
|
|
end;
|
|
FreeAndNil(Zip);
|
|
if aIndex<0 then
|
|
inc(fCount);
|
|
end;
|
|
|
|
procedure TZipWriter.ZipCreate(const aZipName: RawUTF8;
|
|
CompressionLevel: integer; FileAge: integer = 0; Algorithm: integer=0);
|
|
begin
|
|
assert(Zip=nil);
|
|
with AddEntry(aZipName,FileAge)^.Header do begin
|
|
if Algorithm>0 then
|
|
fileInfo.SetAlgoID(Algorithm) else
|
|
if CompressionLevel>=0 then
|
|
fileInfo.zzipMethod := 8;
|
|
Zip := TZipCompressor.Create(outFile, CompressionLevel,Algorithm);
|
|
outFile.WriteBuffer(fileInfo,sizeof(fileInfo)); // save bulk fileInfo
|
|
outFile.WriteBuffer(aZipName[1],length(aZipName));
|
|
// now the caller will use Zip.Write to compress data into outFile
|
|
// and will end compression with ZipClose
|
|
end;
|
|
end;
|
|
|
|
function TZipWriter.AddEntry(const aZipName: RawUTF8; FileAge: integer = 0): PZipEntry;
|
|
var sign: integer;
|
|
tmp: WinAnsiString;
|
|
begin
|
|
if Count=length(Entry) then
|
|
SetLength(Entry,Count+100);
|
|
result := @Entry[Count];
|
|
with result^ do begin
|
|
Header.Init; // signature, madeBy, extFileAttr, fileInfo.neededVersion init
|
|
{$ifdef MSWINDOWS}
|
|
if IsWinAnsiU(pointer(aZipName)) then begin
|
|
// Win-Ansi code page -> encode as DOS/OEM charset (old format)
|
|
tmp := Utf8ToWinAnsi(aZipName);
|
|
SetLength(ZipName,length(tmp));
|
|
CharToOemBuffA(pointer(tmp),pointer(ZipName),length(tmp));
|
|
end else
|
|
{$endif} // Linux will use only UTF-8 encoding
|
|
begin
|
|
ZipName := aZipName;
|
|
Header.fileInfo.SetUTF8FileName; // mark file name is UTF-8 encoded
|
|
end;
|
|
with Header do begin
|
|
localHeadOff := outFile.Position;
|
|
with fileInfo do begin
|
|
if FileAge=0 then
|
|
zlastMod := fNow else
|
|
zlastMod := FileAge;
|
|
nameLen := length(ZipName);
|
|
end;
|
|
end;
|
|
end;
|
|
sign := $04034b50; outFile.WriteBuffer(sign,sizeof(dword));
|
|
end;
|
|
|
|
function TZipWriter.LastCRC32Added: cardinal;
|
|
begin
|
|
if Count>0 then
|
|
result := Entry[Count-1].Header.fileInfo.zcrc32 else
|
|
result := 0;
|
|
end;
|
|
|
|
|
|
{ TZipValues }
|
|
|
|
procedure TZipValues.BeginWriter;
|
|
var i: integer;
|
|
begin
|
|
if Writer<>nil then exit;
|
|
Modified := true;
|
|
Count := 0;
|
|
for i := 0 to Reader.Count-1 do // update Values[] with MarkDeleted
|
|
if Reader.Entry[i].Header.signature<>0 then begin
|
|
CopyValue(i,Count);
|
|
inc(Count);
|
|
end;
|
|
inherited BeginWriter; // Writer := TZipWriter.Create(Reader) = calc MarkDeleted
|
|
assert(Writer.Count=Count);
|
|
end;
|
|
|
|
constructor TZipValues.Create(const aFileName: TFileName);
|
|
var n: integer;
|
|
begin
|
|
inherited Create(aFileName); // Reader := TZipReader.Create
|
|
n := Reader.Count-1; // '-index-' must be last Entry[n] -> otherwise gap in Values[]
|
|
if n=0 then
|
|
Reader.Clear; // must contains at least: Values[0] + '-index-'
|
|
if n<1 then exit;
|
|
with Reader.Entry[n] do // read Values[] from last Entry[]:
|
|
if (ZipName='-index-') and (Header.fileInfo.zzipMethod=0) then begin
|
|
Count := n;
|
|
LoadValues(LocalHeader(Reader.Map.Buffer).LocalData);
|
|
Reader.DeleteLastEntry; // ignore '-index-' from now
|
|
end else begin
|
|
Count := 0;
|
|
Assert(false,'wrong file format for '+FileName);
|
|
end;
|
|
end;
|
|
|
|
destructor TZipValues.Destroy;
|
|
begin
|
|
if Modified and (Count>0) then begin
|
|
BeginWriter; // will truncate to the last block
|
|
with Writer do begin
|
|
ZipCreate('-index-',-1);
|
|
SaveValues(Zip);
|
|
ZipClose;
|
|
end;
|
|
end;
|
|
inherited;
|
|
end;
|
|
|
|
function TZipValues.GetValue(aReaderIndex: integer; aStream: TStream): PAnsiChar;
|
|
begin
|
|
if Writer<>nil then
|
|
result := nil else
|
|
result := Reader.GetData(aReaderIndex,aStream);
|
|
end;
|
|
|
|
function TZipValues.MarkDeleted(aReaderIndex: integer): boolean;
|
|
begin
|
|
if aReaderIndex<0 then begin
|
|
result := false;
|
|
exit;
|
|
end;
|
|
Modified := true;
|
|
result := inherited MarkDeleted(aReaderIndex);
|
|
end;
|
|
|
|
{ TBlobData }
|
|
|
|
procedure CompressAsBlobData(const data; size: integer; aStream: TStream;
|
|
CompressionLevel: integer=6; Algorithm: integer=0);
|
|
// create a TBlobData in aStream (encryption algo: 6=AES 7=AES+Zip 8=AES+SynLz)
|
|
begin
|
|
with TZipCompressor.CreateAsBlobData(aStream,CompressionLevel,Algorithm) do
|
|
try
|
|
Write(data,size);
|
|
Finish;
|
|
finally
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
{ use algo 6=AES 7=AES+Zip-chunked 8=AES+SynLz-chunked
|
|
function CompressAsBlobData(data: PAnsiChar; size: integer;
|
|
AESKey: pointer=nil; AESKeySize: integer=0): string;
|
|
// optional AES-encrypt AFTER compression -> 0% ZIP compatible but security safe
|
|
begin
|
|
SetLength(result,sizeof(TBlobData)+(size*11)div 10+12);
|
|
with PBlobData(pointer(result))^ do begin
|
|
dataFullSize := size;
|
|
dataCRC := crc32(0,data,size);
|
|
dataSize := CompressMem(data,@dataBuf,size,length(result)-sizeof(TBlobData));
|
|
if dataSize>=dataFullSize then begin // compress only if efficient
|
|
dataMethod := 0; // store
|
|
dataSize := dataFullSize;
|
|
if (AESKey<>nil) and (AESKeySize>0) then
|
|
AES(AESKey^,AESKeySize,data,@databuf,dataSize,true) else
|
|
move(data^,databuf,dataSize);
|
|
end else begin
|
|
dataMethod := 8;
|
|
if (AESKey<>nil) and (AESKeySize>0) then
|
|
AES(AESKey^,AESKeySize,@databuf,@databuf,dataSize,true);
|
|
end;
|
|
SetLength(result,dataSize+BLOBDATA_HEADSIZE);
|
|
end;
|
|
end;}
|
|
|
|
function TBlobData.AlgoCreate(data: pointer): TSynCompressionAlgo;
|
|
// test if algo is registered, perform crc32 check and create one instance
|
|
var Algo: TSynCompressionAlgoClass;
|
|
begin
|
|
if DataMethod<15 then
|
|
result := nil else begin
|
|
Algo := SynCompressionAlgos.Algo(AlgoID); // registered?
|
|
if not Assigned(Algo) then // error: unregistered algo
|
|
raise TZipException.CreateFmt(sZipAlgoIDNUnknownN,[
|
|
AlgoID,'TBlobData']);
|
|
if crc32(0,data,dataFullSize)<>dataCRC then // always check integrity
|
|
raise TZipException.CreateFmt(sZipCrcErrorNN,[IntToStr(AlgoID),'TBlobData']);
|
|
result := Algo.Create; // create algo instance
|
|
end;
|
|
end;
|
|
|
|
function TBlobData.AlgoID: cardinal;
|
|
begin
|
|
// 0=stored 8=inflate >=16: AlgoID=dataMethod shr 4
|
|
result := (dataMethod shr 4) and 15;
|
|
end;
|
|
|
|
function TBlobData.Expand: RawByteString;
|
|
begin
|
|
case DataMethod of // 0=stored 8=inflate >16: AlgoID=dataMethod shr 4
|
|
16..31:
|
|
with AlgoCreate(@dataBuf) do // crc32+algo object create
|
|
try
|
|
SetString(result,nil,UnCompressedLength(@dataBuf,dataFullSize));
|
|
UnCompress(@dataBuf,dataFullSize,pointer(result));
|
|
finally
|
|
Free;
|
|
end;
|
|
8: begin
|
|
SetString(result,nil,dataFullSize);
|
|
if (UnCompressMem(
|
|
@dataBuf,pointer(result),dataSize,dataFullSize)<>integer(dataFullSize)) or
|
|
(crc32(0,pointer(result),dataFullSize)<>dataCRC) then begin
|
|
assert(false);
|
|
result := '';
|
|
end;
|
|
end;
|
|
0: if dataSize=0 then
|
|
result := '' else
|
|
SetString(result,PAnsiChar(@dataBuf),dataSize);
|
|
else begin
|
|
assert(false); // impossible dataMethod -> probably bad PBlobData
|
|
result := '';
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TBlobData.ExpandBuf(out destSize: cardinal): pointer;
|
|
// uncompress and alloc memory if necessary (i.e. DataMethod<>0);
|
|
// no direct AES since may be mapped and DataMethod=0
|
|
begin
|
|
case DataMethod of // 0=stored 8=inflate >16: AlgoID=dataMethod shr 4
|
|
16..31:
|
|
with AlgoCreate(@dataBuf) do // crc32+algo object create
|
|
try
|
|
destsize := UnCompressedLength(@dataBuf,dataFullSize);
|
|
Getmem(result,destSize);
|
|
UnCompress(@dataBuf,dataFullSize,result);
|
|
finally
|
|
Free;
|
|
end;
|
|
0: result := @dataBuf;
|
|
8: begin
|
|
GetMem(result,dataFullSize);
|
|
if (UnCompressMem(@dataBuf,result,dataSize,dataFullSize)<>integer(dataFullSize)) or
|
|
(crc32(0,result,dataFullSize)<>dataCRC) then begin
|
|
Freemem(result);
|
|
assert(false);
|
|
result := nil;
|
|
end;
|
|
end;
|
|
else begin
|
|
assert(false); // impossible dataMethod -> probably bad PBlobData
|
|
result := nil;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TBlobData.ExpandStream(Stream: TStream);
|
|
var tmp: RawByteString;
|
|
begin
|
|
case DataMethod of
|
|
16..31: begin
|
|
tmp := Expand;
|
|
Stream.WriteBuffer(pointer(tmp)^,length(tmp));
|
|
end;
|
|
0: Stream.WriteBuffer(dataBuf,dataFullsize);
|
|
8: UnCompressStream(@dataBuf,dataSize,Stream,nil);
|
|
else assert(false);
|
|
end;
|
|
end;
|
|
|
|
function TBlobData.Next: PAnsiChar;
|
|
{$ifdef PUREPASCAL}
|
|
begin
|
|
result := PAnsiChar(@databuf)+dataSize;
|
|
end;
|
|
{$else} {$ifdef FPC} nostackframe; assembler; {$endif}
|
|
asm
|
|
lea ecx,[eax+TBlobData.databuf]
|
|
mov eax,[eax].TBlobData.datasize
|
|
add eax,ecx
|
|
end;
|
|
{$endif}
|
|
|
|
procedure TBlobData.SetFrom(const FileInfo: TFileInfo);
|
|
begin
|
|
dataSize := FileInfo.zzipsize;
|
|
dataFullSize := FileInfo.zfullSize;
|
|
dataCRC := FileInfo.zcrc32;
|
|
// dataMethod: 0=stored 8=inflate >16: AlgoID=dataMethod shr 4
|
|
if FileInfo.AlgoID<>0 then
|
|
dataMethod := FileInfo.AlgoID shl 4 else // stored + AlgoID
|
|
dataMethod := FileInfo.zzipMethod;
|
|
end;
|
|
|
|
|
|
{ TZipEntry }
|
|
|
|
function TZipEntry.AlgoCreate(data: pointer; const FileName: TFileName): TSynCompressionAlgo;
|
|
// test if algo is registered, perform crc32 check and create one instance
|
|
var Algo: TSynCompressionAlgoClass;
|
|
begin
|
|
if Header.fileInfo.AlgoID=0 then
|
|
result := nil else begin
|
|
Algo := SynCompressionAlgos.Algo(Header.fileInfo.AlgoID); // registered?
|
|
if not Assigned(Algo) then // error: unregistered algo
|
|
raise TZipException.CreateFmt(sZipAlgoIDNUnknownN,[
|
|
Header.fileInfo.AlgoID,ZipName]);
|
|
if crc32(0,data,Header.fileInfo.zfullSize)<>
|
|
Header.fileInfo.zcrc32 then // always check integrity
|
|
raise TZipException.CreateFmt(sZipCrcErrorNN,[ZipName,FileName]);
|
|
result := Algo.Create; // create algo instance
|
|
end;
|
|
end;
|
|
|
|
function TZipEntry.SameAs(const aEntry: TZipEntry): boolean;
|
|
begin
|
|
result := (ZipName=aEntry.ZipName) and
|
|
Header.fileInfo.SameAs(@aEntry.Header.fileInfo);
|
|
end;
|
|
|
|
function TZipEntry.LocalHeader(ZipStart: PAnsiChar): PLocalFileHeader;
|
|
begin
|
|
result := @ZipStart[Header.localHeadOff];
|
|
end;
|
|
|
|
function TZipEntry.LocalDataPosition(ZipStart: PAnsiChar): PtrUInt;
|
|
begin
|
|
with LocalHeader(ZipStart)^ do
|
|
result := PtrUInt(LocalData)-PtrUInt(ZipStart);
|
|
end;
|
|
|
|
|
|
{ TSynCompressionAlgos }
|
|
|
|
function TSynCompressionAlgos.Algo(aID: integer): TSynCompressionAlgoClass;
|
|
var i: integer;
|
|
begin
|
|
for i := 0 to length(Values)-1 do
|
|
with Values[i] do
|
|
if ID=aID then begin
|
|
result := func;
|
|
exit;
|
|
end;
|
|
result := nil;
|
|
end;
|
|
|
|
procedure TSynCompressionAlgos.AlgoRegister(aAlgo: TSynCompressionAlgoClass;
|
|
aID, aWholeID: integer);
|
|
var L: integer;
|
|
begin
|
|
if not Assigned(aAlgo) then
|
|
exit;
|
|
aID := aID and 15;
|
|
if (aID=0) or Assigned(Algo(aID)) then exit;
|
|
L := length(Values);
|
|
SetLength(Values,L+1);
|
|
with Values[L] do begin
|
|
ID := aID;
|
|
WholeID := aWholeID;
|
|
func := aAlgo;
|
|
end;
|
|
end;
|
|
|
|
function TSynCompressionAlgos.WholeAlgoID(aID: integer): integer;
|
|
var i: integer;
|
|
begin
|
|
for i := 0 to length(Values)-1 do
|
|
with Values[i] do
|
|
if ID=aID then begin
|
|
result := WholeID;
|
|
exit;
|
|
end;
|
|
result := 0;
|
|
end;
|
|
|
|
|
|
{ TSynCompressionAlgo }
|
|
|
|
function TSynCompressionAlgo.CompressFinish: cardinal;
|
|
begin
|
|
result := 0; // default implementation: just do nothing
|
|
end;
|
|
|
|
procedure TSynCompressionAlgo.CompressInit(OutStream: TStream);
|
|
begin
|
|
fDestStream := OutStream;
|
|
end;
|
|
|
|
|
|
{ TSynCompressionAlgoBuf }
|
|
|
|
function TSynCompressionAlgoBuf.Compress(InP: pointer; InLen: cardinal;
|
|
CRC: PCardinal): cardinal;
|
|
begin
|
|
if InLen=0 then begin
|
|
result := 0;
|
|
exit;
|
|
end;
|
|
assert(InLen<=65536); // // fCompressBuf[] size if fixed
|
|
result := AlgoCompress(InP,InLen,fCompressBuf+3); // compress InP[InLen]
|
|
if result+128>InLen then begin // compression was not effective -> store
|
|
pWord(fCompressBuf)^ := 0; // fCompressBuf[0..2] = 0 = no compression
|
|
pInteger(@fCompressBuf[2])^ := (InLen-1)shl 8; // [3..4]=uncompressed len-1
|
|
move(InP^,fCompressBuf[5],InLen); // store after uncompressed len
|
|
result := InLen+5;
|
|
end else begin // compression was effective -> store compressed chunk len
|
|
pWord(fCompressBuf)^ := result; // fCompressBuf[0..2] = chunk len
|
|
fCompressBuf[2] := AnsiChar(result shr 16);
|
|
inc(result,3);
|
|
end;
|
|
fDestStream.WriteBuffer(fCompressBuf^,result);
|
|
if CRC<>nil then
|
|
CRC^ := crc32(CRC^,fCompressBuf,result);
|
|
end;
|
|
|
|
procedure TSynCompressionAlgoBuf.CompressInit(OutStream: TStream);
|
|
begin
|
|
inherited; // fDestSteram := OutStream
|
|
// size = worse case with 64KB chunk
|
|
Getmem(fCompressBuf,AlgoCompressLength(65536)); // = 73744 for SynLZ, e.g.
|
|
end;
|
|
|
|
destructor TSynCompressionAlgoBuf.Destroy;
|
|
begin
|
|
Freemem(fCompressBuf);
|
|
inherited;
|
|
end;
|
|
|
|
function TSynCompressionAlgoBuf.UnCompress(InP: pointer; InLen: cardinal;
|
|
OutP: pointer): cardinal;
|
|
var sP,sEnd, dP: PAnsiChar;
|
|
L: integer;
|
|
begin
|
|
sP := InP;
|
|
sEnd := sP+InLen;
|
|
dP := OutP;
|
|
while sP<sEnd do begin // -> uncompress InP[InLen] into PAnsiChar(OutStream)
|
|
L := PInteger(sP)^ and $ffffff;
|
|
if L=0 then begin // no compression
|
|
inc(sP,3);
|
|
L := pWord(sP)^+1;
|
|
inc(sP,2);
|
|
move(sP^,dP^,L);
|
|
inc(sP,L);
|
|
inc(dp,L);
|
|
end else begin // SynLZ compression
|
|
inc(dP,AlgoUnCompress(sP+3,L,dP));
|
|
inc(sP,L+3);
|
|
end;
|
|
end;
|
|
result := dp-PAnsiChar(OutP);
|
|
end;
|
|
|
|
function TSynCompressionAlgoBuf.UnCompressedLength(InP: pointer;
|
|
InLen: cardinal): cardinal;
|
|
var sP,sEnd: PAnsiChar;
|
|
L: integer;
|
|
begin
|
|
sP := InP;
|
|
sEnd := sP+InLen;
|
|
result := 0;
|
|
while sP<sEnd do begin // return uncompressed len
|
|
L := PInteger(sP)^ and $ffffff;
|
|
if L=0 then begin // no compression
|
|
L := pWord(sP+3)^+1;
|
|
inc(result,L);
|
|
inc(sP,L+5);
|
|
end else begin
|
|
inc(result,AlgoUnCompressLength(sP+3,InLen)); // very fast length calc
|
|
inc(sP,L+3);
|
|
end;
|
|
end
|
|
end;
|
|
|
|
{ TSynCompressionAlgoWhole }
|
|
|
|
function TSynCompressionAlgoWhole.Compress(InP: pointer; InLen: cardinal;
|
|
CRC: PCardinal): cardinal;
|
|
var tmp: PAnsiChar;
|
|
begin
|
|
getmem(tmp,AlgoCompressLength(InLen));
|
|
try
|
|
result := AlgoCompress(InP,InLen,tmp+1);
|
|
if result+128>InLen then begin // compression not effective
|
|
tmp[0] := #0; // mark stored
|
|
move(InP^,tmp[1],InLen);
|
|
result := InLen;
|
|
end else // compression was effective
|
|
tmp[0] := #1; // mark compressed
|
|
inc(result);
|
|
fDestStream.WriteBuffer(tmp^,result);
|
|
if CRC<>nil then
|
|
CRC^ := crc32(CRC^,tmp,result);
|
|
finally
|
|
freemem(tmp);
|
|
end;
|
|
end;
|
|
|
|
function TSynCompressionAlgoWhole.UnCompress(InP: pointer; InLen: cardinal;
|
|
OutP: pointer): cardinal;
|
|
var tmp: PAnsiChar absolute InP;
|
|
begin
|
|
case tmp[0] of
|
|
#0: begin
|
|
result := InLen-1;
|
|
move(tmp[1],OutP^,result);
|
|
end;
|
|
#1: result := AlgoUnCompress(tmp+1,InLen-1,OutP);
|
|
else result := 0;
|
|
end;
|
|
end;
|
|
|
|
function TSynCompressionAlgoWhole.UnCompressedLength(InP: pointer;
|
|
InLen: cardinal): cardinal;
|
|
var tmp: PAnsiChar absolute InP;
|
|
begin
|
|
case tmp[0] of
|
|
#0: result := InLen-1;
|
|
#1: result := AlgoUnCompressLength(tmp+1,InLen-1);
|
|
else result := 0;
|
|
end;
|
|
end;
|
|
|
|
|
|
end.
|