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 }