1 /******************************************************************************* 2 3 Simple zlib / gzip stream decompressor. 4 5 Decompresses a stream of data which is received in one or more chunks. The 6 decompressed data is passed to a provided delegate. 7 8 Needs linking with -lz. 9 10 Usage example: 11 12 --- 13 14 import ocean.io.compress.ZlibStream; 15 16 auto decompress = new ZlibStreamDecompressor; 17 18 ubyte[] decompressed_data; 19 20 decompress.start(ZlibStreamDecompressor.Encoding.Zlib); 21 22 // Hypothetical function which receives chunks of compressed data. 23 receiveData( 24 ( ubyte[] compressed_chunk ) 25 { 26 decompress.decodeChunk(compressed_chunk, 27 ( ubyte[] decompressed_chunk ) 28 { 29 decompressed_data ~= decompressed_chunk; 30 }); 31 }); 32 33 decompress.end(); 34 35 --- 36 37 Copyright: 38 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 39 All rights reserved. 40 41 License: 42 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 43 Alternatively, this file may be distributed under the terms of the Tango 44 3-Clause BSD License (see LICENSE_BSD.txt for details). 45 46 Bear in mind this module provides bindings to an external library that 47 has its own license, which might be more restrictive. Please check the 48 external library license to see which conditions apply for linking. 49 50 *******************************************************************************/ 51 52 module ocean.io.compress.ZlibStream; 53 54 55 56 57 import ocean.core.Verify; 58 59 import ocean.util.compress.c.zlib; 60 61 import ocean.io.stream.Zlib_internal : ZlibException, ZlibInput; 62 63 import ocean.core.TypeConvert; 64 65 66 /******************************************************************************* 67 68 Simple zlib stream decompressor. 69 70 *******************************************************************************/ 71 72 class ZlibStreamDecompressor 73 { 74 /*************************************************************************** 75 76 Alias for encoding enum, to avoid public import. 77 78 The enum has the following values: 79 Guess (guesses data encoding type, but cannot guess unencoded) 80 Zlib 81 Gzip 82 None 83 84 ***************************************************************************/ 85 86 public alias ZlibInput.Encoding Encoding; 87 88 89 /*************************************************************************** 90 91 zlib stream object. C-allocated. 92 93 ***************************************************************************/ 94 95 private z_stream stream; 96 97 98 /*************************************************************************** 99 100 Flag telling whether the z_stream has been initialised. 101 102 ***************************************************************************/ 103 104 private bool stream_valid; 105 106 107 /*************************************************************************** 108 109 The status of the zlib stream object 110 111 ***************************************************************************/ 112 113 private int stream_status; 114 115 116 /*************************************************************************** 117 118 Destructor. Makes sure the C-allocated stream is destroyed. 119 120 ***************************************************************************/ 121 122 ~this ( ) 123 { 124 this.killStream(); 125 } 126 127 128 /*************************************************************************** 129 130 Starts decompression of a stream. 131 132 Params: 133 encoding = encoding type of data in stream 134 135 ***************************************************************************/ 136 137 public void start ( Encoding encoding = Encoding.Guess ) 138 { 139 this.killStream(); 140 141 // Setup correct window bits for specified encoding. 142 // (See zlib.h for a description of how window bits work.) 143 static immutable WINDOWBITS_DEFAULT = 15; 144 int windowBits = WINDOWBITS_DEFAULT; 145 146 switch ( encoding ) 147 { 148 case Encoding.Zlib: 149 // no-op 150 break; 151 152 case Encoding.Gzip: 153 windowBits += 16; 154 break; 155 156 case Encoding.Guess: 157 windowBits += 32; 158 break; 159 160 case Encoding.None: 161 windowBits *= -1; 162 break; 163 164 default: 165 assert (false); 166 } 167 168 // Initialise stream settings 169 this.stream.zalloc = null; 170 this.stream.zfree = null; 171 this.stream.opaque = null; 172 this.stream.avail_in = 0; 173 this.stream.next_in = null; 174 175 // Allocate inflate state 176 this.stream_status = inflateInit2(&this.stream, windowBits); 177 178 if ( this.stream_status != Z_OK ) 179 { 180 // TODO: reusable exception instance 181 throw new ZlibException(this.stream_status); 182 } 183 184 this.stream_valid = true; 185 } 186 187 188 /*************************************************************************** 189 190 Ends decompression of a stream. Releases the C-allocated resources. 191 192 Returns: 193 true if decompression completed normally, false if the stream 194 was incomplete 195 196 ***************************************************************************/ 197 198 public bool end ( ) 199 { 200 this.killStream(); 201 202 return this.stream_status == Z_STREAM_END; 203 } 204 205 206 /*************************************************************************** 207 208 Decodes a chunk of data from the stream and passes the resulting 209 decompressed data chunks to the provided output delegate. A single input 210 chunk may invoke the output delegate several times. 211 212 Params: 213 compressed_chunk = chunk of compressed data from stream 214 output_dg = delegate to receive decompressed data chunks 215 216 ***************************************************************************/ 217 218 public void decodeChunk ( ubyte[] compressed_chunk, 219 scope void delegate ( ubyte[] decompressed_chunk ) output_dg ) 220 { 221 verify(this.stream_valid, typeof(this).stringof ~ ".decodeChunk: stream not started"); 222 223 ubyte[1024] buffer; // stack buffer for decoding 224 225 // Set stream input chunk. 226 this.stream.avail_in = castFrom!(size_t).to!(uint)(compressed_chunk.length); 227 this.stream.next_in = compressed_chunk.ptr; 228 229 do 230 { 231 if ( this.stream_status == Z_STREAM_END ) 232 { 233 // Z_STREAM_END is not the same as EOF. 234 // Inside a concatenated gzip file, it may be followed by 235 // additional compressed data. 236 237 inflateReset(&this.stream); 238 } 239 do 240 { 241 // Set stream output chunk. 242 this.stream.avail_out = buffer.length; 243 this.stream.next_out = buffer.ptr; 244 245 // Decompress. 246 this.stream_status = inflate(&this.stream, Z_NO_FLUSH); 247 248 // Z_BUF_ERROR is not an error, it indicates that no progress 249 // can be made until more input data is provided. It exists 250 // to distinguish a special case: when the previous call to 251 // inflate() consumed all the input, but coincidentally 252 // happened to completely fill the output buffer, the next 253 // call to inflate() will return Z_BUF_ERROR because no more 254 // data is available. 255 256 if ( this.stream_status != Z_OK 257 && this.stream_status != Z_STREAM_END 258 && this.stream_status != Z_BUF_ERROR ) 259 { 260 // Handle errors. 261 262 this.killStream(); 263 264 // TODO: reusable exception instance 265 throw new ZlibException(this.stream_status); 266 } 267 268 // Pass decompressed data chunk to output delegate. 269 270 auto filled_len = buffer.length - this.stream.avail_out; 271 272 if ( filled_len > 0 ) 273 { 274 output_dg(buffer[0 .. filled_len]); 275 } 276 } 277 while ( this.stream.avail_out == 0 ); 278 } 279 while ( this.stream.avail_in > 0 ); 280 } 281 282 283 /*************************************************************************** 284 285 Deallocates the C-allocated stream object. 286 287 ***************************************************************************/ 288 289 private void killStream ( ) 290 { 291 if ( this.stream_valid ) 292 { 293 inflateEnd(&stream); 294 this.stream_valid = false; 295 } 296 } 297 } 298