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