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
; }
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.
236 * ed = Expanded Data.
238 * Returns: The file data.
240 @property @safe pure nothrow @nogc ubyte[] expandedData() { return _expandedData
; }
243 @property @safe void expandedData(ubyte[] ed
)
246 _expandedSize
= to
!uint(_expandedData
.length
);
248 // Clean old compressed data, if any
249 _compressedData
.length
= 0;
254 * Get or set the OS specific file attributes for this archive member.
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
)
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
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);
291 @property @nogc nothrow uint fileAttributes() const
295 if ((_madeVersion
& 0xFF00) == 0x0300)
296 return _externalAttributes
>> 16;
299 else version (Windows
)
301 if ((_madeVersion
& 0xFF00) == 0x0000)
302 return _externalAttributes
;
307 static assert(0, "Unimplemented platform");
312 * Get or set the last modification time for this member.
315 * time = Time to set (will be saved as DosFileTime, which is less accurate).
318 * The last modification time in DosFileFormat.
320 @property DosFileTime
time() const @safe pure nothrow @nogc
326 @property void time(SysTime time
)
328 _time
= SysTimeToDosFileTime(time
);
332 @property void time(DosFileTime time
) @safe pure nothrow @nogc
338 * Get or set compression method used for this member.
341 * cm = Compression method.
343 * Returns: Compression method.
346 * $(LREF CompressionMethod)
348 @property @safe @nogc pure nothrow CompressionMethod
compressionMethod() const { return _compressionMethod
; }
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.
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
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
);
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
;
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;
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.
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
; }
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:
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
;
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.
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
;
537 case CompressionMethod
.deflate
:
538 import std
.zlib
: compress
;
541 de._compressedData
= cast(ubyte[]) compress(cast(void[]) de._expandedData
);
543 de._compressedData
= de._compressedData
[2 .. de._compressedData
.length
- 4];
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.");
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.
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
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,
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
;
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
+
637 directorySize
+= centralFileHeaderLength
+ de.name
.length
+
642 if (!isZip64
&& _directory
.length
> ushort.max
)
644 uint dataSize
= archiveSize
+ directorySize
+ endOfCentralDirLength
+ cast(uint) comment
.length
;
646 dataSize
+= zip64EndOfCentralDirLocatorLength
+ zip64EndOfCentralDirLength
;
648 _data
= uninitializedArray
!(ubyte[])(dataSize
);
650 // Populate the data[]
652 // Store each archive member
654 foreach (ArchiveMember
de; directory
)
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
)[];
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
;
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
)[];
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
;
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
);
728 i
+= zip64EndOfCentralDirLocatorLength
;
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
;
751 import std
.exception
: assertNotThrown
;
753 ZipArchive zip
= new ZipArchive();
755 assertNotThrown(zip
.build());
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.
781 * buffer = The entire contents of the archive.
783 * Throws: ZipException when the archive was invalid or when malware was detected.
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
)
807 // zip64 end of central dir record locator
808 removeSegment(k
, k
+ zip64EndOfCentralDirLocatorLength
);
812 uint directoryOffset
;
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
);
854 // Read end record data
855 directoryCount
= getUshort(i
+ 10);
856 directorySize
= getUint(i
+ 12);
857 directoryOffset
= getUint(i
+ 16);
861 for (int n
= 0; n
< directoryCount
; n
++)
863 /* The format of an entry is:
875 enforce
!ZipException(_data
[i
.. i
+ 4] == centralFileHeaderSignature
,
876 "wrong central file header signature found");
877 ArchiveMember
de = new ArchiveMember();
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
]);
904 de.extra
= _data
[i
.. i
+ extralen
];
906 de.comment
= cast(string
)(_data
[i
.. i
+ commentlen
]);
909 auto localFileHeaderNamelen
= getUshort(de.offset
+ 26);
910 auto localFileHeaderExtralen
= getUshort(de.offset
+ 28);
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");
928 import std
.exception
: assertThrown
;
930 // contains wrong directorySize (extra byte 0xff)
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"~
944 assertThrown
!ZipException(new ZipArchive(cast(void[]) file
));
949 import std
.exception
: assertThrown
;
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"~
961 assertThrown
!ZipException(new ZipArchive(cast(void[]) file
));
966 import std
.exception
: assertThrown
;
968 // wrong signature of zip64 end of central directory
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"~
978 assertThrown
!ZipException(new ZipArchive(cast(void[]) file
));
983 import std
.exception
: assertThrown
;
985 // wrong size of zip64 end of central directory
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"~
995 assertThrown
!ZipException(new ZipArchive(cast(void[]) file
));
1000 import std
.exception
: assertThrown
;
1002 // too many entries in zip64 end of central directory
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"~
1012 assertThrown
!ZipException(new ZipArchive(cast(void[]) file
));
1017 import std
.exception
: assertThrown
;
1019 // zip64: numEntries and totalEntries differ
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"~
1029 assertThrown
!ZipException(new ZipArchive(cast(void[]) file
));
1034 import std
.exception
: assertThrown
;
1036 // zip64: directorySize too large
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"~
1046 assertThrown
!ZipException(new ZipArchive(cast(void[]) file
));
1048 // zip64: directoryOffset too large
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"~
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
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"~
1071 assertThrown
!ZipException(new ZipArchive(cast(void[]) file
));
1076 import std
.exception
: assertThrown
;
1078 // wrong central file header signature
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"~
1092 assertThrown
!ZipException(new ZipArchive(cast(void[]) file
));
1097 import std
.exception
: assertThrown
;
1099 // invalid field lengths in file header
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"~
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[].
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
;
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);
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
;
1228 throw new ZipException("unsupported compression method");
1234 import std
.exception
: assertThrown
;
1236 // check for correct local file header signature
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"~
1250 auto za
= new ZipArchive(cast(void[]) file
);
1252 assertThrown
!ZipException(za
.expand(za
._directory
["file"]));
1257 import std
.exception
: assertThrown
;
1259 // check for encryption flag
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"~
1273 auto za
= new ZipArchive(cast(void[]) file
);
1275 assertThrown
!ZipException(za
.expand(za
._directory
["file"]));
1280 import std
.exception
: assertThrown
;
1282 // check for invalid compression method
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"~
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 =============== */
1340 // defines a segment of the zip file, including start, excluding 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")
1354 foreach (i
,seg
;_segs
)
1355 if (seg
.start
<= start
&& seg
.end
>= end
1356 && (!found || seg
.start
> _segs
[pos
].start
))
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 .. $];
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));
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)
1428 // @system due to (at least) ZipArchive.build
1429 auto zip1
= new ZipArchive();
1430 auto zip2
= new ZipArchive();
1431 auto am1
= new ArchiveMember();
1433 am1
.expandedData
= new ubyte[](1024);
1434 zip1
.addMember(am1
);
1435 auto data1
= zip1
.build();
1436 zip2
.addMember(zip1
.directory
["foo"]);
1438 auto am2
= zip2
.directory
["foo"];
1440 assert(am1
.expandedData
== am2
.expandedData
);
1441 auto zip3
= new ZipArchive(data1
);
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
;
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
);
1471 am2
= zip2
.directory
[am
.name
];
1473 assert(am
.crc32
== am2
.crc32
);
1474 assert(am
.expandedData
== am2
.expandedData
);
1481 import std
.conv
: to
;
1482 import std
.random
: Mt19937
, randomShuffle
;
1483 // Test if packing and unpacking preserves order.
1484 auto rand
= Mt19937(15966);
1487 // Generate a series of unique numbers as filenames.
1488 foreach (i
; 0 .. 20)
1490 value
+= 1 + rand
.front
& 0xFFFF;
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();
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
);
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
);
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
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
);
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"));
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"));
1579 // https://issues.dlang.org/show_bug.cgi?id=20239
1580 // chameleon file, containing two valid end of central directory entries
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
));
1607 // https://issues.dlang.org/show_bug.cgi?id=20287
1608 // check for correct compressed data
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"~
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
1629 // central file header overlaps end of central directory
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"~
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
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"~
1661 assertThrown
!ZipException(new ZipArchive(cast(void[]) file
));
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
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"~
1678 auto za
= new ZipArchive(cast(void[]) file
);
1679 assert(za
.directory
.length
== 0);
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");
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";
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);