IdZLib.pas 33 KB

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