1 /******************************************************************************* 2 * 3 * copyright: Copyright (c) 2007 Daniel Keep. All rights reserved. 4 * 5 * license: BSD style: $(LICENSE) 6 * 7 * version: Initial release: December 2007 8 * 9 * author: Daniel Keep 10 * 11 ******************************************************************************/ 12 13 module tango.util.compress.Zip; 14 15 /* 16 17 TODO 18 ==== 19 20 * Disable UTF encoding until I've worked out what version of Zip that's 21 related to... (actually; it's entirely possible that's it's merely a 22 *proposal* at the moment.) (*Done*) 23 24 * Make ZipEntry safe: make them aware that their creating reader has been 25 destroyed. 26 27 */ 28 29 import tango.core.ByteSwap : ByteSwap; 30 import tango.io.device.Array : Array; 31 import tango.io.device.File : File; 32 import Path = tango.io.Path; 33 import tango.io.device.FileMap : FileMap; 34 import tango.io.stream.Zlib : ZlibInput, ZlibOutput; 35 import tango.util.digest.Crc32 : Crc32; 36 import tango.io.model.IConduit : IConduit, InputStream, OutputStream; 37 import tango.io.stream.Digester : DigestInput; 38 import tango.time.Time : Time, TimeSpan; 39 import tango.time.WallClock : WallClock; 40 import tango.time.chrono.Gregorian : Gregorian; 41 42 import Integer = tango.text.convert.Integer; 43 44 debug(Zip) import tango.io.Stdout : Stderr; 45 46 ////////////////////////////////////////////////////////////////////////////// 47 ////////////////////////////////////////////////////////////////////////////// 48 // 49 // Implementation crap 50 // 51 // Why is this here, you ask? Because of bloody DMD forward reference bugs. 52 // For pete's sake, Walter, FIX THEM, please! 53 // 54 // To skip to the actual user-visible stuff, search for "Shared stuff". 55 56 private 57 { 58 59 ////////////////////////////////////////////////////////////////////////////// 60 ////////////////////////////////////////////////////////////////////////////// 61 // 62 // LocalFileHeader 63 // 64 65 align(1) 66 struct LocalFileHeaderData 67 { 68 align(1): 69 ushort extract_version = ushort.max; 70 ushort general_flags = 0; 71 ushort compression_method = 0; 72 ushort modification_file_time = 0; 73 ushort modification_file_date = 0; 74 uint crc_32 = 0; // offsetof = 10 75 uint compressed_size = 0; 76 uint uncompressed_size = 0; 77 ushort file_name_length = 0; 78 ushort extra_field_length = 0; 79 80 debug(Zip) void dump() 81 { 82 Stderr 83 ("LocalFileHeader.Data {")("\n") 84 (" extract_version = ")(extract_version)("\n") 85 (" general_flags = ")(general_flags)("\n") 86 (" compression_method = ")(compression_method)("\n") 87 (" modification_file_time = ")(modification_file_time)("\n") 88 (" modification_file_date = ")(modification_file_date)("\n") 89 (" crc_32 = ")(crc_32)("\n") 90 (" compressed_size = ")(compressed_size)("\n") 91 (" uncompressed_size = ")(uncompressed_size)("\n") 92 (" file_name_length = ")(file_name_length)("\n") 93 (" extra_field_length = ")(extra_field_length)("\n") 94 ("}").newline; 95 } 96 } 97 98 struct LocalFileHeader 99 { 100 enum uint signature = 0x04034b50; 101 102 alias LocalFileHeaderData Data; 103 Data data; 104 static assert( Data.sizeof == 26 ); 105 106 char[] file_name; 107 ubyte[] extra_field; 108 109 void[] data_arr() 110 { 111 return (&data)[0..1]; 112 } 113 114 void put(OutputStream output) 115 { 116 // Make sure var-length fields will fit. 117 if( file_name.length > ushort.max ) 118 ZipException.fntoolong; 119 120 if( extra_field.length > ushort.max ) 121 ZipException.eftoolong; 122 123 // Encode filename 124 auto file_name = utf8_to_cp437(this.file_name); 125 scope(exit) if( file_name !is cast(ubyte[])this.file_name ) 126 delete file_name; 127 128 if( file_name is null ) 129 ZipException.fnencode; 130 131 // Update lengths in data 132 Data data = this.data; 133 data.file_name_length = cast(ushort) file_name.length; 134 data.extra_field_length = cast(ushort) extra_field.length; 135 136 // Do it 137 version( BigEndian ) swapAll(data); 138 writeExact(output, (&data)[0..1]); 139 writeExact(output, file_name); 140 writeExact(output, extra_field); 141 } 142 143 void fill(InputStream src) 144 { 145 readExact(src, data_arr()); 146 version( BigEndian ) swapAll(data); 147 148 //debug(Zip) data.dump; 149 150 auto tmp = new ubyte[data.file_name_length]; 151 readExact(src, tmp); 152 file_name = cp437_to_utf8(tmp); 153 if( cast(char*) tmp.ptr !is file_name.ptr ) delete tmp; 154 155 extra_field = new ubyte[data.extra_field_length]; 156 readExact(src, extra_field); 157 } 158 159 /* 160 * This method will check to make sure that the local and central headers 161 * are the same; if they're not, then that indicates that the archive is 162 * corrupt. 163 */ 164 bool agrees_with(FileHeader h) 165 { 166 // NOTE: extra_field used to be compared with h.extra_field, but this caused 167 // an assertion in certain archives. I found a mention of these fields being 168 // allowed to be different, so I think it in general is wrong to include in 169 // this sanity check. larsivi 20081111 170 if( data.extract_version != h.data.extract_version 171 || data.general_flags != h.data.general_flags 172 || data.compression_method != h.data.compression_method 173 || data.modification_file_time != h.data.modification_file_time 174 || data.modification_file_date != h.data.modification_file_date 175 || file_name != h.file_name ) 176 return false; 177 178 // We need a separate check for the sizes and crc32, since these will 179 // be zero if a trailing descriptor was used. 180 if( !h.usingDataDescriptor() && ( 181 data.crc_32 != h.data.crc_32 182 || data.compressed_size != h.data.compressed_size 183 || data.uncompressed_size != h.data.uncompressed_size ) ) 184 return false; 185 186 return true; 187 } 188 } 189 190 ////////////////////////////////////////////////////////////////////////////// 191 ////////////////////////////////////////////////////////////////////////////// 192 // 193 // FileHeader 194 // 195 196 align(1) 197 struct FileHeaderData 198 { 199 align(1): 200 ubyte zip_version; 201 ubyte file_attribute_type; 202 ushort extract_version; 203 ushort general_flags; 204 ushort compression_method; 205 ushort modification_file_time; 206 ushort modification_file_date; 207 uint crc_32; 208 uint compressed_size; 209 uint uncompressed_size; 210 ushort file_name_length; 211 ushort extra_field_length; 212 ushort file_comment_length; 213 ushort disk_number_start; 214 ushort internal_file_attributes = 0; 215 uint external_file_attributes = 0; 216 int relative_offset_of_local_header; 217 218 debug(Zip) void dump() 219 { 220 Stderr 221 ("FileHeader.Data {\n") 222 (" zip_version = ")(zip_version)("\n") 223 (" file_attribute_type = ")(file_attribute_type)("\n") 224 (" extract_version = ")(extract_version)("\n") 225 (" general_flags = ")(general_flags)("\n") 226 (" compression_method = ")(compression_method)("\n") 227 (" modification_file_time = ")(modification_file_time)("\n") 228 (" modification_file_date = ")(modification_file_date)("\n") 229 (" crc_32 = ")(crc_32)("\n") 230 (" compressed_size = ")(compressed_size)("\n") 231 (" uncompressed_size = ")(uncompressed_size)("\n") 232 (" file_name_length = ")(file_name_length)("\n") 233 (" extra_field_length = ")(extra_field_length)("\n") 234 (" file_comment_length = ")(file_comment_length)("\n") 235 (" disk_number_start = ")(disk_number_start)("\n") 236 (" internal_file_attributes = ")(internal_file_attributes)("\n") 237 (" external_file_attributes = ")(external_file_attributes)("\n") 238 (" relative_offset_of_local_header = ")(relative_offset_of_local_header) 239 ("\n") 240 ("}").newline; 241 } 242 243 void fromLocal(LocalFileHeader.Data data) 244 { 245 extract_version = data.extract_version; 246 general_flags = data.general_flags; 247 compression_method = data.compression_method; 248 modification_file_time = data.modification_file_time; 249 modification_file_date = data.modification_file_date; 250 crc_32 = data.crc_32; 251 compressed_size = data.compressed_size; 252 uncompressed_size = data.uncompressed_size; 253 file_name_length = data.file_name_length; 254 extra_field_length = data.extra_field_length; 255 } 256 } 257 258 struct FileHeader 259 { 260 enum uint signature = 0x02014b50; 261 262 alias FileHeaderData Data; 263 Data* data; 264 static assert( Data.sizeof == 42 ); 265 266 const(char)[] file_name; 267 ubyte[] extra_field; 268 const(char)[] file_comment; 269 270 bool usingDataDescriptor() 271 { 272 return !!(data.general_flags & 1<<3); 273 } 274 275 uint compressionOptions() 276 { 277 return (data.general_flags >> 1) & 0b11; 278 } 279 280 bool usingUtf8() 281 { 282 //return !!(data.general_flags & 1<<11); 283 return false; 284 } 285 286 void[] data_arr() 287 { 288 return (cast(void*)data)[0 .. Data.sizeof]; 289 } 290 291 void put(OutputStream output) 292 { 293 // Make sure the var-length fields will fit. 294 if( file_name.length > ushort.max ) 295 ZipException.fntoolong; 296 297 if( extra_field.length > ushort.max ) 298 ZipException.eftoolong; 299 300 if( file_comment.length > ushort.max ) 301 ZipException.cotoolong; 302 303 // encode the filename and comment 304 auto file_name = utf8_to_cp437(this.file_name); 305 scope(exit) if( file_name !is cast(ubyte[])this.file_name ) 306 delete file_name; 307 auto file_comment = utf8_to_cp437(this.file_comment); 308 scope(exit) if( file_comment !is cast(ubyte[])this.file_comment ) 309 delete file_comment; 310 311 if( file_name is null ) 312 ZipException.fnencode; 313 314 if( file_comment is null && this.file_comment !is null ) 315 ZipException.coencode; 316 317 // Update the lengths 318 Data data = *(this.data); 319 data.file_name_length = cast(ushort) file_name.length; 320 data.extra_field_length = cast(ushort) extra_field.length; 321 data.file_comment_length = cast(ushort) file_comment.length; 322 323 // Ok; let's do this! 324 version( BigEndian ) swapAll(data); 325 writeExact(output, (&data)[0..1]); 326 writeExact(output, file_name); 327 writeExact(output, extra_field); 328 writeExact(output, file_comment); 329 } 330 331 long map(void[] src) 332 { 333 //debug(Zip) Stderr.formatln("FileHeader.map([0..{}])",src.length); 334 335 auto old_ptr = src.ptr; 336 337 data = cast(Data*) src.ptr; 338 src = src[Data.sizeof..$]; 339 version( BigEndian ) swapAll(*data); 340 341 //debug(Zip) data.dump; 342 343 inout(char[]) function(inout(ubyte[])) conv_fn; 344 if( usingUtf8() ) 345 conv_fn = &cp437_to_utf8; 346 else 347 conv_fn = &utf8_to_utf8; 348 349 file_name = conv_fn( 350 cast(ubyte[]) src[0..data.file_name_length]); 351 src = src[data.file_name_length..$]; 352 353 extra_field = cast(ubyte[]) src[0..data.extra_field_length]; 354 src = src[data.extra_field_length..$]; 355 356 file_comment = conv_fn( 357 cast(ubyte[]) src[0..data.file_comment_length]); 358 src = src[data.file_comment_length..$]; 359 360 // Return how many bytes we've eaten 361 //debug(Zip) Stderr.formatln(" . used {} bytes", cast(long)(src.ptr - old_ptr)); 362 return cast(long)(src.ptr - old_ptr); 363 } 364 } 365 366 ////////////////////////////////////////////////////////////////////////////// 367 ////////////////////////////////////////////////////////////////////////////// 368 // 369 // EndOfCDRecord 370 // 371 372 align(1) 373 struct EndOfCDRecordData 374 { 375 align(1): 376 ushort disk_number = 0; 377 ushort disk_with_start_of_central_directory = 0; 378 ushort central_directory_entries_on_this_disk; 379 ushort central_directory_entries_total; 380 uint size_of_central_directory; 381 uint offset_of_start_of_cd_from_starting_disk; 382 ushort file_comment_length; 383 384 debug(Zip) void dump() 385 { 386 Stderr 387 .formatln("EndOfCDRecord.Data {}","{") 388 .formatln(" disk_number = {}", disk_number) 389 .formatln(" disk_with_start_of_central_directory = {}", 390 disk_with_start_of_central_directory) 391 .formatln(" central_directory_entries_on_this_disk = {}", 392 central_directory_entries_on_this_disk) 393 .formatln(" central_directory_entries_total = {}", 394 central_directory_entries_total) 395 .formatln(" size_of_central_directory = {}", 396 size_of_central_directory) 397 .formatln(" offset_of_start_of_cd_from_starting_disk = {}", 398 offset_of_start_of_cd_from_starting_disk) 399 .formatln(" file_comment_length = {}", file_comment_length) 400 .formatln("}"); 401 } 402 } 403 404 struct EndOfCDRecord 405 { 406 enum uint signature = 0x06054b50; 407 408 alias EndOfCDRecordData Data; 409 Data data; 410 static assert( data.sizeof == 18 ); 411 412 char[] file_comment; 413 414 void[] data_arr() 415 { 416 return (cast(void*)&data)[0 .. data.sizeof]; 417 } 418 419 void put(OutputStream output) 420 { 421 // Set up the comment; check length, encode 422 if( file_comment.length > ushort.max ) 423 ZipException.cotoolong; 424 425 auto file_comment = utf8_to_cp437(this.file_comment); 426 scope(exit) if( file_comment !is cast(ubyte[])this.file_comment ) 427 delete file_comment; 428 429 // Set up data block 430 Data data = this.data; 431 data.file_comment_length = cast(ushort) file_comment.length; 432 433 version( BigEndian ) swapAll(data); 434 writeExact(output, (&data)[0..1]); 435 } 436 437 void fill(void[] src) 438 { 439 //Stderr.formatln("EndOfCDRecord.fill([0..{}])",src.length); 440 441 auto _data = data_arr(); 442 _data[] = src[0.._data.length]; 443 src = src[_data.length..$]; 444 version( BigEndian ) swapAll(data); 445 446 //data.dump; 447 448 file_comment = cast(char[]) src[0..data.file_comment_length].dup; 449 } 450 } 451 452 // End of implementation crap 453 } 454 455 ////////////////////////////////////////////////////////////////////////////// 456 ////////////////////////////////////////////////////////////////////////////// 457 // 458 // Shared stuff 459 460 public 461 { 462 /** 463 * This enumeration denotes the kind of compression used on a file. 464 */ 465 enum Method 466 { 467 /// No compression should be used. 468 Store, 469 /// Deflate compression. 470 Deflate, 471 /** 472 * This is a special value used for unsupported or unrecognised 473 * compression methods. This value is only used internally. 474 */ 475 Unsupported 476 } 477 } 478 479 private 480 { 481 const ushort ZIP_VERSION = 20; 482 const ushort MAX_EXTRACT_VERSION = 20; 483 484 /* compression flags 485 uses trailing descriptor | 486 utf-8 encoding | | 487 ^ ^ /\ */ 488 const ushort SUPPORTED_FLAGS = 0b00_0_0_0_0000_0_0_0_1_11_0; 489 const ushort UNSUPPORTED_FLAGS = ~SUPPORTED_FLAGS; 490 491 Method toMethod(ushort method) 492 { 493 switch( method ) 494 { 495 case 0: return Method.Store; 496 case 8: return Method.Deflate; 497 default: return Method.Unsupported; 498 } 499 } 500 501 ushort fromMethod(Method method) 502 { 503 switch( method ) 504 { 505 case Method.Store: return 0; 506 case Method.Deflate: return 8; 507 default: 508 assert(false, "unsupported compression method"); 509 } 510 } 511 512 /* NOTE: This doesn't actually appear to work. Using the default magic 513 * number with Tango's Crc32 digest works, however. 514 */ 515 //const CRC_MAGIC = 0xdebb20e3u; 516 } 517 518 ////////////////////////////////////////////////////////////////////////////// 519 ////////////////////////////////////////////////////////////////////////////// 520 // 521 // ZipReader 522 523 interface ZipReader 524 { 525 bool streamed(); 526 void close(); 527 bool more(); 528 ZipEntry get(); 529 ZipEntry get(ZipEntry); 530 int opApply(int delegate(ref ZipEntry)); 531 } 532 533 ////////////////////////////////////////////////////////////////////////////// 534 ////////////////////////////////////////////////////////////////////////////// 535 // 536 // ZipWriter 537 538 interface ZipWriter 539 { 540 void finish(); 541 void putFile(ZipEntryInfo info, const(char)[] path); 542 void putStream(ZipEntryInfo info, InputStream source); 543 void putEntry(ZipEntryInfo info, ZipEntry entry); 544 void putData(ZipEntryInfo info, const(void)[] data); 545 Method method(); 546 Method method(Method); 547 } 548 549 ////////////////////////////////////////////////////////////////////////////// 550 ////////////////////////////////////////////////////////////////////////////// 551 // 552 // ZipBlockReader 553 554 /** 555 * The ZipBlockReader class is used to parse a Zip archive. It exposes the 556 * contents of the archive via an iteration interface. For instance, to loop 557 * over all files in an archive, one can use either 558 * 559 * ----- 560 * foreach( entry ; reader ) 561 * ... 562 * ----- 563 * 564 * Or 565 * 566 * ----- 567 * while( reader.more ) 568 * { 569 * auto entry = reader.get; 570 * ... 571 * } 572 * ----- 573 * 574 * See the ZipEntry class for more information on the contents of entries. 575 * 576 * Note that this class can only be used with input sources which can be 577 * freely seeked. Also note that you may open a ZipEntry instance produced by 578 * this reader at any time until the ZipReader that created it is closed. 579 */ 580 class ZipBlockReader : ZipReader 581 { 582 /** 583 * Creates a ZipBlockReader using the specified file on the local 584 * filesystem. 585 */ 586 this(const(char)[] path) 587 { 588 file_source = new File(path); 589 this(file_source); 590 } 591 592 /** 593 * Creates a ZipBlockReader using the provided InputStream. Please note 594 * that this InputStream must be attached to a conduit implementing the 595 * IConduit.Seek interface. 596 */ 597 this(InputStream source) 598 in 599 { 600 assert( cast(IConduit.Seek) source.conduit, "source stream must be seekable" ); 601 } 602 body 603 { 604 this.source = source; 605 this.seeker = source; //cast(IConduit.Seek) source; 606 } 607 608 bool streamed() { return false; } 609 610 /** 611 * Closes the reader, and releases all resources. After this operation, 612 * all ZipEntry instances created by this ZipReader are invalid and should 613 * not be used. 614 */ 615 void close() 616 { 617 // NOTE: Originally more of the GC allocated data in this class were 618 // explicitly deleted here, such as cd_data - this caused segfaults 619 // and have been removed as they were not necessary from correctness 620 // point of view, and the memory usage win is questionable. 621 state = State.Done; 622 source = null; 623 seeker = null; 624 delete headers; 625 626 if( file_source !is null ) 627 { 628 file_source.close(); 629 delete file_source; 630 } 631 } 632 633 /** 634 * Returns true if and only if there are additional files in the archive 635 * which have not been read via the get method. This returns true before 636 * the first call to get (assuming the opened archive is non-empty), and 637 * false after the last file has been accessed. 638 */ 639 bool more() 640 { 641 switch( state ) 642 { 643 case State.Init: 644 read_cd(); 645 assert( state == State.Open ); 646 return more(); 647 648 case State.Open: 649 return (current_index < headers.length); 650 651 case State.Done: 652 return false; 653 654 default: 655 assert(false); 656 } 657 } 658 659 /** 660 * Retrieves the next file from the archive. Note that although this does 661 * perform IO operations, it will not read the contents of the file. 662 * 663 * The optional reuse argument can be used to instruct the reader to reuse 664 * an existing ZipEntry instance. If passed a null reference, it will 665 * create a new ZipEntry instance. 666 */ 667 ZipEntry get() 668 { 669 if( !more() ) 670 ZipExhaustedException(); 671 672 return new ZipEntry(headers[current_index++], &open_file); 673 } 674 675 /// ditto 676 ZipEntry get(ZipEntry reuse) 677 { 678 if( !more() ) 679 ZipExhaustedException(); 680 681 if( reuse is null ) 682 return new ZipEntry(headers[current_index++], &open_file); 683 else 684 return reuse.reset(headers[current_index++], &open_file); 685 } 686 687 /** 688 * This is used to iterate over the contents of an archive using a foreach 689 * loop. Please note that the iteration will reuse the ZipEntry instance 690 * passed to your loop. If you wish to keep the instance and re-use it 691 * later, you $(B must) use the dup member to create a copy. 692 */ 693 int opApply(int delegate(ref ZipEntry) dg) 694 { 695 int result = 0; 696 ZipEntry entry; 697 698 while( more() ) 699 { 700 entry = get(entry); 701 702 result = dg(entry); 703 if( result ) 704 break; 705 } 706 707 if( entry !is null ) 708 delete entry; 709 710 return result; 711 } 712 713 private: 714 InputStream source; 715 InputStream seeker; //IConduit.Seek seeker; 716 717 enum State { Init, Open, Done } 718 State state; 719 size_t current_index = 0; 720 FileHeader[] headers; 721 722 // These should be killed when the reader is closed. 723 ubyte[] cd_data; 724 File file_source = null; 725 726 /* 727 * This function will read the contents of the central directory. Split 728 * or spanned archives aren't supported. 729 */ 730 void read_cd() 731 in 732 { 733 assert( state == State.Init ); 734 assert( headers is null ); 735 assert( cd_data is null ); 736 } 737 out 738 { 739 assert( state == State.Open ); 740 assert( headers !is null ); 741 assert( cd_data !is null ); 742 assert( current_index == 0 ); 743 } 744 body 745 { 746 //Stderr.formatln("ZipReader.read_cd()"); 747 748 // First, we need to locate the end of cd record, so that we know 749 // where the cd itself is, and how big it is. 750 auto eocdr = read_eocd_record(); 751 752 // Now, make sure the archive is all in one file. 753 if( eocdr.data.disk_number != 754 eocdr.data.disk_with_start_of_central_directory 755 || eocdr.data.central_directory_entries_on_this_disk != 756 eocdr.data.central_directory_entries_total ) 757 ZipNotSupportedException.spanned(); 758 759 // Ok, read the whole damn thing in one go. 760 cd_data = new ubyte[eocdr.data.size_of_central_directory]; 761 long cd_offset = eocdr.data.offset_of_start_of_cd_from_starting_disk; 762 seeker.seek(cd_offset, seeker.Anchor.Begin); 763 readExact(source, cd_data); 764 765 // Cake. Now, we need to break it up into records. 766 headers = new FileHeader[ 767 eocdr.data.central_directory_entries_total]; 768 769 long cdr_offset = cd_offset; 770 771 // Ok, map the CD data into file headers. 772 foreach( i,ref header ; headers ) 773 { 774 //Stderr.formatln(" . reading header {}...", i); 775 776 // Check signature 777 { 778 uint sig = (cast(uint[])(cd_data[0..4]))[0]; 779 version( BigEndian ) swap(sig); 780 if( sig != FileHeader.signature ) 781 ZipException.badsig("file header"); 782 } 783 784 auto used = header.map(cd_data[4..$]); 785 assert( used <= (size_t.max-4) ); 786 cd_data = cd_data[4+cast(size_t)used..$]; 787 788 // Update offset for next record 789 cdr_offset += 4 /* for sig. */ + used; 790 } 791 792 // Done! 793 state = State.Open; 794 } 795 796 /* 797 * This will locate the end of CD record in the open stream. 798 * 799 * This code sucks, but that's because Zip sucks. 800 * 801 * Basically, the EOCD record is stuffed somewhere at the end of the file. 802 * In a brilliant move, the record is *variably sized*, which means we 803 * have to do a linear backwards search to find it. 804 * 805 * The header itself (including the signature) is at minimum 22 bytes 806 * long, plus anywhere between 0 and 2^16-1 bytes of comment. That means 807 * we need to read the last 2^16-1 + 22 bytes from the file, and look for 808 * the signature [0x50,0x4b,0x05,0x06] in [0 .. $-18]. 809 * 810 * If we find the EOCD record, we'll return its contents. If we couldn't 811 * find it, we'll throw an exception. 812 */ 813 EndOfCDRecord read_eocd_record() 814 in 815 { 816 assert( state == State.Init ); 817 } 818 body 819 { 820 //Stderr.formatln("read_eocd_record()"); 821 822 // Signature + record + max. comment length 823 const max_chunk_len = 4 + EndOfCDRecord.Data.sizeof + ushort.max; 824 825 auto file_len = seeker.seek(0, seeker.Anchor.End); 826 assert( file_len <= size_t.max ); 827 828 // We're going to need min(max_chunk_len, file_len) bytes. 829 size_t chunk_len = max_chunk_len; 830 if( file_len < max_chunk_len ) 831 chunk_len = cast(size_t) file_len; 832 //Stderr.formatln(" . chunk_len = {}", chunk_len); 833 834 // Seek back and read in the chunk. Don't forget to clean up after 835 // ourselves. 836 seeker.seek(-cast(long)chunk_len, seeker.Anchor.End); 837 auto chunk_offset = seeker.seek(0, seeker.Anchor.Current); 838 //Stderr.formatln(" . chunk_offset = {}", chunk_offset); 839 auto chunk = new ubyte[chunk_len]; 840 scope(exit) delete chunk; 841 readExact(source, chunk); 842 843 // Now look for our magic number. Don't forget that on big-endian 844 // machines, we need to byteswap the value we're looking for. 845 uint eocd_magic = EndOfCDRecord.signature; 846 version( BigEndian ) 847 swap(eocd_magic); 848 849 size_t eocd_loc = -1; 850 851 if( chunk_len >= 18 ) 852 for( size_t i=chunk_len-18; i>=0; --i ) 853 { 854 if( *(cast(uint*)(chunk.ptr+i)) == eocd_magic ) 855 { 856 // Found the bugger! Make sure we skip the signature (forgot 857 // to do that originally; talk about weird errors :P) 858 eocd_loc = i+4; 859 break; 860 } 861 } 862 863 // If we didn't find it, then we'll assume that this is not a valid 864 // archive. 865 if( eocd_loc == -1 ) 866 ZipException.missingdir; 867 868 // Ok, so we found it; now what? Now we need to read the record 869 // itself in. eocd_loc is the offset within the chunk where the eocd 870 // record was found, so slice it out. 871 EndOfCDRecord eocdr; 872 eocdr.fill(chunk[eocd_loc..$]); 873 874 // Excellent. We're done here. 875 return eocdr; 876 } 877 878 /* 879 * Opens the specified file for reading. If the raw argument passed is 880 * true, then the file is *not* decompressed. 881 */ 882 InputStream open_file(FileHeader header, bool raw) 883 { 884 // Check to make sure that we actually *can* open this file. 885 if( header.data.extract_version > MAX_EXTRACT_VERSION ) 886 ZipNotSupportedException.zipver(header.data.extract_version); 887 888 if( header.data.general_flags & UNSUPPORTED_FLAGS ) 889 ZipNotSupportedException.flags(); 890 891 if( toMethod(header.data.compression_method) == Method.Unsupported ) 892 ZipNotSupportedException.method(header.data.compression_method); 893 894 // Open a raw stream 895 InputStream stream = open_file_raw(header); 896 897 // If that's all they wanted, pass it back. 898 if( raw ) 899 return stream; 900 901 // Next up, wrap in an appropriate decompression stream 902 switch( toMethod(header.data.compression_method) ) 903 { 904 case Method.Store: 905 // Do nothing: \o/ 906 break; 907 908 case Method.Deflate: 909 // Wrap in a zlib stream. We want a raw deflate stream, 910 // so force no encoding. 911 stream = new ZlibInput(stream, ZlibInput.Encoding.None); 912 break; 913 914 default: 915 assert(false); 916 } 917 918 // We done, yo! 919 return stream; 920 } 921 922 /* 923 * Opens a file's raw input stream. Basically, this returns a slice of 924 * the archive's input stream. 925 */ 926 InputStream open_file_raw(FileHeader header) 927 { 928 // Seek to and parse the local file header 929 seeker.seek(header.data.relative_offset_of_local_header, 930 seeker.Anchor.Begin); 931 932 { 933 uint sig; 934 readExact(source, (&sig)[0..1]); 935 version( BigEndian ) swap(sig); 936 if( sig != LocalFileHeader.signature ) 937 ZipException.badsig("local file header"); 938 } 939 940 LocalFileHeader lheader; lheader.fill(source); 941 942 if( !lheader.agrees_with(header) ) 943 ZipException.incons(header.file_name); 944 945 // Ok; get a slice stream for the file 946 return new SliceSeekInputStream( 947 source, seeker.seek(0, seeker.Anchor.Current), 948 header.data.compressed_size); 949 } 950 } 951 952 ////////////////////////////////////////////////////////////////////////////// 953 ////////////////////////////////////////////////////////////////////////////// 954 // 955 // ZipBlockWriter 956 957 /** 958 * The ZipBlockWriter class is used to create a Zip archive. It uses a 959 * writing iterator interface. 960 * 961 * Note that this class can only be used with output streams which can be 962 * freely seeked. 963 */ 964 965 class ZipBlockWriter : ZipWriter 966 { 967 /** 968 * Creates a ZipBlockWriter using the specified file on the local 969 * filesystem. 970 */ 971 this(const(char)[] path) 972 { 973 file_output = new File(path, File.WriteCreate); 974 this(file_output); 975 } 976 977 /** 978 * Creates a ZipBlockWriter using the provided OutputStream. Please note 979 * that this OutputStream must be attached to a conduit implementing the 980 * IConduit.Seek interface. 981 */ 982 this(OutputStream output) 983 in 984 { 985 assert( output !is null ); 986 assert( (cast(IConduit.Seek) output.conduit) !is null ); 987 } 988 body 989 { 990 this.output = output; 991 this.seeker = output; // cast(IConduit.Seek) output; 992 993 // Default to Deflate compression 994 method = Method.Deflate; 995 } 996 997 /** 998 * Finalises the archive, writes out the central directory, and closes the 999 * output stream. 1000 */ 1001 void finish() 1002 { 1003 put_cd(); 1004 output.close(); 1005 output = null; 1006 seeker = null; 1007 1008 if( file_output !is null ) delete file_output; 1009 } 1010 1011 /** 1012 * Adds a file from the local filesystem to the archive. 1013 */ 1014 void putFile(ZipEntryInfo info, const(char)[] path) 1015 { 1016 scope file = new File(path); 1017 scope(exit) file.close(); 1018 putStream(info, file); 1019 } 1020 1021 /** 1022 * Adds a file using the contents of the given InputStream to the archive. 1023 */ 1024 void putStream(ZipEntryInfo info, InputStream source) 1025 { 1026 put_compressed(info, source); 1027 } 1028 1029 /** 1030 * Transfers a file from another archive into this archive. Note that 1031 * this method will not perform any compression: whatever compression was 1032 * applied to the file originally will be preserved. 1033 */ 1034 void putEntry(ZipEntryInfo info, ZipEntry entry) 1035 { 1036 put_raw(info, entry); 1037 } 1038 1039 /** 1040 * Adds a file using the contents of the given array to the archive. 1041 */ 1042 void putData(ZipEntryInfo info, const(void)[] data) 1043 { 1044 //scope mc = new MemoryConduit(data); 1045 scope mc = new Array(data.dup); 1046 scope(exit) mc.close(); 1047 put_compressed(info, mc); 1048 } 1049 1050 /** 1051 * This property allows you to control what compression method should be 1052 * used for files being added to the archive. 1053 */ 1054 @property 1055 Method method() { return _method; } 1056 @property 1057 Method method(Method v) { return _method = v; } /// ditto 1058 1059 private: 1060 OutputStream output; 1061 OutputStream seeker; 1062 File file_output; 1063 1064 Method _method; 1065 1066 struct Entry 1067 { 1068 FileHeaderData data; 1069 long header_position; 1070 const(char)[] filename; 1071 const(char)[] comment; 1072 ubyte[] extra; 1073 } 1074 Entry[] entries; 1075 1076 void put_cd() 1077 { 1078 // check that there aren't too many CD entries 1079 if( entries.length > ushort.max ) 1080 ZipException.toomanyentries; 1081 1082 auto cd_pos = seeker.seek(0, seeker.Anchor.Current); 1083 if( cd_pos > uint.max ) 1084 ZipException.toolong; 1085 1086 foreach( entry ; entries ) 1087 { 1088 FileHeader header; 1089 header.data = &entry.data; 1090 header.file_name = entry.filename; 1091 header.extra_field = entry.extra; 1092 header.file_comment = entry.comment; 1093 1094 write(output, FileHeader.signature); 1095 header.put(output); 1096 } 1097 1098 auto cd_len = seeker.seek(0, seeker.Anchor.Current) - cd_pos; 1099 1100 if( cd_len > uint.max ) 1101 ZipException.cdtoolong; 1102 1103 { 1104 assert( entries.length < ushort.max ); 1105 assert( cd_len < uint.max ); 1106 assert( cd_pos < uint.max ); 1107 1108 EndOfCDRecord eocdr; 1109 eocdr.data.central_directory_entries_on_this_disk = 1110 cast(ushort) entries.length; 1111 eocdr.data.central_directory_entries_total = 1112 cast(ushort) entries.length; 1113 eocdr.data.size_of_central_directory = 1114 cast(uint) cd_len; 1115 eocdr.data.offset_of_start_of_cd_from_starting_disk = 1116 cast(uint) cd_pos; 1117 1118 write(output, EndOfCDRecord.signature); 1119 eocdr.put(output); 1120 } 1121 } 1122 1123 void put_raw(ZipEntryInfo info, ZipEntry entry) 1124 { 1125 // Write out local file header 1126 LocalFileHeader.Data lhdata; 1127 auto chdata = entry.header.data; 1128 lhdata.extract_version = chdata.extract_version; 1129 1130 // Note: we need to mask off the data descriptor bit because we aren't 1131 // going to write one. 1132 lhdata.general_flags = chdata.general_flags & ~(1<<3); 1133 lhdata.compression_method = chdata.compression_method; 1134 lhdata.crc_32 = chdata.crc_32; 1135 lhdata.compressed_size = chdata.compressed_size; 1136 lhdata.uncompressed_size = chdata.uncompressed_size; 1137 1138 timeToDos(info.modified, lhdata.modification_file_time, 1139 lhdata.modification_file_date); 1140 1141 put_local_header(lhdata, info.name); 1142 1143 // Store comment 1144 entries[$-1].comment = info.comment; 1145 1146 // Output file contents 1147 { 1148 auto input = entry.open_raw(); 1149 scope(exit) input.close(); 1150 output.copy(input).flush(); 1151 } 1152 } 1153 1154 void put_compressed(ZipEntryInfo info, InputStream source) 1155 { 1156 debug(Zip) Stderr.formatln("ZipBlockWriter.put_compressed()"); 1157 1158 // Write out partial local file header 1159 auto header_pos = seeker.seek(0, seeker.Anchor.Current); 1160 debug(Zip) Stderr.formatln(" . header for {} at {}", info.name, header_pos); 1161 put_local_header(info, _method); 1162 1163 // Store comment 1164 entries[$-1].comment = info.comment; 1165 1166 uint crc; 1167 uint compressed_size; 1168 uint uncompressed_size; 1169 1170 // Output file contents 1171 { 1172 // Input/output chains 1173 InputStream in_chain = source; 1174 OutputStream out_chain = new WrapSeekOutputStream(output); 1175 1176 // Count number of bytes coming in from the source file 1177 scope in_counter = new CounterInput(in_chain); 1178 in_chain = in_counter; 1179 assert( in_counter.count() <= typeof(uncompressed_size).max ); 1180 scope(success) uncompressed_size = cast(uint) in_counter.count(); 1181 1182 // Count the number of bytes going out to the archive 1183 scope out_counter = new CounterOutput(out_chain); 1184 out_chain = out_counter; 1185 assert( out_counter.count() <= typeof(compressed_size).max ); 1186 scope(success) compressed_size = cast(uint) out_counter.count(); 1187 1188 // Add crc 1189 scope crc_d = new Crc32(/*CRC_MAGIC*/); 1190 scope crc_s = new DigestInput(in_chain, crc_d); 1191 in_chain = crc_s; 1192 scope(success) 1193 { 1194 debug(Zip) Stderr.formatln(" . Success: storing CRC."); 1195 crc = crc_d.crc32Digest(); 1196 } 1197 1198 // Add compression 1199 ZlibOutput compress; 1200 scope(exit) if( compress !is null ) delete compress; 1201 1202 switch( _method ) 1203 { 1204 case Method.Store: 1205 break; 1206 1207 case Method.Deflate: 1208 compress = new ZlibOutput(out_chain, 1209 ZlibOutput.Level.init, ZlibOutput.Encoding.None); 1210 out_chain = compress; 1211 break; 1212 1213 default: 1214 assert(false); 1215 } 1216 1217 // All done. 1218 scope(exit) in_chain.close(); 1219 scope(success) in_chain.flush(); 1220 scope(exit) out_chain.close(); 1221 1222 out_chain.copy(in_chain).flush(); 1223 1224 debug(Zip) if( compress !is null ) 1225 { 1226 Stderr.formatln(" . compressed to {} bytes", compress.written); 1227 } 1228 1229 debug(Zip) Stderr.formatln(" . wrote {} bytes", out_counter.count); 1230 debug(Zip) Stderr.formatln(" . contents written"); 1231 } 1232 1233 debug(Zip) Stderr.formatln(" . CRC for \"{}\": 0x{:x8}", info.name, crc); 1234 1235 // Rewind, and patch the header 1236 auto final_pos = seeker.seek(0, seeker.Anchor.Current); 1237 seeker.seek(header_pos); 1238 patch_local_header(crc, compressed_size, uncompressed_size); 1239 1240 // Seek back to the end of the file, and we're done! 1241 seeker.seek(final_pos); 1242 } 1243 1244 /* 1245 * Patches the local file header starting at the current output location 1246 * with updated crc and size information. Also updates the current last 1247 * Entry. 1248 */ 1249 void patch_local_header(uint crc_32, uint compressed_size, 1250 uint uncompressed_size) 1251 { 1252 /* BUG: For some reason, this code won't compile. No idea why... if 1253 * you instantiate LFHD, it says that there is no "offsetof" property. 1254 */ 1255 /+ 1256 alias LocalFileHeaderData LFHD; 1257 static assert( LFHD.compressed_size.offsetof 1258 == LFHD.crc_32.offsetof + 4 ); 1259 static assert( LFHD.uncompressed_size.offsetof 1260 == LFHD.compressed_size.offsetof + 4 ); 1261 +/ 1262 1263 // Don't forget we have to seek past the signature, too 1264 // BUG: .offsetof is broken here 1265 /+seeker.seek(LFHD.crc_32.offsetof+4, seeker.Anchor.Current);+/ 1266 seeker.seek(10+4, seeker.Anchor.Current); 1267 write(output, crc_32); 1268 write(output, compressed_size); 1269 write(output, uncompressed_size); 1270 1271 with( entries[$-1] ) 1272 { 1273 data.crc_32 = crc_32; 1274 data.compressed_size = compressed_size; 1275 data.uncompressed_size = uncompressed_size; 1276 } 1277 } 1278 1279 /* 1280 * Generates and outputs a local file header from the given info block and 1281 * compression method. Note that the crc_32, compressed_size and 1282 * uncompressed_size header fields will be set to zero, and must be 1283 * patched. 1284 */ 1285 void put_local_header(ZipEntryInfo info, Method method) 1286 { 1287 LocalFileHeader.Data data; 1288 1289 data.compression_method = fromMethod(method); 1290 timeToDos(info.modified, data.modification_file_time, 1291 data.modification_file_date); 1292 1293 put_local_header(data, info.name); 1294 } 1295 1296 /* 1297 * Writes the given local file header data and filename out to the output 1298 * stream. It also appends a new Entry with the data and filename. 1299 */ 1300 void put_local_header(LocalFileHeaderData data, 1301 const(char)[] file_name) 1302 { 1303 auto f_name = Path.normalize(file_name); 1304 auto p = Path.parse(f_name); 1305 1306 // Compute Zip version 1307 if( data.extract_version == data.extract_version.max ) 1308 { 1309 1310 ushort zipver = 10; 1311 void minver(ushort v) { zipver = v>zipver ? v : zipver; } 1312 1313 { 1314 // Compression method 1315 switch( data.compression_method ) 1316 { 1317 case 0: minver(10); break; 1318 case 8: minver(20); break; 1319 default: 1320 assert(false); 1321 } 1322 1323 // File is a folder 1324 if( f_name.length > 0 && f_name[$-1] == '/' ) 1325 // Is a directory, not a real file 1326 minver(20); 1327 } 1328 data.extract_version = zipver; 1329 } 1330 1331 /+// Encode filename 1332 auto file_name_437 = utf8_to_cp437(file_name); 1333 if( file_name_437 is null ) 1334 ZipException.fnencode;+/ 1335 1336 /+// Set up file name length 1337 if( file_name_437.length > ushort.max ) 1338 ZipException.fntoolong; 1339 1340 data.file_name_length = file_name_437.length;+/ 1341 1342 LocalFileHeader header; 1343 header.data = data; 1344 if (p.isAbsolute) 1345 f_name = f_name[p.root.length+1..$]; 1346 header.file_name = f_name; 1347 1348 // Write out the header and the filename 1349 auto header_pos = seeker.seek(0, seeker.Anchor.Current); 1350 1351 write(output, LocalFileHeader.signature); 1352 header.put(output); 1353 1354 // Save the header 1355 assert( header_pos <= int.max ); 1356 Entry entry; 1357 entry.data.fromLocal(header.data); 1358 entry.filename = header.file_name; 1359 entry.header_position = header_pos; 1360 entry.data.relative_offset_of_local_header = cast(int) header_pos; 1361 entries ~= entry; 1362 } 1363 } 1364 1365 ////////////////////////////////////////////////////////////////////////////// 1366 ////////////////////////////////////////////////////////////////////////////// 1367 // 1368 // ZipEntry 1369 1370 /** 1371 * This class is used to represent a single entry in an archive. 1372 * Specifically, it combines meta-data about the file (see the info field) 1373 * along with the two basic operations on an entry: open and verify. 1374 */ 1375 class ZipEntry 1376 { 1377 /** 1378 * Header information on the file. See the ZipEntryInfo structure for 1379 * more information. 1380 */ 1381 ZipEntryInfo info; 1382 1383 /** 1384 * Size (in bytes) of the file's uncompressed contents. 1385 */ 1386 uint size() 1387 { 1388 return header.data.uncompressed_size; 1389 } 1390 1391 /** 1392 * Opens a stream for reading from the file. The contents of this stream 1393 * represent the decompressed contents of the file stored in the archive. 1394 * 1395 * You should not assume that the returned stream is seekable. 1396 * 1397 * Note that the returned stream may be safely closed without affecting 1398 * the underlying archive stream. 1399 * 1400 * If the file has not yet been verified, then the stream will be checked 1401 * as you read from it. When the stream is either exhausted or closed, 1402 * then the integrity of the file's data will be checked. This means that 1403 * if the file is corrupt, an exception will be thrown only after you have 1404 * finished reading from the stream. If you wish to make sure the data is 1405 * valid before you read from the file, call the verify method. 1406 */ 1407 InputStream open() 1408 { 1409 // If we haven't verified yet, wrap the stream in the appropriate 1410 // decorators. 1411 if( !verified ) 1412 return new ZipEntryVerifier(this, open_dg(header, false)); 1413 1414 else 1415 return open_dg(header, false); 1416 } 1417 1418 /** 1419 * Verifies the contents of this file by computing the CRC32 checksum, 1420 * and comparing it against the stored one. Throws an exception if the 1421 * checksums do not match. 1422 * 1423 * Not valid on streamed Zip archives. 1424 */ 1425 void verify() 1426 { 1427 // If we haven't verified the contents yet, just read everything in 1428 // to trigger it. 1429 auto s = open(); 1430 auto buffer = new ubyte[s.conduit.bufferSize]; 1431 while( s.read(buffer) != s.Eof ) 1432 {/*Do nothing*/} 1433 s.close(); 1434 } 1435 1436 /** 1437 * Creates a new, independent copy of this instance. 1438 */ 1439 ZipEntry dup() 1440 { 1441 return new ZipEntry(header, open_dg); 1442 } 1443 1444 private: 1445 /* 1446 * Callback used to open the file. 1447 */ 1448 alias InputStream delegate(FileHeader, bool raw) open_dg_t; 1449 open_dg_t open_dg; 1450 1451 /* 1452 * Raw ZIP header. 1453 */ 1454 FileHeader header; 1455 1456 /* 1457 * The flag used to keep track of whether the file's contents have been 1458 * verified. 1459 */ 1460 bool verified = false; 1461 1462 /* 1463 * Opens a stream that does not perform any decompression or 1464 * transformation of the file contents. This is used internally by 1465 * ZipWriter to perform fast zip to zip transfers without having to 1466 * decompress and then recompress the contents. 1467 * 1468 * Note that because zip stores CRCs for the *uncompressed* data, this 1469 * method currently does not do any verification. 1470 */ 1471 InputStream open_raw() 1472 { 1473 return open_dg(header, true); 1474 } 1475 1476 /* 1477 * Creates a new ZipEntry from the FileHeader. 1478 */ 1479 this(FileHeader header, open_dg_t open_dg) 1480 { 1481 this.reset(header, open_dg); 1482 } 1483 1484 /* 1485 * Resets the current instance with new values. 1486 */ 1487 ZipEntry reset(FileHeader header, open_dg_t open_dg) 1488 { 1489 this.header = header; 1490 this.open_dg = open_dg; 1491 with( info ) 1492 { 1493 name = Path.standard(header.file_name.dup); 1494 dosToTime(header.data.modification_file_time, 1495 header.data.modification_file_date, 1496 modified); 1497 comment = header.file_comment.dup; 1498 } 1499 1500 this.verified = false; 1501 1502 return this; 1503 } 1504 } 1505 1506 /** 1507 * This structure contains various pieces of meta-data on a file. The 1508 * contents of this structure may be safely mutated. 1509 * 1510 * This structure is also used to specify meta-data about a file when adding 1511 * it to an archive. 1512 */ 1513 struct ZipEntryInfo 1514 { 1515 /// Full path and file name of this file. 1516 const(char)[] name; 1517 /// Modification timestamp. If this is left uninitialised when passed to 1518 /// a ZipWriter, it will be reset to the current system time. 1519 Time modified = Time.min; 1520 /// Comment on the file. 1521 const(char)[] comment; 1522 } 1523 1524 ////////////////////////////////////////////////////////////////////////////// 1525 ////////////////////////////////////////////////////////////////////////////// 1526 ////////////////////////////////////////////////////////////////////////////// 1527 // 1528 // Exceptions 1529 // 1530 1531 /** 1532 * This is the base class from which all exceptions generated by this module 1533 * derive from. 1534 */ 1535 class ZipException : Exception 1536 { 1537 this(immutable(char)[] msg) { super(msg); } 1538 1539 private: 1540 alias typeof(this) thisT; 1541 static void opCall(immutable(char)[] msg) { throw new ZipException(msg); } 1542 1543 @property static void badsig() 1544 { 1545 thisT("corrupt signature or unexpected section found"); 1546 } 1547 1548 @property static void badsig(const(char)[] type) 1549 { 1550 thisT("corrupt "~type.idup~" signature or unexpected section found"); 1551 } 1552 1553 @property static void incons(const(char)[] name) 1554 { 1555 thisT("inconsistent headers for file \""~name.idup~"\"; " 1556 "archive is likely corrupted"); 1557 } 1558 1559 @property static void missingdir() 1560 { 1561 thisT("could not locate central archive directory; " 1562 "file is corrupt or possibly not a Zip archive"); 1563 } 1564 1565 @property static void toomanyentries() 1566 { 1567 thisT("too many archive entries"); 1568 } 1569 1570 @property static void toolong() 1571 { 1572 thisT("archive is too long; limited to 4GB total"); 1573 } 1574 1575 @property static void cdtoolong() 1576 { 1577 thisT("central directory is too long; limited to 4GB total"); 1578 } 1579 1580 @property static void fntoolong() 1581 { 1582 thisT("file name too long; limited to 65,535 characters"); 1583 } 1584 1585 @property static void eftoolong() 1586 { 1587 thisT("extra field too long; limited to 65,535 characters"); 1588 } 1589 1590 @property static void cotoolong() 1591 { 1592 thisT("extra field too long; limited to 65,535 characters"); 1593 } 1594 1595 @property static void fnencode() 1596 { 1597 thisT("could not encode filename into codepage 437"); 1598 } 1599 1600 @property static void coencode() 1601 { 1602 thisT("could not encode comment into codepage 437"); 1603 } 1604 1605 @property static void tooold() 1606 { 1607 thisT("cannot represent dates before January 1, 1980"); 1608 } 1609 } 1610 1611 /** 1612 * This exception is thrown if a ZipReader detects that a file's contents do 1613 * not match the stored checksum. 1614 */ 1615 class ZipChecksumException : ZipException 1616 { 1617 this(const(char)[] name) 1618 { 1619 super("checksum failed on zip entry \""~name.idup~"\""); 1620 } 1621 1622 private: 1623 static void opCall(const(char)[] name) { throw new ZipChecksumException(name); } 1624 } 1625 1626 /** 1627 * This exception is thrown if you call get reader method when there are no 1628 * more files in the archive. 1629 */ 1630 class ZipExhaustedException : ZipException 1631 { 1632 this() { super("no more entries in archive"); } 1633 1634 private: 1635 static void opCall() { throw new ZipExhaustedException; } 1636 } 1637 1638 /** 1639 * This exception is thrown if you attempt to read an archive that uses 1640 * features not supported by the reader. 1641 */ 1642 class ZipNotSupportedException : ZipException 1643 { 1644 this(immutable(char)[] msg) { super(msg); } 1645 1646 private: 1647 alias ZipNotSupportedException thisT; 1648 1649 static void opCall(const(char)[] msg) 1650 { 1651 throw new thisT(msg.idup ~ " not supported"); 1652 } 1653 1654 static void spanned() 1655 { 1656 thisT("split and multi-disk archives"); 1657 } 1658 1659 static void zipver(ushort ver) 1660 { 1661 throw new thisT("zip format version " 1662 ~Integer.toString(ver / 10).idup 1663 ~"." 1664 ~Integer.toString(ver % 10).idup 1665 ~" not supported; maximum of version " 1666 ~Integer.toString(MAX_EXTRACT_VERSION / 10).idup 1667 ~"." 1668 ~Integer.toString(MAX_EXTRACT_VERSION % 10).idup 1669 ~" supported."); 1670 } 1671 1672 static void flags() 1673 { 1674 throw new thisT("unknown or unsupported file flags enabled"); 1675 } 1676 1677 static void method(ushort m) 1678 { 1679 // Cheat here and work out what the method *actually* is 1680 immutable(char)[] ms; 1681 switch( m ) 1682 { 1683 case 0: 1684 case 8: assert(false); // supported 1685 1686 case 1: ms = "Shrink"; break; 1687 case 2: ms = "Reduce (factor 1)"; break; 1688 case 3: ms = "Reduce (factor 2)"; break; 1689 case 4: ms = "Reduce (factor 3)"; break; 1690 case 5: ms = "Reduce (factor 4)"; break; 1691 case 6: ms = "Implode"; break; 1692 1693 case 9: ms = "Deflate64"; break; 1694 case 10: ms = "TERSE (old)"; break; 1695 1696 case 12: ms = "Bzip2"; break; 1697 case 14: ms = "LZMA"; break; 1698 1699 case 18: ms = "TERSE (new)"; break; 1700 case 19: ms = "LZ77"; break; 1701 1702 case 97: ms = "WavPack"; break; 1703 case 98: ms = "PPMd"; break; 1704 1705 default: ms = "unknown"; 1706 } 1707 1708 thisT(ms ~ " compression method"); 1709 } 1710 } 1711 1712 ////////////////////////////////////////////////////////////////////////////// 1713 ////////////////////////////////////////////////////////////////////////////// 1714 // 1715 // Convenience methods 1716 1717 void createArchive(const(char)[] archive, Method method, const(char[])[] files...) 1718 { 1719 scope zw = new ZipBlockWriter(archive); 1720 zw.method = method; 1721 1722 foreach( file ; files ) 1723 { 1724 ZipEntryInfo zi; 1725 zi.name = file; 1726 zi.modified = Path.modified(file); 1727 1728 zw.putFile(zi, file); 1729 } 1730 1731 zw.finish(); 1732 } 1733 1734 void extractArchive(const(char)[] archive, const(char)[] dest) 1735 { 1736 scope zr = new ZipBlockReader(archive); 1737 1738 foreach( entry ; zr ) 1739 { 1740 // Skip directories 1741 if( entry.info.name[$-1] == '/' || 1742 entry.info.name[$-1] == '\\') continue; 1743 1744 auto path = Path.join(dest, entry.info.name); 1745 path = Path.normalize(path); 1746 1747 // Create the parent directory if necessary. 1748 auto parent = Path.parse(path).parent; 1749 if( !Path.exists(parent) ) 1750 { 1751 Path.createPath(parent); 1752 } 1753 1754 path = Path.native(path); 1755 1756 // Write out the file 1757 scope fout = new File(path, File.WriteCreate); 1758 fout.copy(entry.open()); 1759 fout.close(); 1760 1761 // Update timestamps 1762 auto oldTS = Path.timeStamps(path); 1763 Path.timeStamps(path, oldTS.accessed, entry.info.modified); 1764 } 1765 } 1766 1767 ////////////////////////////////////////////////////////////////////////////// 1768 ////////////////////////////////////////////////////////////////////////////// 1769 ////////////////////////////////////////////////////////////////////////////// 1770 // 1771 // Private implementation stuff 1772 // 1773 1774 private: 1775 1776 ////////////////////////////////////////////////////////////////////////////// 1777 ////////////////////////////////////////////////////////////////////////////// 1778 // 1779 // Verification stuff 1780 1781 /* 1782 * This class wraps an input stream, and computes the CRC as it passes 1783 * through. On the event of either a close or EOF, it checks the CRC against 1784 * the one in the provided ZipEntry. If they don't match, it throws an 1785 * exception. 1786 */ 1787 1788 class ZipEntryVerifier : InputStream 1789 { 1790 this(ZipEntry entry, InputStream source) 1791 in 1792 { 1793 assert( entry !is null ); 1794 assert( source !is null ); 1795 } 1796 body 1797 { 1798 this.entry = entry; 1799 this.digest = new Crc32; 1800 this.source = new DigestInput(source, digest); 1801 } 1802 1803 IConduit conduit() 1804 { 1805 return source.conduit; 1806 } 1807 1808 InputStream input() 1809 { 1810 return source; 1811 } 1812 1813 long seek (long ofs, Anchor anchor = Anchor.Begin) 1814 { 1815 return source.seek (ofs, anchor); 1816 } 1817 1818 void close() 1819 { 1820 check(); 1821 1822 this.source.close(); 1823 this.entry = null; 1824 this.digest = null; 1825 this.source = null; 1826 } 1827 1828 size_t read(void[] dst) 1829 { 1830 auto bytes = source.read(dst); 1831 if( bytes == IConduit.Eof ) 1832 check(); 1833 return bytes; 1834 } 1835 1836 override void[] load(size_t max=-1) 1837 { 1838 return Conduit.load(this, max); 1839 } 1840 1841 override InputStream flush() 1842 { 1843 this.source.flush(); 1844 return this; 1845 } 1846 1847 private: 1848 Crc32 digest; 1849 InputStream source; 1850 ZipEntry entry; 1851 1852 void check() 1853 { 1854 if( digest is null ) return; 1855 1856 auto crc = digest.crc32Digest(); 1857 delete digest; 1858 1859 if( crc != entry.header.data.crc_32 ) 1860 ZipChecksumException(entry.info.name); 1861 1862 else 1863 entry.verified = true; 1864 } 1865 } 1866 1867 ////////////////////////////////////////////////////////////////////////////// 1868 ////////////////////////////////////////////////////////////////////////////// 1869 // 1870 // IO functions 1871 1872 /* 1873 * Really, seriously, read some bytes without having to go through a sodding 1874 * buffer. 1875 */ 1876 void readExact(InputStream s, void[] dst) 1877 { 1878 //Stderr.formatln("readExact(s, [0..{}])", dst.length); 1879 while( dst.length > 0 ) 1880 { 1881 auto octets = s.read(dst); 1882 //Stderr.formatln(" . octets = {}", octets); 1883 if( octets == -1 ) // Beware the dangers of MAGICAL THINKING 1884 throw new Exception("unexpected end of stream"); 1885 dst = dst[octets..$]; 1886 } 1887 } 1888 1889 /* 1890 * Really, seriously, write some bytes. 1891 */ 1892 void writeExact(OutputStream s, const(void)[] src) 1893 { 1894 while( src.length > 0 ) 1895 { 1896 auto octets = s.write(src); 1897 if( octets == -1 ) 1898 throw new Exception("unexpected end of stream"); 1899 src = src[octets..$]; 1900 } 1901 } 1902 1903 void write(T)(OutputStream s, T value) 1904 { 1905 version( BigEndian ) swap(value); 1906 writeExact(s, (&value)[0..1]); 1907 } 1908 1909 ////////////////////////////////////////////////////////////////////////////// 1910 ////////////////////////////////////////////////////////////////////////////// 1911 // 1912 // Endian garbage 1913 1914 void swapAll(T)(ref T data) 1915 { 1916 static if( is(typeof(T.record_fields)) ) 1917 const fields = T.record_fields; 1918 else 1919 const fields = data.tupleof.length; 1920 1921 foreach( i,_ ; data.tupleof ) 1922 { 1923 if( i == fields ) break; 1924 swap(data.tupleof[i]); 1925 } 1926 } 1927 1928 void swap(T)(ref T data) 1929 { 1930 static if( T.sizeof == 1 ) 1931 {} 1932 else static if( T.sizeof == 2 ) 1933 ByteSwap.swap16(&data, 2); 1934 else static if( T.sizeof == 4 ) 1935 ByteSwap.swap32(&data, 4); 1936 else static if( T.sizeof == 8 ) 1937 ByteSwap.swap64(&data, 8); 1938 else static if( T.sizeof == 10 ) 1939 ByteSwap.swap80(&data, 10); 1940 else 1941 static assert(false, "Can't swap "~T.stringof~"s."); 1942 } 1943 1944 ////////////////////////////////////////////////////////////////////////////// 1945 ////////////////////////////////////////////////////////////////////////////// 1946 // 1947 // IBM Code Page 437 stuff 1948 // 1949 1950 const char[][] cp437_to_utf8_map_low = [ 1951 "\u0000"[], "\u263a", "\u263b", "\u2665", 1952 "\u2666", "\u2663", "\u2660", "\u2022", 1953 "\u25d8", "\u25cb", "\u25d9", "\u2642", 1954 "\u2640", "\u266a", "\u266b", "\u263c", 1955 1956 "\u25b6", "\u25c0", "\u2195", "\u203c", 1957 "\u00b6", "\u00a7", "\u25ac", "\u21a8", 1958 "\u2191", "\u2193", "\u2192", "\u2190", 1959 "\u221f", "\u2194", "\u25b2", "\u25bc" 1960 ]; 1961 1962 const char[][] cp437_to_utf8_map_high = [ 1963 "\u00c7"[], "\u00fc", "\u00e9", "\u00e2", 1964 "\u00e4", "\u00e0", "\u00e5", "\u00e7", 1965 "\u00ea", "\u00eb", "\u00e8", "\u00ef", 1966 "\u00ee", "\u00ec", "\u00c4", "\u00c5", 1967 1968 "\u00c9", "\u00e6", "\u00c6", "\u00f4", 1969 "\u00f6", "\u00f2", "\u00fb", "\u00f9", 1970 "\u00ff", "\u00d6", "\u00dc", "\u00f8", 1971 "\u00a3", "\u00a5", "\u20a7", "\u0192", 1972 1973 "\u00e1", "\u00ed", "\u00f3", "\u00fa", 1974 "\u00f1", "\u00d1", "\u00aa", "\u00ba", 1975 "\u00bf", "\u2310", "\u00ac", "\u00bd", 1976 "\u00bc", "\u00a1", "\u00ab", "\u00bb", 1977 1978 "\u2591", "\u2592", "\u2593", "\u2502", 1979 "\u2524", "\u2561", "\u2562", "\u2556", 1980 "\u2555", "\u2563", "\u2551", "\u2557", 1981 "\u255d", "\u255c", "\u255b", "\u2510", 1982 1983 "\u2514", "\u2534", "\u252c", "\u251c", 1984 "\u2500", "\u253c", "\u255e", "\u255f", 1985 "\u255a", "\u2554", "\u2569", "\u2566", 1986 "\u2560", "\u2550", "\u256c", "\u2567", 1987 1988 "\u2568", "\u2564", "\u2565", "\u2559", 1989 "\u2558", "\u2552", "\u2553", "\u256b", 1990 "\u256a", "\u2518", "\u250c", "\u2588", 1991 "\u2584", "\u258c", "\u2590", "\u2580", 1992 "\u03b1", "\u00df", "\u0393", "\u03c0", 1993 "\u03a3", "\u03c3", "\u00b5", "\u03c4", 1994 "\u03a6", "\u0398", "\u03a9", "\u03b4", 1995 "\u221e", "\u03c6", "\u03b5", "\u2229", 1996 1997 "\u2261", "\u00b1", "\u2265", "\u2264", 1998 "\u2320", "\u2321", "\u00f7", "\u2248", 1999 "\u00b0", "\u2219", "\u00b7", "\u221a", 2000 "\u207f", "\u00b2", "\u25a0", "\u00a0" 2001 ]; 2002 2003 inout(char[]) cp437_to_utf8(inout(ubyte[]) s) 2004 { 2005 foreach( i,c ; s ) 2006 { 2007 if( (1 <= c && c <= 31) || c >= 127 ) 2008 { 2009 /* Damn; we got a character not in ASCII. Since this is the first 2010 * non-ASCII character we found, copy everything up to this point 2011 * into the output verbatim. We'll allocate twice as much space 2012 * as there are remaining characters to ensure we don't need to do 2013 * any further allocations. 2014 */ 2015 auto r = new char[i+2*(s.length-i)]; 2016 r[0..i] = (cast(char[]) s[0..i])[]; 2017 size_t k=i; // current length 2018 2019 // We insert new characters at r[i+j+k] 2020 2021 foreach( d ; s[i..$] ) 2022 { 2023 if( 32 <= d && d <= 126 || d == 0 ) 2024 { 2025 r[k++] = d; 2026 } 2027 else if( 1 <= d && d <= 31 ) 2028 { 2029 const(char)[] repl = cp437_to_utf8_map_low[d]; 2030 r[k..k+repl.length] = repl[]; 2031 k += repl.length; 2032 } 2033 else if( d == 127 ) 2034 { 2035 const(char)[] repl = "\u2302"; 2036 r[k..k+repl.length] = repl[]; 2037 k += repl.length; 2038 } 2039 else if( d > 127 ) 2040 { 2041 const(char)[] repl = cp437_to_utf8_map_high[d-128]; 2042 r[k..k+repl.length] = repl[]; 2043 k += repl.length; 2044 } 2045 else 2046 assert(false); 2047 } 2048 2049 return cast(typeof(return))r[0..k]; 2050 } 2051 } 2052 2053 /* If we got here, then all the characters in s are also in ASCII, which 2054 * means it's also valid UTF-8; return the string unmodified. 2055 */ 2056 return cast(typeof(return)) s; 2057 } 2058 2059 debug( UnitTest ) 2060 { 2061 unittest 2062 { 2063 const(char)[] c(const(char)[] s) { return cp437_to_utf8(cast(const(ubyte)[]) s); } 2064 2065 auto s = c("Hi there \x01 old \x0c!"); 2066 assert( s == "Hi there \u263a old \u2640!", "\""~s~"\"" ); 2067 s = c("Marker \x7f and divide \xf6."); 2068 assert( s == "Marker \u2302 and divide \u00f7.", "\""~s~"\"" ); 2069 } 2070 } 2071 2072 __gshared const char[dchar] utf8_to_cp437_map; 2073 2074 shared static this() 2075 { 2076 utf8_to_cp437_map = [ 2077 '\u0000': '\x00', '\u263a': '\x01', '\u263b': '\x02', '\u2665': '\x03', 2078 '\u2666': '\x04', '\u2663': '\x05', '\u2660': '\x06', '\u2022': '\x07', 2079 '\u25d8': '\x08', '\u25cb': '\x09', '\u25d9': '\x0a', '\u2642': '\x0b', 2080 '\u2640': '\x0c', '\u266a': '\x0d', '\u266b': '\x0e', '\u263c': '\x0f', 2081 2082 '\u25b6': '\x10', '\u25c0': '\x11', '\u2195': '\x12', '\u203c': '\x13', 2083 '\u00b6': '\x14', '\u00a7': '\x15', '\u25ac': '\x16', '\u21a8': '\x17', 2084 '\u2191': '\x18', '\u2193': '\x19', '\u2192': '\x1a', '\u2190': '\x1b', 2085 '\u221f': '\x1c', '\u2194': '\x1d', '\u25b2': '\x1e', '\u25bc': '\x1f', 2086 2087 /* 2088 * Printable ASCII range (well, most of it) is handled specially. 2089 */ 2090 2091 '\u00c7': '\x80', '\u00fc': '\x81', '\u00e9': '\x82', '\u00e2': '\x83', 2092 '\u00e4': '\x84', '\u00e0': '\x85', '\u00e5': '\x86', '\u00e7': '\x87', 2093 '\u00ea': '\x88', '\u00eb': '\x89', '\u00e8': '\x8a', '\u00ef': '\x8b', 2094 '\u00ee': '\x8c', '\u00ec': '\x8d', '\u00c4': '\x8e', '\u00c5': '\x8f', 2095 2096 '\u00c9': '\x90', '\u00e6': '\x91', '\u00c6': '\x92', '\u00f4': '\x93', 2097 '\u00f6': '\x94', '\u00f2': '\x95', '\u00fb': '\x96', '\u00f9': '\x97', 2098 '\u00ff': '\x98', '\u00d6': '\x99', '\u00dc': '\x9a', '\u00f8': '\x9b', 2099 '\u00a3': '\x9c', '\u00a5': '\x9d', '\u20a7': '\x9e', '\u0192': '\x9f', 2100 2101 '\u00e1': '\xa0', '\u00ed': '\xa1', '\u00f3': '\xa2', '\u00fa': '\xa3', 2102 '\u00f1': '\xa4', '\u00d1': '\xa5', '\u00aa': '\xa6', '\u00ba': '\xa7', 2103 '\u00bf': '\xa8', '\u2310': '\xa9', '\u00ac': '\xaa', '\u00bd': '\xab', 2104 '\u00bc': '\xac', '\u00a1': '\xad', '\u00ab': '\xae', '\u00bb': '\xaf', 2105 2106 '\u2591': '\xb0', '\u2592': '\xb1', '\u2593': '\xb2', '\u2502': '\xb3', 2107 '\u2524': '\xb4', '\u2561': '\xb5', '\u2562': '\xb6', '\u2556': '\xb7', 2108 '\u2555': '\xb8', '\u2563': '\xb9', '\u2551': '\xba', '\u2557': '\xbb', 2109 '\u255d': '\xbc', '\u255c': '\xbd', '\u255b': '\xbe', '\u2510': '\xbf', 2110 2111 '\u2514': '\xc0', '\u2534': '\xc1', '\u252c': '\xc2', '\u251c': '\xc3', 2112 '\u2500': '\xc4', '\u253c': '\xc5', '\u255e': '\xc6', '\u255f': '\xc7', 2113 '\u255a': '\xc8', '\u2554': '\xc9', '\u2569': '\xca', '\u2566': '\xcb', 2114 '\u2560': '\xcc', '\u2550': '\xcd', '\u256c': '\xce', '\u2567': '\xcf', 2115 2116 '\u2568': '\xd0', '\u2564': '\xd1', '\u2565': '\xd2', '\u2559': '\xd3', 2117 '\u2558': '\xd4', '\u2552': '\xd5', '\u2553': '\xd6', '\u256b': '\xd7', 2118 '\u256a': '\xd8', '\u2518': '\xd9', '\u250c': '\xda', '\u2588': '\xdb', 2119 '\u2584': '\xdc', '\u258c': '\xdd', '\u2590': '\xde', '\u2580': '\xdf', 2120 2121 '\u03b1': '\xe0', '\u00df': '\xe1', '\u0393': '\xe2', '\u03c0': '\xe3', 2122 '\u03a3': '\xe4', '\u03c3': '\xe5', '\u00b5': '\xe6', '\u03c4': '\xe7', 2123 '\u03a6': '\xe8', '\u0398': '\xe9', '\u03a9': '\xea', '\u03b4': '\xeb', 2124 '\u221e': '\xec', '\u03c6': '\xed', '\u03b5': '\xee', '\u2229': '\xef', 2125 2126 '\u2261': '\xf0', '\u00b1': '\xf1', '\u2265': '\xf2', '\u2264': '\xf3', 2127 '\u2320': '\xf4', '\u2321': '\xf5', '\u00f7': '\xf6', '\u2248': '\xf7', 2128 '\u00b0': '\xf8', '\u2219': '\xf9', '\u00b7': '\xfa', '\u221a': '\xfb', 2129 '\u207f': '\xfc', '\u00b2': '\xfd', '\u25a0': '\xfe', '\u00a0': '\xff' 2130 ]; 2131 } 2132 2133 inout(ubyte)[] utf8_to_cp437(inout(char)[] s) 2134 { 2135 alias typeof(return) ret_type; /* Some sort of strange bug here */ 2136 ubyte[] bug_6867(const(char)[] cs) 2137 { 2138 foreach( i,dchar c ; cs ) 2139 { 2140 if( !((32 <= c && c <= 126) || c == 0) ) 2141 { 2142 /* We got a character not in CP 437: we need to create a buffer to 2143 * hold the new string. Since UTF-8 is *always* larger than CP 2144 * 437, we need, at most, an array of the same number of elements. 2145 */ 2146 auto r = new ubyte[cs.length]; 2147 r[0..i] = (cast(ubyte[]) cs[0..i])[]; 2148 size_t k=i; 2149 2150 foreach( dchar d ; cs[i..$] ) 2151 { 2152 if( 32 <= d && d <= 126 || d == 0 ) 2153 r[k++] = cast(ubyte)d; 2154 2155 else if( d == '\u2302' ) 2156 r[k++] = '\x7f'; 2157 2158 else if( auto e_ptr = d in utf8_to_cp437_map ) 2159 r[k++] = *e_ptr; 2160 2161 else 2162 { 2163 throw new Exception("cannot encode character \"" 2164 ~ Integer.toString(cast(uint)d).idup 2165 ~ "\" in codepage 437."); 2166 } 2167 } 2168 2169 return r[0..k]; 2170 } 2171 } 2172 2173 return null; 2174 } 2175 2176 auto ret = bug_6867(s); 2177 if (ret !is null) 2178 return cast(ret_type)ret; 2179 2180 // If we got here, then the entire string is printable ASCII, which just 2181 // happens to *also* be valid CP 437! Huzzah! 2182 return cast(typeof(return)) s; 2183 } 2184 2185 debug( UnitTest ) 2186 { 2187 unittest 2188 { 2189 alias cp437_to_utf8 x; 2190 alias utf8_to_cp437 y; 2191 2192 ubyte[256] s; 2193 foreach( i,ref c ; s ) 2194 c = cast(ubyte)i; 2195 2196 auto a = x(s); 2197 auto b = y(a); 2198 if(!( b == s )) 2199 { 2200 // Display list of characters that failed to convert as expected, 2201 // and what value we got. 2202 auto hex = "0123456789abcdef"; 2203 auto msg = "".dup; 2204 foreach( i,ch ; b ) 2205 { 2206 if( ch != i ) 2207 { 2208 msg ~= hex[i>>4]; 2209 msg ~= hex[i&15]; 2210 msg ~= " ("; 2211 msg ~= hex[ch>>4]; 2212 msg ~= hex[ch&15]; 2213 msg ~= "), "; 2214 } 2215 } 2216 msg ~= "failed."; 2217 2218 assert( false, msg ); 2219 } 2220 } 2221 } 2222 2223 /* 2224 * This is here to simplify the code elsewhere. 2225 */ 2226 inout(char[]) utf8_to_utf8(inout(ubyte[]) s) { return cast(typeof(return)) s; } 2227 ubyte[] utf8_to_utf8(char[] s) { return cast(ubyte[]) s; } 2228 2229 ////////////////////////////////////////////////////////////////////////////// 2230 ////////////////////////////////////////////////////////////////////////////// 2231 // 2232 // Date/time stuff 2233 2234 void dosToTime(ushort dostime, ushort dosdate, out Time time) 2235 { 2236 uint sec, min, hour, day, mon, year; 2237 sec = (dostime & 0b00000_000000_11111) * 2; 2238 min = (dostime & 0b00000_111111_00000) >> 5; 2239 hour= (dostime & 0b11111_000000_00000) >> 11; 2240 day = (dosdate & 0b0000000_0000_11111); 2241 mon = (dosdate & 0b0000000_1111_00000) >> 5; 2242 year=((dosdate & 0b1111111_0000_00000) >> 9) + 1980; 2243 2244 // This code rules! 2245 time = Gregorian.generic.toTime(year, mon, day, hour, min, sec); 2246 } 2247 2248 void timeToDos(Time time, out ushort dostime, out ushort dosdate) 2249 { 2250 // Treat Time.min specially 2251 if( time == Time.min ) 2252 time = WallClock.now; 2253 2254 // *muttering happily* 2255 auto date = Gregorian.generic.toDate(time); 2256 if( date.year < 1980 ) 2257 ZipException.tooold; 2258 2259 auto tod = time.time(); 2260 dostime = cast(ushort) ( 2261 (tod.seconds / 2) 2262 | (tod.minutes << 5) 2263 | (tod.hours << 11)); 2264 2265 dosdate = cast(ushort) ( 2266 (date.day) 2267 | (date.month << 5) 2268 | ((date.year - 1980) << 9)); 2269 } 2270 2271 // ************************************************************************** // 2272 // ************************************************************************** // 2273 // ************************************************************************** // 2274 2275 // Dependencies 2276 private: 2277 2278 import tango.io.device.Conduit : Conduit; 2279 2280 /******************************************************************************* 2281 2282 copyright: Copyright © 2007 Daniel Keep. All rights reserved. 2283 2284 license: BSD style: $(LICENSE) 2285 2286 version: Prerelease 2287 2288 author: Daniel Keep 2289 2290 *******************************************************************************/ 2291 2292 //module tangox.io.stream.CounterStream; 2293 2294 //import tango.io.device.Conduit : Conduit; 2295 //import tango.io.model.IConduit : IConduit, InputStream, OutputStream; 2296 2297 /** 2298 * The counter stream classes are used to keep track of how many bytes flow 2299 * through a stream. 2300 * 2301 * To use them, simply wrap it around an existing stream. The number of bytes 2302 * that have flowed through the wrapped stream may be accessed using the 2303 * count member. 2304 */ 2305 class CounterInput : InputStream 2306 { 2307 /// 2308 this(InputStream input) 2309 in 2310 { 2311 assert( input !is null ); 2312 } 2313 body 2314 { 2315 this.source = input; 2316 } 2317 2318 override IConduit conduit() 2319 { 2320 return source.conduit; 2321 } 2322 2323 InputStream input() 2324 { 2325 return source; 2326 } 2327 2328 long seek (long ofs, Anchor anchor = Anchor.Begin) 2329 { 2330 return source.seek (ofs, anchor); 2331 } 2332 2333 override void close() 2334 { 2335 source.close(); 2336 source = null; 2337 } 2338 2339 override size_t read(void[] dst) 2340 { 2341 auto read = source.read(dst); 2342 if( read != IConduit.Eof ) 2343 _count += read; 2344 return read; 2345 } 2346 2347 override void[] load(size_t max=-1) 2348 { 2349 return Conduit.load(this, max); 2350 } 2351 2352 override InputStream flush() 2353 { 2354 source.flush(); 2355 return this; 2356 } 2357 2358 /// 2359 long count() { return _count; } 2360 2361 private: 2362 InputStream source; 2363 long _count; 2364 } 2365 2366 /// ditto 2367 class CounterOutput : OutputStream 2368 { 2369 /// 2370 this(OutputStream output) 2371 in 2372 { 2373 assert( output !is null ); 2374 } 2375 body 2376 { 2377 this.sink = output; 2378 } 2379 2380 override IConduit conduit() 2381 { 2382 return sink.conduit; 2383 } 2384 2385 OutputStream output() 2386 { 2387 return sink; 2388 } 2389 2390 long seek (long ofs, Anchor anchor = Anchor.Begin) 2391 { 2392 return sink.seek (ofs, anchor); 2393 } 2394 2395 override void close() 2396 { 2397 sink.close(); 2398 sink = null; 2399 } 2400 2401 override size_t write(const(void)[] dst) 2402 { 2403 auto wrote = sink.write(dst); 2404 if( wrote != IConduit.Eof ) 2405 _count += wrote; 2406 return wrote; 2407 } 2408 2409 override OutputStream copy(InputStream src, size_t max=-1) 2410 { 2411 Conduit.transfer(src, this, max); 2412 return this; 2413 } 2414 2415 override OutputStream flush() 2416 { 2417 sink.flush(); 2418 return this; 2419 } 2420 2421 /// 2422 long count() { return _count; } 2423 2424 private: 2425 OutputStream sink; 2426 long _count; 2427 } 2428 2429 /******************************************************************************* 2430 2431 copyright: Copyright © 2007 Daniel Keep. All rights reserved. 2432 2433 license: BSD style: $(LICENSE) 2434 2435 version: Prerelease 2436 2437 author: Daniel Keep 2438 2439 *******************************************************************************/ 2440 2441 //module tangox.io.stream.SliceStream; 2442 2443 //import tango.io.device.Conduit : Conduit; 2444 //import tango.io.model.IConduit : IConduit, InputStream, OutputStream; 2445 2446 /** 2447 * This stream can be used to provide stream-based access to a subset of 2448 * another stream. It is akin to slicing an array. 2449 * 2450 * This stream fully supports seeking, and as such requires that the 2451 * underlying stream also support seeking. 2452 */ 2453 class SliceSeekInputStream : InputStream 2454 { 2455 //alias IConduit.Seek.Anchor Anchor; 2456 2457 /** 2458 * Create a new slice stream from the given source, covering the content 2459 * starting at position begin, for length bytes. 2460 */ 2461 this(InputStream source, long begin, long length) 2462 in 2463 { 2464 assert( source !is null ); 2465 assert( (cast(IConduit.Seek) source.conduit) !is null ); 2466 assert( begin >= 0 ); 2467 assert( length >= 0 ); 2468 } 2469 body 2470 { 2471 this.source = source; 2472 this.seeker = source; //cast(IConduit.Seek) source; 2473 this.begin = begin; 2474 this.length = length; 2475 } 2476 2477 override IConduit conduit() 2478 { 2479 return source.conduit; 2480 } 2481 2482 override void close() 2483 { 2484 source = null; 2485 seeker = null; 2486 } 2487 2488 override size_t read(void[] dst) 2489 { 2490 // If we're at the end of the slice, return eof 2491 if( _position >= length ) 2492 return IConduit.Eof; 2493 2494 // Otherwise, make sure we don't try to read past the end of the slice 2495 if( _position+dst.length > length ) 2496 dst.length = cast(size_t) (length-_position); 2497 2498 // Seek source stream to the appropriate location. 2499 if( seeker.seek(0, Anchor.Current) != begin+_position ) 2500 seeker.seek(begin+_position, Anchor.Begin); 2501 2502 // Do the read 2503 auto read = source.read(dst); 2504 if( read == IConduit.Eof ) 2505 // If we got an Eof, we'll consider that a bug for the moment. 2506 // TODO: proper exception 2507 throw new Exception("unexpected end-of-stream"); 2508 2509 _position += read; 2510 return read; 2511 } 2512 2513 override void[] load(size_t max=-1) 2514 { 2515 return Conduit.load(this, max); 2516 } 2517 2518 override InputStream flush() 2519 { 2520 source.flush(); 2521 return this; 2522 } 2523 2524 InputStream input() 2525 { 2526 return source; 2527 } 2528 2529 override long seek(long offset, Anchor anchor = cast(Anchor)0) 2530 { 2531 switch( anchor ) 2532 { 2533 case Anchor.Begin: 2534 _position = offset; 2535 break; 2536 2537 case Anchor.Current: 2538 _position += offset; 2539 if( _position < 0 ) _position = 0; 2540 break; 2541 2542 case Anchor.End: 2543 _position = length+offset; 2544 if( _position < 0 ) _position = 0; 2545 break; 2546 2547 default: 2548 assert(false); 2549 } 2550 2551 return _position; 2552 } 2553 2554 private: 2555 InputStream source; 2556 InputStream seeker; 2557 2558 long _position, begin, length; 2559 2560 invariant() 2561 { 2562 assert( cast(Object) source is cast(Object) seeker ); 2563 assert( begin >= 0 ); 2564 assert( length >= 0 ); 2565 assert( _position >= 0 ); 2566 } 2567 } 2568 2569 /** 2570 * This stream can be used to provide stream-based access to a subset of 2571 * another stream. It is akin to slicing an array. 2572 */ 2573 class SliceInputStream : InputStream 2574 { 2575 /** 2576 * Create a new slice stream from the given source, covering the content 2577 * starting at the current seek position for length bytes. 2578 */ 2579 this(InputStream source, long length) 2580 in 2581 { 2582 assert( source !is null ); 2583 assert( length >= 0 ); 2584 } 2585 body 2586 { 2587 this.source = source; 2588 this._length = length; 2589 } 2590 2591 override IConduit conduit() 2592 { 2593 return source.conduit; 2594 } 2595 2596 override void close() 2597 { 2598 source = null; 2599 } 2600 2601 InputStream input() 2602 { 2603 return source; 2604 } 2605 2606 long seek (long ofs, Anchor anchor = Anchor.Begin) 2607 { 2608 return source.seek (ofs, anchor); 2609 } 2610 2611 override size_t read(void[] dst) 2612 { 2613 // If we're at the end of the slice, return eof 2614 if( _length <= 0 ) 2615 return IConduit.Eof; 2616 2617 // Otherwise, make sure we don't try to read past the end of the slice 2618 if( dst.length > _length ) 2619 dst.length = cast(size_t) _length; 2620 2621 // Do the read 2622 auto read = source.read(dst); 2623 if( read == IConduit.Eof ) 2624 // If we got an Eof, we'll consider that a bug for the moment. 2625 // TODO: proper exception 2626 throw new Exception("unexpected end-of-stream"); 2627 2628 _length -= read; 2629 return read; 2630 } 2631 2632 override void[] load(size_t max=-1) 2633 { 2634 return Conduit.load(this, max); 2635 } 2636 2637 override InputStream flush() 2638 { 2639 source.flush(); 2640 return this; 2641 } 2642 2643 private: 2644 InputStream source; 2645 long _length; 2646 2647 invariant() 2648 { 2649 if( _length > 0 ) assert( source !is null ); 2650 } 2651 } 2652 2653 /** 2654 * This stream can be used to provide stream-based access to a subset of 2655 * another stream. It is akin to slicing an array. 2656 * 2657 * This stream fully supports seeking, and as such requires that the 2658 * underlying stream also support seeking. 2659 */ 2660 class SliceSeekOutputStream : OutputStream 2661 { 2662 //alias IConduit.Seek.Anchor Anchor; 2663 2664 /** 2665 * Create a new slice stream from the given source, covering the content 2666 * starting at position begin, for length bytes. 2667 */ 2668 this(OutputStream source, long begin, long length) 2669 in 2670 { 2671 assert( (cast(IConduit.Seek) source.conduit) !is null ); 2672 assert( begin >= 0 ); 2673 assert( length >= 0 ); 2674 } 2675 body 2676 { 2677 this.source = source; 2678 this.seeker = source; //cast(IConduit.Seek) source; 2679 this.begin = begin; 2680 this.length = length; 2681 } 2682 2683 override IConduit conduit() 2684 { 2685 return source.conduit; 2686 } 2687 2688 override void close() 2689 { 2690 source = null; 2691 seeker = null; 2692 } 2693 2694 size_t write(const(void)[] src) 2695 { 2696 // If we're at the end of the slice, return eof 2697 if( _position >= length ) 2698 return IConduit.Eof; 2699 2700 // Otherwise, make sure we don't try to write past the end of the 2701 // slice 2702 if( _position+src.length > length ) 2703 src.length = cast(size_t) (length-_position); 2704 2705 // Seek source stream to the appropriate location. 2706 if( seeker.seek(0, Anchor.Current) != begin+_position ) 2707 seeker.seek(begin+_position, Anchor.Begin); 2708 2709 // Do the write 2710 auto wrote = source.write(src); 2711 if( wrote == IConduit.Eof ) 2712 // If we got an Eof, we'll consider that a bug for the moment. 2713 // TODO: proper exception 2714 throw new Exception("unexpected end-of-stream"); 2715 2716 _position += wrote; 2717 return wrote; 2718 } 2719 2720 override OutputStream copy(InputStream src, size_t max=-1) 2721 { 2722 Conduit.transfer(src, this, max); 2723 return this; 2724 } 2725 2726 override OutputStream flush() 2727 { 2728 source.flush(); 2729 return this; 2730 } 2731 2732 override OutputStream output() 2733 { 2734 return source; 2735 } 2736 2737 override long seek(long offset, Anchor anchor = cast(Anchor)0) 2738 { 2739 switch( anchor ) 2740 { 2741 case Anchor.Begin: 2742 _position = offset; 2743 break; 2744 2745 case Anchor.Current: 2746 _position += offset; 2747 if( _position < 0 ) _position = 0; 2748 break; 2749 2750 case Anchor.End: 2751 _position = length+offset; 2752 if( _position < 0 ) _position = 0; 2753 break; 2754 2755 default: 2756 assert(false); 2757 } 2758 2759 return _position; 2760 } 2761 2762 private: 2763 OutputStream source; 2764 OutputStream seeker; 2765 2766 long _position, begin, length; 2767 2768 invariant() 2769 { 2770 assert( cast(Object) source is cast(Object) seeker ); 2771 assert( begin >= 0 ); 2772 assert( length >= 0 ); 2773 assert( _position >= 0 ); 2774 } 2775 } 2776 2777 /******************************************************************************* 2778 2779 copyright: Copyright © 2007 Daniel Keep. All rights reserved. 2780 2781 license: BSD style: $(LICENSE) 2782 2783 version: Prerelease 2784 2785 author: Daniel Keep 2786 2787 *******************************************************************************/ 2788 2789 //module tangox.io.stream.WrapStream; 2790 2791 //import tango.io.device.Conduit : Conduit; 2792 //import tango.io.model.IConduit : IConduit, InputStream, OutputStream; 2793 2794 /** 2795 * This stream can be used to provide access to another stream. 2796 * Its distinguishing feature is that users cannot close the underlying 2797 * stream. 2798 * 2799 * This stream fully supports seeking, and as such requires that the 2800 * underlying stream also support seeking. 2801 */ 2802 class WrapSeekInputStream : InputStream 2803 { 2804 //alias IConduit.Seek.Anchor Anchor; 2805 2806 /** 2807 * Create a new wrap stream from the given source. 2808 */ 2809 this(InputStream source) 2810 in 2811 { 2812 assert( source !is null ); 2813 assert( (cast(IConduit.Seek) source.conduit) !is null ); 2814 } 2815 body 2816 { 2817 this.source = source; 2818 this.seeker = source; //cast(IConduit.Seek) source; 2819 this._position = seeker.seek(0, Anchor.Current); 2820 } 2821 2822 /// ditto 2823 this(InputStream source, long position) 2824 in 2825 { 2826 assert( position >= 0 ); 2827 } 2828 body 2829 { 2830 this(source); 2831 this._position = position; 2832 } 2833 2834 override IConduit conduit() 2835 { 2836 return source.conduit; 2837 } 2838 2839 override void close() 2840 { 2841 source = null; 2842 seeker = null; 2843 } 2844 2845 override size_t read(void[] dst) 2846 { 2847 if( seeker.seek(0, Anchor.Current) != _position ) 2848 seeker.seek(_position, Anchor.Begin); 2849 2850 auto read = source.read(dst); 2851 if( read != IConduit.Eof ) 2852 _position += read; 2853 2854 return read; 2855 } 2856 2857 override void[] load(size_t max=-1) 2858 { 2859 return Conduit.load(this, max); 2860 } 2861 2862 override InputStream flush() 2863 { 2864 source.flush(); 2865 return this; 2866 } 2867 2868 InputStream input() 2869 { 2870 return source; 2871 } 2872 2873 override long seek(long offset, Anchor anchor = cast(Anchor)0) 2874 { 2875 seeker.seek(_position, Anchor.Begin); 2876 return (_position = seeker.seek(offset, anchor)); 2877 } 2878 2879 private: 2880 InputStream source; 2881 InputStream seeker; 2882 long _position; 2883 2884 invariant() 2885 { 2886 assert( cast(Object) source is cast(Object) seeker ); 2887 assert( _position >= 0 ); 2888 } 2889 } 2890 2891 /** 2892 * This stream can be used to provide access to another stream. 2893 * Its distinguishing feature is that the users cannot close the underlying 2894 * stream. 2895 * 2896 * This stream fully supports seeking, and as such requires that the 2897 * underlying stream also support seeking. 2898 */ 2899 class WrapSeekOutputStream : OutputStream 2900 { 2901 //alias IConduit.Seek.Anchor Anchor; 2902 2903 /** 2904 * Create a new wrap stream from the given source. 2905 */ 2906 this(OutputStream source) 2907 in 2908 { 2909 assert( (cast(IConduit.Seek) source.conduit) !is null ); 2910 } 2911 body 2912 { 2913 this.source = source; 2914 this.seeker = source; //cast(IConduit.Seek) source; 2915 this._position = seeker.seek(0, Anchor.Current); 2916 } 2917 2918 /// ditto 2919 this(OutputStream source, long position) 2920 in 2921 { 2922 assert( position >= 0 ); 2923 } 2924 body 2925 { 2926 this(source); 2927 this._position = position; 2928 } 2929 2930 override IConduit conduit() 2931 { 2932 return source.conduit; 2933 } 2934 2935 override void close() 2936 { 2937 source = null; 2938 seeker = null; 2939 } 2940 2941 size_t write(const(void)[] src) 2942 { 2943 if( seeker.seek(0, Anchor.Current) != _position ) 2944 seeker.seek(_position, Anchor.Begin); 2945 2946 auto wrote = source.write(src); 2947 if( wrote != IConduit.Eof ) 2948 _position += wrote; 2949 return wrote; 2950 } 2951 2952 override OutputStream copy(InputStream src, size_t max=-1) 2953 { 2954 Conduit.transfer(src, this, max); 2955 return this; 2956 } 2957 2958 override OutputStream flush() 2959 { 2960 source.flush(); 2961 return this; 2962 } 2963 2964 override OutputStream output() 2965 { 2966 return source; 2967 } 2968 2969 override long seek(long offset, Anchor anchor = cast(Anchor)0) 2970 { 2971 seeker.seek(_position, Anchor.Begin); 2972 return (_position = seeker.seek(offset, anchor)); 2973 } 2974 2975 private: 2976 OutputStream source; 2977 OutputStream seeker; 2978 long _position; 2979 2980 invariant() 2981 { 2982 assert( cast(Object) source is cast(Object) seeker ); 2983 assert( _position >= 0 ); 2984 } 2985 } 2986 2987