1 /*******************************************************************************
2 
3     copyright:  Copyright (C) 2007 Daniel Keep.  All rights reserved.
4 
5     license:    BSD style: $(LICENSE)
6 
7     author:     Daniel Keep
8 
9     version:
10       Feb 08: Added support for different stream encodings, removed
11               old "window bits" ctors.$(BR)
12       Dec 07: Added support for "window bits", needed for Zip support.$(BR)
13       Jul 07: Initial release.
14 
15 *******************************************************************************/
16 
17 module tango.io.stream.Zlib;
18 
19 private import tango.util.compress.c.zlib;
20 
21 private import tango.stdc.stringz : fromStringz;
22 
23 private import tango.core.Exception : IOException;
24 
25 private import tango.io.device.Conduit : InputFilter, OutputFilter;
26 
27 private import tango.io.model.IConduit : InputStream, OutputStream, IConduit;
28 
29 private import tango.text.convert.Integer : toString;
30 
31 
32 /* This constant controls the size of the input/output buffers we use
33  * internally.  This should be a fairly sane value (it's suggested by the zlib
34  * documentation), that should only need changing for memory-constrained
35  * platforms/use cases.
36  *
37  * An alternative would be to make the chunk size a template parameter to the
38  * filters themselves, but Tango already has more than enough template
39  * parameters getting in the way :)
40  */
41 
42 private enum { CHUNKSIZE = 256 * 1024 };
43 
44 /* This constant specifies the default windowBits value.  This is taken from
45  * documentation in zlib.h.  It shouldn't break anything if zlib changes to
46  * a different default.
47  */
48 
49 private enum { WINDOWBITS_DEFAULT = 15 };
50 
51 /*******************************************************************************
52 
53     This input filter can be used to perform decompression of zlib streams.
54 
55 *******************************************************************************/
56 
57 class ZlibInput : InputFilter
58 {
59     /***************************************************************************
60 
61         This enumeration allows you to specify the encoding of the compressed
62         stream.
63 
64     ***************************************************************************/
65 
66     enum Encoding : int
67     {
68         /**
69          *  The code should attempt to automatically determine what the encoding
70          *  of the stream should be.  Note that this cannot detect the case
71          *  where the stream was compressed with no encoding.
72          */
73         Guess,
74         /**
75          *  Stream has zlib encoding.
76          */
77         Zlib,
78         /**
79          *  Stream has gzip encoding.
80          */
81         Gzip,
82         /**
83          *  Stream has no encoding.
84          */
85         None
86     }
87 
88     private
89     {
90         /* Used to make sure we don't try to perform operations on a dead
91          * stream. */
92         bool zs_valid = false;
93 
94         z_stream zs;
95         ubyte[] in_chunk;
96     }
97 
98     /***************************************************************************
99 
100         Constructs a new zlib decompression filter.  You need to pass in the
101         stream that the decompression filter will read from.  If you are using
102         this filter with a conduit, the idiom to use is:
103 
104         ---
105         auto input = new ZlibInput(myConduit.input));
106         input.read(myContent);
107         ---
108 
109         The optional windowBits parameter is the base two logarithm of the
110         window size, and should be in the range 8-15, defaulting to 15 if not
111         specified.  Additionally, the windowBits parameter may be negative to
112         indicate that zlib should omit the standard zlib header and trailer,
113         with the window size being -windowBits.
114 
115       Params:
116         stream = Compressed input stream.
117 
118         encoding =
119             Stream encoding.  Defaults to Encoding.Guess, which
120             should be sufficient unless the stream was compressed with
121             no encoding; in this case, you must manually specify
122             Encoding.None.
123 
124         windowBits =
125             The base two logarithm of the window size, and should be in the
126             range 8-15, defaulting to 15 if not specified.
127 
128     ***************************************************************************/
129 
130     this(InputStream stream, Encoding encoding,
131             int windowBits = WINDOWBITS_DEFAULT)
132     {
133         init(stream, encoding, windowBits);
134         scope(failure) kill_zs();
135 
136         super(stream);
137         in_chunk = new ubyte[CHUNKSIZE];
138     }
139 
140     /// ditto
141     this(InputStream stream)
142     {
143         // DRK 2009-02-26
144         // Removed unique implementation in favour of passing on to another
145         // constructor.  The specific implementation was because the default
146         // value of windowBits is documented in zlib.h, but not actually
147         // exposed.  Using inflateInit over inflateInit2 ensured we would
148         // never get it wrong.  That said, the default value of 15 is REALLY
149         // unlikely to change: values below that aren't terribly useful, and
150         // values higher than 15 are already used for other purposes.
151         // Also, this leads to less code which is always good.  :D
152         this(stream, Encoding.init);
153     }
154 
155     /*
156      * This method performs initialisation for the stream.  Note that this may
157      * be called more than once for an instance, provided the instance is
158      * either new or as part of a call to reset.
159      */
160     private void init(InputStream stream, Encoding encoding, int windowBits)
161     {
162         /*
163          * Here's how windowBits works, according to zlib.h:
164          *
165          * 8 .. 15
166          *      zlib encoding.
167          *
168          * (8 .. 15) + 16
169          *      gzip encoding.
170          *
171          * (8 .. 15) + 32
172          *      auto-detect encoding.
173          *
174          * (8 .. 15) * -1
175          *      raw/no encoding.
176          *
177          * Since we're going to be playing with the value, we DO care whether
178          * windowBits is in the expected range, so we'll check it.
179          */
180         if( !( 8 <= windowBits && windowBits <= 15 ) )
181         {
182             // No compression for you!
183             throw new ZlibException("invalid windowBits argument"
184                 ~ .toString(windowBits).idup);
185         }
186 
187         switch( encoding )
188         {
189         case Encoding.Zlib:
190             // no-op
191             break;
192 
193         case Encoding.Gzip:
194             windowBits += 16;
195             break;
196 
197         case Encoding.Guess:
198             windowBits += 32;
199             break;
200 
201         case Encoding.None:
202             windowBits *= -1;
203             break;
204 
205         default:
206             assert (false);
207         }
208 
209         // Allocate inflate state
210         with( zs )
211         {
212             zalloc = null;
213             zfree = null;
214             opaque = null;
215             avail_in = 0;
216             next_in = null;
217         }
218 
219         auto ret = inflateInit2(&zs, windowBits);
220         if( ret != Z_OK )
221             throw new ZlibException(ret);
222 
223         zs_valid = true;
224 
225         // Note that this is redundant when init is called from the ctor, but
226         // it is NOT REDUNDANT when called from reset.  source is declared in
227         // InputFilter.
228         //
229         // This code is a wee bit brittle, since if the ctor of InputFilter
230         // changes, this code might break in subtle, hard to find ways.
231         //
232         // See ticket #1837
233         this.source = stream;
234     }
235 
236     ~this()
237     {
238         if( zs_valid )
239             kill_zs();
240     }
241 
242     /***************************************************************************
243 
244         Resets and re-initialises this instance.
245 
246         If you are creating compression streams inside a loop, you may wish to
247         use this method to re-use a single instance.  This prevents the
248         potentially costly re-allocation of internal buffers.
249 
250         The stream must have already been closed before calling reset.
251 
252     ***************************************************************************/
253 
254     void reset(InputStream stream, Encoding encoding,
255             int windowBits = WINDOWBITS_DEFAULT)
256     {
257         // If the stream is still valid, bail.
258         if( zs_valid )
259             throw new ZlibStillOpenException;
260 
261         init(stream, encoding, windowBits);
262     }
263 
264     /// ditto
265 
266     void reset(InputStream stream)
267     {
268         reset(stream, Encoding.init);
269     }
270 
271     /***************************************************************************
272 
273         Decompresses data from the underlying conduit into a target array.
274 
275         Returns the number of bytes stored into dst, which may be less than
276         requested.
277 
278     ***************************************************************************/
279 
280     override size_t read(void[] dst)
281     {
282         if( !zs_valid )
283             return IConduit.Eof;
284 
285         // Check to see if we've run out of input data.  If we have, get some
286         // more.
287         if( zs.avail_in == 0 )
288         {
289             auto len = source.read(in_chunk);
290             if( len == IConduit.Eof )
291                 return IConduit.Eof;
292 
293             zs.avail_in = cast(uint)len;
294             zs.next_in = in_chunk.ptr;
295         }
296 
297         // We'll tell zlib to inflate straight into the target array.
298         zs.avail_out = cast(uint)dst.length;
299         zs.next_out = cast(ubyte*)dst.ptr;
300         auto ret = inflate(&zs, Z_NO_FLUSH);
301 
302         switch( ret )
303         {
304             case Z_NEED_DICT:
305                 // Whilst not technically an error, this should never happen
306                 // for general-use code, so treat it as an error.
307             case Z_DATA_ERROR:
308             case Z_MEM_ERROR:
309                 kill_zs();
310                 throw new ZlibException(ret);
311 
312             case Z_STREAM_END:
313                 // zlib stream is finished; kill the stream so we don't try to
314                 // read from it again.
315                 kill_zs();
316                 break;
317 
318             default:
319         }
320 
321         return dst.length - zs.avail_out;
322     }
323 
324     /***************************************************************************
325 
326         Closes the compression stream.
327 
328     ***************************************************************************/
329 
330     override void close()
331     {
332         // Kill the stream.  Don't deallocate the buffer since the user may
333         // yet reset the stream.
334         if( zs_valid )
335             kill_zs();
336 
337         super.close();
338     }
339 
340     // Disable seeking
341     override long seek(long offset, Anchor anchor = Anchor.Begin)
342     {
343         throw new IOException("ZlibInput does not support seek requests");
344     }
345 
346     // This function kills the stream: it deallocates the internal state, and
347     // unsets the zs_valid flag.
348     private void kill_zs()
349     {
350         check_valid();
351 
352         inflateEnd(&zs);
353         zs_valid = false;
354     }
355 
356     // Asserts that the stream is still valid and usable (except that this
357     // check doesn't get elided with -release).
358     private void check_valid()
359     {
360         if( !zs_valid )
361             throw new ZlibClosedException;
362     }
363 }
364 
365 /*******************************************************************************
366 
367     This output filter can be used to perform compression of data into a zlib
368     stream.
369 
370 *******************************************************************************/
371 
372 class ZlibOutput : OutputFilter
373 {
374     /***************************************************************************
375 
376         This enumeration represents several pre-defined compression levels.
377 
378         Any integer between -1 and 9 inclusive may be used as a level,
379         although the symbols in this enumeration should suffice for most
380         use-cases.
381 
382     ***************************************************************************/
383 
384     enum Level : int
385     {
386         /**
387          * Default compression level.  This is selected for a good compromise
388          * between speed and compression, and the exact compression level is
389          * determined by the underlying zlib library.  Should be roughly
390          * equivalent to compression level 6.
391          */
392         Normal = -1,
393         /**
394          * Do not perform compression.  This will cause the stream to expand
395          * slightly to accommodate stream metadata.
396          */
397         None = 0,
398         /**
399          * Minimal compression; the fastest level which performs at least
400          * some compression.
401          */
402         Fast = 1,
403         /**
404          * Maximal compression.
405          */
406         Best = 9
407     }
408 
409     /***************************************************************************
410 
411         This enumeration allows you to specify what the encoding of the
412         compressed stream should be.
413 
414     ***************************************************************************/
415 
416     enum Encoding : int
417     {
418         /**
419          *  Stream should use zlib encoding.
420          */
421         Zlib,
422         /**
423          *  Stream should use gzip encoding.
424          */
425         Gzip,
426         /**
427          *  Stream should use no encoding.
428          */
429         None
430     }
431 
432     private
433     {
434         bool zs_valid = false;
435         z_stream zs;
436         ubyte[] out_chunk;
437         size_t _written = 0;
438     }
439 
440     /***************************************************************************
441 
442         Constructs a new zlib compression filter.  You need to pass in the
443         stream that the compression filter will write to.  If you are using
444         this filter with a conduit, the idiom to use is:
445 
446         ---
447         auto output = new ZlibOutput(myConduit.output);
448         output.write(myContent);
449         ---
450 
451         The optional windowBits parameter is the base two logarithm of the
452         window size, and should be in the range 8-15, defaulting to 15 if not
453         specified.  Additionally, the windowBits parameter may be negative to
454         indicate that zlib should omit the standard zlib header and trailer,
455         with the window size being -windowBits.
456 
457     ***************************************************************************/
458 
459     this(OutputStream stream, Level level, Encoding encoding,
460             int windowBits = WINDOWBITS_DEFAULT)
461     {
462         init(stream, level, encoding, windowBits);
463         scope(failure) kill_zs();
464 
465         super(stream);
466         out_chunk = new ubyte[CHUNKSIZE];
467     }
468 
469     /// ditto
470     this(OutputStream stream, Level level = Level.Normal)
471     {
472         // DRK 2009-02-26
473         // Removed unique implementation in favour of passing on to another
474         // constructor.  See ZlibInput.this(InputStream).
475         this(stream, level, Encoding.init);
476     }
477 
478     /*
479      * This method performs initialisation for the stream.  Note that this may
480      * be called more than once for an instance, provided the instance is
481      * either new or as part of a call to reset.
482      */
483     private void init(OutputStream stream, Level level, Encoding encoding,
484             int windowBits)
485     {
486         /*
487          * Here's how windowBits works, according to zlib.h:
488          *
489          * 8 .. 15
490          *      zlib encoding.
491          *
492          * (8 .. 15) + 16
493          *      gzip encoding.
494          *
495          * (8 .. 15) + 32
496          *      auto-detect encoding.
497          *
498          * (8 .. 15) * -1
499          *      raw/no encoding.
500          *
501          * Since we're going to be playing with the value, we DO care whether
502          * windowBits is in the expected range, so we'll check it.
503          *
504          * Also, note that OUR Encoding enum doesn't contain the 'Guess'
505          * member.  I'm still waiting on tango.io.psychic...
506          */
507         if( !( 8 <= windowBits && windowBits <= 15 ) )
508         {
509             // No compression for you!
510             throw new ZlibException("invalid windowBits argument"
511                 ~ .toString(windowBits).idup);
512         }
513 
514         switch( encoding )
515         {
516         case Encoding.Zlib:
517             // no-op
518             break;
519 
520         case Encoding.Gzip:
521             windowBits += 16;
522             break;
523 
524         case Encoding.None:
525             windowBits *= -1;
526             break;
527 
528         default:
529             assert (false);
530         }
531 
532         // Allocate deflate state
533         with( zs )
534         {
535             zalloc = null;
536             zfree = null;
537             opaque = null;
538         }
539 
540         auto ret = deflateInit2(&zs, level, Z_DEFLATED, windowBits, 8,
541                 Z_DEFAULT_STRATEGY);
542         if( ret != Z_OK )
543             throw new ZlibException(ret);
544 
545         zs_valid = true;
546 
547         // This is NOT REDUNDANT.  See ZlibInput.init.
548         this.sink = stream;
549     }
550 
551     ~this()
552     {
553         if( zs_valid )
554             kill_zs();
555     }
556 
557     /***************************************************************************
558 
559         Resets and re-initialises this instance.
560 
561         If you are creating compression streams inside a loop, you may wish to
562         use this method to re-use a single instance.  This prevents the
563         potentially costly re-allocation of internal buffers.
564 
565         The stream must have already been closed or committed before calling
566         reset.
567 
568     ***************************************************************************/
569 
570     void reset(OutputStream stream, Level level, Encoding encoding,
571             int windowBits = WINDOWBITS_DEFAULT)
572     {
573         // If the stream is still valid, bail.
574         if( zs_valid )
575             throw new ZlibStillOpenException;
576 
577         init(stream, level, encoding, windowBits);
578     }
579 
580     /// ditto
581     void reset(OutputStream stream, Level level = Level.Normal)
582     {
583         reset(stream, level, Encoding.init);
584     }
585 
586     /***************************************************************************
587 
588         Compresses the given data to the underlying conduit.
589 
590         Returns the number of bytes from src that were compressed; write
591         should always consume all data provided to it, although it may not be
592         immediately written to the underlying output stream.
593 
594     ***************************************************************************/
595 
596     override size_t write(const(void)[] src)
597     {
598         check_valid();
599         scope(failure) kill_zs();
600 
601         zs.avail_in = cast(uint)src.length;
602         zs.next_in = cast(ubyte*)src.ptr;
603 
604         do
605         {
606             zs.avail_out = cast(uint)out_chunk.length;
607             zs.next_out = out_chunk.ptr;
608 
609             auto ret = deflate(&zs, Z_NO_FLUSH);
610             if( ret == Z_STREAM_ERROR )
611                 throw new ZlibException(ret);
612 
613             // Push the compressed bytes out to the stream, until it's either
614             // written them all, or choked.
615             auto have = out_chunk.length-zs.avail_out;
616             auto out_buffer = out_chunk[0..have];
617             do
618             {
619                 auto w = sink.write(out_buffer);
620                 if( w == IConduit.Eof )
621                     return w;
622 
623                 out_buffer = out_buffer[w..$];
624                 _written += w;
625             }
626             while( out_buffer.length > 0 );
627         }
628         // Loop while we are still using up the whole output buffer
629         while( zs.avail_out == 0 );
630 
631         assert( zs.avail_in == 0, "failed to compress all provided data" );
632 
633         return src.length;
634     }
635 
636     /***************************************************************************
637 
638         This read-only property returns the number of compressed bytes that
639         have been written to the underlying stream.  Following a call to
640         either close or commit, this will contain the total compressed size of
641         the input data stream.
642 
643     ***************************************************************************/
644 
645     size_t written()
646     {
647         return _written;
648     }
649 
650     /***************************************************************************
651 
652         Close the compression stream.  This will cause any buffered content to
653         be committed to the underlying stream.
654 
655     ***************************************************************************/
656 
657     override void close()
658     {
659         // Only commit if the stream is still open.
660         if( zs_valid ) commit();
661 
662         super.close();
663     }
664 
665     /***************************************************************************
666 
667         Purge any buffered content.  Calling this will implicitly end the zlib
668         stream, so it should not be called until you are finished compressing
669         data.  Any calls to either write or commit after a compression filter
670         has been committed will throw an exception.
671 
672         The only difference between calling this method and calling close is
673         that the underlying stream will not be closed.
674 
675     ***************************************************************************/
676 
677     void commit()
678     {
679         check_valid();
680         scope(failure) kill_zs();
681 
682         zs.avail_in = 0;
683         zs.next_in = null;
684 
685         bool finished = false;
686 
687         do
688         {
689             zs.avail_out = cast(uint)out_chunk.length;
690             zs.next_out = out_chunk.ptr;
691 
692             auto ret = deflate(&zs, Z_FINISH);
693             switch( ret )
694             {
695                 case Z_OK:
696                     // Keep going
697                     break;
698 
699                 case Z_STREAM_END:
700                     // We're done!
701                     finished = true;
702                     break;
703 
704                 default:
705                     throw new ZlibException(ret);
706             }
707 
708             auto have = out_chunk.length - zs.avail_out;
709             auto out_buffer = out_chunk[0..have];
710             if( have > 0 )
711             {
712                 do
713                 {
714                     auto w = sink.write(out_buffer);
715                     if( w == IConduit.Eof )
716                         return;
717 
718                     out_buffer = out_buffer[w..$];
719                     _written += w;
720                 }
721                 while( out_buffer.length > 0 );
722             }
723         }
724         while( !finished );
725 
726         kill_zs();
727     }
728 
729     // Disable seeking
730     override long seek(long offset, Anchor anchor = Anchor.Begin)
731     {
732         throw new IOException("ZlibOutput does not support seek requests");
733     }
734 
735     // This function kills the stream: it deallocates the internal state, and
736     // unsets the zs_valid flag.
737     private void kill_zs()
738     {
739         check_valid();
740 
741         deflateEnd(&zs);
742         zs_valid = false;
743     }
744 
745     // Asserts that the stream is still valid and usable (except that this
746     // check doesn't get elided with -release).
747     private void check_valid()
748     {
749         if( !zs_valid )
750             throw new ZlibClosedException;
751     }
752 }
753 
754 /*******************************************************************************
755 
756     This exception is thrown if you attempt to perform a read, write or flush
757     operation on a closed zlib filter stream.  This can occur if the input
758     stream has finished, or an output stream was flushed.
759 
760 *******************************************************************************/
761 
762 class ZlibClosedException : IOException
763 {
764     this()
765     {
766         super("cannot operate on closed zlib stream");
767     }
768 }
769 
770 /*******************************************************************************
771 
772     This exception is thrown if you attempt to reset a compression stream that
773     is still open.  You must either close or commit a stream before it can be
774     reset.
775 
776 *******************************************************************************/
777 
778 class ZlibStillOpenException : IOException
779 {
780     this()
781     {
782         super("cannot reset an open zlib stream");
783     }
784 }
785 
786 /*******************************************************************************
787 
788     This exception is thrown when an error occurs in the underlying zlib
789     library.  Where possible, it will indicate both the name of the error, and
790     any textural message zlib has provided.
791 
792 *******************************************************************************/
793 
794 class ZlibException : IOException
795 {
796     /*
797      * Use this if you want to throw an exception that isn't actually
798      * generated by zlib.
799      */
800     this(immutable(char)[] msg)
801     {
802         super(msg);
803     }
804 
805     /*
806      * code is the error code returned by zlib.  The exception message will
807      * be the name of the error code.
808      */
809     this(int code)
810     {
811         super(codeName(code));
812     }
813 
814     /*
815      * As above, except that it appends msg as well.
816      */
817     this(int code, char* msg)
818     {
819         super(codeName(code)~": "~fromStringz(msg).idup);
820     }
821 
822     protected immutable(char)[] codeName(int code)
823     {
824         immutable(char)[] name;
825 
826         switch( code )
827         {
828             case Z_OK:              name = "Z_OK";              break;
829             case Z_STREAM_END:      name = "Z_STREAM_END";      break;
830             case Z_NEED_DICT:       name = "Z_NEED_DICT";       break;
831             case Z_ERRNO:           name = "Z_ERRNO";           break;
832             case Z_STREAM_ERROR:    name = "Z_STREAM_ERROR";    break;
833             case Z_DATA_ERROR:      name = "Z_DATA_ERROR";      break;
834             case Z_MEM_ERROR:       name = "Z_MEM_ERROR";       break;
835             case Z_BUF_ERROR:       name = "Z_BUF_ERROR";       break;
836             case Z_VERSION_ERROR:   name = "Z_VERSION_ERROR";   break;
837             default:                name = "Z_UNKNOWN";
838         }
839 
840         return name;
841     }
842 }
843 
844 /* *****************************************************************************
845 
846     This section contains a simple unit test for this module.  It is hidden
847     behind a version statement because it introduces additional dependencies.
848 
849 ***************************************************************************** */
850 
851 debug(UnitTest) {
852 
853 import tango.io.device.Array : Array;
854 
855 void check_array(immutable(char)[] FILE=__FILE__, int LINE=__LINE__)(
856         const(ubyte)[] as, const(ubyte)[] bs, lazy const(char)[] msg)
857 {
858     assert( as.length == bs.length,
859         FILE ~":"~ toString(LINE) ~ ": " ~ msg()
860         ~ "array lengths differ (" ~ toString(as.length)
861         ~ " vs " ~ toString(bs.length) ~ ")" );
862 
863     foreach( i, a ; as )
864     {
865         auto b = bs[i];
866 
867         assert( a == b,
868             FILE ~":"~ toString(LINE) ~ ": " ~ msg()
869             ~ "arrays differ at " ~ toString(i)
870             ~ " (" ~ toString(cast(int) a)
871             ~ " vs " ~ toString(cast(int) b) ~ ")" );
872     }
873 }
874 
875 unittest
876 {
877     // One ring to rule them all, one ring to find them,
878     // One ring to bring them all and in the darkness bind them.
879     const char[] message =
880         "Ash nazg durbatulûk, ash nazg gimbatul, "
881         "ash nazg thrakatulûk, agh burzum-ishi krimpatul.";
882 
883     static assert( message.length == 90 );
884 
885     // This compressed data was created using Python 2.5's built in zlib
886     // module, with the default compression level.
887     {
888         const ubyte[] message_z = [
889             0x78,0x9c,0x73,0x2c,0xce,0x50,0xc8,0x4b,
890             0xac,0x4a,0x57,0x48,0x29,0x2d,0x4a,0x4a,
891             0x2c,0x29,0xcd,0x39,0xbc,0x3b,0x5b,0x47,
892             0x21,0x11,0x26,0x9a,0x9e,0x99,0x0b,0x16,
893             0x45,0x12,0x2a,0xc9,0x28,0x4a,0xcc,0x46,
894             0xa8,0x4c,0xcf,0x50,0x48,0x2a,0x2d,0xaa,
895             0x2a,0xcd,0xd5,0xcd,0x2c,0xce,0xc8,0x54,
896             0xc8,0x2e,0xca,0xcc,0x2d,0x00,0xc9,0xea,
897             0x01,0x00,0x1f,0xe3,0x22,0x99];
898 
899         scope cond_z = new Array(2048);
900         scope comp = new ZlibOutput(cond_z);
901         comp.write (message);
902         comp.close();
903 
904         assert( comp.written() == message_z.length );
905 
906         /+
907         Stdout("message_z:").newline;
908         foreach( b ; cast(ubyte[]) cond_z.slice )
909             Stdout.format("0x{0:x2},", b);
910         Stdout.newline.newline;
911         +/
912 
913         //assert( message_z == cast(ubyte[])(cond_z.slice) );
914         check_array!(__FILE__,__LINE__)
915             ( message_z, cast(ubyte[]) cond_z.slice(), "message_z " );
916 
917         scope decomp = new ZlibInput(cond_z);
918         auto buffer = new ubyte[256];
919         buffer = buffer[0 .. decomp.read(buffer)];
920 
921         //assert( cast(ubyte[])message == buffer );
922         check_array!(__FILE__,__LINE__)
923             ( cast(ubyte[]) message, buffer, "message (zlib) " );
924     }
925 
926     // This compressed data was created using the Cygwin gzip program
927     // with default options.  The original file was called "testdata.txt".
928     {
929         const ubyte[] message_gz = [
930             0x1f,0x8b,0x08,0x00,0x80,0x70,0x6f,0x45,
931             0x00,0x03,0x73,0x2c,0xce,0x50,0xc8,0x4b,
932             0xac,0x4a,0x57,0x48,0x29,0x2d,0x4a,0x4a,
933             0x2c,0x29,0xcd,0x39,0xbc,0x3b,0x5b,0x47,
934             0x21,0x11,0x26,0x9a,0x9e,0x99,0x0b,0x16,
935             0x45,0x12,0x2a,0xc9,0x28,0x4a,0xcc,0x46,
936             0xa8,0x4c,0xcf,0x50,0x48,0x2a,0x2d,0xaa,
937             0x2a,0xcd,0xd5,0xcd,0x2c,0xce,0xc8,0x54,
938             0xc8,0x2e,0xca,0xcc,0x2d,0x00,0xc9,0xea,
939             0x01,0x00,0x45,0x38,0xbc,0x58,0x5a,0x00,
940             0x00,0x00];
941 
942         // Compresses the original message, and outputs the bytes.  You can use
943         // this to test the output of ZlibOutput with gzip.  If you use this,
944         // don't forget to import Stdout somewhere.
945         /+
946         scope comp_gz = new Array(2048);
947         scope comp = new ZlibOutput(comp_gz, ZlibOutput.Level.Normal, ZlibOutput.Encoding.Gzip, WINDOWBITS_DEFAULT);
948         comp.write(message);
949         comp.close;
950 
951         Stdout.format("message_gz ({0} bytes):", comp_gz.slice.length).newline;
952         foreach( b ; cast(ubyte[]) comp_gz.slice )
953             Stdout.format("0x{0:x2},", b);
954         Stdout.newline;
955         +/
956 
957         // We aren't going to test that we can compress to a gzip stream
958         // since gzip itself always adds stuff like the filename, timestamps,
959         // etc.  We'll just make sure we can DECOMPRESS gzip streams.
960         scope decomp_gz = new Array(message_gz.dup);
961         scope decomp = new ZlibInput(decomp_gz);
962         auto buffer = new ubyte[256];
963         buffer = buffer[0 .. decomp.read(buffer)];
964 
965         //assert( cast(ubyte[]) message == buffer );
966         check_array!(__FILE__,__LINE__)
967             ( cast(ubyte[]) message, buffer, "message (gzip) ");
968     }
969 }
970 }