1 // Written in the D programming language. 2 3 /** 4 Read and write data in the 5 $(LINK2 https://en.wikipedia.org/wiki/Zip_%28file_format%29, zip archive) 6 format. 7 8 Standards: 9 10 The current implementation mostly conforms to 11 $(LINK2 https://www.iso.org/standard/60101.html, ISO/IEC 21320-1:2015), 12 which means, 13 $(UL 14 $(LI that files can only be stored uncompressed or using the deflate mechanism,) 15 $(LI that encryption features are not used,) 16 $(LI that digital signature features are not used,) 17 $(LI that patched data features are not used, and) 18 $(LI that archives may not span multiple volumes.) 19 ) 20 21 Additionally, archives are checked for malware attacks and rejected if detected. 22 This includes 23 $(UL 24 $(LI $(LINK2 https://news.ycombinator.com/item?id=20352439, zip bombs) which 25 generate gigantic amounts of unpacked data) 26 $(LI zip archives that contain overlapping records) 27 $(LI chameleon zip archives which generate different unpacked data, depending 28 on the implementation of the unpack algorithm) 29 ) 30 31 The current implementation makes use of the zlib compression library. 32 33 Usage: 34 35 There are two main ways of usage: Extracting files from a zip archive 36 and storing files into a zip archive. These can be mixed though (e.g. 37 read an archive, remove some files, add others and write the new 38 archive). 39 40 Examples: 41 42 Example for reading an existing zip archive: 43 --- 44 import std.stdio : writeln, writefln; 45 import std.file : read; 46 import std.zip; 47 48 void main(string[] args) 49 { 50 // read a zip file into memory 51 auto zip = new ZipArchive(read(args[1])); 52 53 // iterate over all zip members 54 writefln("%-10s %-8s Name", "Length", "CRC-32"); 55 foreach (name, am; zip.directory) 56 { 57 // print some data about each member 58 writefln("%10s %08x %s", am.expandedSize, am.crc32, name); 59 assert(am.expandedData.length == 0); 60 61 // decompress the archive member 62 zip.expand(am); 63 assert(am.expandedData.length == am.expandedSize); 64 } 65 } 66 --- 67 68 Example for writing files into a zip archive: 69 --- 70 import std.file : write; 71 import std.string : representation; 72 import std.zip; 73 74 void main() 75 { 76 // Create an ArchiveMembers for each file. 77 ArchiveMember file1 = new ArchiveMember(); 78 file1.name = "test1.txt"; 79 file1.expandedData("Test data.\n".dup.representation); 80 file1.compressionMethod = CompressionMethod.none; // don't compress 81 82 ArchiveMember file2 = new ArchiveMember(); 83 file2.name = "test2.txt"; 84 file2.expandedData("More test data.\n".dup.representation); 85 file2.compressionMethod = CompressionMethod.deflate; // compress 86 87 // Create an archive and add the member. 88 ZipArchive zip = new ZipArchive(); 89 90 // add ArchiveMembers 91 zip.addMember(file1); 92 zip.addMember(file2); 93 94 // Build the archive 95 void[] compressed_data = zip.build(); 96 97 // Write to a file 98 write("test.zip", compressed_data); 99 } 100 --- 101 102 * Copyright: Copyright The D Language Foundation 2000 - 2009. 103 * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 104 * Authors: $(HTTP digitalmars.com, Walter Bright) 105 * Source: $(PHOBOSSRC std/zip.d) 106 */ 107 108 /* Copyright The D Language Foundation 2000 - 2009. 109 * Distributed under the Boost Software License, Version 1.0. 110 * (See accompanying file LICENSE_1_0.txt or copy at 111 * http://www.boost.org/LICENSE_1_0.txt) 112 */ 113 module std.zip; 114 115 import std.exception : enforce; 116 117 // Non-Android/Apple ARM POSIX-only, because we can't rely on the unzip 118 // command being available on Android, Apple ARM or Windows 119 version (Android) {} 120 else version (iOS) {} 121 else version (TVOS) {} 122 else version (WatchOS) {} 123 else version (Posix) 124 version = HasUnzip; 125 126 //debug=print; 127 128 /// Thrown on error. 129 class ZipException : Exception 130 { 131 import std.exception : basicExceptionCtors; 132 /// 133 mixin basicExceptionCtors; 134 } 135 136 /// Compression method used by `ArchiveMember`. 137 enum CompressionMethod : ushort 138 { 139 none = 0, /// No compression, just archiving. 140 deflate = 8 /// Deflate algorithm. Use zlib library to compress. 141 } 142 143 /// A single file or directory inside the archive. 144 final class ArchiveMember 145 { 146 import std.conv : to, octal; 147 import std.datetime.systime : DosFileTime, SysTime, SysTimeToDosFileTime; 148 149 /** 150 * The name of the archive member; it is used to index the 151 * archive directory for the member. Each member must have a 152 * unique name. Do not change without removing member from the 153 * directory first. 154 */ 155 string name; 156 157 /** 158 * The content of the extra data field for this member. See 159 * $(LINK2 https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT, 160 * original documentation) 161 * for a description of the general format of this data. May contain 162 * undocumented 3rd-party data. 163 */ 164 ubyte[] extra; 165 166 string comment; /// Comment associated with this member. 167 168 private ubyte[] _compressedData; 169 private ubyte[] _expandedData; 170 private uint offset; 171 private uint _crc32; 172 private uint _compressedSize; 173 private uint _expandedSize; 174 private CompressionMethod _compressionMethod; 175 private ushort _madeVersion = 20; 176 private ushort _extractVersion = 20; 177 private uint _externalAttributes; 178 private DosFileTime _time; 179 // by default, no explicit order goes after explicit order 180 private uint _index = uint.max; 181 182 /** 183 * Contains some information on how to extract this archive. See 184 * $(LINK2 https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT, 185 * original documentation) 186 * for details. 187 */ 188 ushort flags; 189 190 /** 191 * Internal attributes. Bit 1 is set, if the member is apparently in binary format 192 * and bit 2 is set, if each record is preceded by the length of the record. 193 */ 194 ushort internalAttributes; 195 196 /** 197 * The zip file format version needed to extract this member. 198 * 199 * Returns: Format version needed to extract this member. 200 */ 201 @property @safe pure nothrow @nogc ushort extractVersion() const { return _extractVersion; } 202 203 /** 204 * Cyclic redundancy check (CRC) value. 205 * 206 * Returns: CRC32 value. 207 */ 208 @property @safe pure nothrow @nogc uint crc32() const { return _crc32; } 209 210 /** 211 * Size of data of member in compressed form. 212 * 213 * Returns: Size of the compressed archive. 214 */ 215 @property @safe pure nothrow @nogc uint compressedSize() const { return _compressedSize; } 216 217 /** 218 * Size of data of member in uncompressed form. 219 * 220 * Returns: Size of uncompressed archive. 221 */ 222 @property @safe pure nothrow @nogc uint expandedSize() const { return _expandedSize; } 223 224 /** 225 * Should be 0. 226 * 227 * Returns: The number of the disk where this member can be found. 228 */ 229 deprecated("Multidisk not supported; will be removed in 2.099.0") 230 @property @safe pure nothrow @nogc ushort diskNumber() const { return 0; } 231 232 /** 233 * Data of member in compressed form. 234 * 235 * Returns: The file data in compressed form. 236 */ 237 @property @safe pure nothrow @nogc ubyte[] compressedData() { return _compressedData; } 238 239 /** 240 * Get or set data of member in uncompressed form. When an existing archive is 241 * read `ZipArchive.expand` needs to be called before this can be accessed. 242 * 243 * Params: 244 * ed = Expanded Data. 245 * 246 * Returns: The file data. 247 */ 248 @property @safe pure nothrow @nogc ubyte[] expandedData() { return _expandedData; } 249 250 /// ditto 251 @property @safe void expandedData(ubyte[] ed) 252 { 253 _expandedData = ed; 254 _expandedSize = to!uint(_expandedData.length); 255 256 // Clean old compressed data, if any 257 _compressedData.length = 0; 258 _compressedSize = 0; 259 } 260 261 /** 262 * Get or set the OS specific file attributes for this archive member. 263 * 264 * Params: 265 * attr = Attributes as obtained by $(REF getAttributes, std,file) or 266 * $(REF DirEntry.attributes, std,file). 267 * 268 * Returns: The file attributes or 0 if the file attributes were 269 * encoded for an incompatible OS (Windows vs. POSIX). 270 */ 271 @property @safe void fileAttributes(uint attr) 272 { 273 version (Posix) 274 { 275 _externalAttributes = (attr & 0xFFFF) << 16; 276 _madeVersion &= 0x00FF; 277 _madeVersion |= 0x0300; // attributes are in UNIX format 278 } 279 else version (Windows) 280 { 281 _externalAttributes = attr; 282 _madeVersion &= 0x00FF; // attributes are in MS-DOS and OS/2 format 283 } 284 else 285 { 286 static assert(0, "Unimplemented platform"); 287 } 288 } 289 290 version (Posix) @safe unittest 291 { 292 auto am = new ArchiveMember(); 293 am.fileAttributes = octal!100644; 294 assert(am._externalAttributes == octal!100644 << 16); 295 assert((am._madeVersion & 0xFF00) == 0x0300); 296 } 297 298 /// ditto 299 @property @nogc nothrow uint fileAttributes() const 300 { 301 version (Posix) 302 { 303 if ((_madeVersion & 0xFF00) == 0x0300) 304 return _externalAttributes >> 16; 305 return 0; 306 } 307 else version (Windows) 308 { 309 if ((_madeVersion & 0xFF00) == 0x0000) 310 return _externalAttributes; 311 return 0; 312 } 313 else 314 { 315 static assert(0, "Unimplemented platform"); 316 } 317 } 318 319 /** 320 * Get or set the last modification time for this member. 321 * 322 * Params: 323 * time = Time to set (will be saved as DosFileTime, which is less accurate). 324 * 325 * Returns: 326 * The last modification time in DosFileFormat. 327 */ 328 @property DosFileTime time() const @safe pure nothrow @nogc 329 { 330 return _time; 331 } 332 333 /// ditto 334 @property void time(SysTime time) 335 { 336 _time = SysTimeToDosFileTime(time); 337 } 338 339 /// ditto 340 @property void time(DosFileTime time) @safe pure nothrow @nogc 341 { 342 _time = time; 343 } 344 345 /** 346 * Get or set compression method used for this member. 347 * 348 * Params: 349 * cm = Compression method. 350 * 351 * Returns: Compression method. 352 * 353 * See_Also: 354 * $(LREF CompressionMethod) 355 **/ 356 @property @safe @nogc pure nothrow CompressionMethod compressionMethod() const { return _compressionMethod; } 357 358 /// ditto 359 @property @safe pure void compressionMethod(CompressionMethod cm) 360 { 361 if (cm == _compressionMethod) return; 362 363 enforce!ZipException(_compressedSize == 0, "Can't change compression method for a compressed element"); 364 365 _compressionMethod = cm; 366 } 367 368 /** 369 * The index of this archive member within the archive. Set this to a 370 * different value for reordering the members of an archive. 371 * 372 * Params: 373 * value = Index value to set. 374 * 375 * Returns: The index. 376 */ 377 @property uint index(uint value) @safe pure nothrow @nogc { return _index = value; } 378 @property uint index() const @safe pure nothrow @nogc { return _index; } /// ditto 379 380 debug(print) 381 { 382 void print() 383 { 384 printf("name = '%.*s'\n", cast(int) name.length, name.ptr); 385 printf("\tcomment = '%.*s'\n", cast(int) comment.length, comment.ptr); 386 printf("\tmadeVersion = x%04x\n", _madeVersion); 387 printf("\textractVersion = x%04x\n", extractVersion); 388 printf("\tflags = x%04x\n", flags); 389 printf("\tcompressionMethod = %d\n", compressionMethod); 390 printf("\ttime = %d\n", time); 391 printf("\tcrc32 = x%08x\n", crc32); 392 printf("\texpandedSize = %d\n", expandedSize); 393 printf("\tcompressedSize = %d\n", compressedSize); 394 printf("\tinternalAttributes = x%04x\n", internalAttributes); 395 printf("\texternalAttributes = x%08x\n", externalAttributes); 396 printf("\tindex = x%08x\n", index); 397 } 398 } 399 } 400 401 @safe pure unittest 402 { 403 import std.exception : assertThrown, assertNotThrown; 404 405 auto am = new ArchiveMember(); 406 407 assertNotThrown(am.compressionMethod(CompressionMethod.deflate)); 408 assertNotThrown(am.compressionMethod(CompressionMethod.none)); 409 410 am._compressedData = [0x65]; // not strictly necessary, but for consistency 411 am._compressedSize = 1; 412 413 assertThrown!ZipException(am.compressionMethod(CompressionMethod.deflate)); 414 } 415 416 /** 417 * Object representing the entire archive. 418 * ZipArchives are collections of ArchiveMembers. 419 */ 420 final class ZipArchive 421 { 422 import std.algorithm.comparison : max; 423 import std.bitmanip : littleEndianToNative, nativeToLittleEndian; 424 import std.conv : to; 425 import std.datetime.systime : DosFileTime; 426 427 private: 428 // names are taken directly from the specification 429 // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT 430 static immutable ubyte[] centralFileHeaderSignature = [ 0x50, 0x4b, 0x01, 0x02 ]; 431 static immutable ubyte[] localFileHeaderSignature = [ 0x50, 0x4b, 0x03, 0x04 ]; 432 static immutable ubyte[] endOfCentralDirSignature = [ 0x50, 0x4b, 0x05, 0x06 ]; 433 static immutable ubyte[] archiveExtraDataSignature = [ 0x50, 0x4b, 0x06, 0x08 ]; 434 static immutable ubyte[] digitalSignatureSignature = [ 0x50, 0x4b, 0x05, 0x05 ]; 435 static immutable ubyte[] zip64EndOfCentralDirSignature = [ 0x50, 0x4b, 0x06, 0x06 ]; 436 static immutable ubyte[] zip64EndOfCentralDirLocatorSignature = [ 0x50, 0x4b, 0x06, 0x07 ]; 437 438 enum centralFileHeaderLength = 46; 439 enum localFileHeaderLength = 30; 440 enum endOfCentralDirLength = 22; 441 enum archiveExtraDataLength = 8; 442 enum digitalSignatureLength = 6; 443 enum zip64EndOfCentralDirLength = 56; 444 enum zip64EndOfCentralDirLocatorLength = 20; 445 enum dataDescriptorLength = 12; 446 447 public: 448 string comment; /// The archive comment. Must be less than 65536 bytes in length. 449 450 private ubyte[] _data; 451 452 private bool _isZip64; 453 static const ushort zip64ExtractVersion = 45; 454 455 deprecated("Use digitalSignatureLength instead; will be removed in 2.098.0") 456 static const int digiSignLength = 6; 457 deprecated("Use zip64EndOfCentralDirLocatorLength instead; will be removed in 2.098.0") 458 static const int eocd64LocLength = 20; 459 deprecated("Use zip64EndOfCentralDirLength instead; will be removed in 2.098.0") 460 static const int eocd64Length = 56; 461 462 private Segment[] _segs; 463 464 /** 465 * Array representing the entire contents of the archive. 466 * 467 * Returns: Data of the entire contents of the archive. 468 */ 469 @property @safe @nogc pure nothrow ubyte[] data() { return _data; } 470 471 /** 472 * 0 since multi-disk zip archives are not supported. 473 * 474 * Returns: Number of this disk. 475 */ 476 deprecated("Multidisk not supported; will be removed in 2.099.0") 477 @property @safe @nogc pure nothrow uint diskNumber() const { return 0; } 478 479 /** 480 * 0 since multi-disk zip archives are not supported. 481 * 482 * Returns: Number of the disk, where the central directory starts. 483 */ 484 deprecated("Multidisk not supported; will be removed in 2.099.0") 485 @property @safe @nogc pure nothrow uint diskStartDir() const { return 0; } 486 487 /** 488 * Number of ArchiveMembers in the directory. 489 * 490 * Returns: The number of files in this archive. 491 */ 492 deprecated("Use totalEntries instead; will be removed in 2.099.0") 493 @property @safe @nogc pure nothrow uint numEntries() const { return cast(uint) _directory.length; } 494 @property @safe @nogc pure nothrow uint totalEntries() const { return cast(uint) _directory.length; } /// ditto 495 496 /** 497 * True when the archive is in Zip64 format. Set this to true to force building a Zip64 archive. 498 * 499 * Params: 500 * value = True, when the archive is forced to be build in Zip64 format. 501 * 502 * Returns: True, when the archive is in Zip64 format. 503 */ 504 @property @safe @nogc pure nothrow bool isZip64() const { return _isZip64; } 505 506 /// ditto 507 @property @safe @nogc pure nothrow void isZip64(bool value) { _isZip64 = value; } 508 509 /** 510 * Associative array indexed by the name of each member of the archive. 511 * 512 * All the members of the archive can be accessed with a foreach loop: 513 * 514 * Example: 515 * -------------------- 516 * ZipArchive archive = new ZipArchive(data); 517 * foreach (ArchiveMember am; archive.directory) 518 * { 519 * writefln("member name is '%s'", am.name); 520 * } 521 * -------------------- 522 * 523 * Returns: Associative array with all archive members. 524 */ 525 @property @safe @nogc pure nothrow ArchiveMember[string] directory() { return _directory; } 526 527 private ArchiveMember[string] _directory; 528 529 debug (print) 530 { 531 @safe void print() 532 { 533 printf("\tdiskNumber = %u\n", diskNumber); 534 printf("\tdiskStartDir = %u\n", diskStartDir); 535 printf("\tnumEntries = %u\n", numEntries); 536 printf("\ttotalEntries = %u\n", totalEntries); 537 printf("\tcomment = '%.*s'\n", cast(int) comment.length, comment.ptr); 538 } 539 } 540 541 /* ============ Creating a new archive =================== */ 542 543 /** 544 * Constructor to use when creating a new archive. 545 */ 546 this() @safe @nogc pure nothrow 547 { 548 } 549 550 /** 551 * Add a member to the archive. The file is compressed on the fly. 552 * 553 * Params: 554 * de = Member to be added. 555 * 556 * Throws: ZipException when an unsupported compression method is used or when 557 * compression failed. 558 */ 559 @safe void addMember(ArchiveMember de) 560 { 561 _directory[de.name] = de; 562 if (!de._compressedData.length) 563 { 564 switch (de.compressionMethod) 565 { 566 case CompressionMethod.none: 567 de._compressedData = de._expandedData; 568 break; 569 570 case CompressionMethod.deflate: 571 import std.zlib : compress; 572 () @trusted 573 { 574 de._compressedData = cast(ubyte[]) compress(cast(void[]) de._expandedData); 575 }(); 576 de._compressedData = de._compressedData[2 .. de._compressedData.length - 4]; 577 break; 578 579 default: 580 throw new ZipException("unsupported compression method"); 581 } 582 583 de._compressedSize = to!uint(de._compressedData.length); 584 import std.zlib : crc32; 585 () @trusted { de._crc32 = crc32(0, cast(void[]) de._expandedData); }(); 586 } 587 assert(de._compressedData.length == de._compressedSize, "Archive member compressed failed."); 588 } 589 590 @safe unittest 591 { 592 import std.exception : assertThrown; 593 594 ArchiveMember am = new ArchiveMember(); 595 am.compressionMethod = cast(CompressionMethod) 3; 596 597 ZipArchive zip = new ZipArchive(); 598 599 assertThrown!ZipException(zip.addMember(am)); 600 } 601 602 /** 603 * Delete member `de` from the archive. Uses the name of the member 604 * to detect which element to delete. 605 * 606 * Params: 607 * de = Member to be deleted. 608 */ 609 @safe void deleteMember(ArchiveMember de) 610 { 611 _directory.remove(de.name); 612 } 613 614 // https://issues.dlang.org/show_bug.cgi?id=20398 615 @safe unittest 616 { 617 import std..string : representation; 618 619 ArchiveMember file1 = new ArchiveMember(); 620 file1.name = "test1.txt"; 621 file1.expandedData("Test data.\n".dup.representation); 622 623 ZipArchive zip = new ZipArchive(); 624 625 zip.addMember(file1); 626 assert(zip.totalEntries == 1); 627 628 zip.deleteMember(file1); 629 assert(zip.totalEntries == 0); 630 } 631 632 /** 633 * Construct the entire contents of the current members of the archive. 634 * 635 * Fills in the properties data[], totalEntries, and directory[]. 636 * For each ArchiveMember, fills in properties crc32, compressedSize, 637 * compressedData[]. 638 * 639 * Returns: Array representing the entire archive. 640 * 641 * Throws: ZipException when the archive could not be build. 642 */ 643 void[] build() @safe pure 644 { 645 import std.array : array, uninitializedArray; 646 import std.algorithm.sorting : sort; 647 import std..string : representation; 648 649 uint i; 650 uint directoryOffset; 651 652 enforce!ZipException(comment.length <= 0xFFFF, "archive comment longer than 65535"); 653 654 // Compress each member; compute size 655 uint archiveSize = 0; 656 uint directorySize = 0; 657 auto directory = _directory.byValue.array.sort!((x, y) => x.index < y.index).release; 658 foreach (ArchiveMember de; directory) 659 { 660 enforce!ZipException(to!ulong(archiveSize) + localFileHeaderLength + de.name.length 661 + de.extra.length + de.compressedSize + directorySize 662 + centralFileHeaderLength + de.name.length + de.extra.length 663 + de.comment.length + endOfCentralDirLength + comment.length 664 + zip64EndOfCentralDirLocatorLength + zip64EndOfCentralDirLength <= uint.max, 665 "zip files bigger than 4 GB are unsupported"); 666 667 archiveSize += localFileHeaderLength + de.name.length + 668 de.extra.length + 669 de.compressedSize; 670 directorySize += centralFileHeaderLength + de.name.length + 671 de.extra.length + 672 de.comment.length; 673 } 674 675 if (!isZip64 && _directory.length > ushort.max) 676 _isZip64 = true; 677 uint dataSize = archiveSize + directorySize + endOfCentralDirLength + cast(uint) comment.length; 678 if (isZip64) 679 dataSize += zip64EndOfCentralDirLocatorLength + zip64EndOfCentralDirLength; 680 681 _data = uninitializedArray!(ubyte[])(dataSize); 682 683 // Populate the data[] 684 685 // Store each archive member 686 i = 0; 687 foreach (ArchiveMember de; directory) 688 { 689 de.offset = i; 690 _data[i .. i + 4] = localFileHeaderSignature; 691 putUshort(i + 4, de.extractVersion); 692 putUshort(i + 6, de.flags); 693 putUshort(i + 8, de._compressionMethod); 694 putUint (i + 10, cast(uint) de.time); 695 putUint (i + 14, de.crc32); 696 putUint (i + 18, de.compressedSize); 697 putUint (i + 22, to!uint(de.expandedSize)); 698 putUshort(i + 26, cast(ushort) de.name.length); 699 putUshort(i + 28, cast(ushort) de.extra.length); 700 i += localFileHeaderLength; 701 702 _data[i .. i + de.name.length] = (de.name.representation)[]; 703 i += de.name.length; 704 _data[i .. i + de.extra.length] = (cast(ubyte[]) de.extra)[]; 705 i += de.extra.length; 706 _data[i .. i + de.compressedSize] = de.compressedData[]; 707 i += de.compressedSize; 708 } 709 710 // Write directory 711 directoryOffset = i; 712 foreach (ArchiveMember de; directory) 713 { 714 _data[i .. i + 4] = centralFileHeaderSignature; 715 putUshort(i + 4, de._madeVersion); 716 putUshort(i + 6, de.extractVersion); 717 putUshort(i + 8, de.flags); 718 putUshort(i + 10, de._compressionMethod); 719 putUint (i + 12, cast(uint) de.time); 720 putUint (i + 16, de.crc32); 721 putUint (i + 20, de.compressedSize); 722 putUint (i + 24, de.expandedSize); 723 putUshort(i + 28, cast(ushort) de.name.length); 724 putUshort(i + 30, cast(ushort) de.extra.length); 725 putUshort(i + 32, cast(ushort) de.comment.length); 726 putUshort(i + 34, cast(ushort) 0); 727 putUshort(i + 36, de.internalAttributes); 728 putUint (i + 38, de._externalAttributes); 729 putUint (i + 42, de.offset); 730 i += centralFileHeaderLength; 731 732 _data[i .. i + de.name.length] = (de.name.representation)[]; 733 i += de.name.length; 734 _data[i .. i + de.extra.length] = (cast(ubyte[]) de.extra)[]; 735 i += de.extra.length; 736 _data[i .. i + de.comment.length] = (de.comment.representation)[]; 737 i += de.comment.length; 738 } 739 740 if (isZip64) 741 { 742 // Write zip64 end of central directory record 743 uint eocd64Offset = i; 744 _data[i .. i + 4] = zip64EndOfCentralDirSignature; 745 putUlong (i + 4, zip64EndOfCentralDirLength - 12); 746 putUshort(i + 12, zip64ExtractVersion); 747 putUshort(i + 14, zip64ExtractVersion); 748 putUint (i + 16, cast(ushort) 0); 749 putUint (i + 20, cast(ushort) 0); 750 putUlong (i + 24, directory.length); 751 putUlong (i + 32, directory.length); 752 putUlong (i + 40, directorySize); 753 putUlong (i + 48, directoryOffset); 754 i += zip64EndOfCentralDirLength; 755 756 // Write zip64 end of central directory record locator 757 _data[i .. i + 4] = zip64EndOfCentralDirLocatorSignature; 758 putUint (i + 4, cast(ushort) 0); 759 putUlong (i + 8, eocd64Offset); 760 putUint (i + 16, 1); 761 i += zip64EndOfCentralDirLocatorLength; 762 } 763 764 // Write end record 765 _data[i .. i + 4] = endOfCentralDirSignature; 766 putUshort(i + 4, cast(ushort) 0); 767 putUshort(i + 6, cast(ushort) 0); 768 putUshort(i + 8, (totalEntries > ushort.max ? ushort.max : cast(ushort) totalEntries)); 769 putUshort(i + 10, (totalEntries > ushort.max ? ushort.max : cast(ushort) totalEntries)); 770 putUint (i + 12, directorySize); 771 putUint (i + 16, directoryOffset); 772 putUshort(i + 20, cast(ushort) comment.length); 773 i += endOfCentralDirLength; 774 775 // Write archive comment 776 assert(i + comment.length == data.length, "Writing the archive comment failed."); 777 _data[i .. data.length] = (comment.representation)[]; 778 779 return cast(void[]) data; 780 } 781 782 @safe pure unittest 783 { 784 import std.exception : assertNotThrown; 785 786 ZipArchive zip = new ZipArchive(); 787 zip.comment = "A"; 788 assertNotThrown(zip.build()); 789 } 790 791 @safe pure unittest 792 { 793 import std.range : repeat, array; 794 import std.exception : assertThrown; 795 796 ZipArchive zip = new ZipArchive(); 797 zip.comment = 'A'.repeat(70_000).array; 798 assertThrown!ZipException(zip.build()); 799 } 800 801 /* ============ Reading an existing archive =================== */ 802 803 /** 804 * Constructor to use when reading an existing archive. 805 * 806 * Fills in the properties data[], totalEntries, comment[], and directory[]. 807 * For each ArchiveMember, fills in 808 * properties madeVersion, extractVersion, flags, compressionMethod, time, 809 * crc32, compressedSize, expandedSize, compressedData[], 810 * internalAttributes, externalAttributes, name[], extra[], comment[]. 811 * Use expand() to get the expanded data for each ArchiveMember. 812 * 813 * Params: 814 * buffer = The entire contents of the archive. 815 * 816 * Throws: ZipException when the archive was invalid or when malware was detected. 817 */ 818 this(void[] buffer) 819 { 820 this._data = cast(ubyte[]) buffer; 821 822 enforce!ZipException(data.length <= uint.max - 2, "zip files bigger than 4 GB are unsupported"); 823 824 _segs = [Segment(0, cast(uint) data.length)]; 825 826 uint i = findEndOfCentralDirRecord(); 827 828 int endCommentLength = getUshort(i + 20); 829 comment = cast(string)(_data[i + endOfCentralDirLength .. i + endOfCentralDirLength + endCommentLength]); 830 831 // end of central dir record 832 removeSegment(i, i + endOfCentralDirLength + endCommentLength); 833 834 uint k = i - zip64EndOfCentralDirLocatorLength; 835 if (k < i && _data[k .. k + 4] == zip64EndOfCentralDirLocatorSignature) 836 { 837 _isZip64 = true; 838 i = k; 839 840 // zip64 end of central dir record locator 841 removeSegment(k, k + zip64EndOfCentralDirLocatorLength); 842 } 843 844 uint directorySize; 845 uint directoryOffset; 846 uint directoryCount; 847 848 if (isZip64) 849 { 850 // Read Zip64 record data 851 ulong eocdOffset = getUlong(i + 8); 852 enforce!ZipException(eocdOffset + zip64EndOfCentralDirLength <= _data.length, 853 "corrupted directory"); 854 855 i = to!uint(eocdOffset); 856 enforce!ZipException(_data[i .. i + 4] == zip64EndOfCentralDirSignature, 857 "invalid Zip EOCD64 signature"); 858 859 ulong eocd64Size = getUlong(i + 4); 860 enforce!ZipException(eocd64Size + i - 12 <= data.length, 861 "invalid Zip EOCD64 size"); 862 863 // zip64 end of central dir record 864 removeSegment(i, cast(uint) (i + 12 + eocd64Size)); 865 866 ulong numEntriesUlong = getUlong(i + 24); 867 ulong totalEntriesUlong = getUlong(i + 32); 868 ulong directorySizeUlong = getUlong(i + 40); 869 ulong directoryOffsetUlong = getUlong(i + 48); 870 871 enforce!ZipException(numEntriesUlong <= uint.max, 872 "supposedly more than 4294967296 files in archive"); 873 874 enforce!ZipException(numEntriesUlong == totalEntriesUlong, 875 "multiple disk zips not supported"); 876 877 enforce!ZipException(directorySizeUlong <= i && directoryOffsetUlong <= i 878 && directorySizeUlong + directoryOffsetUlong <= i, 879 "corrupted directory"); 880 881 directoryCount = to!uint(totalEntriesUlong); 882 directorySize = to!uint(directorySizeUlong); 883 directoryOffset = to!uint(directoryOffsetUlong); 884 } 885 else 886 { 887 // Read end record data 888 directoryCount = getUshort(i + 10); 889 directorySize = getUint(i + 12); 890 directoryOffset = getUint(i + 16); 891 } 892 893 i = directoryOffset; 894 for (int n = 0; n < directoryCount; n++) 895 { 896 /* The format of an entry is: 897 * 'PK' 1, 2 898 * directory info 899 * path 900 * extra data 901 * comment 902 */ 903 904 uint namelen; 905 uint extralen; 906 uint commentlen; 907 908 enforce!ZipException(_data[i .. i + 4] == centralFileHeaderSignature, 909 "wrong central file header signature found"); 910 ArchiveMember de = new ArchiveMember(); 911 de._index = n; 912 de._madeVersion = getUshort(i + 4); 913 de._extractVersion = getUshort(i + 6); 914 de.flags = getUshort(i + 8); 915 de._compressionMethod = cast(CompressionMethod) getUshort(i + 10); 916 de.time = cast(DosFileTime) getUint(i + 12); 917 de._crc32 = getUint(i + 16); 918 de._compressedSize = getUint(i + 20); 919 de._expandedSize = getUint(i + 24); 920 namelen = getUshort(i + 28); 921 extralen = getUshort(i + 30); 922 commentlen = getUshort(i + 32); 923 de.internalAttributes = getUshort(i + 36); 924 de._externalAttributes = getUint(i + 38); 925 de.offset = getUint(i + 42); 926 927 // central file header 928 removeSegment(i, i + centralFileHeaderLength + namelen + extralen + commentlen); 929 930 i += centralFileHeaderLength; 931 932 enforce!ZipException(i + namelen + extralen + commentlen <= directoryOffset + directorySize, 933 "invalid field lengths in file header found"); 934 935 de.name = cast(string)(_data[i .. i + namelen]); 936 i += namelen; 937 de.extra = _data[i .. i + extralen]; 938 i += extralen; 939 de.comment = cast(string)(_data[i .. i + commentlen]); 940 i += commentlen; 941 942 auto localFileHeaderNamelen = getUshort(de.offset + 26); 943 auto localFileHeaderExtralen = getUshort(de.offset + 28); 944 945 // file data 946 removeSegment(de.offset, de.offset + localFileHeaderLength + localFileHeaderNamelen 947 + localFileHeaderExtralen + de._compressedSize); 948 949 immutable uint dataOffset = de.offset + localFileHeaderLength 950 + localFileHeaderNamelen + localFileHeaderExtralen; 951 de._compressedData = _data[dataOffset .. dataOffset + de.compressedSize]; 952 953 _directory[de.name] = de; 954 } 955 956 enforce!ZipException(i == directoryOffset + directorySize, "invalid directory entry 3"); 957 } 958 959 @system unittest 960 { 961 import std.exception : assertThrown; 962 963 // contains wrong directorySize (extra byte 0xff) 964 auto file = 965 "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~ 966 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~ 967 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ 968 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ 969 "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ 970 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ 971 "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ 972 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ 973 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\xff\x50\x4b\x05"~ 974 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4b\x00\x00\x00\x43\x00\x00"~ 975 "\x00\x00\x00"; 976 977 assertThrown!ZipException(new ZipArchive(cast(void[]) file)); 978 } 979 980 @system unittest 981 { 982 import std.exception : assertThrown; 983 984 // wrong eocdOffset 985 auto file = 986 "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ 987 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ 988 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ 989 "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ 990 "\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ 991 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ 992 "\x00\x00"; 993 994 assertThrown!ZipException(new ZipArchive(cast(void[]) file)); 995 } 996 997 @system unittest 998 { 999 import std.exception : assertThrown; 1000 1001 // wrong signature of zip64 end of central directory 1002 auto file = 1003 "\x50\x4b\x06\x07\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ 1004 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ 1005 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ 1006 "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ 1007 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ 1008 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ 1009 "\x00\x00"; 1010 1011 assertThrown!ZipException(new ZipArchive(cast(void[]) file)); 1012 } 1013 1014 @system unittest 1015 { 1016 import std.exception : assertThrown; 1017 1018 // wrong size of zip64 end of central directory 1019 auto file = 1020 "\x50\x4b\x06\x06\xff\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ 1021 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ 1022 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ 1023 "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ 1024 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ 1025 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ 1026 "\x00\x00"; 1027 1028 assertThrown!ZipException(new ZipArchive(cast(void[]) file)); 1029 } 1030 1031 @system unittest 1032 { 1033 import std.exception : assertThrown; 1034 1035 // too many entries in zip64 end of central directory 1036 auto file = 1037 "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ 1038 "\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\x00\x00\x00"~ 1039 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ 1040 "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ 1041 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ 1042 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ 1043 "\x00\x00"; 1044 1045 assertThrown!ZipException(new ZipArchive(cast(void[]) file)); 1046 } 1047 1048 @system unittest 1049 { 1050 import std.exception : assertThrown; 1051 1052 // zip64: numEntries and totalEntries differ 1053 auto file = 1054 "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ 1055 "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"~ 1056 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ 1057 "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ 1058 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ 1059 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ 1060 "\x00\x00"; 1061 1062 assertThrown!ZipException(new ZipArchive(cast(void[]) file)); 1063 } 1064 1065 @system unittest 1066 { 1067 import std.exception : assertThrown; 1068 1069 // zip64: directorySize too large 1070 auto file = 1071 "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ 1072 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ 1073 "\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00"~ 1074 "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ 1075 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ 1076 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ 1077 "\x00\x00"; 1078 1079 assertThrown!ZipException(new ZipArchive(cast(void[]) file)); 1080 1081 // zip64: directoryOffset too large 1082 file = 1083 "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ 1084 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ 1085 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ 1086 "\xff\xff\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ 1087 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ 1088 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ 1089 "\x00\x00"; 1090 1091 assertThrown!ZipException(new ZipArchive(cast(void[]) file)); 1092 1093 // zip64: directorySize + directoryOffset too large 1094 // we need to add a useless byte at the beginning to avoid that one of the other two checks allready fires 1095 file = 1096 "\x00\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ 1097 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ 1098 "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"~ 1099 "\x01\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ 1100 "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ 1101 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ 1102 "\x00\x00"; 1103 1104 assertThrown!ZipException(new ZipArchive(cast(void[]) file)); 1105 } 1106 1107 @system unittest 1108 { 1109 import std.exception : assertThrown; 1110 1111 // wrong central file header signature 1112 auto file = 1113 "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~ 1114 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~ 1115 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ 1116 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ 1117 "\x6c\x6c\x6f\x50\x4b\x01\x03\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ 1118 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ 1119 "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ 1120 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ 1121 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~ 1122 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~ 1123 "\x00\x00\x00"; 1124 1125 assertThrown!ZipException(new ZipArchive(cast(void[]) file)); 1126 } 1127 1128 @system unittest 1129 { 1130 import std.exception : assertThrown; 1131 1132 // invalid field lengths in file header 1133 auto file = 1134 "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~ 1135 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~ 1136 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ 1137 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ 1138 "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ 1139 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ 1140 "\x00\x18\x00\x01\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ 1141 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ 1142 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\xff\x50\x4b\x05"~ 1143 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~ 1144 "\x00\x00\x00"; 1145 1146 assertThrown!ZipException(new ZipArchive(cast(void[]) file)); 1147 } 1148 1149 private uint findEndOfCentralDirRecord() 1150 { 1151 // end of central dir record can be followed by a comment of up to 2^^16-1 bytes 1152 // therefore we have to scan 2^^16 positions 1153 1154 uint endrecOffset = to!uint(data.length); 1155 foreach (i; 0 .. 2 ^^ 16) 1156 { 1157 if (endOfCentralDirLength + i > data.length) break; 1158 uint start = to!uint(data.length) - endOfCentralDirLength - i; 1159 1160 if (data[start .. start + 4] != endOfCentralDirSignature) continue; 1161 1162 auto numberOfThisDisc = getUshort(start + 4); 1163 if (numberOfThisDisc != 0) continue; // no support for multiple volumes yet 1164 1165 auto numberOfStartOfCentralDirectory = getUshort(start + 6); 1166 if (numberOfStartOfCentralDirectory != 0) continue; // dito 1167 1168 if (numberOfThisDisc < numberOfStartOfCentralDirectory) continue; 1169 1170 uint k = start - zip64EndOfCentralDirLocatorLength; 1171 auto maybeZip64 = k < start && _data[k .. k + 4] == zip64EndOfCentralDirLocatorSignature; 1172 1173 auto totalNumberOfEntriesOnThisDisk = getUshort(start + 8); 1174 auto totalNumberOfEntriesInCentralDir = getUshort(start + 10); 1175 1176 if (totalNumberOfEntriesOnThisDisk > totalNumberOfEntriesInCentralDir && 1177 (!maybeZip64 || totalNumberOfEntriesOnThisDisk < 0xffff)) continue; 1178 1179 auto sizeOfCentralDirectory = getUint(start + 12); 1180 if (sizeOfCentralDirectory > start && 1181 (!maybeZip64 || sizeOfCentralDirectory < 0xffff)) continue; 1182 1183 auto offsetOfCentralDirectory = getUint(start + 16); 1184 if (offsetOfCentralDirectory > start - sizeOfCentralDirectory && 1185 (!maybeZip64 || offsetOfCentralDirectory < 0xffff)) continue; 1186 1187 auto zipfileCommentLength = getUshort(start + 20); 1188 if (start + zipfileCommentLength + endOfCentralDirLength != data.length) continue; 1189 1190 enforce!ZipException(endrecOffset == to!uint(data.length), 1191 "found more than one valid 'end of central dir record'"); 1192 1193 endrecOffset = start; 1194 } 1195 1196 enforce!ZipException(endrecOffset != to!uint(data.length), 1197 "found no valid 'end of central dir record'"); 1198 1199 return endrecOffset; 1200 } 1201 1202 /** 1203 * Decompress the contents of a member. 1204 * 1205 * Fills in properties extractVersion, flags, compressionMethod, time, 1206 * crc32, compressedSize, expandedSize, expandedData[], name[], extra[]. 1207 * 1208 * Params: 1209 * de = Member to be decompressed. 1210 * 1211 * Returns: The expanded data. 1212 * 1213 * Throws: ZipException when the entry is invalid or the compression method is not supported. 1214 */ 1215 ubyte[] expand(ArchiveMember de) 1216 { 1217 import std..string : representation; 1218 1219 uint namelen; 1220 uint extralen; 1221 1222 enforce!ZipException(_data[de.offset .. de.offset + 4] == localFileHeaderSignature, 1223 "wrong local file header signature found"); 1224 1225 // These values should match what is in the main zip archive directory 1226 de._extractVersion = getUshort(de.offset + 4); 1227 de.flags = getUshort(de.offset + 6); 1228 de._compressionMethod = cast(CompressionMethod) getUshort(de.offset + 8); 1229 de.time = cast(DosFileTime) getUint(de.offset + 10); 1230 de._crc32 = getUint(de.offset + 14); 1231 de._compressedSize = max(getUint(de.offset + 18), de.compressedSize); 1232 de._expandedSize = max(getUint(de.offset + 22), de.expandedSize); 1233 namelen = getUshort(de.offset + 26); 1234 extralen = getUshort(de.offset + 28); 1235 1236 debug(print) 1237 { 1238 printf("\t\texpandedSize = %d\n", de.expandedSize); 1239 printf("\t\tcompressedSize = %d\n", de.compressedSize); 1240 printf("\t\tnamelen = %d\n", namelen); 1241 printf("\t\textralen = %d\n", extralen); 1242 } 1243 1244 enforce!ZipException((de.flags & 1) == 0, "encryption not supported"); 1245 1246 switch (de.compressionMethod) 1247 { 1248 case CompressionMethod.none: 1249 de._expandedData = de.compressedData; 1250 return de.expandedData; 1251 1252 case CompressionMethod.deflate: 1253 // -15 is a magic value used to decompress zip files. 1254 // It has the effect of not requiring the 2 byte header 1255 // and 4 byte trailer. 1256 import std.zlib : uncompress; 1257 de._expandedData = cast(ubyte[]) uncompress(cast(void[]) de.compressedData, de.expandedSize, -15); 1258 return de.expandedData; 1259 1260 default: 1261 throw new ZipException("unsupported compression method"); 1262 } 1263 } 1264 1265 @system unittest 1266 { 1267 import std.exception : assertThrown; 1268 1269 // check for correct local file header signature 1270 auto file = 1271 "\x50\x4b\x04\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~ 1272 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~ 1273 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ 1274 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ 1275 "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ 1276 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ 1277 "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ 1278 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ 1279 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~ 1280 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~ 1281 "\x00\x00\x00"; 1282 1283 auto za = new ZipArchive(cast(void[]) file); 1284 1285 assertThrown!ZipException(za.expand(za._directory["file"])); 1286 } 1287 1288 @system unittest 1289 { 1290 import std.exception : assertThrown; 1291 1292 // check for encryption flag 1293 auto file = 1294 "\x50\x4b\x03\x04\x0a\x00\x01\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~ 1295 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~ 1296 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ 1297 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ 1298 "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ 1299 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ 1300 "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ 1301 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ 1302 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~ 1303 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~ 1304 "\x00\x00\x00"; 1305 1306 auto za = new ZipArchive(cast(void[]) file); 1307 1308 assertThrown!ZipException(za.expand(za._directory["file"])); 1309 } 1310 1311 @system unittest 1312 { 1313 import std.exception : assertThrown; 1314 1315 // check for invalid compression method 1316 auto file = 1317 "\x50\x4b\x03\x04\x0a\x00\x00\x00\x03\x00\x8f\x72\x4a\x4f\x86\xa6"~ 1318 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~ 1319 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ 1320 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ 1321 "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ 1322 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ 1323 "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ 1324 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ 1325 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~ 1326 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~ 1327 "\x00\x00\x00"; 1328 1329 auto za = new ZipArchive(cast(void[]) file); 1330 1331 assertThrown!ZipException(za.expand(za._directory["file"])); 1332 } 1333 1334 /* ============ Utility =================== */ 1335 1336 @safe @nogc pure nothrow ushort getUshort(uint i) 1337 { 1338 ubyte[2] result = data[i .. i + 2]; 1339 return littleEndianToNative!ushort(result); 1340 } 1341 1342 @safe @nogc pure nothrow uint getUint(uint i) 1343 { 1344 ubyte[4] result = data[i .. i + 4]; 1345 return littleEndianToNative!uint(result); 1346 } 1347 1348 @safe @nogc pure nothrow ulong getUlong(uint i) 1349 { 1350 ubyte[8] result = data[i .. i + 8]; 1351 return littleEndianToNative!ulong(result); 1352 } 1353 1354 @safe @nogc pure nothrow void putUshort(uint i, ushort us) 1355 { 1356 data[i .. i + 2] = nativeToLittleEndian(us); 1357 } 1358 1359 @safe @nogc pure nothrow void putUint(uint i, uint ui) 1360 { 1361 data[i .. i + 4] = nativeToLittleEndian(ui); 1362 } 1363 1364 @safe @nogc pure nothrow void putUlong(uint i, ulong ul) 1365 { 1366 data[i .. i + 8] = nativeToLittleEndian(ul); 1367 } 1368 1369 /* ============== for detecting overlaps =============== */ 1370 1371 private: 1372 1373 // defines a segment of the zip file, including start, excluding end 1374 struct Segment 1375 { 1376 uint start; 1377 uint end; 1378 } 1379 1380 // removes Segment start .. end from _segs 1381 // throws zipException if start .. end is not completely available in _segs; 1382 void removeSegment(uint start, uint end) pure @safe 1383 in (start < end, "segment invalid") 1384 { 1385 auto found = false; 1386 size_t pos; 1387 foreach (i,seg;_segs) 1388 if (seg.start <= start && seg.end >= end 1389 && (!found || seg.start > _segs[pos].start)) 1390 { 1391 found = true; 1392 pos = i; 1393 } 1394 1395 enforce!ZipException(found, "overlapping data detected"); 1396 1397 if (start>_segs[pos].start) 1398 _segs ~= Segment(_segs[pos].start, start); 1399 if (end<_segs[pos].end) 1400 _segs ~= Segment(end, _segs[pos].end); 1401 _segs = _segs[0 .. pos] ~ _segs[pos + 1 .. $]; 1402 } 1403 1404 pure @safe unittest 1405 { 1406 with (new ZipArchive()) 1407 { 1408 _segs = [Segment(0,100)]; 1409 removeSegment(10,20); 1410 assert(_segs == [Segment(0,10),Segment(20,100)]); 1411 1412 _segs = [Segment(0,100)]; 1413 removeSegment(0,20); 1414 assert(_segs == [Segment(20,100)]); 1415 1416 _segs = [Segment(0,100)]; 1417 removeSegment(10,100); 1418 assert(_segs == [Segment(0,10)]); 1419 1420 _segs = [Segment(0,100), Segment(200,300), Segment(400,500)]; 1421 removeSegment(220,230); 1422 assert(_segs == [Segment(0,100),Segment(400,500),Segment(200,220),Segment(230,300)]); 1423 1424 _segs = [Segment(200,300), Segment(0,100), Segment(400,500)]; 1425 removeSegment(20,30); 1426 assert(_segs == [Segment(200,300),Segment(400,500),Segment(0,20),Segment(30,100)]); 1427 1428 import std.exception : assertThrown; 1429 1430 _segs = [Segment(0,100), Segment(200,300), Segment(400,500)]; 1431 assertThrown(removeSegment(120,230)); 1432 1433 _segs = [Segment(0,100), Segment(200,300), Segment(400,500)]; 1434 removeSegment(0,100); 1435 assertThrown(removeSegment(0,100)); 1436 1437 _segs = [Segment(0,100)]; 1438 removeSegment(0,100); 1439 assertThrown(removeSegment(0,100)); 1440 } 1441 } 1442 } 1443 1444 debug(print) 1445 { 1446 @safe void arrayPrint(ubyte[] array) 1447 { 1448 printf("array %p,%d\n", cast(void*) array, array.length); 1449 for (int i = 0; i < array.length; i++) 1450 { 1451 printf("%02x ", array[i]); 1452 if (((i + 1) & 15) == 0) 1453 printf("\n"); 1454 } 1455 printf("\n"); 1456 } 1457 } 1458 1459 @system unittest 1460 { 1461 // @system due to (at least) ZipArchive.build 1462 auto zip1 = new ZipArchive(); 1463 auto zip2 = new ZipArchive(); 1464 auto am1 = new ArchiveMember(); 1465 am1.name = "foo"; 1466 am1.expandedData = new ubyte[](1024); 1467 zip1.addMember(am1); 1468 auto data1 = zip1.build(); 1469 zip2.addMember(zip1.directory["foo"]); 1470 zip2.build(); 1471 auto am2 = zip2.directory["foo"]; 1472 zip2.expand(am2); 1473 assert(am1.expandedData == am2.expandedData); 1474 auto zip3 = new ZipArchive(data1); 1475 zip3.build(); 1476 assert(zip3.directory["foo"].compressedSize == am1.compressedSize); 1477 1478 // Test if packing and unpacking produces the original data 1479 import std.conv, std.stdio; 1480 import std.random : uniform, MinstdRand0; 1481 MinstdRand0 gen; 1482 const uint itemCount = 20, minSize = 10, maxSize = 500; 1483 foreach (variant; 0 .. 2) 1484 { 1485 bool useZip64 = !!variant; 1486 zip1 = new ZipArchive(); 1487 zip1.isZip64 = useZip64; 1488 ArchiveMember[itemCount] ams; 1489 foreach (i; 0 .. itemCount) 1490 { 1491 ams[i] = new ArchiveMember(); 1492 ams[i].name = to!string(i); 1493 ams[i].expandedData = new ubyte[](uniform(minSize, maxSize)); 1494 foreach (ref ubyte c; ams[i].expandedData) 1495 c = cast(ubyte)(uniform(0, 256)); 1496 ams[i].compressionMethod = CompressionMethod.deflate; 1497 zip1.addMember(ams[i]); 1498 } 1499 auto zippedData = zip1.build(); 1500 zip2 = new ZipArchive(zippedData); 1501 assert(zip2.isZip64 == useZip64); 1502 foreach (am; ams) 1503 { 1504 am2 = zip2.directory[am.name]; 1505 zip2.expand(am2); 1506 assert(am.crc32 == am2.crc32); 1507 assert(am.expandedData == am2.expandedData); 1508 } 1509 } 1510 } 1511 1512 @system unittest 1513 { 1514 import std.conv : to; 1515 import std.random : Mt19937, randomShuffle; 1516 // Test if packing and unpacking preserves order. 1517 auto rand = Mt19937(15966); 1518 string[] names; 1519 int value = 0; 1520 // Generate a series of unique numbers as filenames. 1521 foreach (i; 0 .. 20) 1522 { 1523 value += 1 + rand.front & 0xFFFF; 1524 rand.popFront; 1525 names ~= value.to!string; 1526 } 1527 // Insert them in a random order. 1528 names.randomShuffle(rand); 1529 auto zip1 = new ZipArchive(); 1530 foreach (i, name; names) 1531 { 1532 auto member = new ArchiveMember(); 1533 member.name = name; 1534 member.expandedData = cast(ubyte[]) name; 1535 member.index = cast(int) i; 1536 zip1.addMember(member); 1537 } 1538 auto data = zip1.build(); 1539 1540 // Ensure that they appear in the same order. 1541 auto zip2 = new ZipArchive(data); 1542 foreach (i, name; names) 1543 { 1544 const member = zip2.directory[name]; 1545 assert(member.index == i, "member " ~ name ~ " had index " ~ 1546 member.index.to!string ~ " but we expected index " ~ i.to!string ~ 1547 ". The input array was " ~ names.to!string); 1548 } 1549 } 1550 1551 @system unittest 1552 { 1553 import std.zlib; 1554 1555 ubyte[] src = cast(ubyte[]) 1556 "the quick brown fox jumps over the lazy dog\r 1557 the quick brown fox jumps over the lazy dog\r 1558 "; 1559 auto dst = cast(ubyte[]) compress(cast(void[]) src); 1560 auto after = cast(ubyte[]) uncompress(cast(void[]) dst); 1561 assert(src == after); 1562 } 1563 1564 @system unittest 1565 { 1566 // @system due to ZipArchive.build 1567 import std.datetime; 1568 ubyte[] buf = [1, 2, 3, 4, 5, 0, 7, 8, 9]; 1569 1570 auto ar = new ZipArchive; 1571 auto am = new ArchiveMember; // 10 1572 am.name = "buf"; 1573 am.expandedData = buf; 1574 am.compressionMethod = CompressionMethod.deflate; 1575 am.time = SysTimeToDosFileTime(Clock.currTime()); 1576 ar.addMember(am); // 15 1577 1578 auto zip1 = ar.build(); 1579 auto arAfter = new ZipArchive(zip1); 1580 assert(arAfter.directory.length == 1); 1581 auto amAfter = arAfter.directory["buf"]; 1582 arAfter.expand(amAfter); 1583 assert(amAfter.name == am.name); 1584 assert(amAfter.expandedData == am.expandedData); 1585 assert(amAfter.time == am.time); 1586 } 1587 1588 @system unittest 1589 { 1590 // invalid format of end of central directory entry 1591 import std.exception : assertThrown; 1592 assertThrown!ZipException(new ZipArchive(cast(void[]) "\x50\x4B\x05\x06aaaaaaaaaaaaaaaaaaaa")); 1593 } 1594 1595 @system unittest 1596 { 1597 // minimum (empty) archive should pass 1598 auto za = new ZipArchive(cast(void[]) "\x50\x4B\x05\x06\x00\x00\x00\x00\x00\x00\x00"~ 1599 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"); 1600 assert(za.directory.length == 0); 1601 1602 // one byte too short or too long should not pass 1603 import std.exception : assertThrown; 1604 assertThrown!ZipException(new ZipArchive(cast(void[]) "\x50\x4B\x05\x06\x00\x00\x00\x00\x00\x00\x00"~ 1605 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")); 1606 assertThrown!ZipException(new ZipArchive(cast(void[]) "\x50\x4B\x05\x06\x00\x00\x00\x00\x00\x00\x00"~ 1607 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")); 1608 } 1609 1610 @system unittest 1611 { 1612 // https://issues.dlang.org/show_bug.cgi?id=20239 1613 // chameleon file, containing two valid end of central directory entries 1614 auto file = 1615 "\x50\x4B\x03\x04\x0A\x00\x00\x00\x00\x00\x89\x36\x39\x4F\x04\x6A\xB3\xA3\x01\x00"~ 1616 "\x00\x00\x01\x00\x00\x00\x0D\x00\x1C\x00\x62\x65\x73\x74\x5F\x6C\x61\x6E\x67\x75"~ 1617 "\x61\x67\x65\x55\x54\x09\x00\x03\x82\xF2\x8A\x5D\x82\xF2\x8A\x5D\x75\x78\x0B\x00"~ 1618 "\x01\x04\xEB\x03\x00\x00\x04\xEB\x03\x00\x00\x44\x50\x4B\x01\x02\x1E\x03\x0A\x00"~ 1619 "\x00\x00\x00\x00\x89\x36\x39\x4F\x04\x6A\xB3\xA3\x01\x00\x00\x00\x01\x00\x00\x00"~ 1620 "\x0D\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xB0\x81\x00\x00\x00\x00\x62\x65"~ 1621 "\x73\x74\x5F\x6C\x61\x6E\x67\x75\x61\x67\x65\x55\x54\x05\x00\x03\x82\xF2\x8A\x5D"~ 1622 "\x75\x78\x0B\x00\x01\x04\xEB\x03\x00\x00\x04\xEB\x03\x00\x00\x50\x4B\x05\x06\x00"~ 1623 "\x00\x00\x00\x01\x00\x01\x00\x53\x00\x00\x00\x48\x00\x00\x00\xB7\x00\x50\x4B\x03"~ 1624 "\x04\x0A\x00\x00\x00\x00\x00\x94\x36\x39\x4F\xD7\xCB\x3B\x55\x07\x00\x00\x00\x07"~ 1625 "\x00\x00\x00\x0D\x00\x1C\x00\x62\x65\x73\x74\x5F\x6C\x61\x6E\x67\x75\x61\x67\x65"~ 1626 "\x55\x54\x09\x00\x03\x97\xF2\x8A\x5D\x8C\xF2\x8A\x5D\x75\x78\x0B\x00\x01\x04\xEB"~ 1627 "\x03\x00\x00\x04\xEB\x03\x00\x00\x46\x4F\x52\x54\x52\x41\x4E\x50\x4B\x01\x02\x1E"~ 1628 "\x03\x0A\x00\x00\x00\x00\x00\x94\x36\x39\x4F\xD7\xCB\x3B\x55\x07\x00\x00\x00\x07"~ 1629 "\x00\x00\x00\x0D\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xB0\x81\xB1\x00\x00"~ 1630 "\x00\x62\x65\x73\x74\x5F\x6C\x61\x6E\x67\x75\x61\x67\x65\x55\x54\x05\x00\x03\x97"~ 1631 "\xF2\x8A\x5D\x75\x78\x0B\x00\x01\x04\xEB\x03\x00\x00\x04\xEB\x03\x00\x00\x50\x4B"~ 1632 "\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00\x53\x00\x00\x00\xFF\x00\x00\x00\x00\x00"; 1633 1634 import std.exception : assertThrown; 1635 assertThrown!ZipException(new ZipArchive(cast(void[]) file)); 1636 } 1637 1638 @system unittest 1639 { 1640 // https://issues.dlang.org/show_bug.cgi?id=20287 1641 // check for correct compressed data 1642 auto file = 1643 "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~ 1644 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~ 1645 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ 1646 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ 1647 "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ 1648 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ 1649 "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ 1650 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ 1651 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~ 1652 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~ 1653 "\x00\x00\x00"; 1654 1655 auto za = new ZipArchive(cast(void[]) file); 1656 assert(za.directory["file"].compressedData == [104, 101, 108, 108, 111]); 1657 } 1658 1659 // https://issues.dlang.org/show_bug.cgi?id=20027 1660 @system unittest 1661 { 1662 // central file header overlaps end of central directory 1663 auto file = 1664 // lfh 1665 "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~ 1666 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~ 1667 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ 1668 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ 1669 "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ 1670 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ 1671 "\x00\x18\x00\x04\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ 1672 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ 1673 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~ 1674 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~ 1675 "\x00\x00\x00"; 1676 1677 import std.exception : assertThrown; 1678 assertThrown!ZipException(new ZipArchive(cast(void[]) file)); 1679 1680 // local file header and file data overlap second local file header and file data 1681 file = 1682 "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~ 1683 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1e\x00\x66\x69"~ 1684 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~ 1685 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~ 1686 "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~ 1687 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~ 1688 "\x00\x18\x00\x04\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~ 1689 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~ 1690 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~ 1691 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~ 1692 "\x00\x00\x00"; 1693 1694 assertThrown!ZipException(new ZipArchive(cast(void[]) file)); 1695 } 1696 1697 @system unittest 1698 { 1699 // https://issues.dlang.org/show_bug.cgi?id=20295 1700 // zip64 with 0xff bytes in end of central dir record do not work 1701 // minimum (empty zip64) archive should pass 1702 auto file = 1703 "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~ 1704 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ 1705 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~ 1706 "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~ 1707 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~ 1708 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~ 1709 "\x00\x00"; 1710 1711 auto za = new ZipArchive(cast(void[]) file); 1712 assert(za.directory.length == 0); 1713 } 1714 1715 version (HasUnzip) 1716 @system unittest 1717 { 1718 import std.datetime, std.file, std.format, std.path, std.process, std.stdio; 1719 1720 if (executeShell("unzip").status != 0) 1721 { 1722 writeln("Can't run unzip, skipping unzip test"); 1723 return; 1724 } 1725 1726 auto zr = new ZipArchive(); 1727 auto am = new ArchiveMember(); 1728 am.compressionMethod = CompressionMethod.deflate; 1729 am.name = "foo.bar"; 1730 am.time = SysTimeToDosFileTime(Clock.currTime()); 1731 am.expandedData = cast(ubyte[])"We all live in a yellow submarine, a yellow submarine"; 1732 zr.addMember(am); 1733 auto data2 = zr.build(); 1734 1735 mkdirRecurse(deleteme); 1736 scope(exit) rmdirRecurse(deleteme); 1737 string zipFile = buildPath(deleteme, "foo.zip"); 1738 std.file.write(zipFile, cast(byte[]) data2); 1739 1740 auto result = executeShell(format("unzip -l %s", zipFile)); 1741 scope(failure) writeln(result.output); 1742 assert(result.status == 0); 1743 }