IdZLib.pas 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153
  1. (*
  2. Enhanced zlib implementation
  3. Gabriel Corneanu <gabrielcorneanu(AT)yahoo.com>
  4. Base implementation follows the original zlib unit.
  5. Key features:
  6. Using last zlib library (1.2.3).
  7. Removed all imported functions, which are now in zlibpas. This can be used
  8. standalone (as many other projects that need zlib do).
  9. The compression stream can create different type of streams:
  10. zlib, gzip and raw deflate (see constructors).
  11. The decompression stream can read all type of streams (autodetect),
  12. plus that the stream type and gzip info is available for public access.
  13. If the stream is not zlib or gzip, it is assumed raw. An error will
  14. occur during decompressing if the data format is not valid.
  15. The DecompressStream function is using the InflateBack call together
  16. with direct memory access on the source stream
  17. (if available, which means TStringStream or TCustomMemoryStream descendant).
  18. It should be the fastest decompression routine!
  19. The CompressStreamEx function is using direct memory access on both
  20. source and destination stream (if available).
  21. It should be faster than CompressStream.
  22. CompressString or CompressStream can be used to compress a http response
  23. History:
  24. - Aug 2005: Initial release
  25. *)
  26. unit IdZLib;
  27. interface
  28. {$I IdCompilerDefines.inc}
  29. uses
  30. SysUtils,
  31. Classes,
  32. IdCTypes,
  33. IdGlobal,
  34. IdZLibHeaders;
  35. type
  36. // Abstract ancestor class
  37. TCustomZlibStream = class(TIdBaseStream)
  38. protected
  39. FStrm: TStream;
  40. FStrmPos: Integer;
  41. FOnProgress: TNotifyEvent;
  42. FZRec: TZStreamRec;
  43. FBuffer: array [Word] of TIdAnsiChar;
  44. FNameBuffer: array [0..255] of TIdAnsiChar;
  45. FGZHeader : IdZLibHeaders.gz_header;
  46. FStreamType : TZStreamType;
  47. procedure Progress; dynamic;
  48. procedure IdSetSize(ASize: Int64); override;
  49. property OnProgress: TNotifyEvent read FOnProgress write FOnProgress;
  50. public
  51. constructor Create(Strm: TStream);
  52. destructor Destroy; override;
  53. property GZHeader: gz_header read FGZHeader;
  54. end;
  55. TCompressionLevel = (clNone, clFastest, clDefault, clMax);
  56. TCompressionStream = class(TCustomZlibStream)
  57. protected
  58. function GetCompressionRate: Single;
  59. function IdRead(var VBuffer: TIdBytes; AOffset, ACount: Longint): Longint; override;
  60. function IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint; override;
  61. function IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64; override;
  62. public
  63. constructor CreateEx(CompressionLevel: TCompressionLevel; Dest: TStream;
  64. const StreamType: TZStreamType;
  65. const AName: string = ''; ATime: Integer = 0);
  66. constructor Create(CompressionLevel: TCompressionLevel; Dest: TStream; const AIncludeHeaders : Boolean = True);
  67. constructor CreateGZ(CompressionLevel: TCompressionLevel; Dest: TStream;
  68. const AName: string = ''; ATime: Integer = 0); overload;
  69. destructor Destroy; override;
  70. property CompressionRate: Single read GetCompressionRate;
  71. property OnProgress;
  72. end;
  73. TDecompressionStream = class(TCustomZlibStream)
  74. protected
  75. FInitialPos : Int64;
  76. function IdRead(var VBuffer: TIdBytes; AOffset, ACount: Longint): Longint; override;
  77. function IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint; override;
  78. function IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64; override;
  79. public
  80. constructor Create(Source: TStream);
  81. destructor Destroy; override;
  82. procedure InitRead;
  83. function IsGZip: boolean;
  84. property OnProgress;
  85. end;
  86. { CompressBuf compresses data, buffer to buffer, in one call.
  87. In: InBuf = ptr to compressed data
  88. InBytes = number of bytes in InBuf
  89. Out: OutBuf = ptr to newly allocated buffer containing decompressed data
  90. OutBytes = number of bytes in OutBuf }
  91. procedure CompressBuf(const InBuf: Pointer; InBytes: Integer;
  92. out OutBuf: Pointer; out OutBytes: TIdC_UINT);
  93. //generic read header from a buffer
  94. function GetStreamType(InBuffer: Pointer; InCount: TIdC_UINT; gzheader: gz_headerp; out HeaderSize: TIdC_UINT): TZStreamType; overload;
  95. //generic read header from a stream
  96. //the stream position is preserved
  97. function GetStreamType(InStream: TStream; gzheader: gz_headerp; out HeaderSize: TIdC_UINT): TZStreamType; overload;
  98. //Note that unlike other things in this unit, you specify things with number
  99. //values. This is deliberate on my part because some things in Indy rely on
  100. //API's where you specify the ZLib parameter as a number. This is for the
  101. //utmost flexibility. In the FTP server, you can actually specify something
  102. //like a compression level.
  103. //The WinBits parameter is extremely powerful so do not underestimate it.
  104. procedure IndyCompressStream(InStream, OutStream: TStream;
  105. const level: Integer = Z_DEFAULT_COMPRESSION;
  106. const WinBits : Integer = MAX_WBITS;
  107. const MemLevel : Integer = MAX_MEM_LEVEL;
  108. const Stratagy : Integer = Z_DEFAULT_STRATEGY);
  109. //compress stream; tries to use direct memory access on input stream
  110. procedure CompressStream(InStream, OutStream: TStream; level: TCompressionLevel; StreamType : TZStreamType);
  111. //compress stream; tries to use direct memory access on both streams
  112. procedure CompressStreamEx(InStream, OutStream: TStream; level: TCompressionLevel; StreamType : TZStreamType);
  113. //compress a string
  114. function CompressString(const InString: string; level: TCompressionLevel; StreamType : TZStreamType): string;
  115. //this is for where we know what the stream's WindowBits setting should be
  116. //Note that this does have special handling for ZLIB values greater than
  117. //32. I'm trying to treat it as the inflateInit2_ call would. I don't think
  118. //InflateBack uses values greater than 16 so you have to make a workaround.
  119. procedure IndyDecompressStream(InStream, OutStream: TStream;
  120. const AWindowBits : Integer);
  121. //fast decompress stream!
  122. //using direct memory access to source stream (if available) and
  123. //direct write (using inflateBack)
  124. procedure DecompressStream(InStream, OutStream: TStream);
  125. { DecompressBuf decompresses data, buffer to buffer, in one call.
  126. In: InBuf = ptr to compressed data
  127. InBytes = number of bytes in InBuf
  128. OutEstimate = zero, or est. size of the decompressed data
  129. Out: OutBuf = ptr to newly allocated buffer containing decompressed data
  130. OutBytes = number of bytes in OutBuf }
  131. procedure DecompressBuf(const InBuf: Pointer; InBytes: Integer;
  132. OutEstimate: Integer; out OutBuf: Pointer; out OutBytes: Integer);
  133. { DecompressToUserBuf decompresses data, buffer to buffer, in one call.
  134. In: InBuf = ptr to compressed data
  135. InBytes = number of bytes in InBuf
  136. Out: OutBuf = ptr to user-allocated buffer to contain decompressed data
  137. BufSize = number of bytes in OutBuf }
  138. procedure DecompressToUserBuf(const InBuf: Pointer; InBytes: Integer;
  139. const OutBuf: Pointer; BufSize: Integer);
  140. type
  141. EZlibError = class(Exception)
  142. {JPM Additions, we need to be able to provide diagnostic info in an exception}
  143. protected
  144. FErrorCode : Integer;
  145. public
  146. class procedure RaiseException(const AError: Integer);
  147. //
  148. property ErrorCode : Integer read FErrorCode;
  149. end;
  150. ECompressionError = class(EZlibError);
  151. EDecompressionError = class(EZlibError);
  152. //ZLib error functions. They raise an exception for ZLib codes less than zero
  153. function DCheck(code: Integer): Integer;
  154. function CCheck(code: Integer): Integer;
  155. const
  156. //winbit constants
  157. MAX_WBITS = IdZLibHeaders.MAX_WBITS;
  158. {$EXTERNALSYM MAX_WBITS}
  159. GZIP_WINBITS = MAX_WBITS + 16; //GZip format
  160. {$EXTERNALSYM GZIP_WINBITS}
  161. //negative values mean do not add any headers
  162. //adapted from "Enhanced zlib implementation"
  163. //by Gabriel Corneanu <gabrielcorneanu(AT)yahoo.com>
  164. RAW_WBITS = -MAX_WBITS; //raw stream (without any header)
  165. {$EXTERNALSYM RAW_WBITS}
  166. implementation
  167. uses
  168. IdGlobalProtocols, IdStream, IdZLibConst
  169. {$IFDEF HAS_AnsiStrings_StrPLCopy}
  170. , AnsiStrings
  171. {$ENDIF}
  172. ;
  173. const
  174. Levels: array [TCompressionLevel] of Int8 =
  175. (Z_NO_COMPRESSION, Z_BEST_SPEED, Z_DEFAULT_COMPRESSION, Z_BEST_COMPRESSION);
  176. function CCheck(code: Integer): Integer;
  177. {$IFDEF USE_INLINE} inline; {$ENDIF}
  178. begin
  179. Result := code;
  180. if code < 0 then begin
  181. ECompressionError.RaiseException(code);
  182. end;
  183. end;
  184. function DCheck(code: Integer): Integer;
  185. {$IFDEF USE_INLINE} inline; {$ENDIF}
  186. begin
  187. Result := code;
  188. if code < 0 then begin
  189. EDecompressionError.RaiseException(code);
  190. end;
  191. end;
  192. procedure CompressBuf(const InBuf: Pointer; InBytes: Integer;
  193. out OutBuf: Pointer; out OutBytes: TIdC_UINT);
  194. var
  195. strm: z_stream;
  196. P: Pointer;
  197. begin
  198. FillChar(strm, sizeof(strm), 0);
  199. OutBytes := ((InBytes + (InBytes div 10) + 12) + 255) and not 255;
  200. GetMem(OutBuf, OutBytes);
  201. try
  202. strm.next_in := InBuf;
  203. strm.avail_in := InBytes;
  204. strm.next_out := OutBuf;
  205. strm.avail_out := OutBytes;
  206. CCheck(deflateInit(strm, Z_BEST_COMPRESSION));
  207. try
  208. while CCheck(deflate(strm, Z_FINISH)) <> Z_STREAM_END do
  209. begin
  210. P := OutBuf;
  211. Inc(OutBytes, 256);
  212. ReallocMem(OutBuf, OutBytes);
  213. strm.next_out := PIdAnsiChar(PtrUInt(OutBuf) + (PtrUInt(strm.next_out) - PtrUInt(P)));
  214. strm.avail_out := 256;
  215. end;
  216. finally
  217. CCheck(deflateEnd(strm));
  218. end;
  219. ReallocMem(OutBuf, strm.total_out);
  220. OutBytes := strm.total_out;
  221. except
  222. FreeMem(OutBuf);
  223. raise;
  224. end;
  225. end;
  226. function DMAOfStream(AStream: TStream; out Available: TIdC_UINT): Pointer;
  227. {$IFDEF USE_INLINE} inline; {$ENDIF}
  228. begin
  229. if AStream is TCustomMemoryStream then begin
  230. Result := TCustomMemoryStream(AStream).Memory;
  231. end
  232. {$IFDEF STRING_IS_ANSI}
  233. // In D2009, the DataString property was changed to use a getter method
  234. // that returns a temporary string, so it is not a direct access to the
  235. // stream contents anymore. TStringStream was updated to derive from
  236. // TBytesStream now, which is a TCustomMemoryStream descendant, and so
  237. // will be handled above...
  238. else if AStream is TStringStream then begin
  239. Result := Pointer(TStringStream(AStream).DataString);
  240. end
  241. {$ENDIF}
  242. else begin
  243. Result := nil;
  244. end;
  245. if Result <> nil then
  246. begin
  247. //handle integer overflow
  248. {$IFDEF STREAM_SIZE_64}
  249. Available := TIdC_UINT(IndyMin(AStream.Size - AStream.Position, High(TIdC_UINT)));
  250. // TODO: account for a 64-bit position in a 32-bit environment
  251. Inc(PtrUInt(Result), AStream.Position);
  252. {$ELSE}
  253. Available := AStream.Size - AStream.Position;
  254. Inc(PtrUInt(Result), AStream.Position);
  255. {$ENDIF}
  256. end else begin
  257. Available := 0;
  258. end;
  259. end;
  260. function CanResizeDMAStream(AStream: TStream): boolean;
  261. {$IFDEF USE_INLINE} inline; {$ENDIF}
  262. begin
  263. Result := (AStream is TCustomMemoryStream)
  264. {$IFDEF STRING_IS_ANSI}
  265. // In D2009, TStringStream was updated to derive from TBytesStream now,
  266. // which is a TCustomMemoryStream descendant, and so will be handled above...
  267. or (AStream is TStringStream)
  268. {$ENDIF}
  269. ;
  270. end;
  271. ///tries to get the stream info
  272. //strm.next_in and available_in needs enough data!
  273. //strm should not contain an initialized inflate
  274. function TryStreamType(var strm: TZStreamRec; gzheader: PgzHeaderRec; const AWinBitsValue : Integer): boolean;
  275. var
  276. InitBuf: PIdAnsiChar;
  277. InitIn : TIdC_UINT;
  278. begin
  279. InitBuf := strm.next_in;
  280. InitIn := strm.avail_in;
  281. DCheck(inflateInit2_(strm, AWinBitsValue, zlib_version, SizeOf(TZStreamRec)));
  282. if (AWinBitsValue = GZIP_WINBITS) and (gzheader <> nil) then begin
  283. DCheck(inflateGetHeader(strm, gzheader^));
  284. end;
  285. Result := inflate(strm, Z_BLOCK) = Z_OK;
  286. DCheck(inflateEnd(strm));
  287. if Result then begin
  288. Exit;
  289. end;
  290. //rollback
  291. strm.next_in := InitBuf;
  292. strm.avail_in := InitIn;
  293. end;
  294. //tries to get the stream info
  295. //strm.next_in and available_in needs enough data!
  296. //strm should not contain an initialized inflate
  297. function CheckInitInflateStream(var strm: TZStreamRec; gzheader: gz_headerp): TZStreamType; overload;
  298. var
  299. InitBuf: PIdAnsiChar;
  300. InitIn: Integer;
  301. function LocalTryStreamType(AStreamType: TZStreamType): Boolean;
  302. begin
  303. DCheck(inflateInitEx(strm, AStreamType));
  304. if (AStreamType = zsGZip) and (gzheader <> nil) then begin
  305. DCheck(inflateGetHeader(strm, gzheader^));
  306. end;
  307. Result := inflate(strm, Z_BLOCK) = Z_OK;
  308. DCheck(inflateEnd(strm));
  309. if Result then begin
  310. Exit;
  311. end;
  312. //rollback
  313. strm.next_in := InitBuf;
  314. strm.avail_in := InitIn;
  315. end;
  316. begin
  317. if strm.next_out = nil then begin
  318. //needed for reading, but not used
  319. strm.next_out := strm.next_in;
  320. end;
  321. InitBuf := strm.next_in;
  322. InitIn := strm.avail_in;
  323. for Result := zsZLib to zsGZip do
  324. begin
  325. if LocalTryStreamType(Result) then begin
  326. Exit;
  327. end;
  328. end;
  329. Result := zsRaw;
  330. end;
  331. function GetStreamType(InBuffer: Pointer; InCount: TIdC_UINT; gzheader: gz_headerp;
  332. out HeaderSize: TIdC_UINT): TZStreamType;
  333. var
  334. strm : TZStreamRec;
  335. begin
  336. FillChar(strm, SizeOf(strm), 0);
  337. strm.next_in := InBuffer;
  338. strm.avail_in := InCount;
  339. Result := CheckInitInflateStream(strm, gzheader);
  340. HeaderSize := InCount - strm.avail_in;
  341. end;
  342. function GetStreamType(InStream: TStream; gzheader: gz_headerp;
  343. out HeaderSize: TIdC_UINT): TZStreamType;
  344. const
  345. StepSize = 20; //one step be enough, but who knows...
  346. var
  347. N : TIdC_UINT;
  348. Buff : PIdAnsiChar;
  349. UseBuffer: Boolean;
  350. begin
  351. Buff := DMAOfStream(InStream, N);
  352. UseBuffer := Buff = nil;
  353. if UseBuffer then begin
  354. GetMem(Buff, StepSize);
  355. end;
  356. try
  357. repeat
  358. if UseBuffer then begin
  359. Inc(N, InStream.Read(Buff[N], StepSize));
  360. end;
  361. Result := GetStreamType(Buff, N, gzheader, HeaderSize);
  362. //do we need more data?
  363. //N mod StepSize <> 0 means no more data available
  364. if (HeaderSize < N) or (not UseBuffer) or ((N mod StepSize) <> 0) then begin
  365. Break;
  366. end;
  367. ReallocMem(Buff, N + StepSize);
  368. until False;
  369. finally
  370. if UseBuffer then
  371. begin
  372. try
  373. TIdStreamHelper.Seek(InStream, -N, soCurrent);
  374. finally
  375. FreeMem(Buff);
  376. end;
  377. end;
  378. end;
  379. end;
  380. const
  381. WindowSize = 1 shl MAX_WBITS;
  382. type
  383. PZBack = ^TZBack;
  384. TZBack = record
  385. InStream : TStream;
  386. OutStream : TStream;
  387. InMem : PIdAnsiChar; //direct memory access
  388. InMemSize : TIdC_UINT;
  389. ReadBuf : array[Word] of TIdAnsiChar;
  390. Window : array[0..WindowSize] of TIdAnsiChar;
  391. end;
  392. function Strm_in_func(opaque: Pointer; var buf: PByte): TIdC_UNSIGNED; cdecl;
  393. var
  394. S : TStream;
  395. BackObj : PZBack;
  396. begin
  397. BackObj := PZBack( opaque );
  398. S := BackObj.InStream; //help optimizations
  399. if BackObj.InMem <> nil then
  400. begin
  401. //direct memory access if available!
  402. buf := Pointer(BackObj.InMem);
  403. //handle integer overflow
  404. {$IFDEF STREAM_SIZE_64}
  405. Result := TIdC_UNSIGNED(IndyMin(S.Size - S.Position, High(TIdC_UNSIGNED)));
  406. {$ELSE}
  407. Result := S.Size - S.Position;
  408. {$ENDIF}
  409. TIdStreamHelper.Seek(S, Result, soCurrent);
  410. end else
  411. begin
  412. buf := PByte(@BackObj.ReadBuf);
  413. Result := S.Read(buf^, SizeOf(BackObj.ReadBuf));
  414. end;
  415. end;
  416. function Strm_out_func(opaque: Pointer; buf: PByte; size: TIdC_UNSIGNED): TIdC_INT; cdecl;
  417. begin
  418. Result := TIdC_INT(PZBack(opaque).OutStream.Write(buf^, size) - TIdC_SIGNED(size));
  419. end;
  420. procedure DecompressStream(InStream, OutStream: TStream);
  421. var
  422. strm : z_stream;
  423. BackObj: PZBack;
  424. begin
  425. FillChar(strm, sizeof(strm), 0);
  426. GetMem(BackObj, SizeOf(TZBack));
  427. try
  428. //Darcy
  429. FillChar(BackObj^, sizeof(TZBack), 0);
  430. //direct memory access if possible!
  431. BackObj.InMem := DMAOfStream(InStream, BackObj.InMemSize);
  432. BackObj.InStream := InStream;
  433. BackObj.OutStream := OutStream;
  434. //use our own function for reading
  435. strm.avail_in := Strm_in_func(BackObj, PByte(strm.next_in));
  436. strm.next_out := PIdAnsiChar(@BackObj.Window[0]);
  437. strm.avail_out := 0;
  438. CheckInitInflateStream(strm, nil);
  439. strm.next_out := nil;
  440. strm.avail_out := 0;
  441. DCheck(inflateBackInit(strm, MAX_WBITS, @BackObj.Window[0]));
  442. try
  443. DCheck(inflateBack(strm, Strm_in_func, BackObj, Strm_out_func, BackObj));
  444. // DCheck(inflateBack(strm, @Strm_in_func, BackObj, @Strm_out_func, BackObj));
  445. //seek back when unused data
  446. TIdStreamHelper.Seek(InStream, -strm.avail_in, soCurrent);
  447. //now trailer can be checked
  448. finally
  449. DCheck(inflateBackEnd(strm));
  450. end;
  451. finally
  452. FreeMem(BackObj);
  453. end;
  454. end;
  455. procedure IndyDecompressStream(InStream, OutStream: TStream;
  456. const AWindowBits : Integer);
  457. var
  458. strm : TZStreamRec;
  459. BackObj: PZBack;
  460. LWindowBits : Integer;
  461. begin
  462. LWindowBits := AWindowBits;
  463. FillChar(strm, sizeof(strm), 0);
  464. GetMem(BackObj, SizeOf(TZBack));
  465. try
  466. //direct memory access if possible!
  467. BackObj.InMem := DMAOfStream(InStream, BackObj.InMemSize);
  468. BackObj.InStream := InStream;
  469. BackObj.OutStream := OutStream;
  470. //use our own function for reading
  471. strm.avail_in := Strm_in_func(BackObj, PByte(strm.next_in));
  472. strm.next_out := PIdAnsiChar(@BackObj.Window[0]);
  473. strm.avail_out := 0;
  474. //note that you can not use a WinBits parameter greater than 32 with
  475. //InflateBackInit. That was used in the inflate functions
  476. //for automatic detection of header bytes and trailer bytes.
  477. //Se lets try this ugly workaround for it.
  478. if AWindowBits > 32 then
  479. begin
  480. LWindowBits := Abs(AWindowBits - 32);
  481. if not TryStreamType(strm, nil, LWindowBits) then
  482. begin
  483. if TryStreamType(strm, nil, LWindowBits + 16) then
  484. begin
  485. Inc(LWindowBits, 16);
  486. end else
  487. begin
  488. TryStreamType(strm, nil, -LWindowBits);
  489. end;
  490. end;
  491. end;
  492. strm.next_out := nil;
  493. strm.avail_out := 0;
  494. DCheck(inflateBackInit_(strm,LWindowBits, @BackObj.Window[0],
  495. zlib_version, SizeOf(TZStreamRec)));
  496. try
  497. DCheck(inflateBack(strm, Strm_in_func, BackObj, Strm_out_func, BackObj));
  498. //seek back when unused data
  499. TIdStreamHelper.Seek(InStream, -strm.avail_in, soCurrent);
  500. //now trailer can be checked
  501. finally
  502. DCheck(inflateBackEnd(strm));
  503. end;
  504. finally
  505. FreeMem(BackObj);
  506. end;
  507. end;
  508. type
  509. TMemStreamAccess = class(TMemoryStream);
  510. function ExpandStream(AStream: TStream; const ACapacity : TIdStreamSize): Boolean;
  511. {$IFDEF USE_INLINE} inline; {$ENDIF}
  512. begin
  513. Result := True;
  514. AStream.Size := ACapacity;
  515. if AStream is TMemoryStream then begin
  516. {$I IdObjectChecksOff.inc}
  517. AStream.Size := TMemStreamAccess(AStream).Capacity;
  518. {$I IdObjectChecksOn.inc}
  519. end;
  520. end;
  521. procedure DoCompressStream(var strm: z_stream; InStream, OutStream: TStream; UseDirectOut: boolean);
  522. const
  523. //64 KB buffer
  524. BufSize = 65536;
  525. var
  526. InBuf, OutBuf : array of TIdAnsiChar;
  527. pLastOutBuf : PIdAnsiChar;
  528. UseInBuf, UseOutBuf : boolean;
  529. LastOutCount : TIdC_UINT;
  530. procedure WriteOut;
  531. var
  532. NumWritten : TIdC_UINT;
  533. begin
  534. if (LastOutCount > 0) and (strm.avail_out < LastOutCount) then begin
  535. NumWritten := LastOutCount - strm.avail_out;
  536. if UseOutBuf then begin
  537. OutStream.Write(pLastOutBuf^, NumWritten);
  538. end else begin
  539. TIdStreamHelper.Seek(OutStream, NumWritten, soCurrent);
  540. end;
  541. end;
  542. end;
  543. procedure NextOut;
  544. begin
  545. if UseOutBuf then
  546. begin
  547. strm.next_out := PIdAnsiChar(OutBuf);
  548. strm.avail_out := Length(OutBuf);
  549. end else
  550. begin
  551. ExpandStream(OutStream, OutStream.Size + BufSize);
  552. strm.next_out := DMAOfStream(OutStream, strm.avail_out);
  553. //because we can't really know how much resize is increasing!
  554. end;
  555. end;
  556. procedure ExpandOut;
  557. begin
  558. if UseOutBuf then begin
  559. SetLength(OutBuf, Length(OutBuf) + BufSize);
  560. end;
  561. NextOut;
  562. end;
  563. function DeflateOut(FlushFlag: TIdC_INT): TIdC_INT;
  564. begin
  565. if strm.avail_out = 0 then begin
  566. NextOut;
  567. end;
  568. repeat
  569. pLastOutBuf := strm.next_out;
  570. LastOutCount := strm.avail_out;
  571. Result := deflate(strm, FlushFlag);
  572. if Result <> Z_BUF_ERROR then begin
  573. Break;
  574. end;
  575. ExpandOut;
  576. until False;
  577. CCheck(Result);
  578. WriteOut;
  579. end;
  580. begin
  581. pLastOutBuf := nil;
  582. LastOutCount := 0;
  583. strm.next_in := DMAOfStream(InStream, strm.avail_in);
  584. UseInBuf := strm.next_in = nil;
  585. if UseInBuf then begin
  586. SetLength(InBuf, BufSize);
  587. end;
  588. UseOutBuf := not (UseDirectOut and CanResizeDMAStream(OutStream));
  589. if UseOutBuf then begin
  590. SetLength(OutBuf, BufSize);
  591. end;
  592. { From the zlib manual at http://www.zlib.net/manual.html
  593. deflate() returns Z_OK if some progress has been made (more input processed
  594. or more output produced), Z_STREAM_END if all input has been consumed and all
  595. output has been produced (only when flush is set to Z_FINISH), Z_STREAM_ERROR
  596. if the stream state was inconsistent (for example if next_in or next_out was
  597. NULL), Z_BUF_ERROR if no progress is possible (for example avail_in or avail_out
  598. was zero). Note that Z_BUF_ERROR is not fatal, and deflate() can be called again
  599. with more input and more output space to continue compressing.
  600. }
  601. { From the ZLIB FAQ at http://www.gzip.org/zlib/FAQ.txt
  602. 5. deflate() or inflate() returns Z_BUF_ERROR
  603. Before making the call, make sure that avail_in and avail_out are not
  604. zero. When setting the parameter flush equal to Z_FINISH, also make sure
  605. that avail_out is big enough to allow processing all pending input.
  606. Note that a Z_BUF_ERROR is not fatal--another call to deflate() or
  607. inflate() can be made with more input or output space. A Z_BUF_ERROR
  608. may in fact be unavoidable depending on how the functions are used, since
  609. it is not possible to tell whether or not there is more output pending
  610. when strm.avail_out returns with zero.
  611. }
  612. repeat
  613. if strm.avail_in = 0 then
  614. begin
  615. if UseInBuf then
  616. begin
  617. strm.next_in := PIdAnsiChar(InBuf);
  618. strm.avail_in := InStream.Read(strm.next_in^, Length(InBuf));
  619. // TODO: if Read() returns < 0, raise an exception
  620. end;
  621. if strm.avail_in = 0 then begin
  622. Break;
  623. end;
  624. end;
  625. DeflateOut(Z_NO_FLUSH);
  626. until False;
  627. repeat until DeflateOut(Z_FINISH) = Z_STREAM_END;
  628. if not UseOutBuf then
  629. begin
  630. //truncate when using direct output
  631. OutStream.Size := OutStream.Position;
  632. end;
  633. if not UseInBuf then begin
  634. //adjust position of direct input
  635. TIdStreamHelper.Seek(InStream, strm.total_in, soCurrent);
  636. end;
  637. end;
  638. procedure IndyCompressStream(InStream, OutStream: TStream;
  639. const level: Integer = Z_DEFAULT_COMPRESSION;
  640. const WinBits : Integer = MAX_WBITS;
  641. const MemLevel : Integer = MAX_MEM_LEVEL;
  642. const Stratagy : Integer = Z_DEFAULT_STRATEGY);
  643. var
  644. strm : z_stream;
  645. begin
  646. FillChar(strm, SizeOf(strm), 0);
  647. CCheck(deflateInit2_(strm, level, Z_DEFLATED, WinBits, MemLevel, Stratagy, zlib_version, SizeOf(TZStreamRec)));
  648. try
  649. DoCompressStream(strm, InStream, OutStream, True);
  650. finally
  651. CCheck(deflateEnd(strm));
  652. end;
  653. end;
  654. procedure CompressStream(InStream, OutStream: TStream; level: TCompressionLevel; StreamType : TZStreamType);
  655. var
  656. strm : z_stream;
  657. begin
  658. FillChar(strm, SizeOf(strm), 0);
  659. CCheck(deflateInitEx(strm, Levels[level], StreamType));
  660. try
  661. DoCompressStream(strm, InStream, OutStream, False);
  662. finally
  663. CCheck(deflateEnd(strm));
  664. end;
  665. end;
  666. procedure CompressStreamEx(InStream, OutStream: TStream; level: TCompressionLevel; StreamType : TZStreamType);
  667. var
  668. strm : z_stream;
  669. begin
  670. FillChar(strm, SizeOf(strm), 0);
  671. CCheck(deflateInitEx(strm, Levels[level], StreamType));
  672. try
  673. DoCompressStream(strm, InStream, OutStream, True);
  674. finally
  675. CCheck(deflateEnd(strm));
  676. end;
  677. end;
  678. function CompressString(const InString: string; level: TCompressionLevel; StreamType : TZStreamType): string;
  679. var
  680. S, D : TStringStream;
  681. begin
  682. S := TStringStream.Create(InString);
  683. try
  684. D := TStringStream.Create('');
  685. try
  686. CompressStream(S, D, level, StreamType);
  687. Result := D.DataString;
  688. finally
  689. D.Free;
  690. end;
  691. finally
  692. S.Free;
  693. end;
  694. end;
  695. procedure DecompressBuf(const InBuf: Pointer; InBytes: Integer;
  696. OutEstimate: Integer; out OutBuf: Pointer; out OutBytes: Integer);
  697. var
  698. strm: z_stream;
  699. P: Pointer;
  700. BufInc: Integer;
  701. begin
  702. FillChar(strm, SizeOf(strm), 0);
  703. BufInc := (InBytes + 255) and not 255;
  704. if OutEstimate = 0 then begin
  705. OutBytes := BufInc;
  706. end else begin
  707. OutBytes := OutEstimate;
  708. end;
  709. GetMem(OutBuf, OutBytes);
  710. try
  711. strm.next_in := InBuf;
  712. strm.avail_in := InBytes;
  713. strm.next_out := OutBuf;
  714. strm.avail_out := OutBytes;
  715. DCheck(inflateInit(strm));
  716. try
  717. while DCheck(inflate(strm, Z_NO_FLUSH)) <> Z_STREAM_END do
  718. begin
  719. P := OutBuf;
  720. Inc(OutBytes, BufInc);
  721. ReallocMem(OutBuf, OutBytes);
  722. strm.next_out := PIdAnsiChar(PtrUInt(OutBuf) + (PtrUInt(strm.next_out) - PtrUInt(P)));
  723. strm.avail_out := BufInc;
  724. end;
  725. finally
  726. DCheck(inflateEnd(strm));
  727. end;
  728. ReallocMem(OutBuf, strm.total_out);
  729. OutBytes := strm.total_out;
  730. except
  731. FreeMem(OutBuf);
  732. raise;
  733. end;
  734. end;
  735. procedure DecompressToUserBuf(const InBuf: Pointer; InBytes: Integer;
  736. const OutBuf: Pointer; BufSize: Integer);
  737. var
  738. strm: z_stream;
  739. begin
  740. FillChar(strm, SizeOf(strm), 0);
  741. strm.next_in := InBuf;
  742. strm.avail_in := InBytes;
  743. strm.next_out := OutBuf;
  744. strm.avail_out := BufSize;
  745. DCheck(inflateInit(strm));
  746. try
  747. if DCheck(inflate(strm, Z_FINISH)) <> Z_STREAM_END then begin
  748. raise EZlibError.Create(sTargetBufferTooSmall);
  749. end;
  750. finally
  751. DCheck(inflateEnd(strm));
  752. end;
  753. end;
  754. { EZlibError }
  755. class procedure EZlibError.RaiseException(const AError: Integer);
  756. var
  757. LException: EZlibError;
  758. begin
  759. LException := CreateFmt(sZLibError, [AError]);
  760. LException.FErrorCode := AError;
  761. raise LException;
  762. end;
  763. // TCustomZlibStream
  764. constructor TCustomZLibStream.Create(Strm: TStream);
  765. begin
  766. inherited Create;
  767. FStrm := Strm;
  768. FStrmPos := Strm.Position;
  769. fillchar(FZRec, SizeOf(FZRec), 0);
  770. FZRec.next_out := @FBuffer[0];
  771. FZRec.avail_out := 0;
  772. FZRec.next_in := @FBuffer[0];
  773. FZRec.avail_in := 0;
  774. fillchar(FGZHeader, SizeOf(FGZHeader), 0);
  775. FStreamType := zsZLib;
  776. FGZHeader.name := @FNameBuffer[0];
  777. FGZHeader.name_max := SizeOf(FNameBuffer);
  778. end;
  779. destructor TCustomZlibStream.Destroy;
  780. begin
  781. inherited Destroy;
  782. end;
  783. procedure TCustomZLibStream.Progress;
  784. begin
  785. if Assigned(FOnProgress) then begin
  786. FOnProgress(Self);
  787. end;
  788. end;
  789. procedure TCustomZLibStream.IdSetSize(ASize: Int64);
  790. begin
  791. // do nothing here. IdSetSize is abstract, so it has
  792. // to be overriden, but we don't actually use it here
  793. end;
  794. // TCompressionStream
  795. constructor TCompressionStream.CreateEx(CompressionLevel: TCompressionLevel;
  796. Dest: TStream; const StreamType: TZStreamType;
  797. const AName: string = ''; ATime: Integer = 0);
  798. {$IFDEF USE_MARSHALLED_PTRS}
  799. type
  800. TBytesPtr = ^TBytes;
  801. {$ENDIF}
  802. var
  803. LBytes: TIdBytes;
  804. {$IFDEF HAS_AnsiString}
  805. LName: AnsiString;
  806. {$ENDIF}
  807. begin
  808. inherited Create(Dest);
  809. LBytes := nil; // keep the compiler happy
  810. FZRec.next_out := @FBuffer[0];
  811. FZRec.avail_out := SizeOf(FBuffer);
  812. FStreamType := StreamType;
  813. CCheck(deflateInitEx(FZRec, Levels[CompressionLevel], StreamType));
  814. if StreamType = zsGZip then
  815. begin
  816. FGZHeader.time := ATime;
  817. //zero-terminated file name
  818. //RFC 1952
  819. // The name must consist of ISO
  820. //8859-1 (LATIN-1) characters; on operating systems using
  821. //EBCDIC or any other character set for file names, the name
  822. //must be translated to the ISO LATIN-1 character set.
  823. // Rebeau 2/20/09: Indy's 8-bit encoding class currently uses ISO-8859-1
  824. // so we could technically use that, but since the RFC is very specific
  825. // about the charset, we'll force it here in case Indy's 8-bit encoding
  826. // class is changed later on...
  827. LBytes := CharsetToEncoding('ISO-8859-1').GetBytes(AName);
  828. {$IFDEF USE_MARSHALLED_PTRS}
  829. // TODO: optimize this
  830. FillChar(FGZHeader.name^, FGZHeader.name_max, 0);
  831. TMarshal.Copy(TBytesPtr(@LBytes)^, 0, TPtrWrapper.Create(FGZHeader.name), IndyMin(Length(LBytes), FGZHeader.name_max));
  832. {$ELSE}
  833. // TODO: use Move() instead...
  834. SetString(LName, PAnsiChar(LBytes), Length(LBytes));
  835. {$IFDEF HAS_AnsiStrings_StrPLCopy}AnsiStrings.{$ENDIF}StrPLCopy(FGZHeader.name, LName, FGZHeader.name_max);
  836. {$ENDIF}
  837. deflateSetHeader(FZRec, FGZHeader);
  838. end;
  839. end;
  840. constructor TCompressionStream.Create(CompressionLevel: TCompressionLevel;
  841. Dest: TStream; const AIncludeHeaders : Boolean = True);
  842. begin
  843. if AIncludeHeaders then begin
  844. CreateEx(CompressionLevel, Dest, zsZLib);
  845. end else begin
  846. CreateEx(CompressionLevel, Dest, zsRaw);
  847. end;
  848. end;
  849. constructor TCompressionStream.CreateGZ(CompressionLevel: TCompressionLevel;
  850. Dest: TStream; const AName: string; ATime: Integer);
  851. begin
  852. CreateEx(CompressionLevel, Dest, zsGZip, AName, ATime);
  853. end;
  854. destructor TCompressionStream.Destroy;
  855. begin
  856. FZRec.next_in := nil;
  857. FZRec.avail_in := 0;
  858. try
  859. if FStrm.Position <> FStrmPos then begin
  860. FStrm.Position := FStrmPos;
  861. end;
  862. while (CCheck(deflate(FZRec, Z_FINISH)) <> Z_STREAM_END) and (FZRec.avail_out = 0) do
  863. begin
  864. FStrm.WriteBuffer(FBuffer[0], SizeOf(FBuffer));
  865. FZRec.next_out := @FBuffer[0];
  866. FZRec.avail_out := SizeOf(FBuffer);
  867. end;
  868. if FZRec.avail_out < SizeOf(FBuffer) then begin
  869. FStrm.WriteBuffer(FBuffer, SizeOf(FBuffer) - FZRec.avail_out);
  870. end;
  871. finally
  872. deflateEnd(FZRec);
  873. end;
  874. inherited Destroy;
  875. end;
  876. function TCompressionStream.IdRead(var VBuffer: TIdBytes; AOffset, ACount: Longint): Longint;
  877. begin
  878. raise ECompressionError.Create(sInvalidStreamOp);
  879. end;
  880. function TCompressionStream.IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint;
  881. begin
  882. FZRec.next_in := PIdAnsiChar(@ABuffer[AOffset]);
  883. FZRec.avail_in := ACount;
  884. if FStrm.Position <> FStrmPos then begin
  885. FStrm.Position := FStrmPos;
  886. end;
  887. while FZRec.avail_in > 0 do
  888. begin
  889. CCheck(deflate(FZRec, 0));
  890. if FZRec.avail_out = 0 then
  891. begin
  892. FStrm.WriteBuffer(FBuffer[0], SizeOf(FBuffer));
  893. FZRec.next_out := @FBuffer[0];
  894. FZRec.avail_out := SizeOf(FBuffer);
  895. FStrmPos := FStrm.Position;
  896. Progress;
  897. end;
  898. end;
  899. Result := ACount;
  900. end;
  901. function TCompressionStream.IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64;
  902. begin
  903. if (AOffset = 0) and (AOrigin = soCurrent) then begin
  904. Result := FZRec.total_in;
  905. end else begin
  906. raise ECompressionError.Create(sInvalidStreamOp);
  907. end;
  908. end;
  909. function TCompressionStream.GetCompressionRate: Single;
  910. begin
  911. if FZRec.total_in = 0 then begin
  912. Result := 0;
  913. end else begin
  914. Result := (1.0 - (FZRec.total_out / FZRec.total_in)) * 100.0;
  915. end;
  916. end;
  917. // TDecompressionStream
  918. constructor TDecompressionStream.Create(Source: TStream);
  919. begin
  920. inherited Create(Source);
  921. FInitialPos := FStrmPos;
  922. FStreamType := zsRaw; //unknown
  923. InitRead;
  924. end;
  925. destructor TDecompressionStream.Destroy;
  926. begin
  927. TIdStreamHelper.Seek(FStrm, -FZRec.avail_in, soCurrent);
  928. inflateEnd(FZRec);
  929. inherited Destroy;
  930. end;
  931. procedure TDecompressionStream.InitRead;
  932. var
  933. N, S : TIdC_UINT;
  934. begin
  935. //never call this after starting!
  936. if FZRec.total_in > 0 then begin
  937. Exit;
  938. end;
  939. N := FStrm.Read(FBuffer, SizeOf(FBuffer));
  940. //64k should always be enough
  941. FStreamType := GetStreamType(@FBuffer, N, @FGZHeader, S);
  942. if (S = N) or (FStreamType = zsGZip) and (FGZHeader.done = 0) then
  943. //need more data???
  944. //theoretically it can happen with a veeeeery long gzip name or comment
  945. //this is more generic, but some extra steps
  946. begin
  947. TIdStreamHelper.Seek(FStrm, -N, soCurrent);
  948. FStreamType := GetStreamType(FStrm, @FGZHeader, S);
  949. end;
  950. //open
  951. FZRec.next_in := @FBuffer[0];
  952. FZRec.avail_in := N;
  953. DCheck(inflateInitEx(FZRec, FStreamType));
  954. end;
  955. function TDecompressionStream.IdRead(var VBuffer: TIdBytes; AOffset,
  956. ACount: Longint): Longint;
  957. begin
  958. FZRec.next_out := PIdAnsiChar(@VBuffer[AOffset]);
  959. FZRec.avail_out := ACount;
  960. if FStrm.Position <> FStrmPos then begin
  961. FStrm.Position := FStrmPos;
  962. end;
  963. while FZRec.avail_out > 0 do
  964. begin
  965. if FZRec.avail_in = 0 then
  966. begin
  967. //init read if necessary
  968. //if FZRec.total_in = 0 then InitRead;
  969. FZRec.avail_in := FStrm.Read(FBuffer[0], SizeOf(FBuffer));
  970. if FZRec.avail_in = 0 then begin
  971. Break;
  972. end;
  973. FZRec.next_in := @FBuffer[0];
  974. FStrmPos := FStrm.Position;
  975. Progress;
  976. end;
  977. if CCheck(inflate(FZRec, 0)) = Z_STREAM_END then begin
  978. Break;
  979. end;
  980. end;
  981. Result := TIdC_UINT(ACount) - FZRec.avail_out;
  982. end;
  983. function TDecompressionStream.IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint;
  984. begin
  985. raise EDecompressionError.Create(sInvalidStreamOp);
  986. end;
  987. function TDecompressionStream.IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64;
  988. var
  989. I: Integer;
  990. Buf: array [0..4095] of TIdAnsiChar;
  991. LOffset : Int64;
  992. begin
  993. if (AOffset = 0) and (AOrigin = soBeginning) then
  994. begin
  995. DCheck(inflateReset(FZRec));
  996. FZRec.next_in := @FBuffer[0];
  997. FZRec.avail_in := 0;
  998. FStrm.Position := FInitialPos;
  999. FStrmPos := FInitialPos;
  1000. end
  1001. else if ((AOffset >= 0) and (AOrigin = soCurrent)) or
  1002. (((TIdC_UINT(AOffset) - FZRec.total_out) > 0) and (AOrigin = soBeginning)) then
  1003. begin
  1004. LOffset := AOffset;
  1005. if AOrigin = soBeginning then begin
  1006. Dec(LOffset, FZRec.total_out);
  1007. end;
  1008. if LOffset > 0 then
  1009. begin
  1010. for I := 1 to LOffset div sizeof(Buf) do begin
  1011. ReadBuffer(Buf, sizeof(Buf));
  1012. end;
  1013. ReadBuffer(Buf, LOffset mod sizeof(Buf));
  1014. end;
  1015. end else
  1016. begin
  1017. // raise EDecompressionError.CreateRes(@sInvalidStreamOp);
  1018. raise EDecompressionError.Create(sInvalidStreamOp);
  1019. end;
  1020. Result := FZRec.total_out;
  1021. end;
  1022. function TDecompressionStream.IsGZip: boolean;
  1023. begin
  1024. Result := (FStreamType = zsGZip) and (FGZHeader.done = 1);
  1025. end;
  1026. end.