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