1 // Written in the D programming language.
4 Read and write data in the
5 $(LINK2 https://en.wikipedia.org/wiki/Zip_%28file_format%29, zip archive)
10 The current implementation mostly conforms to
11 $(LINK2 https://www.iso.org/standard/60101.html, ISO/IEC 21320-1:2015),
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.)
21 Additionally, archives are checked for malware attacks and rejected if detected.
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)
31 The current implementation makes use of the zlib compression library.
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
42 Example for reading an existing zip archive:
44 import std.stdio : writeln, writefln;
45 import std.file : read;
48 void main(string[] args)
50 // read a zip file into memory
51 auto zip = new ZipArchive(read(args[1]));
53 // iterate over all zip members
54 writefln("%-10s %-8s Name", "Length", "CRC-32");
55 foreach (name, am; zip.directory)
57 // print some data about each member
58 writefln("%10s %08x %s", am.expandedSize, am.crc32, name);
59 assert(am.expandedData.length == 0);
61 // decompress the archive member
63 assert(am.expandedData.length == am.expandedSize);
68 Example for writing files into a zip archive:
70 import std.file : write;
71 import std.string : representation;
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
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
87 // Create an archive and add the member.
88 ZipArchive zip = new ZipArchive();
95 void[] compressed_data = zip.build();
98 write("test.zip", compressed_data);
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)
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)
115 import std.exception : enforce;
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
120 else version (iOS) {}
121 else version (TVOS) {}
122 else version (WatchOS) {}
129 class ZipException : Exception
131 import std.exception : basicExceptionCtors;
133 mixin basicExceptionCtors;
136 /// Compression method used by `ArchiveMember`.
137 enum CompressionMethod : ushort
139 none = 0, /// No compression, just archiving.
140 deflate = 8 /// Deflate algorithm. Use zlib library to compress.
143 /// A single file or directory inside the archive.
144 final class ArchiveMember
146 import std.conv : to, octal;
147 import std.datetime.systime : DosFileTime, SysTime, SysTimeToDosFileTime;
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
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.
166 string comment; /// Comment associated with this member.
168 private ubyte[] _compressedData;
169 private ubyte[] _expandedData;
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;
183 * Contains some information on how to extract this archive. See
184 * $(LINK2 https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT,
185 * original documentation)
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.
194 ushort internalAttributes;
197 * The zip file format version needed to extract this member.
199 * Returns: Format version needed to extract this member.
201 @property @safe pure nothrow @nogc ushort extractVersion() const { return _extractVersion; }
204 * Cyclic redundancy check (CRC) value.
206 * Returns: CRC32 value.
208 @property @safe pure nothrow @nogc uint crc32() const { return _crc32; }
211 * Size of data of member in compressed form.
213 * Returns: Size of the compressed archive.
215 @property @safe pure nothrow @nogc uint compressedSize() const { return _compressedSize; }
218 * Size of data of member in uncompressed form.
220 * Returns: Size of uncompressed archive.
222 @property @safe pure nothrow @nogc uint expandedSize() const { return _expandedSize; }
227 * Returns: The number of the disk where this member can be found.
229 deprecated("Multidisk not supported; will be removed in 2.099.0")
230 @property @safe pure nothrow @nogc ushort diskNumber() const { return 0; }
233 * Data of member in compressed form.
235 * Returns: The file data in compressed form.
237 @property @safe pure nothrow @nogc ubyte[] compressedData() { return _compressedData; }
240 * Get or set data of member in uncompressed form. When an existing archive is
241 * read `ZipArchive.expand` needs to be called before this can be accessed.
244 * ed = Expanded Data.
246 * Returns: The file data.
248 @property @safe pure nothrow @nogc ubyte[] expandedData() { return _expandedData; }
251 @property @safe void expandedData(ubyte[] ed)
254 _expandedSize = to!uint(_expandedData.length);
256 // Clean old compressed data, if any
257 _compressedData.length = 0;
262 * Get or set the OS specific file attributes for this archive member.
265 * attr = Attributes as obtained by $(REF getAttributes, std,file) or
266 * $(REF DirEntry.attributes, std,file).
268 * Returns: The file attributes or 0 if the file attributes were
269 * encoded for an incompatible OS (Windows vs. POSIX).
271 @property @safe void fileAttributes(uint attr)
275 _externalAttributes = (attr & 0xFFFF) << 16;
276 _madeVersion &= 0x00FF;
277 _madeVersion |= 0x0300; // attributes are in UNIX format
279 else version (Windows)
281 _externalAttributes = attr;
282 _madeVersion &= 0x00FF; // attributes are in MS-DOS and OS/2 format
286 static assert(0, "Unimplemented platform");
290 version (Posix) @safe unittest
292 auto am = new ArchiveMember();
293 am.fileAttributes = octal!100644;
294 assert(am._externalAttributes == octal!100644 << 16);
295 assert((am._madeVersion & 0xFF00) == 0x0300);
299 @property @nogc nothrow uint fileAttributes() const
303 if ((_madeVersion & 0xFF00) == 0x0300)
304 return _externalAttributes >> 16;
307 else version (Windows)
309 if ((_madeVersion & 0xFF00) == 0x0000)
310 return _externalAttributes;
315 static assert(0, "Unimplemented platform");
320 * Get or set the last modification time for this member.
323 * time = Time to set (will be saved as DosFileTime, which is less accurate).
326 * The last modification time in DosFileFormat.
328 @property DosFileTime time() const @safe pure nothrow @nogc
334 @property void time(SysTime time)
336 _time = SysTimeToDosFileTime(time);
340 @property void time(DosFileTime time) @safe pure nothrow @nogc
346 * Get or set compression method used for this member.
349 * cm = Compression method.
351 * Returns: Compression method.
354 * $(LREF CompressionMethod)
356 @property @safe @nogc pure nothrow CompressionMethod compressionMethod() const { return _compressionMethod; }
359 @property @safe pure void compressionMethod(CompressionMethod cm)
361 if (cm == _compressionMethod) return;
363 enforce!ZipException(_compressedSize == 0, "Can't change compression method for a compressed element");
365 _compressionMethod = cm;
369 * The index of this archive member within the archive. Set this to a
370 * different value for reordering the members of an archive.
373 * value = Index value to set.
375 * Returns: The index.
377 @property uint index(uint value) @safe pure nothrow @nogc { return _index = value; }
378 @property uint index() const @safe pure nothrow @nogc { return _index; } /// ditto
384 printf("name = '%.*s'\n", cast(int) name.length, name.ptr);
385 printf("\tcomment = '%.*s'\n", cast(int) comment.length, comment.ptr);
386 printf("\tmadeVersion = x%04x\n", _madeVersion);
387 printf("\textractVersion = x%04x\n", extractVersion);
388 printf("\tflags = x%04x\n", flags);
389 printf("\tcompressionMethod = %d\n", compressionMethod);
390 printf("\ttime = %d\n", time);
391 printf("\tcrc32 = x%08x\n", crc32);
392 printf("\texpandedSize = %d\n", expandedSize);
393 printf("\tcompressedSize = %d\n", compressedSize);
394 printf("\tinternalAttributes = x%04x\n", internalAttributes);
395 printf("\texternalAttributes = x%08x\n", externalAttributes);
396 printf("\tindex = x%08x\n", index);
403 import std.exception : assertThrown, assertNotThrown;
405 auto am = new ArchiveMember();
407 assertNotThrown(am.compressionMethod(CompressionMethod.deflate));
408 assertNotThrown(am.compressionMethod(CompressionMethod.none));
410 am._compressedData = [0x65]; // not strictly necessary, but for consistency
411 am._compressedSize = 1;
413 assertThrown!ZipException(am.compressionMethod(CompressionMethod.deflate));
417 * Object representing the entire archive.
418 * ZipArchives are collections of ArchiveMembers.
420 final class ZipArchive
422 import std.algorithm.comparison : max;
423 import std.bitmanip : littleEndianToNative, nativeToLittleEndian;
424 import std.conv : to;
425 import std.datetime.systime : DosFileTime;
428 // names are taken directly from the specification
429 // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
430 static immutable ubyte[] centralFileHeaderSignature = [ 0x50, 0x4b, 0x01, 0x02 ];
431 static immutable ubyte[] localFileHeaderSignature = [ 0x50, 0x4b, 0x03, 0x04 ];
432 static immutable ubyte[] endOfCentralDirSignature = [ 0x50, 0x4b, 0x05, 0x06 ];
433 static immutable ubyte[] archiveExtraDataSignature = [ 0x50, 0x4b, 0x06, 0x08 ];
434 static immutable ubyte[] digitalSignatureSignature = [ 0x50, 0x4b, 0x05, 0x05 ];
435 static immutable ubyte[] zip64EndOfCentralDirSignature = [ 0x50, 0x4b, 0x06, 0x06 ];
436 static immutable ubyte[] zip64EndOfCentralDirLocatorSignature = [ 0x50, 0x4b, 0x06, 0x07 ];
438 enum centralFileHeaderLength = 46;
439 enum localFileHeaderLength = 30;
440 enum endOfCentralDirLength = 22;
441 enum archiveExtraDataLength = 8;
442 enum digitalSignatureLength = 6;
443 enum zip64EndOfCentralDirLength = 56;
444 enum zip64EndOfCentralDirLocatorLength = 20;
445 enum dataDescriptorLength = 12;
448 string comment; /// The archive comment. Must be less than 65536 bytes in length.
450 private ubyte[] _data;
452 private bool _isZip64;
453 static const ushort zip64ExtractVersion = 45;
455 deprecated("Use digitalSignatureLength instead; will be removed in 2.098.0")
456 static const int digiSignLength = 6;
457 deprecated("Use zip64EndOfCentralDirLocatorLength instead; will be removed in 2.098.0")
458 static const int eocd64LocLength = 20;
459 deprecated("Use zip64EndOfCentralDirLength instead; will be removed in 2.098.0")
460 static const int eocd64Length = 56;
462 private Segment[] _segs;
465 * Array representing the entire contents of the archive.
467 * Returns: Data of the entire contents of the archive.
469 @property @safe @nogc pure nothrow ubyte[] data() { return _data; }
472 * 0 since multi-disk zip archives are not supported.
474 * Returns: Number of this disk.
476 deprecated("Multidisk not supported; will be removed in 2.099.0")
477 @property @safe @nogc pure nothrow uint diskNumber() const { return 0; }
480 * 0 since multi-disk zip archives are not supported.
482 * Returns: Number of the disk, where the central directory starts.
484 deprecated("Multidisk not supported; will be removed in 2.099.0")
485 @property @safe @nogc pure nothrow uint diskStartDir() const { return 0; }
488 * Number of ArchiveMembers in the directory.
490 * Returns: The number of files in this archive.
492 deprecated("Use totalEntries instead; will be removed in 2.099.0")
493 @property @safe @nogc pure nothrow uint numEntries() const { return cast(uint) _directory.length; }
494 @property @safe @nogc pure nothrow uint totalEntries() const { return cast(uint) _directory.length; } /// ditto
497 * True when the archive is in Zip64 format. Set this to true to force building a Zip64 archive.
500 * value = True, when the archive is forced to be build in Zip64 format.
502 * Returns: True, when the archive is in Zip64 format.
504 @property @safe @nogc pure nothrow bool isZip64() const { return _isZip64; }
507 @property @safe @nogc pure nothrow void isZip64(bool value) { _isZip64 = value; }
510 * Associative array indexed by the name of each member of the archive.
512 * All the members of the archive can be accessed with a foreach loop:
515 * --------------------
516 * ZipArchive archive = new ZipArchive(data);
517 * foreach (ArchiveMember am; archive.directory)
519 * writefln("member name is '%s'", am.name);
521 * --------------------
523 * Returns: Associative array with all archive members.
525 @property @safe @nogc pure nothrow ArchiveMember[string] directory() { return _directory; }
527 private ArchiveMember[string] _directory;
533 printf("\tdiskNumber = %u\n", diskNumber);
534 printf("\tdiskStartDir = %u\n", diskStartDir);
535 printf("\tnumEntries = %u\n", numEntries);
536 printf("\ttotalEntries = %u\n", totalEntries);
537 printf("\tcomment = '%.*s'\n", cast(int) comment.length, comment.ptr);
541 /* ============ Creating a new archive =================== */
544 * Constructor to use when creating a new archive.
546 this() @safe @nogc pure nothrow
551 * Add a member to the archive. The file is compressed on the fly.
554 * de = Member to be added.
556 * Throws: ZipException when an unsupported compression method is used or when
557 * compression failed.
559 @safe void addMember(ArchiveMember de)
561 _directory[de.name] = de;
562 if (!de._compressedData.length)
564 switch (de.compressionMethod)
566 case CompressionMethod.none:
567 de._compressedData = de._expandedData;
570 case CompressionMethod.deflate:
571 import std.zlib : compress;
574 de._compressedData = cast(ubyte[]) compress(cast(void[]) de._expandedData);
576 de._compressedData = de._compressedData[2 .. de._compressedData.length - 4];
580 throw new ZipException("unsupported compression method");
583 de._compressedSize = to!uint(de._compressedData.length);
584 import std.zlib : crc32;
585 () @trusted { de._crc32 = crc32(0, cast(void[]) de._expandedData); }();
587 assert(de._compressedData.length == de._compressedSize, "Archive member compressed failed.");
592 import std.exception : assertThrown;
594 ArchiveMember am = new ArchiveMember();
595 am.compressionMethod = cast(CompressionMethod) 3;
597 ZipArchive zip = new ZipArchive();
599 assertThrown!ZipException(zip.addMember(am));
603 * Delete member `de` from the archive. Uses the name of the member
604 * to detect which element to delete.
607 * de = Member to be deleted.
609 @safe void deleteMember(ArchiveMember de)
611 _directory.remove(de.name);
614 // https://issues.dlang.org/show_bug.cgi?id=20398
617 import std.string : representation;
619 ArchiveMember file1 = new ArchiveMember();
620 file1.name = "test1.txt";
621 file1.expandedData("Test data.\n".dup.representation);
623 ZipArchive zip = new ZipArchive();
625 zip.addMember(file1);
626 assert(zip.totalEntries == 1);
628 zip.deleteMember(file1);
629 assert(zip.totalEntries == 0);
633 * Construct the entire contents of the current members of the archive.
635 * Fills in the properties data[], totalEntries, and directory[].
636 * For each ArchiveMember, fills in properties crc32, compressedSize,
639 * Returns: Array representing the entire archive.
641 * Throws: ZipException when the archive could not be build.
643 void[] build() @safe pure
645 import std.array : array, uninitializedArray;
646 import std.algorithm.sorting : sort;
647 import std.string : representation;
650 uint directoryOffset;
652 enforce!ZipException(comment.length <= 0xFFFF, "archive comment longer than 65535");
654 // Compress each member; compute size
655 uint archiveSize = 0;
656 uint directorySize = 0;
657 auto directory = _directory.byValue.array.sort!((x, y) => x.index < y.index).release;
658 foreach (ArchiveMember de; directory)
660 enforce!ZipException(to!ulong(archiveSize) + localFileHeaderLength + de.name.length
661 + de.extra.length + de.compressedSize + directorySize
662 + centralFileHeaderLength + de.name.length + de.extra.length
663 + de.comment.length + endOfCentralDirLength + comment.length
664 + zip64EndOfCentralDirLocatorLength + zip64EndOfCentralDirLength <= uint.max,
665 "zip files bigger than 4 GB are unsupported");
667 archiveSize += localFileHeaderLength + de.name.length +
670 directorySize += centralFileHeaderLength + de.name.length +
675 if (!isZip64 && _directory.length > ushort.max)
677 uint dataSize = archiveSize + directorySize + endOfCentralDirLength + cast(uint) comment.length;
679 dataSize += zip64EndOfCentralDirLocatorLength + zip64EndOfCentralDirLength;
681 _data = uninitializedArray!(ubyte[])(dataSize);
683 // Populate the data[]
685 // Store each archive member
687 foreach (ArchiveMember de; directory)
690 _data[i .. i + 4] = localFileHeaderSignature;
691 putUshort(i + 4, de.extractVersion);
692 putUshort(i + 6, de.flags);
693 putUshort(i + 8, de._compressionMethod);
694 putUint (i + 10, cast(uint) de.time);
695 putUint (i + 14, de.crc32);
696 putUint (i + 18, de.compressedSize);
697 putUint (i + 22, to!uint(de.expandedSize));
698 putUshort(i + 26, cast(ushort) de.name.length);
699 putUshort(i + 28, cast(ushort) de.extra.length);
700 i += localFileHeaderLength;
702 _data[i .. i + de.name.length] = (de.name.representation)[];
704 _data[i .. i + de.extra.length] = (cast(ubyte[]) de.extra)[];
705 i += de.extra.length;
706 _data[i .. i + de.compressedSize] = de.compressedData[];
707 i += de.compressedSize;
712 foreach (ArchiveMember de; directory)
714 _data[i .. i + 4] = centralFileHeaderSignature;
715 putUshort(i + 4, de._madeVersion);
716 putUshort(i + 6, de.extractVersion);
717 putUshort(i + 8, de.flags);
718 putUshort(i + 10, de._compressionMethod);
719 putUint (i + 12, cast(uint) de.time);
720 putUint (i + 16, de.crc32);
721 putUint (i + 20, de.compressedSize);
722 putUint (i + 24, de.expandedSize);
723 putUshort(i + 28, cast(ushort) de.name.length);
724 putUshort(i + 30, cast(ushort) de.extra.length);
725 putUshort(i + 32, cast(ushort) de.comment.length);
726 putUshort(i + 34, cast(ushort) 0);
727 putUshort(i + 36, de.internalAttributes);
728 putUint (i + 38, de._externalAttributes);
729 putUint (i + 42, de.offset);
730 i += centralFileHeaderLength;
732 _data[i .. i + de.name.length] = (de.name.representation)[];
734 _data[i .. i + de.extra.length] = (cast(ubyte[]) de.extra)[];
735 i += de.extra.length;
736 _data[i .. i + de.comment.length] = (de.comment.representation)[];
737 i += de.comment.length;
742 // Write zip64 end of central directory record
743 uint eocd64Offset = i;
744 _data[i .. i + 4] = zip64EndOfCentralDirSignature;
745 putUlong (i + 4, zip64EndOfCentralDirLength - 12);
746 putUshort(i + 12, zip64ExtractVersion);
747 putUshort(i + 14, zip64ExtractVersion);
748 putUint (i + 16, cast(ushort) 0);
749 putUint (i + 20, cast(ushort) 0);
750 putUlong (i + 24, directory.length);
751 putUlong (i + 32, directory.length);
752 putUlong (i + 40, directorySize);
753 putUlong (i + 48, directoryOffset);
754 i += zip64EndOfCentralDirLength;
756 // Write zip64 end of central directory record locator
757 _data[i .. i + 4] = zip64EndOfCentralDirLocatorSignature;
758 putUint (i + 4, cast(ushort) 0);
759 putUlong (i + 8, eocd64Offset);
761 i += zip64EndOfCentralDirLocatorLength;
765 _data[i .. i + 4] = endOfCentralDirSignature;
766 putUshort(i + 4, cast(ushort) 0);
767 putUshort(i + 6, cast(ushort) 0);
768 putUshort(i + 8, (totalEntries > ushort.max ? ushort.max : cast(ushort) totalEntries));
769 putUshort(i + 10, (totalEntries > ushort.max ? ushort.max : cast(ushort) totalEntries));
770 putUint (i + 12, directorySize);
771 putUint (i + 16, directoryOffset);
772 putUshort(i + 20, cast(ushort) comment.length);
773 i += endOfCentralDirLength;
775 // Write archive comment
776 assert(i + comment.length == data.length, "Writing the archive comment failed.");
777 _data[i .. data.length] = (comment.representation)[];
779 return cast(void[]) data;
784 import std.exception : assertNotThrown;
786 ZipArchive zip = new ZipArchive();
788 assertNotThrown(zip.build());
793 import std.range : repeat, array;
794 import std.exception : assertThrown;
796 ZipArchive zip = new ZipArchive();
797 zip.comment = 'A'.repeat(70_000).array;
798 assertThrown!ZipException(zip.build());
801 /* ============ Reading an existing archive =================== */
804 * Constructor to use when reading an existing archive.
806 * Fills in the properties data[], totalEntries, comment[], and directory[].
807 * For each ArchiveMember, fills in
808 * properties madeVersion, extractVersion, flags, compressionMethod, time,
809 * crc32, compressedSize, expandedSize, compressedData[],
810 * internalAttributes, externalAttributes, name[], extra[], comment[].
811 * Use expand() to get the expanded data for each ArchiveMember.
814 * buffer = The entire contents of the archive.
816 * Throws: ZipException when the archive was invalid or when malware was detected.
820 this._data = cast(ubyte[]) buffer;
822 enforce!ZipException(data.length <= uint.max - 2, "zip files bigger than 4 GB are unsupported");
824 _segs = [Segment(0, cast(uint) data.length)];
826 uint i = findEndOfCentralDirRecord();
828 int endCommentLength = getUshort(i + 20);
829 comment = cast(string)(_data[i + endOfCentralDirLength .. i + endOfCentralDirLength + endCommentLength]);
831 // end of central dir record
832 removeSegment(i, i + endOfCentralDirLength + endCommentLength);
834 uint k = i - zip64EndOfCentralDirLocatorLength;
835 if (k < i && _data[k .. k + 4] == zip64EndOfCentralDirLocatorSignature)
840 // zip64 end of central dir record locator
841 removeSegment(k, k + zip64EndOfCentralDirLocatorLength);
845 uint directoryOffset;
850 // Read Zip64 record data
851 ulong eocdOffset = getUlong(i + 8);
852 enforce!ZipException(eocdOffset + zip64EndOfCentralDirLength <= _data.length,
853 "corrupted directory");
855 i = to!uint(eocdOffset);
856 enforce!ZipException(_data[i .. i + 4] == zip64EndOfCentralDirSignature,
857 "invalid Zip EOCD64 signature");
859 ulong eocd64Size = getUlong(i + 4);
860 enforce!ZipException(eocd64Size + i - 12 <= data.length,
861 "invalid Zip EOCD64 size");
863 // zip64 end of central dir record
864 removeSegment(i, cast(uint) (i + 12 + eocd64Size));
866 ulong numEntriesUlong = getUlong(i + 24);
867 ulong totalEntriesUlong = getUlong(i + 32);
868 ulong directorySizeUlong = getUlong(i + 40);
869 ulong directoryOffsetUlong = getUlong(i + 48);
871 enforce!ZipException(numEntriesUlong <= uint.max,
872 "supposedly more than 4294967296 files in archive");
874 enforce!ZipException(numEntriesUlong == totalEntriesUlong,
875 "multiple disk zips not supported");
877 enforce!ZipException(directorySizeUlong <= i && directoryOffsetUlong <= i
878 && directorySizeUlong + directoryOffsetUlong <= i,
879 "corrupted directory");
881 directoryCount = to!uint(totalEntriesUlong);
882 directorySize = to!uint(directorySizeUlong);
883 directoryOffset = to!uint(directoryOffsetUlong);
887 // Read end record data
888 directoryCount = getUshort(i + 10);
889 directorySize = getUint(i + 12);
890 directoryOffset = getUint(i + 16);
894 for (int n = 0; n < directoryCount; n++)
896 /* The format of an entry is:
908 enforce!ZipException(_data[i .. i + 4] == centralFileHeaderSignature,
909 "wrong central file header signature found");
910 ArchiveMember de = new ArchiveMember();
912 de._madeVersion = getUshort(i + 4);
913 de._extractVersion = getUshort(i + 6);
914 de.flags = getUshort(i + 8);
915 de._compressionMethod = cast(CompressionMethod) getUshort(i + 10);
916 de.time = cast(DosFileTime) getUint(i + 12);
917 de._crc32 = getUint(i + 16);
918 de._compressedSize = getUint(i + 20);
919 de._expandedSize = getUint(i + 24);
920 namelen = getUshort(i + 28);
921 extralen = getUshort(i + 30);
922 commentlen = getUshort(i + 32);
923 de.internalAttributes = getUshort(i + 36);
924 de._externalAttributes = getUint(i + 38);
925 de.offset = getUint(i + 42);
927 // central file header
928 removeSegment(i, i + centralFileHeaderLength + namelen + extralen + commentlen);
930 i += centralFileHeaderLength;
932 enforce!ZipException(i + namelen + extralen + commentlen <= directoryOffset + directorySize,
933 "invalid field lengths in file header found");
935 de.name = cast(string)(_data[i .. i + namelen]);
937 de.extra = _data[i .. i + extralen];
939 de.comment = cast(string)(_data[i .. i + commentlen]);
942 auto localFileHeaderNamelen = getUshort(de.offset + 26);
943 auto localFileHeaderExtralen = getUshort(de.offset + 28);
946 removeSegment(de.offset, de.offset + localFileHeaderLength + localFileHeaderNamelen
947 + localFileHeaderExtralen + de._compressedSize);
949 immutable uint dataOffset = de.offset + localFileHeaderLength
950 + localFileHeaderNamelen + localFileHeaderExtralen;
951 de._compressedData = _data[dataOffset .. dataOffset + de.compressedSize];
953 _directory[de.name] = de;
956 enforce!ZipException(i == directoryOffset + directorySize, "invalid directory entry 3");
961 import std.exception : assertThrown;
963 // contains wrong directorySize (extra byte 0xff)
965 "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~
966 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~
967 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
968 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
969 "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
970 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
971 "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
972 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
973 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\xff\x50\x4b\x05"~
974 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4b\x00\x00\x00\x43\x00\x00"~
977 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
982 import std.exception : assertThrown;
986 "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
987 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
988 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
989 "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
990 "\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
991 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
994 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
999 import std.exception : assertThrown;
1001 // wrong signature of zip64 end of central directory
1003 "\x50\x4b\x06\x07\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
1004 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
1005 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
1006 "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
1007 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
1008 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
1011 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1016 import std.exception : assertThrown;
1018 // wrong size of zip64 end of central directory
1020 "\x50\x4b\x06\x06\xff\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
1021 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
1022 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
1023 "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
1024 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
1025 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
1028 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1033 import std.exception : assertThrown;
1035 // too many entries in zip64 end of central directory
1037 "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
1038 "\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\x00\x00\x00"~
1039 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
1040 "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
1041 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
1042 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
1045 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1050 import std.exception : assertThrown;
1052 // zip64: numEntries and totalEntries differ
1054 "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
1055 "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"~
1056 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
1057 "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
1058 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
1059 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
1062 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1067 import std.exception : assertThrown;
1069 // zip64: directorySize too large
1071 "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
1072 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
1073 "\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00"~
1074 "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
1075 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
1076 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
1079 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1081 // zip64: directoryOffset too large
1083 "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
1084 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
1085 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
1086 "\xff\xff\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
1087 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
1088 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
1091 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1093 // zip64: directorySize + directoryOffset too large
1094 // we need to add a useless byte at the beginning to avoid that one of the other two checks allready fires
1096 "\x00\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
1097 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
1098 "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"~
1099 "\x01\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
1100 "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
1101 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
1104 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1109 import std.exception : assertThrown;
1111 // wrong central file header signature
1113 "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~
1114 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~
1115 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
1116 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
1117 "\x6c\x6c\x6f\x50\x4b\x01\x03\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
1118 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
1119 "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
1120 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
1121 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~
1122 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~
1125 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1130 import std.exception : assertThrown;
1132 // invalid field lengths in file header
1134 "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~
1135 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~
1136 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
1137 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
1138 "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
1139 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
1140 "\x00\x18\x00\x01\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
1141 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
1142 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\xff\x50\x4b\x05"~
1143 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~
1146 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1149 private uint findEndOfCentralDirRecord()
1151 // end of central dir record can be followed by a comment of up to 2^^16-1 bytes
1152 // therefore we have to scan 2^^16 positions
1154 uint endrecOffset = to!uint(data.length);
1155 foreach (i; 0 .. 2 ^^ 16)
1157 if (endOfCentralDirLength + i > data.length) break;
1158 uint start = to!uint(data.length) - endOfCentralDirLength - i;
1160 if (data[start .. start + 4] != endOfCentralDirSignature) continue;
1162 auto numberOfThisDisc = getUshort(start + 4);
1163 if (numberOfThisDisc != 0) continue; // no support for multiple volumes yet
1165 auto numberOfStartOfCentralDirectory = getUshort(start + 6);
1166 if (numberOfStartOfCentralDirectory != 0) continue; // dito
1168 if (numberOfThisDisc < numberOfStartOfCentralDirectory) continue;
1170 uint k = start - zip64EndOfCentralDirLocatorLength;
1171 auto maybeZip64 = k < start && _data[k .. k + 4] == zip64EndOfCentralDirLocatorSignature;
1173 auto totalNumberOfEntriesOnThisDisk = getUshort(start + 8);
1174 auto totalNumberOfEntriesInCentralDir = getUshort(start + 10);
1176 if (totalNumberOfEntriesOnThisDisk > totalNumberOfEntriesInCentralDir &&
1177 (!maybeZip64 || totalNumberOfEntriesOnThisDisk < 0xffff)) continue;
1179 auto sizeOfCentralDirectory = getUint(start + 12);
1180 if (sizeOfCentralDirectory > start &&
1181 (!maybeZip64 || sizeOfCentralDirectory < 0xffff)) continue;
1183 auto offsetOfCentralDirectory = getUint(start + 16);
1184 if (offsetOfCentralDirectory > start - sizeOfCentralDirectory &&
1185 (!maybeZip64 || offsetOfCentralDirectory < 0xffff)) continue;
1187 auto zipfileCommentLength = getUshort(start + 20);
1188 if (start + zipfileCommentLength + endOfCentralDirLength != data.length) continue;
1190 enforce!ZipException(endrecOffset == to!uint(data.length),
1191 "found more than one valid 'end of central dir record'");
1193 endrecOffset = start;
1196 enforce!ZipException(endrecOffset != to!uint(data.length),
1197 "found no valid 'end of central dir record'");
1199 return endrecOffset;
1203 * Decompress the contents of a member.
1205 * Fills in properties extractVersion, flags, compressionMethod, time,
1206 * crc32, compressedSize, expandedSize, expandedData[], name[], extra[].
1209 * de = Member to be decompressed.
1211 * Returns: The expanded data.
1213 * Throws: ZipException when the entry is invalid or the compression method is not supported.
1215 ubyte[] expand(ArchiveMember de)
1217 import std.string : representation;
1222 enforce!ZipException(_data[de.offset .. de.offset + 4] == localFileHeaderSignature,
1223 "wrong local file header signature found");
1225 // These values should match what is in the main zip archive directory
1226 de._extractVersion = getUshort(de.offset + 4);
1227 de.flags = getUshort(de.offset + 6);
1228 de._compressionMethod = cast(CompressionMethod) getUshort(de.offset + 8);
1229 de.time = cast(DosFileTime) getUint(de.offset + 10);
1230 de._crc32 = getUint(de.offset + 14);
1231 de._compressedSize = max(getUint(de.offset + 18), de.compressedSize);
1232 de._expandedSize = max(getUint(de.offset + 22), de.expandedSize);
1233 namelen = getUshort(de.offset + 26);
1234 extralen = getUshort(de.offset + 28);
1238 printf("\t\texpandedSize = %d\n", de.expandedSize);
1239 printf("\t\tcompressedSize = %d\n", de.compressedSize);
1240 printf("\t\tnamelen = %d\n", namelen);
1241 printf("\t\textralen = %d\n", extralen);
1244 enforce!ZipException((de.flags & 1) == 0, "encryption not supported");
1246 switch (de.compressionMethod)
1248 case CompressionMethod.none:
1249 de._expandedData = de.compressedData;
1250 return de.expandedData;
1252 case CompressionMethod.deflate:
1253 // -15 is a magic value used to decompress zip files.
1254 // It has the effect of not requiring the 2 byte header
1255 // and 4 byte trailer.
1256 import std.zlib : uncompress;
1257 de._expandedData = cast(ubyte[]) uncompress(cast(void[]) de.compressedData, de.expandedSize, -15);
1258 return de.expandedData;
1261 throw new ZipException("unsupported compression method");
1267 import std.exception : assertThrown;
1269 // check for correct local file header signature
1271 "\x50\x4b\x04\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~
1272 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~
1273 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
1274 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
1275 "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
1276 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
1277 "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
1278 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
1279 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~
1280 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~
1283 auto za = new ZipArchive(cast(void[]) file);
1285 assertThrown!ZipException(za.expand(za._directory["file"]));
1290 import std.exception : assertThrown;
1292 // check for encryption flag
1294 "\x50\x4b\x03\x04\x0a\x00\x01\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~
1295 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~
1296 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
1297 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
1298 "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
1299 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
1300 "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
1301 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
1302 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~
1303 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~
1306 auto za = new ZipArchive(cast(void[]) file);
1308 assertThrown!ZipException(za.expand(za._directory["file"]));
1313 import std.exception : assertThrown;
1315 // check for invalid compression method
1317 "\x50\x4b\x03\x04\x0a\x00\x00\x00\x03\x00\x8f\x72\x4a\x4f\x86\xa6"~
1318 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~
1319 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
1320 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
1321 "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
1322 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
1323 "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
1324 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
1325 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~
1326 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~
1329 auto za = new ZipArchive(cast(void[]) file);
1331 assertThrown!ZipException(za.expand(za._directory["file"]));
1334 /* ============ Utility =================== */
1336 @safe @nogc pure nothrow ushort getUshort(uint i)
1338 ubyte[2] result = data[i .. i + 2];
1339 return littleEndianToNative!ushort(result);
1342 @safe @nogc pure nothrow uint getUint(uint i)
1344 ubyte[4] result = data[i .. i + 4];
1345 return littleEndianToNative!uint(result);
1348 @safe @nogc pure nothrow ulong getUlong(uint i)
1350 ubyte[8] result = data[i .. i + 8];
1351 return littleEndianToNative!ulong(result);
1354 @safe @nogc pure nothrow void putUshort(uint i, ushort us)
1356 data[i .. i + 2] = nativeToLittleEndian(us);
1359 @safe @nogc pure nothrow void putUint(uint i, uint ui)
1361 data[i .. i + 4] = nativeToLittleEndian(ui);
1364 @safe @nogc pure nothrow void putUlong(uint i, ulong ul)
1366 data[i .. i + 8] = nativeToLittleEndian(ul);
1369 /* ============== for detecting overlaps =============== */
1373 // defines a segment of the zip file, including start, excluding end
1380 // removes Segment start .. end from _segs
1381 // throws zipException if start .. end is not completely available in _segs;
1382 void removeSegment(uint start, uint end) pure @safe
1383 in (start < end, "segment invalid")
1387 foreach (i,seg;_segs)
1388 if (seg.start <= start && seg.end >= end
1389 && (!found || seg.start > _segs[pos].start))
1395 enforce!ZipException(found, "overlapping data detected");
1397 if (start>_segs[pos].start)
1398 _segs ~= Segment(_segs[pos].start, start);
1399 if (end<_segs[pos].end)
1400 _segs ~= Segment(end, _segs[pos].end);
1401 _segs = _segs[0 .. pos] ~ _segs[pos + 1 .. $];
1406 with (new ZipArchive())
1408 _segs = [Segment(0,100)];
1409 removeSegment(10,20);
1410 assert(_segs == [Segment(0,10),Segment(20,100)]);
1412 _segs = [Segment(0,100)];
1413 removeSegment(0,20);
1414 assert(_segs == [Segment(20,100)]);
1416 _segs = [Segment(0,100)];
1417 removeSegment(10,100);
1418 assert(_segs == [Segment(0,10)]);
1420 _segs = [Segment(0,100), Segment(200,300), Segment(400,500)];
1421 removeSegment(220,230);
1422 assert(_segs == [Segment(0,100),Segment(400,500),Segment(200,220),Segment(230,300)]);
1424 _segs = [Segment(200,300), Segment(0,100), Segment(400,500)];
1425 removeSegment(20,30);
1426 assert(_segs == [Segment(200,300),Segment(400,500),Segment(0,20),Segment(30,100)]);
1428 import std.exception : assertThrown;
1430 _segs = [Segment(0,100), Segment(200,300), Segment(400,500)];
1431 assertThrown(removeSegment(120,230));
1433 _segs = [Segment(0,100), Segment(200,300), Segment(400,500)];
1434 removeSegment(0,100);
1435 assertThrown(removeSegment(0,100));
1437 _segs = [Segment(0,100)];
1438 removeSegment(0,100);
1439 assertThrown(removeSegment(0,100));
1446 @safe void arrayPrint(ubyte[] array)
1448 printf("array %p,%d\n", cast(void*) array, array.length);
1449 for (int i = 0; i < array.length; i++)
1451 printf("%02x ", array[i]);
1452 if (((i + 1) & 15) == 0)
1461 // @system due to (at least) ZipArchive.build
1462 auto zip1 = new ZipArchive();
1463 auto zip2 = new ZipArchive();
1464 auto am1 = new ArchiveMember();
1466 am1.expandedData = new ubyte[](1024);
1467 zip1.addMember(am1);
1468 auto data1 = zip1.build();
1469 zip2.addMember(zip1.directory["foo"]);
1471 auto am2 = zip2.directory["foo"];
1473 assert(am1.expandedData == am2.expandedData);
1474 auto zip3 = new ZipArchive(data1);
1476 assert(zip3.directory["foo"].compressedSize == am1.compressedSize);
1478 // Test if packing and unpacking produces the original data
1479 import std.conv, std.stdio;
1480 import std.random : uniform, MinstdRand0;
1482 const uint itemCount = 20, minSize = 10, maxSize = 500;
1483 foreach (variant; 0 .. 2)
1485 bool useZip64 = !!variant;
1486 zip1 = new ZipArchive();
1487 zip1.isZip64 = useZip64;
1488 ArchiveMember[itemCount] ams;
1489 foreach (i; 0 .. itemCount)
1491 ams[i] = new ArchiveMember();
1492 ams[i].name = to!string(i);
1493 ams[i].expandedData = new ubyte[](uniform(minSize, maxSize));
1494 foreach (ref ubyte c; ams[i].expandedData)
1495 c = cast(ubyte)(uniform(0, 256));
1496 ams[i].compressionMethod = CompressionMethod.deflate;
1497 zip1.addMember(ams[i]);
1499 auto zippedData = zip1.build();
1500 zip2 = new ZipArchive(zippedData);
1501 assert(zip2.isZip64 == useZip64);
1504 am2 = zip2.directory[am.name];
1506 assert(am.crc32 == am2.crc32);
1507 assert(am.expandedData == am2.expandedData);
1514 import std.conv : to;
1515 import std.random : Mt19937, randomShuffle;
1516 // Test if packing and unpacking preserves order.
1517 auto rand = Mt19937(15966);
1520 // Generate a series of unique numbers as filenames.
1521 foreach (i; 0 .. 20)
1523 value += 1 + rand.front & 0xFFFF;
1525 names ~= value.to!string;
1527 // Insert them in a random order.
1528 names.randomShuffle(rand);
1529 auto zip1 = new ZipArchive();
1530 foreach (i, name; names)
1532 auto member = new ArchiveMember();
1534 member.expandedData = cast(ubyte[]) name;
1535 member.index = cast(int) i;
1536 zip1.addMember(member);
1538 auto data = zip1.build();
1540 // Ensure that they appear in the same order.
1541 auto zip2 = new ZipArchive(data);
1542 foreach (i, name; names)
1544 const member = zip2.directory[name];
1545 assert(member.index == i, "member " ~ name ~ " had index " ~
1546 member.index.to!string ~ " but we expected index " ~ i.to!string ~
1547 ". The input array was " ~ names.to!string);
1555 ubyte[] src = cast(ubyte[])
1556 "the quick brown fox jumps over the lazy dog\r
1557 the quick brown fox jumps over the lazy dog\r
1559 auto dst = cast(ubyte[]) compress(cast(void[]) src);
1560 auto after = cast(ubyte[]) uncompress(cast(void[]) dst);
1561 assert(src == after);
1566 // @system due to ZipArchive.build
1567 import std.datetime;
1568 ubyte[] buf = [1, 2, 3, 4, 5, 0, 7, 8, 9];
1570 auto ar = new ZipArchive;
1571 auto am = new ArchiveMember; // 10
1573 am.expandedData = buf;
1574 am.compressionMethod = CompressionMethod.deflate;
1575 am.time = SysTimeToDosFileTime(Clock.currTime());
1576 ar.addMember(am); // 15
1578 auto zip1 = ar.build();
1579 auto arAfter = new ZipArchive(zip1);
1580 assert(arAfter.directory.length == 1);
1581 auto amAfter = arAfter.directory["buf"];
1582 arAfter.expand(amAfter);
1583 assert(amAfter.name == am.name);
1584 assert(amAfter.expandedData == am.expandedData);
1585 assert(amAfter.time == am.time);
1590 // invalid format of end of central directory entry
1591 import std.exception : assertThrown;
1592 assertThrown!ZipException(new ZipArchive(cast(void[]) "\x50\x4B\x05\x06aaaaaaaaaaaaaaaaaaaa"));
1597 // minimum (empty) archive should pass
1598 auto za = new ZipArchive(cast(void[]) "\x50\x4B\x05\x06\x00\x00\x00\x00\x00\x00\x00"~
1599 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
1600 assert(za.directory.length == 0);
1602 // one byte too short or too long should not pass
1603 import std.exception : assertThrown;
1604 assertThrown!ZipException(new ZipArchive(cast(void[]) "\x50\x4B\x05\x06\x00\x00\x00\x00\x00\x00\x00"~
1605 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"));
1606 assertThrown!ZipException(new ZipArchive(cast(void[]) "\x50\x4B\x05\x06\x00\x00\x00\x00\x00\x00\x00"~
1607 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"));
1612 // https://issues.dlang.org/show_bug.cgi?id=20239
1613 // chameleon file, containing two valid end of central directory entries
1615 "\x50\x4B\x03\x04\x0A\x00\x00\x00\x00\x00\x89\x36\x39\x4F\x04\x6A\xB3\xA3\x01\x00"~
1616 "\x00\x00\x01\x00\x00\x00\x0D\x00\x1C\x00\x62\x65\x73\x74\x5F\x6C\x61\x6E\x67\x75"~
1617 "\x61\x67\x65\x55\x54\x09\x00\x03\x82\xF2\x8A\x5D\x82\xF2\x8A\x5D\x75\x78\x0B\x00"~
1618 "\x01\x04\xEB\x03\x00\x00\x04\xEB\x03\x00\x00\x44\x50\x4B\x01\x02\x1E\x03\x0A\x00"~
1619 "\x00\x00\x00\x00\x89\x36\x39\x4F\x04\x6A\xB3\xA3\x01\x00\x00\x00\x01\x00\x00\x00"~
1620 "\x0D\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xB0\x81\x00\x00\x00\x00\x62\x65"~
1621 "\x73\x74\x5F\x6C\x61\x6E\x67\x75\x61\x67\x65\x55\x54\x05\x00\x03\x82\xF2\x8A\x5D"~
1622 "\x75\x78\x0B\x00\x01\x04\xEB\x03\x00\x00\x04\xEB\x03\x00\x00\x50\x4B\x05\x06\x00"~
1623 "\x00\x00\x00\x01\x00\x01\x00\x53\x00\x00\x00\x48\x00\x00\x00\xB7\x00\x50\x4B\x03"~
1624 "\x04\x0A\x00\x00\x00\x00\x00\x94\x36\x39\x4F\xD7\xCB\x3B\x55\x07\x00\x00\x00\x07"~
1625 "\x00\x00\x00\x0D\x00\x1C\x00\x62\x65\x73\x74\x5F\x6C\x61\x6E\x67\x75\x61\x67\x65"~
1626 "\x55\x54\x09\x00\x03\x97\xF2\x8A\x5D\x8C\xF2\x8A\x5D\x75\x78\x0B\x00\x01\x04\xEB"~
1627 "\x03\x00\x00\x04\xEB\x03\x00\x00\x46\x4F\x52\x54\x52\x41\x4E\x50\x4B\x01\x02\x1E"~
1628 "\x03\x0A\x00\x00\x00\x00\x00\x94\x36\x39\x4F\xD7\xCB\x3B\x55\x07\x00\x00\x00\x07"~
1629 "\x00\x00\x00\x0D\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xB0\x81\xB1\x00\x00"~
1630 "\x00\x62\x65\x73\x74\x5F\x6C\x61\x6E\x67\x75\x61\x67\x65\x55\x54\x05\x00\x03\x97"~
1631 "\xF2\x8A\x5D\x75\x78\x0B\x00\x01\x04\xEB\x03\x00\x00\x04\xEB\x03\x00\x00\x50\x4B"~
1632 "\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00\x53\x00\x00\x00\xFF\x00\x00\x00\x00\x00";
1634 import std.exception : assertThrown;
1635 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1640 // https://issues.dlang.org/show_bug.cgi?id=20287
1641 // check for correct compressed data
1643 "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~
1644 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~
1645 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
1646 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
1647 "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
1648 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
1649 "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
1650 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
1651 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~
1652 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~
1655 auto za = new ZipArchive(cast(void[]) file);
1656 assert(za.directory["file"].compressedData == [104, 101, 108, 108, 111]);
1659 // https://issues.dlang.org/show_bug.cgi?id=20027
1662 // central file header overlaps end of central directory
1665 "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~
1666 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~
1667 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
1668 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
1669 "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
1670 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
1671 "\x00\x18\x00\x04\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
1672 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
1673 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~
1674 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~
1677 import std.exception : assertThrown;
1678 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1680 // local file header and file data overlap second local file header and file data
1682 "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~
1683 "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1e\x00\x66\x69"~
1684 "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
1685 "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
1686 "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
1687 "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
1688 "\x00\x18\x00\x04\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
1689 "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
1690 "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~
1691 "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~
1694 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1699 // https://issues.dlang.org/show_bug.cgi?id=20295
1700 // zip64 with 0xff bytes in end of central dir record do not work
1701 // minimum (empty zip64) archive should pass
1703 "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
1704 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
1705 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
1706 "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
1707 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
1708 "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
1711 auto za = new ZipArchive(cast(void[]) file);
1712 assert(za.directory.length == 0);
1718 import std.datetime, std.file, std.format, std.path, std.process, std.stdio;
1720 if (executeShell("unzip").status != 0)
1722 writeln("Can't run unzip, skipping unzip test");
1726 auto zr = new ZipArchive();
1727 auto am = new ArchiveMember();
1728 am.compressionMethod = CompressionMethod.deflate;
1729 am.name = "foo.bar";
1730 am.time = SysTimeToDosFileTime(Clock.currTime());
1731 am.expandedData = cast(ubyte[])"We all live in a yellow submarine, a yellow submarine";
1733 auto data2 = zr.build();
1735 mkdirRecurse(deleteme);
1736 scope(exit) rmdirRecurse(deleteme);
1737 string zipFile = buildPath(deleteme, "foo.zip");
1738 std.file.write(zipFile, cast(byte[]) data2);
1740 auto result = executeShell(format("unzip -l %s", zipFile));
1741 scope(failure) writeln(result.output);
1742 assert(result.status == 0);