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