c++: auto in trailing-return-type in parameter [PR117778]
[gcc.git] / libphobos / src / std / zip.d
blob72d128700bf19d74157d185e7f6fd451c73f854f
1 // Written in the D programming language.
3 /**
4 Read and write data in the
5 $(LINK2 https://en.wikipedia.org/wiki/Zip_%28file_format%29, zip archive)
6 format.
8 Standards:
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.)
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)
31 The current implementation makes use of the zlib compression library.
33 Usage:
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).
40 Examples:
42 Example for reading an existing zip archive:
43 ---
44 import std.stdio : writeln, writefln;
45 import std.file : read;
46 import std.zip;
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
62 zip.expand(am);
63 assert(am.expandedData.length == am.expandedSize);
66 ---
68 Example for writing files into a zip archive:
69 ---
70 import std.file : write;
71 import std.string : representation;
72 import std.zip;
74 void main()
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();
90 // add ArchiveMembers
91 zip.addMember(file1);
92 zip.addMember(file2);
94 // Build the archive
95 void[] compressed_data = zip.build();
97 // Write to a file
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)
113 module std.zip;
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
119 version (Android) {}
120 else version (iOS) {}
121 else version (TVOS) {}
122 else version (WatchOS) {}
123 else version (Posix)
124 version = HasUnzip;
126 //debug=print;
128 /// Thrown on error.
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
153 * directory first.
155 string name;
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.
164 ubyte[] extra;
166 string comment; /// Comment associated with this member.
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;
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.
188 ushort flags;
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; }
225 * Data of member in compressed form.
227 * Returns: The file data in compressed form.
229 @property @safe pure nothrow @nogc ubyte[] compressedData() { return _compressedData; }
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.
235 * Params:
236 * ed = Expanded Data.
238 * Returns: The file data.
240 @property @safe pure nothrow @nogc ubyte[] expandedData() { return _expandedData; }
242 /// ditto
243 @property @safe void expandedData(ubyte[] ed)
245 _expandedData = ed;
246 _expandedSize = to!uint(_expandedData.length);
248 // Clean old compressed data, if any
249 _compressedData.length = 0;
250 _compressedSize = 0;
254 * Get or set the OS specific file attributes for this archive member.
256 * Params:
257 * attr = Attributes as obtained by $(REF getAttributes, std,file) or
258 * $(REF DirEntry.attributes, std,file).
260 * Returns: The file attributes or 0 if the file attributes were
261 * encoded for an incompatible OS (Windows vs. POSIX).
263 @property @safe void fileAttributes(uint attr)
265 version (Posix)
267 _externalAttributes = (attr & 0xFFFF) << 16;
268 _madeVersion &= 0x00FF;
269 _madeVersion |= 0x0300; // attributes are in UNIX format
271 else version (Windows)
273 _externalAttributes = attr;
274 _madeVersion &= 0x00FF; // attributes are in MS-DOS and OS/2 format
276 else
278 static assert(0, "Unimplemented platform");
282 version (Posix) @safe unittest
284 auto am = new ArchiveMember();
285 am.fileAttributes = octal!100644;
286 assert(am._externalAttributes == octal!100644 << 16);
287 assert((am._madeVersion & 0xFF00) == 0x0300);
290 /// ditto
291 @property @nogc nothrow uint fileAttributes() const
293 version (Posix)
295 if ((_madeVersion & 0xFF00) == 0x0300)
296 return _externalAttributes >> 16;
297 return 0;
299 else version (Windows)
301 if ((_madeVersion & 0xFF00) == 0x0000)
302 return _externalAttributes;
303 return 0;
305 else
307 static assert(0, "Unimplemented platform");
312 * Get or set the last modification time for this member.
314 * Params:
315 * time = Time to set (will be saved as DosFileTime, which is less accurate).
317 * Returns:
318 * The last modification time in DosFileFormat.
320 @property DosFileTime time() const @safe pure nothrow @nogc
322 return _time;
325 /// ditto
326 @property void time(SysTime time)
328 _time = SysTimeToDosFileTime(time);
331 /// ditto
332 @property void time(DosFileTime time) @safe pure nothrow @nogc
334 _time = time;
338 * Get or set compression method used for this member.
340 * Params:
341 * cm = Compression method.
343 * Returns: Compression method.
345 * See_Also:
346 * $(LREF CompressionMethod)
348 @property @safe @nogc pure nothrow CompressionMethod compressionMethod() const { return _compressionMethod; }
350 /// ditto
351 @property @safe pure void compressionMethod(CompressionMethod cm)
353 if (cm == _compressionMethod) return;
355 enforce!ZipException(_compressedSize == 0, "Can't change compression method for a compressed element");
357 _compressionMethod = cm;
361 * The index of this archive member within the archive. Set this to a
362 * different value for reordering the members of an archive.
364 * Params:
365 * value = Index value to set.
367 * Returns: The index.
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
372 debug(print)
374 void print()
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);
393 @safe pure unittest
395 import std.exception : assertThrown, assertNotThrown;
397 auto am = new ArchiveMember();
399 assertNotThrown(am.compressionMethod(CompressionMethod.deflate));
400 assertNotThrown(am.compressionMethod(CompressionMethod.none));
402 am._compressedData = [0x65]; // not strictly necessary, but for consistency
403 am._compressedSize = 1;
405 assertThrown!ZipException(am.compressionMethod(CompressionMethod.deflate));
409 * Object representing the entire archive.
410 * ZipArchives are collections of ArchiveMembers.
412 final class ZipArchive
414 import std.algorithm.comparison : max;
415 import std.bitmanip : littleEndianToNative, nativeToLittleEndian;
416 import std.conv : to;
417 import std.datetime.systime : DosFileTime;
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 ];
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;
439 public:
440 string comment; /// The archive comment. Must be less than 65536 bytes in length.
442 private ubyte[] _data;
444 private bool _isZip64;
445 static const ushort zip64ExtractVersion = 45;
447 private Segment[] _segs;
450 * Array representing the entire contents of the archive.
452 * Returns: Data of the entire contents of the archive.
454 @property @safe @nogc pure nothrow ubyte[] data() { return _data; }
457 * Number of ArchiveMembers in the directory.
459 * Returns: The number of files in this archive.
461 @property @safe @nogc pure nothrow uint totalEntries() const { return cast(uint) _directory.length; }
464 * True when the archive is in Zip64 format. Set this to true to force building a Zip64 archive.
466 * Params:
467 * value = True, when the archive is forced to be build in Zip64 format.
469 * Returns: True, when the archive is in Zip64 format.
471 @property @safe @nogc pure nothrow bool isZip64() const { return _isZip64; }
473 /// ditto
474 @property @safe @nogc pure nothrow void isZip64(bool value) { _isZip64 = value; }
477 * Associative array indexed by the name of each member of the archive.
479 * All the members of the archive can be accessed with a foreach loop:
481 * Example:
482 * --------------------
483 * ZipArchive archive = new ZipArchive(data);
484 * foreach (ArchiveMember am; archive.directory)
486 * writefln("member name is '%s'", am.name);
488 * --------------------
490 * Returns: Associative array with all archive members.
492 @property @safe @nogc pure nothrow ArchiveMember[string] directory() { return _directory; }
494 private ArchiveMember[string] _directory;
496 debug (print)
498 @safe void print()
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);
508 /* ============ Creating a new archive =================== */
511 * Constructor to use when creating a new archive.
513 this() @safe @nogc pure nothrow
518 * Add a member to the archive. The file is compressed on the fly.
520 * Params:
521 * de = Member to be added.
523 * Throws: ZipException when an unsupported compression method is used or when
524 * compression failed.
526 @safe void addMember(ArchiveMember de)
528 _directory[de.name] = de;
529 if (!de._compressedData.length)
531 switch (de.compressionMethod)
533 case CompressionMethod.none:
534 de._compressedData = de._expandedData;
535 break;
537 case CompressionMethod.deflate:
538 import std.zlib : compress;
539 () @trusted
541 de._compressedData = cast(ubyte[]) compress(cast(void[]) de._expandedData);
542 }();
543 de._compressedData = de._compressedData[2 .. de._compressedData.length - 4];
544 break;
546 default:
547 throw new ZipException("unsupported compression method");
550 de._compressedSize = to!uint(de._compressedData.length);
551 import std.zlib : crc32;
552 () @trusted { de._crc32 = crc32(0, cast(void[]) de._expandedData); }();
554 assert(de._compressedData.length == de._compressedSize, "Archive member compressed failed.");
557 @safe unittest
559 import std.exception : assertThrown;
561 ArchiveMember am = new ArchiveMember();
562 am.compressionMethod = cast(CompressionMethod) 3;
564 ZipArchive zip = new ZipArchive();
566 assertThrown!ZipException(zip.addMember(am));
570 * Delete member `de` from the archive. Uses the name of the member
571 * to detect which element to delete.
573 * Params:
574 * de = Member to be deleted.
576 @safe void deleteMember(ArchiveMember de)
578 _directory.remove(de.name);
581 // https://issues.dlang.org/show_bug.cgi?id=20398
582 @safe unittest
584 import std.string : representation;
586 ArchiveMember file1 = new ArchiveMember();
587 file1.name = "test1.txt";
588 file1.expandedData("Test data.\n".dup.representation);
590 ZipArchive zip = new ZipArchive();
592 zip.addMember(file1);
593 assert(zip.totalEntries == 1);
595 zip.deleteMember(file1);
596 assert(zip.totalEntries == 0);
600 * Construct the entire contents of the current members of the archive.
602 * Fills in the properties data[], totalEntries, and directory[].
603 * For each ArchiveMember, fills in properties crc32, compressedSize,
604 * compressedData[].
606 * Returns: Array representing the entire archive.
608 * Throws: ZipException when the archive could not be build.
610 void[] build() @safe pure
612 import std.array : array, uninitializedArray;
613 import std.algorithm.sorting : sort;
614 import std.string : representation;
616 uint i;
617 uint directoryOffset;
619 enforce!ZipException(comment.length <= 0xFFFF, "archive comment longer than 65535");
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)
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");
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;
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;
648 _data = uninitializedArray!(ubyte[])(dataSize);
650 // Populate the data[]
652 // Store each archive member
653 i = 0;
654 foreach (ArchiveMember de; directory)
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;
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;
677 // Write directory
678 directoryOffset = i;
679 foreach (ArchiveMember de; directory)
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;
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;
707 if (isZip64)
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;
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;
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;
742 // Write archive comment
743 assert(i + comment.length == data.length, "Writing the archive comment failed.");
744 _data[i .. data.length] = (comment.representation)[];
746 return cast(void[]) data;
749 @safe pure unittest
751 import std.exception : assertNotThrown;
753 ZipArchive zip = new ZipArchive();
754 zip.comment = "A";
755 assertNotThrown(zip.build());
758 @safe pure unittest
760 import std.range : repeat, array;
761 import std.exception : assertThrown;
763 ZipArchive zip = new ZipArchive();
764 zip.comment = 'A'.repeat(70_000).array;
765 assertThrown!ZipException(zip.build());
768 /* ============ Reading an existing archive =================== */
771 * Constructor to use when reading an existing archive.
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.
780 * Params:
781 * buffer = The entire contents of the archive.
783 * Throws: ZipException when the archive was invalid or when malware was detected.
785 this(void[] buffer)
787 this._data = cast(ubyte[]) buffer;
789 enforce!ZipException(data.length <= uint.max - 2, "zip files bigger than 4 GB are unsupported");
791 _segs = [Segment(0, cast(uint) data.length)];
793 uint i = findEndOfCentralDirRecord();
795 int endCommentLength = getUshort(i + 20);
796 comment = cast(string)(_data[i + endOfCentralDirLength .. i + endOfCentralDirLength + endCommentLength]);
798 // end of central dir record
799 removeSegment(i, i + endOfCentralDirLength + endCommentLength);
801 uint k = i - zip64EndOfCentralDirLocatorLength;
802 if (k < i && _data[k .. k + 4] == zip64EndOfCentralDirLocatorSignature)
804 _isZip64 = true;
805 i = k;
807 // zip64 end of central dir record locator
808 removeSegment(k, k + zip64EndOfCentralDirLocatorLength);
811 uint directorySize;
812 uint directoryOffset;
813 uint directoryCount;
815 if (isZip64)
817 // Read Zip64 record data
818 ulong eocdOffset = getUlong(i + 8);
819 enforce!ZipException(eocdOffset + zip64EndOfCentralDirLength <= _data.length,
820 "corrupted directory");
822 i = to!uint(eocdOffset);
823 enforce!ZipException(_data[i .. i + 4] == zip64EndOfCentralDirSignature,
824 "invalid Zip EOCD64 signature");
826 ulong eocd64Size = getUlong(i + 4);
827 enforce!ZipException(eocd64Size + i - 12 <= data.length,
828 "invalid Zip EOCD64 size");
830 // zip64 end of central dir record
831 removeSegment(i, cast(uint) (i + 12 + eocd64Size));
833 ulong numEntriesUlong = getUlong(i + 24);
834 ulong totalEntriesUlong = getUlong(i + 32);
835 ulong directorySizeUlong = getUlong(i + 40);
836 ulong directoryOffsetUlong = getUlong(i + 48);
838 enforce!ZipException(numEntriesUlong <= uint.max,
839 "supposedly more than 4294967296 files in archive");
841 enforce!ZipException(numEntriesUlong == totalEntriesUlong,
842 "multiple disk zips not supported");
844 enforce!ZipException(directorySizeUlong <= i && directoryOffsetUlong <= i
845 && directorySizeUlong + directoryOffsetUlong <= i,
846 "corrupted directory");
848 directoryCount = to!uint(totalEntriesUlong);
849 directorySize = to!uint(directorySizeUlong);
850 directoryOffset = to!uint(directoryOffsetUlong);
852 else
854 // Read end record data
855 directoryCount = getUshort(i + 10);
856 directorySize = getUint(i + 12);
857 directoryOffset = getUint(i + 16);
860 i = directoryOffset;
861 for (int n = 0; n < directoryCount; n++)
863 /* The format of an entry is:
864 * 'PK' 1, 2
865 * directory info
866 * path
867 * extra data
868 * comment
871 uint namelen;
872 uint extralen;
873 uint commentlen;
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);
894 // central file header
895 removeSegment(i, i + centralFileHeaderLength + namelen + extralen + commentlen);
897 i += centralFileHeaderLength;
899 enforce!ZipException(i + namelen + extralen + commentlen <= directoryOffset + directorySize,
900 "invalid field lengths in file header found");
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;
909 auto localFileHeaderNamelen = getUshort(de.offset + 26);
910 auto localFileHeaderExtralen = getUshort(de.offset + 28);
912 // file data
913 removeSegment(de.offset, de.offset + localFileHeaderLength + localFileHeaderNamelen
914 + localFileHeaderExtralen + de._compressedSize);
916 immutable uint dataOffset = de.offset + localFileHeaderLength
917 + localFileHeaderNamelen + localFileHeaderExtralen;
918 de._compressedData = _data[dataOffset .. dataOffset + de.compressedSize];
920 _directory[de.name] = de;
923 enforce!ZipException(i == directoryOffset + directorySize, "invalid directory entry 3");
926 @system unittest
928 import std.exception : assertThrown;
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";
944 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
947 @system unittest
949 import std.exception : assertThrown;
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";
961 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
964 @system unittest
966 import std.exception : assertThrown;
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";
978 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
981 @system unittest
983 import std.exception : assertThrown;
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";
995 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
998 @system unittest
1000 import std.exception : assertThrown;
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";
1012 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1015 @system unittest
1017 import std.exception : assertThrown;
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";
1029 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1032 @system unittest
1034 import std.exception : assertThrown;
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";
1046 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
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";
1058 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
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";
1071 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1074 @system unittest
1076 import std.exception : assertThrown;
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";
1092 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1095 @system unittest
1097 import std.exception : assertThrown;
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";
1113 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1116 private uint findEndOfCentralDirRecord()
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
1121 uint endrecOffset = to!uint(data.length);
1122 foreach (i; 0 .. 2 ^^ 16)
1124 if (endOfCentralDirLength + i > data.length) break;
1125 uint start = to!uint(data.length) - endOfCentralDirLength - i;
1127 if (data[start .. start + 4] != endOfCentralDirSignature) continue;
1129 auto numberOfThisDisc = getUshort(start + 4);
1130 if (numberOfThisDisc != 0) continue; // no support for multiple volumes yet
1132 auto numberOfStartOfCentralDirectory = getUshort(start + 6);
1133 if (numberOfStartOfCentralDirectory != 0) continue; // dito
1135 if (numberOfThisDisc < numberOfStartOfCentralDirectory) continue;
1137 uint k = start - zip64EndOfCentralDirLocatorLength;
1138 auto maybeZip64 = k < start && _data[k .. k + 4] == zip64EndOfCentralDirLocatorSignature;
1140 auto totalNumberOfEntriesOnThisDisk = getUshort(start + 8);
1141 auto totalNumberOfEntriesInCentralDir = getUshort(start + 10);
1143 if (totalNumberOfEntriesOnThisDisk > totalNumberOfEntriesInCentralDir &&
1144 (!maybeZip64 || totalNumberOfEntriesOnThisDisk < 0xffff)) continue;
1146 auto sizeOfCentralDirectory = getUint(start + 12);
1147 if (sizeOfCentralDirectory > start &&
1148 (!maybeZip64 || sizeOfCentralDirectory < 0xffff)) continue;
1150 auto offsetOfCentralDirectory = getUint(start + 16);
1151 if (offsetOfCentralDirectory > start - sizeOfCentralDirectory &&
1152 (!maybeZip64 || offsetOfCentralDirectory < 0xffff)) continue;
1154 auto zipfileCommentLength = getUshort(start + 20);
1155 if (start + zipfileCommentLength + endOfCentralDirLength != data.length) continue;
1157 enforce!ZipException(endrecOffset == to!uint(data.length),
1158 "found more than one valid 'end of central dir record'");
1160 endrecOffset = start;
1163 enforce!ZipException(endrecOffset != to!uint(data.length),
1164 "found no valid 'end of central dir record'");
1166 return endrecOffset;
1170 * Decompress the contents of a member.
1172 * Fills in properties extractVersion, flags, compressionMethod, time,
1173 * crc32, compressedSize, expandedSize, expandedData[], name[], extra[].
1175 * Params:
1176 * de = Member to be decompressed.
1178 * Returns: The expanded data.
1180 * Throws: ZipException when the entry is invalid or the compression method is not supported.
1182 ubyte[] expand(ArchiveMember de)
1184 import std.string : representation;
1186 uint namelen;
1187 uint extralen;
1189 enforce!ZipException(_data[de.offset .. de.offset + 4] == localFileHeaderSignature,
1190 "wrong local file header signature found");
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);
1203 debug(print)
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);
1211 enforce!ZipException((de.flags & 1) == 0, "encryption not supported");
1213 switch (de.compressionMethod)
1215 case CompressionMethod.none:
1216 de._expandedData = de.compressedData;
1217 return de.expandedData;
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;
1227 default:
1228 throw new ZipException("unsupported compression method");
1232 @system unittest
1234 import std.exception : assertThrown;
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";
1250 auto za = new ZipArchive(cast(void[]) file);
1252 assertThrown!ZipException(za.expand(za._directory["file"]));
1255 @system unittest
1257 import std.exception : assertThrown;
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";
1273 auto za = new ZipArchive(cast(void[]) file);
1275 assertThrown!ZipException(za.expand(za._directory["file"]));
1278 @system unittest
1280 import std.exception : assertThrown;
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";
1296 auto za = new ZipArchive(cast(void[]) file);
1298 assertThrown!ZipException(za.expand(za._directory["file"]));
1301 /* ============ Utility =================== */
1303 @safe @nogc pure nothrow ushort getUshort(uint i)
1305 ubyte[2] result = data[i .. i + 2];
1306 return littleEndianToNative!ushort(result);
1309 @safe @nogc pure nothrow uint getUint(uint i)
1311 ubyte[4] result = data[i .. i + 4];
1312 return littleEndianToNative!uint(result);
1315 @safe @nogc pure nothrow ulong getUlong(uint i)
1317 ubyte[8] result = data[i .. i + 8];
1318 return littleEndianToNative!ulong(result);
1321 @safe @nogc pure nothrow void putUshort(uint i, ushort us)
1323 data[i .. i + 2] = nativeToLittleEndian(us);
1326 @safe @nogc pure nothrow void putUint(uint i, uint ui)
1328 data[i .. i + 4] = nativeToLittleEndian(ui);
1331 @safe @nogc pure nothrow void putUlong(uint i, ulong ul)
1333 data[i .. i + 8] = nativeToLittleEndian(ul);
1336 /* ============== for detecting overlaps =============== */
1338 private:
1340 // defines a segment of the zip file, including start, excluding end
1341 struct Segment
1343 uint start;
1344 uint end;
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")
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))
1358 found = true;
1359 pos = i;
1362 enforce!ZipException(found, "overlapping data detected");
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 .. $];
1371 pure @safe unittest
1373 with (new ZipArchive())
1375 _segs = [Segment(0,100)];
1376 removeSegment(10,20);
1377 assert(_segs == [Segment(0,10),Segment(20,100)]);
1379 _segs = [Segment(0,100)];
1380 removeSegment(0,20);
1381 assert(_segs == [Segment(20,100)]);
1383 _segs = [Segment(0,100)];
1384 removeSegment(10,100);
1385 assert(_segs == [Segment(0,10)]);
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)]);
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)]);
1395 import std.exception : assertThrown;
1397 _segs = [Segment(0,100), Segment(200,300), Segment(400,500)];
1398 assertThrown(removeSegment(120,230));
1400 _segs = [Segment(0,100), Segment(200,300), Segment(400,500)];
1401 removeSegment(0,100);
1402 assertThrown(removeSegment(0,100));
1404 _segs = [Segment(0,100)];
1405 removeSegment(0,100);
1406 assertThrown(removeSegment(0,100));
1411 debug(print)
1413 @safe void arrayPrint(ubyte[] array)
1415 printf("array %p,%d\n", cast(void*) array, array.length);
1416 for (int i = 0; i < array.length; i++)
1418 printf("%02x ", array[i]);
1419 if (((i + 1) & 15) == 0)
1420 printf("\n");
1422 printf("\n");
1426 @system unittest
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);
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)
1452 bool useZip64 = !!variant;
1453 zip1 = new ZipArchive();
1454 zip1.isZip64 = useZip64;
1455 ArchiveMember[itemCount] ams;
1456 foreach (i; 0 .. itemCount)
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]);
1466 auto zippedData = zip1.build();
1467 zip2 = new ZipArchive(zippedData);
1468 assert(zip2.isZip64 == useZip64);
1469 foreach (am; ams)
1471 am2 = zip2.directory[am.name];
1472 zip2.expand(am2);
1473 assert(am.crc32 == am2.crc32);
1474 assert(am.expandedData == am2.expandedData);
1479 @system unittest
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)
1490 value += 1 + rand.front & 0xFFFF;
1491 rand.popFront;
1492 names ~= value.to!string;
1494 // Insert them in a random order.
1495 names.randomShuffle(rand);
1496 auto zip1 = new ZipArchive();
1497 foreach (i, name; names)
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);
1505 auto data = zip1.build();
1507 // Ensure that they appear in the same order.
1508 auto zip2 = new ZipArchive(data);
1509 foreach (i, name; names)
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);
1518 @system unittest
1520 import std.zlib;
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
1526 auto dst = cast(ubyte[]) compress(cast(void[]) src);
1527 auto after = cast(ubyte[]) uncompress(cast(void[]) dst);
1528 assert(src == after);
1531 @system unittest
1533 // @system due to ZipArchive.build
1534 import std.datetime;
1535 ubyte[] buf = [1, 2, 3, 4, 5, 0, 7, 8, 9];
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
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);
1555 @system unittest
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"));
1562 @system unittest
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);
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"));
1577 @system unittest
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";
1601 import std.exception : assertThrown;
1602 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1605 @system unittest
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";
1622 auto za = new ZipArchive(cast(void[]) file);
1623 assert(za.directory["file"].compressedData == [104, 101, 108, 108, 111]);
1626 // https://issues.dlang.org/show_bug.cgi?id=20027
1627 @system unittest
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";
1644 import std.exception : assertThrown;
1645 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
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";
1661 assertThrown!ZipException(new ZipArchive(cast(void[]) file));
1664 @system unittest
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";
1678 auto za = new ZipArchive(cast(void[]) file);
1679 assert(za.directory.length == 0);
1682 version (HasUnzip)
1683 @system unittest
1685 import std.datetime, std.file, std.format, std.path, std.process, std.stdio;
1687 if (executeShell("unzip").status != 0)
1689 writeln("Can't run unzip, skipping unzip test");
1690 return;
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();
1702 mkdirRecurse(deleteme);
1703 scope(exit) rmdirRecurse(deleteme);
1704 string zipFile = buildPath(deleteme, "foo.zip");
1705 std.file.write(zipFile, cast(byte[]) data2);
1707 auto result = executeShell(format("unzip -l %s", zipFile));
1708 scope(failure) writeln(result.output);
1709 assert(result.status == 0);