restemplate/indy/Protocols/IdZLib.pas

1202 lines
35 KiB
Plaintext

(*
Enhanced zlib implementation
Gabriel Corneanu <gabrielcorneanu(AT)yahoo.com>
Base implementation follows the original zlib unit.
Key features:
Using last zlib library (1.2.3).
Removed all imported functions, which are now in zlibpas. This can be used
standalone (as many other projects that need zlib do).
The compression stream can create different type of streams:
zlib, gzip and raw deflate (see constructors).
The decompression stream can read all type of streams (autodetect),
plus that the stream type and gzip info is available for public access.
If the stream is not zlib or gzip, it is assumed raw. An error will
occur during decompressing if the data format is not valid.
The DecompressStream function is using the InflateBack call together
with direct memory access on the source stream
(if available, which means TStringStream or TCustomMemoryStream descendant).
It should be the fastest decompression routine!
The CompressStreamEx function is using direct memory access on both
source and destination stream (if available).
It should be faster than CompressStream.
CompressString or CompressStream can be used to compress a http response
History:
- Aug 2005: Initial release
*)
unit IdZLib;
interface
{$I IdCompilerDefines.inc}
uses
SysUtils,
Classes,
IdCTypes,
IdGlobal,
IdZLibHeaders;
type
// Abstract ancestor class
TCustomZlibStream = class(TIdBaseStream)
protected
FStrm: TStream;
FStrmPos: Integer;
FOnProgress: TNotifyEvent;
FZRec: TZStreamRec;
FBuffer: array [Word] of TIdAnsiChar;
FNameBuffer: array [0..255] of TIdAnsiChar;
FGZHeader : IdZLibHeaders.gz_header;
FStreamType : TZStreamType;
procedure Progress; dynamic;
procedure IdSetSize(ASize: Int64); override;
property OnProgress: TNotifyEvent read FOnProgress write FOnProgress;
public
constructor Create(Strm: TStream);
destructor Destroy; override;
property GZHeader: gz_header read FGZHeader;
end;
TCompressionLevel = (clNone, clFastest, clDefault, clMax);
TCompressionStream = class(TCustomZlibStream)
protected
function GetCompressionRate: Single;
function IdRead(var VBuffer: TIdBytes; AOffset, ACount: Longint): Longint; override;
function IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint; override;
function IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64; override;
public
constructor CreateEx(CompressionLevel: TCompressionLevel; Dest: TStream;
const StreamType: TZStreamType;
const AName: string = ''; ATime: Integer = 0);
constructor Create(CompressionLevel: TCompressionLevel; Dest: TStream; const AIncludeHeaders : Boolean = True);
constructor CreateGZ(CompressionLevel: TCompressionLevel; Dest: TStream;
const AName: string = ''; ATime: Integer = 0); overload;
destructor Destroy; override;
property CompressionRate: Single read GetCompressionRate;
property OnProgress;
end;
TDecompressionStream = class(TCustomZlibStream)
protected
FInitialPos : Int64;
function IdRead(var VBuffer: TIdBytes; AOffset, ACount: Longint): Longint; override;
function IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint; override;
function IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64; override;
public
constructor Create(Source: TStream);
destructor Destroy; override;
procedure InitRead;
function IsGZip: boolean;
property OnProgress;
end;
{ CompressBuf compresses data, buffer to buffer, in one call.
In: InBuf = ptr to compressed data
InBytes = number of bytes in InBuf
Out: OutBuf = ptr to newly allocated buffer containing decompressed data
OutBytes = number of bytes in OutBuf }
procedure CompressBuf(const InBuf: Pointer; InBytes: Integer;
out OutBuf: Pointer; out OutBytes: TIdC_UINT);
//generic read header from a buffer
function GetStreamType(InBuffer: Pointer; InCount: TIdC_UINT; gzheader: gz_headerp; out HeaderSize: TIdC_UINT): TZStreamType; overload;
//generic read header from a stream
//the stream position is preserved
function GetStreamType(InStream: TStream; gzheader: gz_headerp; out HeaderSize: TIdC_UINT): TZStreamType; overload;
//Note that unlike other things in this unit, you specify things with number
//values. This is deliberate on my part because some things in Indy rely on
//API's where you specify the ZLib parameter as a number. This is for the
//utmost flexibility. In the FTP server, you can actually specify something
//like a compression level.
//The WinBits parameter is extremely powerful so do not underestimate it.
procedure IndyCompressStream(InStream, OutStream: TStream;
const level: Integer = Z_DEFAULT_COMPRESSION;
const WinBits : Integer = MAX_WBITS;
const MemLevel : Integer = MAX_MEM_LEVEL;
const Stratagy : Integer = Z_DEFAULT_STRATEGY);
//compress stream; tries to use direct memory access on input stream
procedure CompressStream(InStream, OutStream: TStream; level: TCompressionLevel; StreamType : TZStreamType);
//compress stream; tries to use direct memory access on both streams
procedure CompressStreamEx(InStream, OutStream: TStream; level: TCompressionLevel; StreamType : TZStreamType);
//compress a string
function CompressString(const InString: string; level: TCompressionLevel; StreamType : TZStreamType): string;
//this is for where we know what the stream's WindowBits setting should be
//Note that this does have special handling for ZLIB values greater than
//32. I'm trying to treat it as the inflateInit2_ call would. I don't think
//InflateBack uses values greater than 16 so you have to make a workaround.
procedure IndyDecompressStream(InStream, OutStream: TStream;
const AWindowBits : Integer);
//fast decompress stream!
//using direct memory access to source stream (if available) and
//direct write (using inflateBack)
procedure DecompressStream(InStream, OutStream: TStream);
{ DecompressBuf decompresses data, buffer to buffer, in one call.
In: InBuf = ptr to compressed data
InBytes = number of bytes in InBuf
OutEstimate = zero, or est. size of the decompressed data
Out: OutBuf = ptr to newly allocated buffer containing decompressed data
OutBytes = number of bytes in OutBuf }
procedure DecompressBuf(const InBuf: Pointer; InBytes: Integer;
OutEstimate: Integer; out OutBuf: Pointer; out OutBytes: Integer);
{ DecompressToUserBuf decompresses data, buffer to buffer, in one call.
In: InBuf = ptr to compressed data
InBytes = number of bytes in InBuf
Out: OutBuf = ptr to user-allocated buffer to contain decompressed data
BufSize = number of bytes in OutBuf }
procedure DecompressToUserBuf(const InBuf: Pointer; InBytes: Integer;
const OutBuf: Pointer; BufSize: Integer);
type
EZlibError = class(Exception)
{JPM Additions, we need to be able to provide diagnostic info in an exception}
protected
FErrorCode : Integer;
public
class procedure RaiseException(const AError: Integer);
//
property ErrorCode : Integer read FErrorCode;
end;
ECompressionError = class(EZlibError);
EDecompressionError = class(EZlibError);
//ZLib error functions. They raise an exception for ZLib codes less than zero
function DCheck(code: Integer): Integer;
function CCheck(code: Integer): Integer;
const
//winbit constants
MAX_WBITS = IdZLibHeaders.MAX_WBITS;
{$EXTERNALSYM MAX_WBITS}
GZIP_WINBITS = MAX_WBITS + 16; //GZip format
{$EXTERNALSYM GZIP_WINBITS}
//negative values mean do not add any headers
//adapted from "Enhanced zlib implementation"
//by Gabriel Corneanu <gabrielcorneanu(AT)yahoo.com>
RAW_WBITS = -MAX_WBITS; //raw stream (without any header)
{$EXTERNALSYM RAW_WBITS}
implementation
uses
IdGlobalProtocols, IdStream, IdZLibConst
{$IFDEF HAS_AnsiStrings_StrPLCopy}
, AnsiStrings
{$ENDIF}
;
const
Levels: array [TCompressionLevel] of Int8 =
(Z_NO_COMPRESSION, Z_BEST_SPEED, Z_DEFAULT_COMPRESSION, Z_BEST_COMPRESSION);
function CCheck(code: Integer): Integer;
{$IFDEF USE_INLINE} inline; {$ENDIF}
begin
Result := code;
if code < 0 then begin
ECompressionError.RaiseException(code);
end;
end;
function DCheck(code: Integer): Integer;
{$IFDEF USE_INLINE} inline; {$ENDIF}
begin
Result := code;
if code < 0 then begin
EDecompressionError.RaiseException(code);
end;
end;
procedure CompressBuf(const InBuf: Pointer; InBytes: Integer;
out OutBuf: Pointer; out OutBytes: TIdC_UINT);
var
strm: z_stream;
P: Pointer;
begin
FillChar(strm, sizeof(strm), 0);
OutBytes := ((InBytes + (InBytes div 10) + 12) + 255) and not 255;
GetMem(OutBuf, OutBytes);
try
strm.next_in := InBuf;
strm.avail_in := InBytes;
strm.next_out := OutBuf;
strm.avail_out := OutBytes;
CCheck(deflateInit(strm, Z_BEST_COMPRESSION));
try
while CCheck(deflate(strm, Z_FINISH)) <> Z_STREAM_END do
begin
P := OutBuf;
Inc(OutBytes, 256);
ReallocMem(OutBuf, OutBytes);
strm.next_out := PIdAnsiChar(PtrUInt(OutBuf) + (PtrUInt(strm.next_out) - PtrUInt(P)));
strm.avail_out := 256;
end;
finally
CCheck(deflateEnd(strm));
end;
ReallocMem(OutBuf, strm.total_out);
OutBytes := strm.total_out;
except
FreeMem(OutBuf);
raise
end;
end;
function DMAOfStream(AStream: TStream; out Available: TIdC_UINT): Pointer;
{$IFDEF USE_INLINE} inline; {$ENDIF}
begin
if AStream is TCustomMemoryStream then begin
Result := TCustomMemoryStream(AStream).Memory;
end
{$IFDEF STRING_IS_ANSI}
// In D2009, the DataString property was changed to use a getter method
// that returns a temporary string, so it is not a direct access to the
// stream contents anymore. TStringStream was updated to derive from
// TBytesStream now, which is a TCustomMemoryStream descendant, and so
// will be handled above...
else if AStream is TStringStream then begin
Result := Pointer(TStringStream(AStream).DataString);
end
{$ENDIF}
else begin
Result := nil;
end;
if Result <> nil then
begin
//handle integer overflow
{$IFDEF STREAM_SIZE_64}
Available := TIdC_UINT(IndyMin(AStream.Size - AStream.Position, High(TIdC_UINT)));
// TODO: account for a 64-bit position in a 32-bit environment
Inc(PtrUInt(Result), AStream.Position);
{$ELSE}
Available := AStream.Size - AStream.Position;
Inc(PtrUInt(Result), AStream.Position);
{$ENDIF}
end else begin
Available := 0;
end;
end;
function CanResizeDMAStream(AStream: TStream): boolean;
{$IFDEF USE_INLINE} inline; {$ENDIF}
begin
Result := (AStream is TCustomMemoryStream) or
(AStream is TStringStream);
end;
///tries to get the stream info
//strm.next_in and available_in needs enough data!
//strm should not contain an initialized inflate
function TryStreamType(var strm: TZStreamRec; gzheader: PgzHeaderRec; const AWinBitsValue : Integer): boolean;
var
InitBuf: PIdAnsiChar;
InitIn : TIdC_UINT;
begin
InitBuf := strm.next_in;
InitIn := strm.avail_in;
DCheck(inflateInit2_(strm, AWinBitsValue, zlib_version, SizeOf(TZStreamRec)));
if (AWinBitsValue = GZIP_WINBITS) and (gzheader <> nil) then begin
DCheck(inflateGetHeader(strm, gzheader^));
end;
Result := inflate(strm, Z_BLOCK) = Z_OK;
DCheck(inflateEnd(strm));
if Result then begin
Exit;
end;
//rollback
strm.next_in := InitBuf;
strm.avail_in := InitIn;
end;
//tries to get the stream info
//strm.next_in and available_in needs enough data!
//strm should not contain an initialized inflate
function CheckInitInflateStream(var strm: TZStreamRec; gzheader: gz_headerp): TZStreamType; overload;
var
InitBuf: PIdAnsiChar;
InitIn: Integer;
function LocalTryStreamType(AStreamType: TZStreamType): Boolean;
begin
DCheck(inflateInitEx(strm, AStreamType));
if (AStreamType = zsGZip) and (gzheader <> nil) then begin
DCheck(inflateGetHeader(strm, gzheader^));
end;
Result := inflate(strm, Z_BLOCK) = Z_OK;
DCheck(inflateEnd(strm));
if Result then begin
Exit;
end;
//rollback
strm.next_in := InitBuf;
strm.avail_in := InitIn;
end;
begin
if strm.next_out = nil then begin
//needed for reading, but not used
strm.next_out := strm.next_in;
end;
InitBuf := strm.next_in;
InitIn := strm.avail_in;
for Result := zsZLib to zsGZip do
begin
if LocalTryStreamType(Result) then begin
Exit;
end;
end;
Result := zsRaw;
end;
function GetStreamType(InBuffer: Pointer; InCount: TIdC_UINT; gzheader: gz_headerp;
out HeaderSize: TIdC_UINT): TZStreamType;
var
strm : TZStreamRec;
begin
FillChar(strm, SizeOf(strm), 0);
strm.next_in := InBuffer;
strm.avail_in := InCount;
Result := CheckInitInflateStream(strm, gzheader);
HeaderSize := InCount - strm.avail_in;
end;
function GetStreamType(InStream: TStream; gzheader: gz_headerp;
out HeaderSize: TIdC_UINT): TZStreamType;
const
StepSize = 20; //one step be enough, but who knows...
var
N : TIdC_UINT;
Buff : PIdAnsiChar;
UseBuffer: Boolean;
begin
Buff := DMAOfStream(InStream, N);
UseBuffer := Buff = nil;
if UseBuffer then begin
GetMem(Buff, StepSize);
end;
try
repeat
if UseBuffer then begin
Inc(N, InStream.Read(Buff[N], StepSize));
end;
Result := GetStreamType(Buff, N, gzheader, HeaderSize);
//do we need more data?
//N mod StepSize <> 0 means no more data available
if (HeaderSize < N) or (not UseBuffer) or ((N mod StepSize) <> 0) then begin
Break;
end;
ReallocMem(Buff, N + StepSize);
until False;
finally
if UseBuffer then
begin
try
TIdStreamHelper.Seek(InStream, -N, soCurrent);
finally
FreeMem(Buff);
end;
end;
end;
end;
const
WindowSize = 1 shl MAX_WBITS;
type
PZBack = ^TZBack;
TZBack = record
InStream : TStream;
OutStream : TStream;
InMem : PIdAnsiChar; //direct memory access
InMemSize : TIdC_UINT;
ReadBuf : array[Word] of TIdAnsiChar;
Window : array[0..WindowSize] of TIdAnsiChar;
end;
function Strm_in_func(opaque: Pointer; var buf: PByte): TIdC_UNSIGNED; cdecl;
var
S : TStream;
BackObj : PZBack;
begin
BackObj := PZBack( opaque );
S := BackObj.InStream; //help optimizations
if BackObj.InMem <> nil then
begin
//direct memory access if available!
buf := Pointer(BackObj.InMem);
//handle integer overflow
{$IFDEF STREAM_SIZE_64}
Result := TIdC_UNSIGNED(IndyMin(S.Size - S.Position, High(TIdC_UNSIGNED)));
{$ELSE}
Result := S.Size - S.Position;
{$ENDIF}
TIdStreamHelper.Seek(S, Result, soCurrent);
end else
begin
buf := PByte(@BackObj.ReadBuf);
Result := S.Read(buf^, SizeOf(BackObj.ReadBuf));
end;
end;
function Strm_out_func(opaque: Pointer; buf: PByte; size: TIdC_UNSIGNED): TIdC_INT; cdecl;
begin
Result := TIdC_INT(PZBack(opaque).OutStream.Write(buf^, size) - TIdC_SIGNED(size));
end;
procedure DecompressStream(InStream, OutStream: TStream);
var
strm : z_stream;
BackObj: PZBack;
begin
FillChar(strm, sizeof(strm), 0);
GetMem(BackObj, SizeOf(TZBack));
try
//Darcy
FillChar(BackObj^, sizeof(TZBack), 0);
//direct memory access if possible!
BackObj.InMem := DMAOfStream(InStream, BackObj.InMemSize);
BackObj.InStream := InStream;
BackObj.OutStream := OutStream;
//use our own function for reading
strm.avail_in := Strm_in_func(BackObj, PByte(strm.next_in));
strm.next_out := PIdAnsiChar(@BackObj.Window[0]);
strm.avail_out := 0;
CheckInitInflateStream(strm, nil);
strm.next_out := nil;
strm.avail_out := 0;
DCheck(inflateBackInit(strm, MAX_WBITS, @BackObj.Window[0]));
try
DCheck(inflateBack(strm, Strm_in_func, BackObj, Strm_out_func, BackObj));
// DCheck(inflateBack(strm, @Strm_in_func, BackObj, @Strm_out_func, BackObj));
//seek back when unused data
TIdStreamHelper.Seek(InStream, -strm.avail_in, soCurrent);
//now trailer can be checked
finally
DCheck(inflateBackEnd(strm));
end;
finally
FreeMem(BackObj);
end;
end;
procedure IndyDecompressStream(InStream, OutStream: TStream;
const AWindowBits : Integer);
var
strm : TZStreamRec;
BackObj: PZBack;
LWindowBits : Integer;
begin
LWindowBits := AWindowBits;
FillChar(strm, sizeof(strm), 0);
GetMem(BackObj, SizeOf(TZBack));
try
//direct memory access if possible!
BackObj.InMem := DMAOfStream(InStream, BackObj.InMemSize);
BackObj.InStream := InStream;
BackObj.OutStream := OutStream;
//use our own function for reading
strm.avail_in := Strm_in_func(BackObj, PByte(strm.next_in));
strm.next_out := PIdAnsiChar(@BackObj.Window[0]);
strm.avail_out := 0;
//note that you can not use a WinBits parameter greater than 32 with
//InflateBackInit. That was used in the inflate functions
//for automatic detection of header bytes and trailer bytes.
//Se lets try this ugly workaround for it.
if AWindowBits > 32 then
begin
LWindowBits := Abs(AWindowBits - 32);
if not TryStreamType(strm, nil, LWindowBits) then
begin
if TryStreamType(strm, nil, LWindowBits + 16) then
begin
Inc(LWindowBits, 16);
end else
begin
TryStreamType(strm, nil, -LWindowBits);
end;
end;
end;
strm.next_out := nil;
strm.avail_out := 0;
DCheck(inflateBackInit_(strm,LWindowBits, @BackObj.Window[0],
zlib_version, SizeOf(TZStreamRec)));
try
DCheck(inflateBack(strm, Strm_in_func, BackObj, Strm_out_func, BackObj));
//seek back when unused data
TIdStreamHelper.Seek(InStream, -strm.avail_in, soCurrent);
//now trailer can be checked
finally
DCheck(inflateBackEnd(strm));
end;
finally
FreeMem(BackObj);
end;
end;
type
TMemStreamAccess = class(TMemoryStream);
function ExpandStream(AStream: TStream; const ACapacity : TIdStreamSize): Boolean;
{$IFDEF USE_INLINE} inline; {$ENDIF}
begin
Result := True;
AStream.Size := ACapacity;
if AStream is TMemoryStream then begin
AStream.Size := TMemStreamAccess(AStream).Capacity;
end;
end;
procedure IndyCompressStream(InStream, OutStream: TStream;
const level: Integer = Z_DEFAULT_COMPRESSION;
const WinBits : Integer = MAX_WBITS;
const MemLevel : Integer = MAX_MEM_LEVEL;
const Stratagy : Integer = Z_DEFAULT_STRATEGY);
const
//64 KB buffer
BufSize = 65536;
var
strm : TZStreamRec;
InBuf, OutBuf : PIdAnsiChar;
UseInBuf, UseOutBuf : boolean;
LastOutCount : TIdC_UINT;
procedure WriteOut;
begin
if UseOutBuf then
begin
if LastOutCount > 0 then
begin
OutStream.Write(OutBuf^, LastOutCount - strm.avail_out);
end;
strm.avail_out := BufSize;
strm.next_out := OutBuf;
end else
begin
if strm.avail_out = 0 then
begin
ExpandStream(OutStream, OutStream.Size + BufSize);
end;
TIdStreamHelper.Seek(OutStream, LastOutCount - strm.avail_out, soCurrent);
strm.next_out := DMAOfStream(OutStream, strm.avail_out);
//because we can't really know how much resize is increasing!
end;
LastOutCount := strm.avail_out;
end;
var
Finished : boolean;
begin
FillChar(strm, SizeOf(strm), 0);
InBuf := nil;
OutBuf := nil;
LastOutCount := 0;
strm.next_in := DMAOfStream(InStream, strm.avail_in);
UseInBuf := strm.next_in = nil;
if UseInBuf then begin
GetMem(InBuf, BufSize);
end;
try
UseOutBuf := not CanResizeDMAStream(OutStream);
if UseOutBuf then begin
GetMem(OutBuf, BufSize);
end;
try
CCheck(deflateInit2_(strm, level, Z_DEFLATED, WinBits, MemLevel, Stratagy, zlib_version, SizeOf(TZStreamRec)));
repeat
if strm.avail_in = 0 then
begin
if UseInBuf then
begin
strm.avail_in := InStream.Read(InBuf^, BufSize);
strm.next_in := InBuf;
end;
if strm.avail_in = 0 then begin
Break;
end;
end;
if strm.avail_out = 0 then begin
WriteOut;
end;
CCheck(deflate(strm, Z_NO_FLUSH));
until False;
repeat
Finished := CCheck(deflate(strm, Z_FINISH)) = Z_STREAM_END;
WriteOut;
until Finished;
if not UseOutBuf then
begin
//truncate when using direct output
OutStream.Size := OutStream.Position;
end;
//adjust position of the input stream
if UseInBuf then begin
//seek back when unused data
TIdStreamHelper.Seek(InStream, -strm.avail_in, soCurrent);
end else begin
//simple seek
TIdStreamHelper.Seek(InStream, strm.total_in, soCurrent);
end;
CCheck(deflateEnd(strm));
finally
if OutBuf <> nil then begin
FreeMem(OutBuf);
end;
end;
finally
if InBuf <> nil then begin
FreeMem(InBuf);
end;
end;
end;
procedure DoCompressStreamEx(InStream, OutStream: TStream; level: TCompressionLevel;
StreamType : TZStreamType; UseDirectOut: boolean);
const
//64 KB buffer
BufSize = 65536;
var
strm : z_stream;
InBuf, OutBuf : PIdAnsiChar;
UseInBuf, UseOutBuf : boolean;
LastOutCount : TIdC_UINT;
procedure WriteOut;
begin
if UseOutBuf then
begin
if LastOutCount > 0 then begin
OutStream.Write(OutBuf^, LastOutCount - strm.avail_out);
end;
strm.avail_out := BufSize;
strm.next_out := OutBuf;
end else
begin
if strm.avail_out = 0 then begin
ExpandStream(OutStream, OutStream.Size + BufSize);
end;
TIdStreamHelper.Seek(OutStream, LastOutCount - strm.avail_out, soCurrent);
strm.next_out := DMAOfStream(OutStream, strm.avail_out);
//because we can't really know how much resize is increasing!
end;
LastOutCount := strm.avail_out;
end;
var
Finished : boolean;
begin
FillChar(strm, SizeOf(strm), 0);
InBuf := nil;
OutBuf := nil;
LastOutCount := 0;
strm.next_in := DMAOfStream(InStream, strm.avail_in);
UseInBuf := strm.next_in = nil;
if UseInBuf then begin
GetMem(InBuf, BufSize);
end;
try
UseOutBuf := not (UseDirectOut and CanResizeDMAStream(OutStream));
if UseOutBuf then begin
GetMem(OutBuf, BufSize);
end;
try
CCheck(deflateInitEx(strm, Levels[level], StreamType));
repeat
if strm.avail_in = 0 then
begin
if UseInBuf then
begin
strm.avail_in := InStream.Read(InBuf^, BufSize);
strm.next_in := InBuf;
end;
if strm.avail_in = 0 then begin
Break;
end;
end;
if strm.avail_out = 0 then begin
WriteOut;
end;
CCheck(deflate(strm, Z_NO_FLUSH));
until False;
repeat
if (strm.avail_in = 0) and (strm.avail_out = 0) then begin
WriteOut;
end;
Finished := CCheck(deflate(strm, Z_FINISH)) = Z_STREAM_END;
WriteOut;
until Finished;
if not UseOutBuf then
begin
//truncate when using direct output
OutStream.Size := OutStream.Position;
end;
//adjust position of the input stream
if UseInBuf then begin
//seek back when unused data
TIdStreamHelper.Seek(InStream, -strm.avail_in, soCurrent);
end else begin
//simple seek
TIdStreamHelper.Seek(InStream, strm.total_in, soCurrent);
end;
CCheck(deflateEnd(strm));
finally
if OutBuf <> nil then begin
FreeMem(OutBuf);
end;
end;
finally
if InBuf <> nil then begin
FreeMem(InBuf);
end;
end;
end;
procedure CompressStream(InStream, OutStream: TStream; level: TCompressionLevel; StreamType : TZStreamType);
begin
DoCompressStreamEx(InStream, OutStream, level, StreamType, False);
end;
procedure CompressStreamEx(InStream, OutStream: TStream; level: TCompressionLevel; StreamType : TZStreamType);
begin
DoCompressStreamEx(InStream, OutStream, level, StreamType, True);
end;
function CompressString(const InString: string; level: TCompressionLevel; StreamType : TZStreamType): string;
var
S, D : TStringStream;
begin
S := TStringStream.Create(InString);
try
D := TStringStream.Create('');
try
CompressStream(S, D, level, StreamType);
Result := D.DataString;
finally
D.Free;
end;
finally
S.Free;
end;
end;
procedure DecompressBuf(const InBuf: Pointer; InBytes: Integer;
OutEstimate: Integer; out OutBuf: Pointer; out OutBytes: Integer);
var
strm: z_stream;
P: Pointer;
BufInc: Integer;
begin
FillChar(strm, SizeOf(strm), 0);
BufInc := (InBytes + 255) and not 255;
if OutEstimate = 0 then begin
OutBytes := BufInc;
end else begin
OutBytes := OutEstimate;
end;
GetMem(OutBuf, OutBytes);
try
strm.next_in := InBuf;
strm.avail_in := InBytes;
strm.next_out := OutBuf;
strm.avail_out := OutBytes;
DCheck(inflateInit(strm));
try
while DCheck(inflate(strm, Z_NO_FLUSH)) <> Z_STREAM_END do
begin
P := OutBuf;
Inc(OutBytes, BufInc);
ReallocMem(OutBuf, OutBytes);
strm.next_out := PIdAnsiChar(PtrUInt(OutBuf) + (PtrUInt(strm.next_out) - PtrUInt(P)));
strm.avail_out := BufInc;
end;
finally
DCheck(inflateEnd(strm));
end;
ReallocMem(OutBuf, strm.total_out);
OutBytes := strm.total_out;
except
FreeMem(OutBuf);
raise;
end;
end;
procedure DecompressToUserBuf(const InBuf: Pointer; InBytes: Integer;
const OutBuf: Pointer; BufSize: Integer);
var
strm: z_stream;
begin
FillChar(strm, SizeOf(strm), 0);
strm.next_in := InBuf;
strm.avail_in := InBytes;
strm.next_out := OutBuf;
strm.avail_out := BufSize;
DCheck(inflateInit(strm));
try
if DCheck(inflate(strm, Z_FINISH)) <> Z_STREAM_END then begin
raise EZlibError.Create(sTargetBufferTooSmall);
end;
finally
DCheck(inflateEnd(strm));
end;
end;
{ EZlibError }
class procedure EZlibError.RaiseException(const AError: Integer);
var
LException: EZlibError;
begin
LException := CreateFmt(sZLibError, [AError]);
LException.FErrorCode := AError;
raise LException;
end;
// TCustomZlibStream
constructor TCustomZLibStream.Create(Strm: TStream);
begin
inherited Create;
FStrm := Strm;
FStrmPos := Strm.Position;
fillchar(FZRec, SizeOf(FZRec), 0);
FZRec.next_out := @FBuffer[0];
FZRec.avail_out := 0;
FZRec.next_in := @FBuffer[0];
FZRec.avail_in := 0;
fillchar(FGZHeader, SizeOf(FGZHeader), 0);
FStreamType := zsZLib;
FGZHeader.name := @FNameBuffer[0];
FGZHeader.name_max := SizeOf(FNameBuffer);
end;
destructor TCustomZlibStream.Destroy;
begin
inherited Destroy;
end;
procedure TCustomZLibStream.Progress;
begin
if Assigned(FOnProgress) then begin
FOnProgress(Self);
end;
end;
procedure TCustomZLibStream.IdSetSize(ASize: Int64);
begin
// do nothing here. IdSetSize is abstract, so it has
// to be overriden, but we don't actually use it here
end;
// TCompressionStream
constructor TCompressionStream.CreateEx(CompressionLevel: TCompressionLevel;
Dest: TStream; const StreamType: TZStreamType;
const AName: string = ''; ATime: Integer = 0);
{$IFDEF USE_MARSHALLED_PTRS}
type
TBytesPtr = ^TBytes;
{$ENDIF}
var
LBytes: TIdBytes;
{$IFDEF HAS_AnsiString}
LName: AnsiString;
{$ENDIF}
begin
inherited Create(Dest);
LBytes := nil; // keep the compiler happy
FZRec.next_out := @FBuffer[0];
FZRec.avail_out := SizeOf(FBuffer);
FStreamType := StreamType;
CCheck(deflateInitEx(FZRec, Levels[CompressionLevel], StreamType));
if StreamType = zsGZip then
begin
FGZHeader.time := ATime;
//zero-terminated file name
//RFC 1952
// The name must consist of ISO
//8859-1 (LATIN-1) characters; on operating systems using
//EBCDIC or any other character set for file names, the name
//must be translated to the ISO LATIN-1 character set.
// Rebeau 2/20/09: Indy's 8-bit encoding class currently uses ISO-8859-1
// so we could technically use that, but since the RFC is very specific
// about the charset, we'll force it here in case Indy's 8-bit encoding
// class is changed later on...
LBytes := CharsetToEncoding('ISO-8859-1').GetBytes(AName);
{$IFDEF USE_MARSHALLED_PTRS}
// TODO: optimize this
FillChar(FGZHeader.name^, FGZHeader.name_max, 0);
TMarshal.Copy(TBytesPtr(@LBytes)^, 0, TPtrWrapper.Create(FGZHeader.name), IndyMin(Length(LBytes), FGZHeader.name_max));
{$ELSE}
SetString(LName, PAnsiChar(LBytes), Length(LBytes));
{$IFDEF HAS_AnsiStrings_StrPLCopy}AnsiStrings.{$ENDIF}StrPLCopy(FGZHeader.name, LName, FGZHeader.name_max);
{$ENDIF}
deflateSetHeader(FZRec, FGZHeader);
end;
end;
constructor TCompressionStream.Create(CompressionLevel: TCompressionLevel;
Dest: TStream; const AIncludeHeaders : Boolean = True);
begin
if AIncludeHeaders then begin
CreateEx(CompressionLevel, Dest, zsZLib);
end else begin
CreateEx(CompressionLevel, Dest, zsRaw);
end;
end;
constructor TCompressionStream.CreateGZ(CompressionLevel: TCompressionLevel;
Dest: TStream; const AName: string; ATime: Integer);
begin
CreateEx(CompressionLevel, Dest, zsGZip, AName, ATime);
end;
destructor TCompressionStream.Destroy;
begin
FZRec.next_in := nil;
FZRec.avail_in := 0;
try
if FStrm.Position <> FStrmPos then begin
FStrm.Position := FStrmPos;
end;
while (CCheck(deflate(FZRec, Z_FINISH)) <> Z_STREAM_END) and (FZRec.avail_out = 0) do
begin
FStrm.WriteBuffer(FBuffer[0], SizeOf(FBuffer));
FZRec.next_out := @FBuffer[0];
FZRec.avail_out := SizeOf(FBuffer);
end;
if FZRec.avail_out < SizeOf(FBuffer) then begin
FStrm.WriteBuffer(FBuffer, SizeOf(FBuffer) - FZRec.avail_out);
end;
finally
deflateEnd(FZRec);
end;
inherited Destroy;
end;
function TCompressionStream.IdRead(var VBuffer: TIdBytes; AOffset, ACount: Longint): Longint;
begin
raise ECompressionError.Create(sInvalidStreamOp);
end;
function TCompressionStream.IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint;
begin
FZRec.next_in := PIdAnsiChar(@ABuffer[AOffset]);
FZRec.avail_in := ACount;
if FStrm.Position <> FStrmPos then begin
FStrm.Position := FStrmPos;
end;
while FZRec.avail_in > 0 do
begin
CCheck(deflate(FZRec, 0));
if FZRec.avail_out = 0 then
begin
FStrm.WriteBuffer(FBuffer[0], SizeOf(FBuffer));
FZRec.next_out := @FBuffer[0];
FZRec.avail_out := SizeOf(FBuffer);
FStrmPos := FStrm.Position;
Progress;
end;
end;
Result := ACount;
end;
function TCompressionStream.IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64;
begin
if (AOffset = 0) and (AOrigin = soCurrent) then begin
Result := FZRec.total_in;
end else begin
raise ECompressionError.Create(sInvalidStreamOp);
end;
end;
function TCompressionStream.GetCompressionRate: Single;
begin
if FZRec.total_in = 0 then begin
Result := 0;
end else begin
Result := (1.0 - (FZRec.total_out / FZRec.total_in)) * 100.0;
end;
end;
// TDecompressionStream
constructor TDecompressionStream.Create(Source: TStream);
begin
inherited Create(Source);
FInitialPos := FStrmPos;
FStreamType := zsRaw; //unknown
InitRead;
end;
destructor TDecompressionStream.Destroy;
begin
TIdStreamHelper.Seek(FStrm, -FZRec.avail_in, soCurrent);
inflateEnd(FZRec);
inherited Destroy;
end;
procedure TDecompressionStream.InitRead;
var
N, S : TIdC_UINT;
begin
//never call this after starting!
if FZRec.total_in > 0 then begin
Exit;
end;
N := FStrm.Read(FBuffer, SizeOf(FBuffer));
//64k should always be enough
FStreamType := GetStreamType(@FBuffer, N, @FGZHeader, S);
if (S = N) or (FStreamType = zsGZip) and (FGZHeader.done = 0) then
//need more data???
//theoretically it can happen with a veeeeery long gzip name or comment
//this is more generic, but some extra steps
begin
TIdStreamHelper.Seek(FStrm, -N, soCurrent);
FStreamType := GetStreamType(FStrm, @FGZHeader, S);
end;
//open
FZRec.next_in := @FBuffer[0];
FZRec.avail_in := N;
DCheck(inflateInitEx(FZRec, FStreamType));
end;
function TDecompressionStream.IdRead(var VBuffer: TIdBytes; AOffset,
ACount: Longint): Longint;
begin
FZRec.next_out := PIdAnsiChar(@VBuffer[AOffset]);
FZRec.avail_out := ACount;
if FStrm.Position <> FStrmPos then begin
FStrm.Position := FStrmPos;
end;
while FZRec.avail_out > 0 do
begin
if FZRec.avail_in = 0 then
begin
//init read if necessary
//if FZRec.total_in = 0 then InitRead;
FZRec.avail_in := FStrm.Read(FBuffer[0], SizeOf(FBuffer));
if FZRec.avail_in = 0 then begin
Break;
end;
FZRec.next_in := @FBuffer[0];
FStrmPos := FStrm.Position;
Progress;
end;
if CCheck(inflate(FZRec, 0)) = Z_STREAM_END then begin
Break;
end;
end;
Result := TIdC_UINT(ACount) - FZRec.avail_out;
end;
function TDecompressionStream.IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint;
begin
raise EDecompressionError.Create(sInvalidStreamOp);
end;
function TDecompressionStream.IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64;
var
I: Integer;
Buf: array [0..4095] of TIdAnsiChar;
LOffset : Int64;
begin
if (AOffset = 0) and (AOrigin = soBeginning) then
begin
DCheck(inflateReset(FZRec));
FZRec.next_in := @FBuffer[0];
FZRec.avail_in := 0;
FStrm.Position := FInitialPos;
FStrmPos := FInitialPos;
end
else if ((AOffset >= 0) and (AOrigin = soCurrent)) or
(((TIdC_UINT(AOffset) - FZRec.total_out) > 0) and (AOrigin = soBeginning)) then
begin
LOffset := AOffset;
if AOrigin = soBeginning then begin
Dec(LOffset, FZRec.total_out);
end;
if LOffset > 0 then
begin
for I := 1 to LOffset div sizeof(Buf) do begin
ReadBuffer(Buf, sizeof(Buf));
end;
ReadBuffer(Buf, LOffset mod sizeof(Buf));
end;
end else
begin
// raise EDecompressionError.CreateRes(@sInvalidStreamOp);
raise EDecompressionError.Create(sInvalidStreamOp);
end;
Result := FZRec.total_out;
end;
function TDecompressionStream.IsGZip: boolean;
begin
Result := (FStreamType = zsGZip) and (FGZHeader.done = 1);
end;
end.