1 // Written in the D programming language.
2 
3 /**
4  * Compress/decompress data using the $(HTTP www.zlib.net, zlib library).
5  *
6  * Examples:
7  *
8  * If you have a small buffer you can use $(LREF compress) and
9  * $(LREF uncompress) directly.
10  *
11  * -------
12  * import std.zlib;
13  *
14  * auto src =
15  * "the quick brown fox jumps over the lazy dog\r
16  *  the quick brown fox jumps over the lazy dog\r";
17  *
18  * ubyte[] dst;
19  * ubyte[] result;
20  *
21  * dst = compress(src);
22  * result = cast(ubyte[]) uncompress(dst);
23  * assert(result == src);
24  * -------
25  *
26  * When the data to be compressed doesn't fit in one buffer, use
27  * $(LREF Compress) and $(LREF UnCompress).
28  *
29  * -------
30  * import std.zlib;
31  * import std.stdio;
32  * import std.conv : to;
33  * import std.algorithm.iteration : map;
34  *
35  * UnCompress decmp = new UnCompress;
36  * foreach (chunk; stdin.byChunk(4096).map!(x => decmp.uncompress(x)))
37  * {
38  *     chunk.to!string.write;
39  * }
40 
41  * -------
42  *
43  * References:
44  *  $(HTTP en.wikipedia.org/wiki/Zlib, Wikipedia)
45  *
46  * Copyright: Copyright The D Language Foundation 2000 - 2011.
47  * License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
48  * Authors:   $(HTTP digitalmars.com, Walter Bright)
49  * Source:    $(PHOBOSSRC std/zlib.d)
50  */
51 /*          Copyright The D Language Foundation 2000 - 2011.
52  * Distributed under the Boost Software License, Version 1.0.
53  *    (See accompanying file LICENSE_1_0.txt or copy at
54  *          http://www.boost.org/LICENSE_1_0.txt)
55  */
56 module std.zlib;
57 
58 //debug=zlib;       // uncomment to turn on debugging printf's
59 
60 import etc.c.zlib;
61 
62 // Values for 'mode'
63 
64 enum
65 {
66     Z_NO_FLUSH      = 0,
67     Z_SYNC_FLUSH    = 2,
68     Z_FULL_FLUSH    = 3,
69     Z_FINISH        = 4,
70 }
71 
72 /*************************************
73  * Errors throw a ZlibException.
74  */
75 
76 class ZlibException : Exception
77 {
78     private static string getmsg(int errnum) nothrow @nogc pure @safe
79     {
80         string msg;
81         switch (errnum)
82         {
83             case Z_STREAM_END:      msg = "stream end"; break;
84             case Z_NEED_DICT:       msg = "need dict"; break;
85             case Z_ERRNO:           msg = "errno"; break;
86             case Z_STREAM_ERROR:    msg = "stream error"; break;
87             case Z_DATA_ERROR:      msg = "data error"; break;
88             case Z_MEM_ERROR:       msg = "mem error"; break;
89             case Z_BUF_ERROR:       msg = "buf error"; break;
90             case Z_VERSION_ERROR:   msg = "version error"; break;
91             default:                msg = "unknown error";  break;
92         }
93         return msg;
94     }
95 
96     this(int errnum)
97     {
98         super(getmsg(errnum));
99     }
100 }
101 
102 /**
103  * $(P Compute the Adler-32 checksum of a buffer's worth of data.)
104  *
105  * Params:
106  *     adler = the starting checksum for the computation. Use 1
107  *             for a new checksum. Use the output of this function
108  *             for a cumulative checksum.
109  *     buf = buffer containing input data
110  *
111  * Returns:
112  *     A `uint` checksum for the provided input data and starting checksum
113  *
114  * See_Also:
115  *     $(LINK http://en.wikipedia.org/wiki/Adler-32)
116  */
117 
118 uint adler32(uint adler, const(void)[] buf)
119 {
120     import std.range : chunks;
121     foreach (chunk; (cast(ubyte[]) buf).chunks(0xFFFF0000))
122     {
123         adler = etc.c.zlib.adler32(adler, chunk.ptr, cast(uint) chunk.length);
124     }
125     return adler;
126 }
127 
128 ///
129 @system unittest
130 {
131     static ubyte[] data = [1,2,3,4,5,6,7,8,9,10];
132 
133     uint adler = adler32(0u, data);
134     assert(adler == 0xdc0037);
135 }
136 
137 @system unittest
138 {
139     static string data = "test";
140 
141     uint adler = adler32(1, data);
142     assert(adler == 0x045d01c1);
143 }
144 
145 /**
146  * $(P Compute the CRC32 checksum of a buffer's worth of data.)
147  *
148  * Params:
149  *     crc = the starting checksum for the computation. Use 0
150  *             for a new checksum. Use the output of this function
151  *             for a cumulative checksum.
152  *     buf = buffer containing input data
153  *
154  * Returns:
155  *     A `uint` checksum for the provided input data and starting checksum
156  *
157  * See_Also:
158  *     $(LINK http://en.wikipedia.org/wiki/Cyclic_redundancy_check)
159  */
160 
161 uint crc32(uint crc, const(void)[] buf)
162 {
163     import std.range : chunks;
164     foreach (chunk; (cast(ubyte[]) buf).chunks(0xFFFF0000))
165     {
166         crc = etc.c.zlib.crc32(crc, chunk.ptr, cast(uint) chunk.length);
167     }
168     return crc;
169 }
170 
171 @system unittest
172 {
173     static ubyte[] data = [1,2,3,4,5,6,7,8,9,10];
174 
175     uint crc;
176 
177     debug(zlib) printf("D.zlib.crc32.unittest\n");
178     crc = crc32(0u, cast(void[]) data);
179     debug(zlib) printf("crc = %x\n", crc);
180     assert(crc == 0x2520577b);
181 }
182 
183 /**
184  * $(P Compress data)
185  *
186  * Params:
187  *     srcbuf = buffer containing the data to compress
188  *     level = compression level. Legal values are -1 .. 9, with -1 indicating
189  *             the default level (6), 0 indicating no compression, 1 being the
190  *             least compression and 9 being the most.
191  *
192  * Returns:
193  *     the compressed data
194  */
195 
196 ubyte[] compress(const(void)[] srcbuf, int level)
197 in
198 {
199     assert(-1 <= level && level <= 9, "Compression level needs to be within [-1, 9].");
200 }
201 do
202 {
203     import core.memory : GC;
204     import std.array : uninitializedArray;
205     auto destlen = srcbuf.length + ((srcbuf.length + 1023) / 1024) + 12;
206     auto destbuf = uninitializedArray!(ubyte[])(destlen);
207     auto err = etc.c.zlib.compress2(destbuf.ptr, &destlen, cast(ubyte *) srcbuf.ptr, srcbuf.length, level);
208     if (err)
209     {
210         GC.free(destbuf.ptr);
211         throw new ZlibException(err);
212     }
213 
214     destbuf.length = destlen;
215     return destbuf;
216 }
217 
218 /*********************************************
219  * ditto
220  */
221 
222 ubyte[] compress(const(void)[] srcbuf)
223 {
224     return compress(srcbuf, Z_DEFAULT_COMPRESSION);
225 }
226 
227 /*********************************************
228  * Decompresses the data in srcbuf[].
229  * Params:
230  *  srcbuf  = buffer containing the compressed data.
231  *  destlen = size of the uncompressed data.
232  *            It need not be accurate, but the decompression will be faster
233  *            if the exact size is supplied.
234  *  winbits = the base two logarithm of the maximum window size.
235  * Returns: the decompressed data.
236  */
237 
238 void[] uncompress(const(void)[] srcbuf, size_t destlen = 0u, int winbits = 15)
239 {
240     import std.conv : to;
241     int err;
242     ubyte[] destbuf;
243 
244     if (!destlen)
245         destlen = srcbuf.length * 2 + 1;
246 
247     etc.c.zlib.z_stream zs;
248     zs.next_in = cast(typeof(zs.next_in)) srcbuf.ptr;
249     zs.avail_in = to!uint(srcbuf.length);
250     err = etc.c.zlib.inflateInit2(&zs, winbits);
251     if (err)
252     {
253         throw new ZlibException(err);
254     }
255 
256     size_t olddestlen = 0u;
257 
258     loop:
259     while (true)
260     {
261         destbuf.length = destlen;
262         zs.next_out = cast(typeof(zs.next_out)) &destbuf[olddestlen];
263         zs.avail_out = to!uint(destlen - olddestlen);
264         olddestlen = destlen;
265 
266         err = etc.c.zlib.inflate(&zs, Z_NO_FLUSH);
267         switch (err)
268         {
269             case Z_OK:
270                 destlen = destbuf.length * 2;
271                 continue loop;
272 
273             case Z_STREAM_END:
274                 destbuf.length = zs.total_out;
275                 err = etc.c.zlib.inflateEnd(&zs);
276                 if (err != Z_OK)
277                     throw new ZlibException(err);
278                 return destbuf;
279 
280             default:
281                 etc.c.zlib.inflateEnd(&zs);
282                 throw new ZlibException(err);
283         }
284     }
285     assert(0, "Unreachable code");
286 }
287 
288 @system unittest
289 {
290     auto src =
291 "the quick brown fox jumps over the lazy dog\r
292 the quick brown fox jumps over the lazy dog\r
293 ";
294     ubyte[] dst;
295     ubyte[] result;
296 
297     //arrayPrint(src);
298     dst = compress(src);
299     //arrayPrint(dst);
300     result = cast(ubyte[]) uncompress(dst);
301     //arrayPrint(result);
302     assert(result == src);
303 }
304 
305 @system unittest
306 {
307     ubyte[] src = new ubyte[1000000];
308     ubyte[] dst;
309     ubyte[] result;
310 
311     src[] = 0x80;
312     dst = compress(src);
313     assert(dst.length*2 + 1 < src.length);
314     result = cast(ubyte[]) uncompress(dst);
315     assert(result == src);
316 }
317 
318 /+
319 void arrayPrint(ubyte[] array)
320 {
321     //printf("array %p,%d\n", cast(void*) array, array.length);
322     for (size_t i = 0; i < array.length; i++)
323     {
324         printf("%02x ", array[i]);
325         if (((i + 1) & 15) == 0)
326             printf("\n");
327     }
328     printf("\n\n");
329 }
330 +/
331 
332 /// the header format the compressed stream is wrapped in
333 enum HeaderFormat {
334     deflate, /// a standard zlib header
335     gzip, /// a gzip file format header
336     determineFromData /// used when decompressing. Try to automatically detect the stream format by looking at the data
337 }
338 
339 /*********************************************
340  * Used when the data to be compressed is not all in one buffer.
341  */
342 
343 class Compress
344 {
345     import std.conv : to;
346 
347   private:
348     z_stream zs;
349     int level = Z_DEFAULT_COMPRESSION;
350     int inited;
351     immutable bool gzip;
352 
353     void error(int err)
354     {
355         if (inited)
356         {   deflateEnd(&zs);
357             inited = 0;
358         }
359         throw new ZlibException(err);
360     }
361 
362   public:
363 
364     /**
365      * Constructor.
366      *
367      * Params:
368      *    level = compression level. Legal values are 1 .. 9, with 1 being the least
369      *            compression and 9 being the most. The default value is 6.
370      *    header = sets the compression type to one of the options available
371      *             in $(LREF HeaderFormat). Defaults to HeaderFormat.deflate.
372      *
373      * See_Also:
374      *    $(LREF compress), $(LREF HeaderFormat)
375      */
376     this(int level, HeaderFormat header = HeaderFormat.deflate)
377     in
378     {
379         assert(1 <= level && level <= 9, "Legal compression level are in [1, 9].");
380     }
381     do
382     {
383         this.level = level;
384         this.gzip = header == HeaderFormat.gzip;
385     }
386 
387     /// ditto
388     this(HeaderFormat header = HeaderFormat.deflate)
389     {
390         this.gzip = header == HeaderFormat.gzip;
391     }
392 
393     ~this()
394     {   int err;
395 
396         if (inited)
397         {
398             inited = 0;
399             deflateEnd(&zs);
400         }
401     }
402 
403     /**
404      * Compress the data in buf and return the compressed data.
405      * Params:
406      *    buf = data to compress
407      *
408      * Returns:
409      *    the compressed data. The buffers returned from successive calls to this should be concatenated together.
410      *
411      */
412     const(void)[] compress(const(void)[] buf)
413     {
414         import core.memory : GC;
415         import std.array : uninitializedArray;
416         int err;
417         ubyte[] destbuf;
418 
419         if (buf.length == 0)
420             return null;
421 
422         if (!inited)
423         {
424             err = deflateInit2(&zs, level, Z_DEFLATED, 15 + (gzip ? 16 : 0), 8, Z_DEFAULT_STRATEGY);
425             if (err)
426                 error(err);
427             inited = 1;
428         }
429 
430         destbuf = uninitializedArray!(ubyte[])(zs.avail_in + buf.length);
431         zs.next_out = destbuf.ptr;
432         zs.avail_out = to!uint(destbuf.length);
433 
434         if (zs.avail_in)
435             buf = zs.next_in[0 .. zs.avail_in] ~ cast(ubyte[]) buf;
436 
437         zs.next_in = cast(typeof(zs.next_in)) buf.ptr;
438         zs.avail_in = to!uint(buf.length);
439 
440         err = deflate(&zs, Z_NO_FLUSH);
441         if (err != Z_STREAM_END && err != Z_OK)
442         {
443             GC.free(destbuf.ptr);
444             error(err);
445         }
446         destbuf.length = destbuf.length - zs.avail_out;
447         return destbuf;
448     }
449 
450     /***
451      * Compress and return any remaining data.
452      * The returned data should be appended to that returned by compress().
453      * Params:
454      *  mode = one of the following:
455      *          $(DL
456                     $(DT Z_SYNC_FLUSH )
457                     $(DD Syncs up flushing to the next byte boundary.
458                         Used when more data is to be compressed later on.)
459                     $(DT Z_FULL_FLUSH )
460                     $(DD Syncs up flushing to the next byte boundary.
461                         Used when more data is to be compressed later on,
462                         and the decompressor needs to be restartable at this
463                         point.)
464                     $(DT Z_FINISH)
465                     $(DD (default) Used when finished compressing the data. )
466                 )
467      */
468     void[] flush(int mode = Z_FINISH)
469     in
470     {
471         assert(mode == Z_FINISH || mode == Z_SYNC_FLUSH || mode == Z_FULL_FLUSH,
472                 "Mode must be either Z_FINISH, Z_SYNC_FLUSH or Z_FULL_FLUSH.");
473     }
474     do
475     {
476         import core.memory : GC;
477         ubyte[] destbuf;
478         ubyte[512] tmpbuf = void;
479         int err;
480 
481         if (!inited)
482             return null;
483 
484         /* may be  zs.avail_out+<some constant>
485          * zs.avail_out is set nonzero by deflate in previous compress()
486          */
487         //tmpbuf = new void[zs.avail_out];
488         zs.next_out = tmpbuf.ptr;
489         zs.avail_out = tmpbuf.length;
490 
491         while ( (err = deflate(&zs, mode)) != Z_STREAM_END)
492         {
493             if (err == Z_OK)
494             {
495                 if (zs.avail_out != 0 && mode != Z_FINISH)
496                     break;
497                 else if (zs.avail_out == 0)
498                 {
499                     destbuf ~= tmpbuf;
500                     zs.next_out = tmpbuf.ptr;
501                     zs.avail_out = tmpbuf.length;
502                     continue;
503                 }
504                 err = Z_BUF_ERROR;
505             }
506             GC.free(destbuf.ptr);
507             error(err);
508         }
509         destbuf ~= tmpbuf[0 .. (tmpbuf.length - zs.avail_out)];
510 
511         if (mode == Z_FINISH)
512         {
513             err = deflateEnd(&zs);
514             inited = 0;
515             if (err)
516                 error(err);
517         }
518         return destbuf;
519     }
520 }
521 
522 /******
523  * Used when the data to be decompressed is not all in one buffer.
524  */
525 
526 class UnCompress
527 {
528     import std.conv : to;
529 
530   private:
531     z_stream zs;
532     int inited;
533     int done;
534     bool inputEnded;
535     size_t destbufsize;
536 
537     HeaderFormat format;
538 
539     void error(int err)
540     {
541         if (inited)
542         {   inflateEnd(&zs);
543             inited = 0;
544         }
545         throw new ZlibException(err);
546     }
547 
548   public:
549 
550     /**
551      * Construct. destbufsize is the same as for D.zlib.uncompress().
552      */
553     this(uint destbufsize)
554     {
555         this.destbufsize = destbufsize;
556     }
557 
558     /** ditto */
559     this(HeaderFormat format = HeaderFormat.determineFromData)
560     {
561         this.format = format;
562     }
563 
564     ~this()
565     {   int err;
566 
567         if (inited)
568         {
569             inited = 0;
570             inflateEnd(&zs);
571         }
572         done = 1;
573     }
574 
575     /**
576      * Decompress the data in buf and return the decompressed data.
577      * The buffers returned from successive calls to this should be concatenated
578      * together.
579      */
580     const(void)[] uncompress(const(void)[] buf)
581     in
582     {
583         assert(!done, "Buffer has been flushed.");
584     }
585     do
586     {
587         if (inputEnded || !buf.length)
588             return null;
589 
590         import core.memory : GC;
591         import std.array : uninitializedArray;
592         int err;
593 
594         if (!inited)
595         {
596         int windowBits = 15;
597         if (format == HeaderFormat.gzip)
598             windowBits += 16;
599             else if (format == HeaderFormat.determineFromData)
600             windowBits += 32;
601 
602             err = inflateInit2(&zs, windowBits);
603             if (err)
604                 error(err);
605             inited = 1;
606         }
607 
608         if (!destbufsize)
609             destbufsize = to!uint(buf.length) * 2;
610         auto destbuf = uninitializedArray!(ubyte[])(destbufsize);
611         size_t destFill;
612 
613         zs.next_in = cast(ubyte*) buf.ptr;
614         zs.avail_in = to!uint(buf.length);
615 
616         while (true)
617         {
618             auto oldAvailIn = zs.avail_in;
619 
620             zs.next_out = destbuf[destFill .. $].ptr;
621             zs.avail_out = to!uint(destbuf.length - destFill);
622 
623             err = inflate(&zs, Z_NO_FLUSH);
624             if (err == Z_STREAM_END)
625             {
626                 inputEnded = true;
627                 break;
628             }
629             else if (err != Z_OK)
630             {
631                 GC.free(destbuf.ptr);
632                 error(err);
633             }
634             else if (!zs.avail_in)
635                 break;
636 
637             /*
638                 According to the zlib manual inflate() stops when either there's
639                 no more data to uncompress or the output buffer is full
640                 So at this point, the output buffer is too full
641             */
642 
643             destFill = destbuf.length;
644 
645             if (destbuf.capacity)
646             {
647                 if (destbuf.length < destbuf.capacity)
648                     destbuf.length = destbuf.capacity;
649                 else
650                 {
651                     auto newLength = GC.extend(destbuf.ptr, destbufsize, destbufsize);
652 
653                     if (newLength && destbuf.length < destbuf.capacity)
654                         destbuf.length = destbuf.capacity;
655                     else
656                         destbuf.length += destbufsize;
657                 }
658             }
659             else
660                 destbuf.length += destbufsize;
661         }
662 
663         destbuf.length = destbuf.length - zs.avail_out;
664         return destbuf;
665     }
666 
667     // Test for https://issues.dlang.org/show_bug.cgi?id=3191 and
668     // https://issues.dlang.org/show_bug.cgi?id=9505
669     @system unittest
670     {
671         import std.algorithm.comparison;
672         import std.array;
673         import std.file;
674         import std.zlib;
675 
676         // Data that can be easily compressed
677         ubyte[1024] originalData;
678 
679         // This should yield a compression ratio of at least 1/2
680         auto compressedData = compress(originalData, 9);
681         assert(compressedData.length < originalData.length / 2,
682                 "The compression ratio is too low to accurately test this situation");
683 
684         auto chunkSize = compressedData.length / 4;
685         assert(chunkSize < compressedData.length,
686                 "The length of the compressed data is too small to accurately test this situation");
687 
688         auto decompressor = new UnCompress();
689         ubyte[originalData.length] uncompressedData;
690         ubyte[] reusedBuf;
691         int progress;
692 
693         reusedBuf.length = chunkSize;
694 
695         for (int i = 0; i < compressedData.length; i += chunkSize)
696         {
697             auto len = min(chunkSize, compressedData.length - i);
698             // simulate reading from a stream in small chunks
699             reusedBuf[0 .. len] = compressedData[i .. i + len];
700 
701             // decompress using same input buffer
702             auto chunk = decompressor.uncompress(reusedBuf);
703             assert(progress + chunk.length <= originalData.length,
704                     "The uncompressed result is bigger than the original data");
705 
706             uncompressedData[progress .. progress + chunk.length] = cast(const ubyte[]) chunk[];
707             progress += chunk.length;
708         }
709 
710         auto chunk = decompressor.flush();
711         assert(progress + chunk.length <= originalData.length,
712                 "The uncompressed result is bigger than the original data");
713 
714         uncompressedData[progress .. progress + chunk.length] = cast(const ubyte[]) chunk[];
715         progress += chunk.length;
716 
717         assert(progress == originalData.length,
718                 "The uncompressed and the original data sizes differ");
719         assert(originalData[] == uncompressedData[],
720                 "The uncompressed and the original data differ");
721     }
722 
723     @system unittest
724     {
725         ubyte[1024] invalidData;
726         auto decompressor = new UnCompress();
727 
728         try
729         {
730             auto uncompressedData = decompressor.uncompress(invalidData);
731         }
732         catch (ZlibException e)
733         {
734             assert(e.msg == "data error");
735             return;
736         }
737 
738         assert(false, "Corrupted data didn't result in an error");
739     }
740 
741     @system unittest
742     {
743         ubyte[2014] originalData = void;
744         auto compressedData = compress(originalData, 9);
745 
746         auto decompressor = new UnCompress();
747         auto uncompressedData = decompressor.uncompress(compressedData ~ cast(ubyte[]) "whatever");
748 
749         assert(originalData.length == uncompressedData.length,
750                 "The uncompressed and the original data sizes differ");
751         assert(originalData[] == uncompressedData[],
752                 "The uncompressed and the original data differ");
753         assert(!decompressor.uncompress("whatever").length,
754                 "Compression continued after the end");
755     }
756 
757     /**
758      * Decompress and return any remaining data.
759      * The returned data should be appended to that returned by uncompress().
760      * The UnCompress object cannot be used further.
761      */
762     void[] flush()
763     in
764     {
765         assert(!done, "Buffer has been flushed before.");
766     }
767     out
768     {
769         assert(done, "Flushing failed.");
770     }
771     do
772     {
773         done = 1;
774         return null;
775     }
776 
777     /// Returns true if all input data has been decompressed and no further data
778     /// can be decompressed (inflate() returned Z_STREAM_END)
779     @property bool empty() const
780     {
781         return inputEnded;
782     }
783 
784     ///
785     @system unittest
786     {
787         // some random data
788         ubyte[1024] originalData = void;
789 
790         // append garbage data (or don't, this works in both cases)
791         auto compressedData = cast(ubyte[]) compress(originalData) ~ cast(ubyte[]) "whatever";
792 
793         auto decompressor = new UnCompress();
794         auto uncompressedData = decompressor.uncompress(compressedData);
795 
796         assert(uncompressedData[] == originalData[],
797                 "The uncompressed and the original data differ");
798         assert(decompressor.empty, "The UnCompressor reports not being done");
799     }
800 }
801 
802 /* ========================== unittest ========================= */
803 
804 import std.random;
805 import std.stdio;
806 
807 @system unittest // by Dave
808 {
809     debug(zlib) writeln("std.zlib.unittest");
810 
811     bool CompressThenUncompress (void[] src)
812     {
813         ubyte[] dst = std.zlib.compress(src);
814         double ratio = (dst.length / cast(double) src.length);
815         debug(zlib) writef("src.length: %1$d, dst: %2$d, Ratio = %3$f", src.length, dst.length, ratio);
816         ubyte[] uncompressedBuf;
817         uncompressedBuf = cast(ubyte[]) std.zlib.uncompress(dst);
818         assert(src.length == uncompressedBuf.length);
819         assert(src == uncompressedBuf);
820 
821         return true;
822     }
823 
824 
825     // smallish buffers
826     for (int idx = 0; idx < 25; idx++)
827     {
828         char[] buf = new char[uniform(0, 100)];
829 
830         // Alternate between more & less compressible
831         foreach (ref char c; buf)
832             c = cast(char) (' ' + (uniform(0, idx % 2 ? 91 : 2)));
833 
834         if (CompressThenUncompress(buf))
835         {
836             debug(zlib) writeln("; Success.");
837         }
838         else
839         {
840             return;
841         }
842     }
843 
844     // larger buffers
845     for (int idx = 0; idx < 25; idx++)
846     {
847         char[] buf = new char[uniform(0, 1000/*0000*/)];
848 
849         // Alternate between more & less compressible
850         foreach (ref char c; buf)
851             c = cast(char) (' ' + (uniform(0, idx % 2 ? 91 : 10)));
852 
853         if (CompressThenUncompress(buf))
854         {
855             debug(zlib) writefln("; Success.");
856         }
857         else
858         {
859             return;
860         }
861     }
862 
863     debug(zlib) writefln("PASSED std.zlib.unittest");
864 }
865 
866 
867 @system unittest // by Artem Rebrov
868 {
869     Compress cmp = new Compress;
870     UnCompress decmp = new UnCompress;
871 
872     const(void)[] input;
873     input = "tesatdffadf";
874 
875     const(void)[] buf = cmp.compress(input);
876     buf ~= cmp.flush();
877     const(void)[] output = decmp.uncompress(buf);
878 
879     //writefln("input = '%s'", cast(char[]) input);
880     //writefln("output = '%s'", cast(char[]) output);
881     assert( output[] == input[] );
882 }
883 
884 // https://issues.dlang.org/show_bug.cgi?id=15457
885 @system unittest
886 {
887     static assert(__traits(compiles, etc.c.zlib.gzclose(null)));
888 }