(* Enhanced zlib implementation Gabriel Corneanu 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 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.