{ $Project$ $Workfile$ $Revision$ $DateUTC$ $Id$ This file is part of the Indy (Internet Direct) project, and is offered under the dual-licensing agreement described on the Indy website. (http://www.indyproject.org/) Copyright: (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved. $Log$ Rev 1.10 2/22/2004 12:04:00 AM JPMugaas Updated for file rename. Rev 1.9 2/12/2004 11:28:04 PM JPMugaas Modified compression intercept to use the ZLibEx unit. Rev 1.8 2004.02.09 9:56:00 PM czhower Fixed for lib changes. Rev 1.7 5/12/2003 12:31:00 AM GGrieve Get compiling again with DotNet Changes Rev 1.6 10/12/2003 1:49:26 PM BGooijen Changed comment of last checkin Rev 1.5 10/12/2003 1:43:24 PM BGooijen Changed IdCompilerDefines.inc to Core\IdCompilerDefines.inc Rev 1.3 6/27/2003 2:38:04 PM BGooijen Fixed bug where last part was not compressed/send Rev 1.2 4/10/2003 4:12:42 PM BGooijen Added TIdServerCompressionIntercept Rev 1.1 4/3/2003 2:55:48 PM BGooijen Now calls DeinitCompressors on disconnect Rev 1.0 11/14/2002 02:15:50 PM JPMugaas } unit IdCompressionIntercept; { This file implements an Indy intercept component that compresses a data stream using the open-source zlib compression library. In order for this file to compile on Windows, the follow .obj files *must* be provided as delivered with this file: deflate.obj inflate.obj inftrees.obj trees.obj adler32.obj infblock.obj infcodes.obj infutil.obj inffast.obj On Linux, the shared-object file libz.so.1 *must* be available on the system. Most modern Linux distributions include this file. Simply set the CompressionLevel property to a value between 1 and 9 to enable compressing of the data stream. A setting of 0(zero) disables compression and the component is dormant. The sender *and* received must have compression enabled in order to properly decompress the data stream. They do *not* have to use the same CompressionLevel as long as they are both set to a value between 1 and 9. Original Author: Allen Bauer This source file is submitted to the Indy project on behalf of Borland Sofware Corporation. No warranties, express or implied are given with this source file. } interface {$I IdCompilerDefines.inc} uses Classes, IdException, IdGlobal, IdGlobalProtocols, IdIntercept, IdTCPClient, IdTCPConnection, IdZLibHeaders, IdZLib; type EIdCompressionException = class(EIdException); EIdCompressorInitFailure = class(EIdCompressionException); EIdDecompressorInitFailure = class(EIdCompressionException); EIdCompressionError = class(EIdCompressionException); EIdDecompressionError = class(EIdCompressionException); TIdCompressionLevel = 0..9; TIdCompressionIntercept = class(TIdConnectionIntercept) protected FCompressionLevel: TIdCompressionLevel; FCompressRec: TZStreamRec; FDecompressRec: TZStreamRec; FRecvBuf: TIdBytes; FRecvCount, FRecvSize: UInt32; FSendBuf: TIdBytes; FSendCount, FSendSize: UInt32; procedure SetCompressionLevel(Value: TIdCompressionLevel); procedure InitCompressors; procedure DeinitCompressors; public destructor Destroy; override; procedure Disconnect; override; procedure Receive(var VBuffer: TIdBytes); override; procedure Send(var VBuffer: TIdBytes); override; published property CompressionLevel: TIdCompressionLevel read FCompressionLevel write SetCompressionLevel; end; TIdServerCompressionIntercept = class(TIdServerIntercept) protected FCompressionLevel: TIdCompressionLevel; public procedure Init; override; function Accept(AConnection: TComponent): TIdConnectionIntercept; override; published property CompressionLevel: TIdCompressionLevel read FCompressionLevel write FCompressionLevel; end; implementation uses IdResourceStringsProtocols, IdExceptionCore; { TIdCompressionIntercept } procedure TIdCompressionIntercept.DeinitCompressors; begin if Assigned(FCompressRec.zalloc) then begin deflateEnd(FCompressRec); FillChar(FCompressRec, SizeOf(FCompressRec), 0); end; if Assigned(FDecompressRec.zalloc) then begin inflateEnd(FDecompressRec); FillChar(FDecompressRec, SizeOf(FDecompressRec), 0); end; end; destructor TIdCompressionIntercept.Destroy; begin DeinitCompressors; SetLength(FRecvBuf, 0); SetLength(FSendBuf, 0); inherited Destroy; end; procedure TIdCompressionIntercept.Disconnect; begin inherited Disconnect; DeinitCompressors; end; procedure TIdCompressionIntercept.InitCompressors; begin if not Assigned(FCompressRec.zalloc) then begin FCompressRec.zalloc := IdZLibHeaders.zlibAllocMem; FCompressRec.zfree := IdZLibHeaders.zlibFreeMem; if deflateInit_(FCompressRec, FCompressionLevel, zlib_Version, SizeOf(FCompressRec)) <> Z_OK then begin raise EIdCompressorInitFailure.Create(RSZLCompressorInitializeFailure); end; end; if not Assigned(FDecompressRec.zalloc) then begin FDecompressRec.zalloc := IdZLibHeaders.zlibAllocMem; FDecompressRec.zfree := IdZLibHeaders.zlibFreeMem; if inflateInit_(FDecompressRec, zlib_Version, SizeOf(FDecompressRec)) <> Z_OK then begin raise EIdDecompressorInitFailure.Create(RSZLDecompressorInitializeFailure); end; end; end; procedure TIdCompressionIntercept.Receive(var VBuffer: TIdBytes); var LBuffer: TIdBytes; LPos : integer; nChars, C : UInt32; StreamEnd: Boolean; begin // let the next Intercept in the chain decode its data first inherited Receive(VBuffer); SetLength(LBuffer, 2048); if FCompressionLevel in [1..9] then begin InitCompressors; StreamEnd := False; LPos := 0; repeat nChars := IndyMin(Length(VBuffer) - LPos, Length(LBuffer)); if nChars = 0 then begin Break; end; CopyTIdBytes(VBuffer, LPos, LBuffer, 0, nChars); Inc(LPos, nChars); FDecompressRec.next_in := PIdAnsiChar(@LBuffer[0]); FDecompressRec.avail_in := nChars; FDecompressRec.total_in := 0; while FDecompressRec.avail_in > 0 do begin if FRecvCount = FRecvSize then begin if FRecvSize = 0 then begin FRecvSize := 2048; end else begin Inc(FRecvSize, 1024); end; SetLength(FRecvBuf, FRecvSize); end; FDecompressRec.next_out := PIdAnsiChar(@FRecvBuf[FRecvCount]); C := FRecvSize - FRecvCount; FDecompressRec.avail_out := C; FDecompressRec.total_out := 0; case inflate(FDecompressRec, Z_NO_FLUSH) of Z_STREAM_END: StreamEnd := True; Z_STREAM_ERROR, Z_DATA_ERROR, Z_MEM_ERROR: raise EIdDecompressionError.Create(RSZLDecompressionError); end; Inc(FRecvCount, C - FDecompressRec.avail_out); end; until StreamEnd; SetLength(VBuffer, FRecvCount); CopyTIdBytes(FRecvBuf, 0, VBuffer, 0, FRecvCount); FRecvCount := 0; end; end; procedure TIdCompressionIntercept.Send(var VBuffer: TIdBytes); var LBuffer: TIdBytes; LLen, LSize: UInt32; begin LBuffer := nil; if FCompressionLevel in [1..9] then begin InitCompressors; // Make sure the Send buffer is large enough to hold the input data LSize := Length(VBuffer); if LSize > FSendSize then begin if LSize > 2048 then begin FSendSize := LSize + (LSize + 1023) mod 1024; end else begin FSendSize := 2048; end; SetLength(FSendBuf, FSendSize); end; // Get the data from the input and save it off // TODO: get rid of FSendBuf and use ABuffer directly FSendCount := LSize; CopyTIdBytes(VBuffer, 0, FSendBuf, 0, FSendCount); FCompressRec.next_in := PIdAnsiChar(@FSendBuf[0]); FCompressRec.avail_in := FSendCount; FCompressRec.avail_out := 0; // clear the output stream in preparation for compression SetLength(VBuffer, 0); SetLength(LBuffer, 1024); // As long as data is being outputted, keep compressing while FCompressRec.avail_out = 0 do begin FCompressRec.next_out := PIdAnsiChar(@LBuffer[0]); FCompressRec.avail_out := Length(LBuffer); case deflate(FCompressRec, Z_SYNC_FLUSH) of Z_STREAM_ERROR, Z_DATA_ERROR, Z_MEM_ERROR: raise EIdCompressionError.Create(RSZLCompressionError); end; // Place the compressed data into the output stream LLen := Length(VBuffer); SetLength(VBuffer, LLen + UInt32(Length(LBuffer)) - FCompressRec.avail_out); CopyTIdBytes(LBuffer, 0, VBuffer, LLen, UInt32(Length(LBuffer)) - FCompressRec.avail_out); end; end; // let the next Intercept in the chain encode its data next inherited Send(VBuffer); end; procedure TIdCompressionIntercept.SetCompressionLevel(Value: TIdCompressionLevel); begin if Value < 0 then begin Value := 0; end else if Value > 9 then begin Value := 9; end; if Value <> FCompressionLevel then begin DeinitCompressors; FCompressionLevel := Value; end; end; { TIdServerCompressionIntercept } procedure TIdServerCompressionIntercept.Init; begin end; function TIdServerCompressionIntercept.Accept(AConnection: TComponent): TIdConnectionIntercept; begin Result := TIdCompressionIntercept.Create(nil); TIdCompressionIntercept(Result).CompressionLevel := CompressionLevel; end; end.