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