]> git.ipfire.org Git - thirdparty/gcc.git/blobdiff - libphobos/src/std/zip.d
d: Import dmd b8384668f, druntime e6caaab9, phobos 5ab9ad256 (v2.098.0-beta.1)
[thirdparty/gcc.git] / libphobos / src / std / zip.d
index 9e55d199a9b5ce4d131bd414c90a993124edf361..4d7422bdd1c2e67702c6035653fda4fdab9673a0 100644 (file)
 // Written in the D programming language.
 
 /**
- * Read/write data in the $(LINK2 http://www.info-zip.org, _zip archive) format.
- * Makes use of the etc.c.zlib compression library.
- *
- * Bugs:
- *      $(UL
- *      $(LI Multi-disk zips not supported.)
- *      $(LI Only Zip version 20 formats are supported.)
- *      $(LI Only supports compression modes 0 (no compression) and 8 (deflate).)
- *      $(LI Does not support encryption.)
- *      $(LI $(BUGZILLA 592))
- *      $(LI $(BUGZILLA 2137))
- *      )
- *
- * Example:
- * ---
-// Read existing zip file.
-import std.digest.crc, std.file, std.stdio, std.zip;
+Read and write data in the
+$(LINK2 https://en.wikipedia.org/wiki/Zip_%28file_format%29, zip archive)
+format.
+
+Standards:
+
+The current implementation mostly conforms to
+$(LINK2 https://www.iso.org/standard/60101.html, ISO/IEC 21320-1:2015),
+which means,
+$(UL
+$(LI that files can only be stored uncompressed or using the deflate mechanism,)
+$(LI that encryption features are not used,)
+$(LI that digital signature features are not used,)
+$(LI that patched data features are not used, and)
+$(LI that archives may not span multiple volumes.)
+)
+
+Additionally, archives are checked for malware attacks and rejected if detected.
+This includes
+$(UL
+$(LI $(LINK2 https://news.ycombinator.com/item?id=20352439, zip bombs) which
+     generate gigantic amounts of unpacked data)
+$(LI zip archives that contain overlapping records)
+$(LI chameleon zip archives which generate different unpacked data, depending
+     on the implementation of the unpack algorithm)
+)
+
+The current implementation makes use of the zlib compression library.
+
+Usage:
+
+There are two main ways of usage: Extracting files from a zip archive
+and storing files into a zip archive. These can be mixed though (e.g.
+read an archive, remove some files, add others and write the new
+archive).
+
+Examples:
+
+Example for reading an existing zip archive:
+---
+import std.stdio : writeln, writefln;
+import std.file : read;
+import std.zip;
 
 void main(string[] args)
 {
     // read a zip file into memory
     auto zip = new ZipArchive(read(args[1]));
-    writeln("Archive: ", args[1]);
-    writefln("%-10s  %-8s  Name", "Length", "CRC-32");
+
     // iterate over all zip members
+    writefln("%-10s  %-8s  Name", "Length", "CRC-32");
     foreach (name, am; zip.directory)
     {
         // print some data about each member
         writefln("%10s  %08x  %s", am.expandedSize, am.crc32, name);
         assert(am.expandedData.length == 0);
+
         // decompress the archive member
         zip.expand(am);
         assert(am.expandedData.length == am.expandedSize);
     }
 }
+---
 
-// Create and write new zip file.
+Example for writing files into a zip archive:
+---
 import std.file : write;
 import std.string : representation;
+import std.zip;
 
 void main()
 {
-    char[] data = "Test data.\n".dup;
-    // Create an ArchiveMember for the test file.
-    ArchiveMember am = new ArchiveMember();
-    am.name = "test.txt";
-    am.expandedData(data.representation);
+    // Create an ArchiveMembers for each file.
+    ArchiveMember file1 = new ArchiveMember();
+    file1.name = "test1.txt";
+    file1.expandedData("Test data.\n".dup.representation);
+    file1.compressionMethod = CompressionMethod.none; // don't compress
+
+    ArchiveMember file2 = new ArchiveMember();
+    file2.name = "test2.txt";
+    file2.expandedData("More test data.\n".dup.representation);
+    file2.compressionMethod = CompressionMethod.deflate; // compress
+
     // Create an archive and add the member.
     ZipArchive zip = new ZipArchive();
-    zip.addMember(am);
+
+    // add ArchiveMembers
+    zip.addMember(file1);
+    zip.addMember(file2);
+
     // Build the archive
     void[] compressed_data = zip.build();
+
     // Write to a file
     write("test.zip", compressed_data);
 }
- * ---
- *
- * Copyright: Copyright Digital Mars 2000 - 2009.
+---
+
+ * Copyright: Copyright The D Language Foundation 2000 - 2009.
  * License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
  * Authors:   $(HTTP digitalmars.com, Walter Bright)
- * Source:    $(PHOBOSSRC std/_zip.d)
+ * Source:    $(PHOBOSSRC std/zip.d)
  */
 
-/*          Copyright Digital Mars 2000 - 2009.
+/*          Copyright The D Language Foundation 2000 - 2009.
  * Distributed under the Boost Software License, Version 1.0.
  *    (See accompanying file LICENSE_1_0.txt or copy at
  *          http://www.boost.org/LICENSE_1_0.txt)
  */
 module std.zip;
 
+import std.exception : enforce;
+
+// Non-Android/Apple ARM POSIX-only, because we can't rely on the unzip
+// command being available on Android, Apple ARM or Windows
+version (Android) {}
+else version (iOS) {}
+else version (TVOS) {}
+else version (WatchOS) {}
+else version (Posix)
+    version = HasUnzip;
+
 //debug=print;
 
-/** Thrown on error.
- */
+/// Thrown on error.
 class ZipException : Exception
 {
-    this(string msg) @safe
-    {
-        super("ZipException: " ~ msg);
-    }
+    import std.exception : basicExceptionCtors;
+    ///
+    mixin basicExceptionCtors;
 }
 
-/**
- * Compression method used by ArchiveMember
- */
+/// Compression method used by `ArchiveMember`.
 enum CompressionMethod : ushort
 {
-    none = 0,   /// No compression, just archiving
-    deflate = 8 /// Deflate algorithm. Use zlib library to compress
+    none = 0,   /// No compression, just archiving.
+    deflate = 8 /// Deflate algorithm. Use zlib library to compress.
 }
 
-/**
- * A member of the ZipArchive.
- */
+/// A single file or directory inside the archive.
 final class ArchiveMember
 {
     import std.conv : to, octal;
     import std.datetime.systime : DosFileTime, SysTime, SysTimeToDosFileTime;
 
     /**
-     * Read/Write: Usually the file name of the archive member; it is used to
-     * index the archive directory for the member. Each member must have a unique
-     * name[]. Do not change without removing member from the directory first.
+     * The name of the archive member; it is used to index the
+     * archive directory for the member. Each member must have a
+     * unique name. Do not change without removing member from the
+     * directory first.
      */
     string name;
 
-    ubyte[] extra;              /// Read/Write: extra data for this member.
-    string comment;             /// Read/Write: comment associated with this member.
+    /**
+     * The content of the extra data field for this member. See
+     * $(LINK2 https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT,
+     *         original documentation)
+     * for a description of the general format of this data. May contain
+     * undocumented 3rd-party data.
+     */
+    ubyte[] extra;
+
+    string comment; /// Comment associated with this member.
 
     private ubyte[] _compressedData;
     private ubyte[] _expandedData;
@@ -119,32 +174,80 @@ final class ArchiveMember
     private CompressionMethod _compressionMethod;
     private ushort _madeVersion = 20;
     private ushort _extractVersion = 20;
-    private ushort _diskNumber;
     private uint _externalAttributes;
     private DosFileTime _time;
     // by default, no explicit order goes after explicit order
     private uint _index = uint.max;
 
-    ushort flags;                  /// Read/Write: normally set to 0
-    ushort internalAttributes;     /// Read/Write
+    /**
+     * Contains some information on how to extract this archive. See
+     * $(LINK2 https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT,
+     *         original documentation)
+     * for details.
+     */
+    ushort flags;
+
+    /**
+     * Internal attributes. Bit 1 is set, if the member is apparently in binary format
+     * and bit 2 is set, if each record is preceded by the length of the record.
+     */
+    ushort internalAttributes;
+
+    /**
+     * The zip file format version needed to extract this member.
+     *
+     * Returns: Format version needed to extract this member.
+     */
+    @property @safe pure nothrow @nogc ushort extractVersion() const { return _extractVersion; }
 
-    @property ushort extractVersion()     { return _extractVersion; }    /// Read Only
-    @property uint crc32()         { return _crc32; }    /// Read Only: cyclic redundancy check (CRC) value
+    /**
+     * Cyclic redundancy check (CRC) value.
+     *
+     * Returns: CRC32 value.
+     */
+    @property @safe pure nothrow @nogc uint crc32() const { return _crc32; }
+
+    /**
+     * Size of data of member in compressed form.
+     *
+     * Returns: Size of the compressed archive.
+     */
+    @property @safe pure nothrow @nogc uint compressedSize() const { return _compressedSize; }
 
-    /// Read Only: size of data of member in compressed form.
-    @property uint compressedSize()     { return _compressedSize; }
+    /**
+     * Size of data of member in uncompressed form.
+     *
+     * Returns: Size of uncompressed archive.
+     */
+    @property @safe pure nothrow @nogc uint expandedSize() const { return _expandedSize; }
 
-    /// Read Only: size of data of member in expanded form.
-    @property uint expandedSize()     { return _expandedSize; }
-    @property ushort diskNumber()     { return _diskNumber; }        /// Read Only: should be 0.
+    /**
+     * Should be 0.
+     *
+     * Returns: The number of the disk where this member can be found.
+     */
+    deprecated("Multidisk not supported; will be removed in 2.099.0")
+    @property @safe pure nothrow @nogc ushort diskNumber() const { return 0; }
 
-    /// Read Only: data of member in compressed form.
-    @property ubyte[] compressedData()     { return _compressedData; }
+    /**
+     * Data of member in compressed form.
+     *
+     * Returns: The file data in compressed form.
+     */
+    @property @safe pure nothrow @nogc ubyte[] compressedData() { return _compressedData; }
 
-    /// Read data of member in uncompressed form.
-    @property ubyte[] expandedData()     { return _expandedData; }
+    /**
+     * Get or set data of member in uncompressed form. When an existing archive is
+     * read `ZipArchive.expand` needs to be called before this can be accessed.
+     *
+     * Params:
+     *     ed = Expanded Data.
+     *
+     * Returns: The file data.
+     */
+    @property @safe pure nothrow @nogc ubyte[] expandedData() { return _expandedData; }
 
-    /// Write data of member in uncompressed form.
+    /// ditto
     @property @safe void expandedData(ubyte[] ed)
     {
         _expandedData = ed;
@@ -156,8 +259,14 @@ final class ArchiveMember
     }
 
     /**
-     * Set the OS specific file attributes, as obtained by
-     * $(REF getAttributes, std,file) or $(REF DirEntry.attributes, std,file), for this archive member.
+     * Get or set the OS specific file attributes for this archive member.
+     *
+     * Params:
+     *     attr = Attributes as obtained by $(REF getAttributes, std,file) or
+     *            $(REF DirEntry.attributes, std,file).
+     *
+     * Returns: The file attributes or 0 if the file attributes were
+     * encoded for an incompatible OS (Windows vs. POSIX).
      */
     @property @safe void fileAttributes(uint attr)
     {
@@ -186,14 +295,8 @@ final class ArchiveMember
         assert((am._madeVersion & 0xFF00) == 0x0300);
     }
 
-    /**
-     * Get the OS specific file attributes for the archive member.
-     *
-     * Returns: The file attributes or 0 if the file attributes were
-     * encoded for an incompatible OS (Windows vs. Posix).
-     *
-     */
-    @property uint fileAttributes() const
+    /// ditto
+    @property @nogc nothrow uint fileAttributes() const
     {
         version (Posix)
         {
@@ -213,51 +316,66 @@ final class ArchiveMember
         }
     }
 
-    /// Set the last modification time for this member.
-    @property void time(SysTime time)
+    /**
+     * Get or set the last modification time for this member.
+     *
+     * Params:
+     *     time = Time to set (will be saved as DosFileTime, which is less accurate).
+     *
+     * Returns:
+     *     The last modification time in DosFileFormat.
+     */
+    @property DosFileTime time() const @safe pure nothrow @nogc
     {
-        _time = SysTimeToDosFileTime(time);
+        return _time;
     }
 
     /// ditto
-    @property void time(DosFileTime time)
+    @property void time(SysTime time)
     {
-        _time = time;
+        _time = SysTimeToDosFileTime(time);
     }
 
-    /// Get the last modification time for this member.
-    @property DosFileTime time() const
+    /// ditto
+    @property void time(DosFileTime time) @safe pure nothrow @nogc
     {
-        return _time;
+        _time = time;
     }
 
     /**
-     * Read compression method used for this member
+     * Get or set compression method used for this member.
+     *
+     * Params:
+     *     cm = Compression method.
+     *
+     * Returns: Compression method.
+     *
      * See_Also:
-     *     CompressionMethod
+     *     $(LREF CompressionMethod)
      **/
-    @property @safe CompressionMethod compressionMethod() { return _compressionMethod; }
+    @property @safe @nogc pure nothrow CompressionMethod compressionMethod() const { return _compressionMethod; }
 
-    /**
-     * Write compression method used for this member
-     * See_Also:
-     *     CompressionMethod
-     **/
-    @property void compressionMethod(CompressionMethod cm)
+    /// ditto
+    @property @safe pure void compressionMethod(CompressionMethod cm)
     {
         if (cm == _compressionMethod) return;
 
-        if (_compressedSize > 0)
-            throw new ZipException("Can't change compression method for a compressed element");
+        enforce!ZipException(_compressedSize == 0, "Can't change compression method for a compressed element");
 
         _compressionMethod = cm;
     }
 
     /**
-      * The index of this archive member within the archive.
-      */
-    @property uint index() const pure nothrow @nogc { return _index; }
-    @property uint index(uint value) pure nothrow @nogc { return _index = value; }
+     * The index of this archive member within the archive. Set this to a
+     * different value for reordering the members of an archive.
+     *
+     * Params:
+     *     value = Index value to set.
+     *
+     * Returns: The index.
+     */
+    @property uint index(uint value) @safe pure nothrow @nogc { return _index = value; }
+    @property uint index() const @safe pure nothrow @nogc { return _index; } /// ditto
 
     debug(print)
     {
@@ -280,6 +398,21 @@ final class ArchiveMember
     }
 }
 
+@safe pure unittest
+{
+    import std.exception : assertThrown, assertNotThrown;
+
+    auto am = new ArchiveMember();
+
+    assertNotThrown(am.compressionMethod(CompressionMethod.deflate));
+    assertNotThrown(am.compressionMethod(CompressionMethod.none));
+
+    am._compressedData = [0x65]; // not strictly necessary, but for consistency
+    am._compressedSize = 1;
+
+    assertThrown!ZipException(am.compressionMethod(CompressionMethod.deflate));
+}
+
 /**
  * Object representing the entire archive.
  * ZipArchives are collections of ArchiveMembers.
@@ -291,42 +424,93 @@ final class ZipArchive
     import std.conv : to;
     import std.datetime.systime : DosFileTime;
 
-    string comment;     /// Read/Write: the archive comment. Must be less than 65536 bytes in length.
+private:
+    // names are taken directly from the specification
+    // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
+    static immutable ubyte[] centralFileHeaderSignature = [ 0x50, 0x4b, 0x01, 0x02 ];
+    static immutable ubyte[] localFileHeaderSignature = [ 0x50, 0x4b, 0x03, 0x04 ];
+    static immutable ubyte[] endOfCentralDirSignature = [ 0x50, 0x4b, 0x05, 0x06 ];
+    static immutable ubyte[] archiveExtraDataSignature = [ 0x50, 0x4b, 0x06, 0x08 ];
+    static immutable ubyte[] digitalSignatureSignature = [ 0x50, 0x4b, 0x05, 0x05 ];
+    static immutable ubyte[] zip64EndOfCentralDirSignature = [ 0x50, 0x4b, 0x06, 0x06 ];
+    static immutable ubyte[] zip64EndOfCentralDirLocatorSignature = [ 0x50, 0x4b, 0x06, 0x07 ];
+
+    enum centralFileHeaderLength = 46;
+    enum localFileHeaderLength = 30;
+    enum endOfCentralDirLength = 22;
+    enum archiveExtraDataLength = 8;
+    enum digitalSignatureLength = 6;
+    enum zip64EndOfCentralDirLength = 56;
+    enum zip64EndOfCentralDirLocatorLength = 20;
+    enum dataDescriptorLength = 12;
+
+public:
+    string comment; /// The archive comment. Must be less than 65536 bytes in length.
 
     private ubyte[] _data;
-    private uint endrecOffset;
 
-    private uint _diskNumber;
-    private uint _diskStartDir;
-    private uint _numEntries;
-    private uint _totalEntries;
     private bool _isZip64;
     static const ushort zip64ExtractVersion = 45;
+
+    deprecated("Use digitalSignatureLength instead; will be removed in 2.098.0")
     static const int digiSignLength = 6;
+    deprecated("Use zip64EndOfCentralDirLocatorLength instead; will be removed in 2.098.0")
     static const int eocd64LocLength = 20;
+    deprecated("Use zip64EndOfCentralDirLength instead; will be removed in 2.098.0")
     static const int eocd64Length = 56;
 
-    /// Read Only: array representing the entire contents of the archive.
-    @property @safe ubyte[] data() { return _data; }
+    private Segment[] _segs;
 
-    /// Read Only: 0 since multi-disk zip archives are not supported.
-    @property @safe uint diskNumber()    { return _diskNumber; }
+    /**
+     * Array representing the entire contents of the archive.
+     *
+     * Returns: Data of the entire contents of the archive.
+     */
+    @property @safe @nogc pure nothrow ubyte[] data() { return _data; }
 
-    /// Read Only: 0 since multi-disk zip archives are not supported
-    @property @safe uint diskStartDir()  { return _diskStartDir; }
+    /**
+     * 0 since multi-disk zip archives are not supported.
+     *
+     * Returns: Number of this disk.
+     */
+    deprecated("Multidisk not supported; will be removed in 2.099.0")
+    @property @safe @nogc pure nothrow uint diskNumber() const { return 0; }
 
-    /// Read Only: number of ArchiveMembers in the directory.
-    @property @safe uint numEntries()    { return _numEntries; }
-    @property @safe uint totalEntries()  { return _totalEntries; }    /// ditto
+    /**
+     * 0 since multi-disk zip archives are not supported.
+     *
+     * Returns: Number of the disk, where the central directory starts.
+     */
+    deprecated("Multidisk not supported; will be removed in 2.099.0")
+    @property @safe @nogc pure nothrow uint diskStartDir() const { return 0; }
 
-    /// True when the archive is in Zip64 format.
-    @property @safe bool isZip64()  { return _isZip64; }
+    /**
+     * Number of ArchiveMembers in the directory.
+     *
+     * Returns: The number of files in this archive.
+     */
+    deprecated("Use totalEntries instead; will be removed in 2.099.0")
+    @property @safe @nogc pure nothrow uint numEntries() const { return cast(uint) _directory.length; }
+    @property @safe @nogc pure nothrow uint totalEntries() const { return cast(uint) _directory.length; }    /// ditto
 
-    /// Set this to true to force building a Zip64 archive.
-    @property @safe void isZip64(bool value) { _isZip64 = value; }
     /**
-     * Read Only: array indexed by the name of each member of the archive.
-     *  All the members of the archive can be accessed with a foreach loop:
+     * True when the archive is in Zip64 format. Set this to true to force building a Zip64 archive.
+     *
+     * Params:
+     *     value = True, when the archive is forced to be build in Zip64 format.
+     *
+     * Returns: True, when the archive is in Zip64 format.
+     */
+    @property @safe @nogc pure nothrow bool isZip64() const { return _isZip64; }
+
+    /// ditto
+    @property @safe @nogc pure nothrow void isZip64(bool value) { _isZip64 = value; }
+
+    /**
+     * Associative array indexed by the name of each member of the archive.
+     *
+     * All the members of the archive can be accessed with a foreach loop:
+     *
      * Example:
      * --------------------
      * ZipArchive archive = new ZipArchive(data);
@@ -335,8 +519,10 @@ final class ZipArchive
      *     writefln("member name is '%s'", am.name);
      * }
      * --------------------
+     *
+     * Returns: Associative array with all archive members.
      */
-    @property @safe ArchiveMember[string] directory() { return _directory; }
+    @property @safe @nogc pure nothrow ArchiveMember[string] directory() { return _directory; }
 
     private ArchiveMember[string] _directory;
 
@@ -354,13 +540,21 @@ final class ZipArchive
 
     /* ============ Creating a new archive =================== */
 
-    /** Constructor to use when creating a new archive.
+    /**
+     * Constructor to use when creating a new archive.
      */
-    this() @safe
+    this() @safe @nogc pure nothrow
     {
     }
 
-    /** Add de to the archive. The file is compressed on the fly.
+    /**
+     * Add a member to the archive. The file is compressed on the fly.
+     *
+     * Params:
+     *     de = Member to be added.
+     *
+     * Throws: ZipException when an unsupported compression method is used or when
+     *         compression failed.
      */
     @safe void addMember(ArchiveMember de)
     {
@@ -390,61 +584,101 @@ final class ZipArchive
             import std.zlib : crc32;
             () @trusted { de._crc32 = crc32(0, cast(void[]) de._expandedData); }();
         }
-        assert(de._compressedData.length == de._compressedSize);
+        assert(de._compressedData.length == de._compressedSize, "Archive member compressed failed.");
+    }
+
+    @safe unittest
+    {
+        import std.exception : assertThrown;
+
+        ArchiveMember am = new ArchiveMember();
+        am.compressionMethod = cast(CompressionMethod) 3;
+
+        ZipArchive zip = new ZipArchive();
+
+        assertThrown!ZipException(zip.addMember(am));
     }
 
-    /** Delete de from the archive.
+    /**
+     * Delete member `de` from the archive. Uses the name of the member
+     * to detect which element to delete.
+     *
+     * Params:
+     *     de = Member to be deleted.
      */
     @safe void deleteMember(ArchiveMember de)
     {
         _directory.remove(de.name);
     }
 
+    // https://issues.dlang.org/show_bug.cgi?id=20398
+    @safe unittest
+    {
+        import std.string : representation;
+
+        ArchiveMember file1 = new ArchiveMember();
+        file1.name = "test1.txt";
+        file1.expandedData("Test data.\n".dup.representation);
+
+        ZipArchive zip = new ZipArchive();
+
+        zip.addMember(file1);
+        assert(zip.totalEntries == 1);
+
+        zip.deleteMember(file1);
+        assert(zip.totalEntries == 0);
+    }
+
     /**
-     * Construct an archive out of the current members of the archive.
+     * Construct the entire contents of the current members of the archive.
      *
-     * Fills in the properties data[], diskNumber, diskStartDir, numEntries,
-     * totalEntries, and directory[].
+     * Fills in the properties data[], totalEntries, and directory[].
      * For each ArchiveMember, fills in properties crc32, compressedSize,
      * compressedData[].
      *
-     * Returns: array representing the entire archive.
+     * Returns: Array representing the entire archive.
+     *
+     * Throws: ZipException when the archive could not be build.
      */
-    void[] build()
+    void[] build() @safe pure
     {
+        import std.array : array, uninitializedArray;
         import std.algorithm.sorting : sort;
+        import std.string : representation;
+
         uint i;
         uint directoryOffset;
 
-        if (comment.length > 0xFFFF)
-            throw new ZipException("archive comment longer than 65535");
+        enforce!ZipException(comment.length <= 0xFFFF, "archive comment longer than 65535");
 
         // Compress each member; compute size
         uint archiveSize = 0;
         uint directorySize = 0;
-        auto directory = _directory.values().sort!((x, y) => x.index < y.index).release;
+        auto directory = _directory.byValue.array.sort!((x, y) => x.index < y.index).release;
         foreach (ArchiveMember de; directory)
         {
-            if (to!ulong(archiveSize) + 30 + de.name.length + de.extra.length + de.compressedSize
-                    + directorySize + 46 + de.name.length + de.extra.length + de.comment.length
-                    + 22 + comment.length + eocd64LocLength + eocd64Length > uint.max)
-                throw new ZipException("zip files bigger than 4 GB are unsupported");
-
-            archiveSize += 30 + de.name.length +
+            enforce!ZipException(to!ulong(archiveSize) + localFileHeaderLength + de.name.length
+                                 + de.extra.length + de.compressedSize + directorySize
+                                 + centralFileHeaderLength + de.name.length + de.extra.length
+                                 + de.comment.length + endOfCentralDirLength + comment.length
+                                 + zip64EndOfCentralDirLocatorLength + zip64EndOfCentralDirLength <= uint.max,
+                                 "zip files bigger than 4 GB are unsupported");
+
+            archiveSize += localFileHeaderLength + de.name.length +
                                 de.extra.length +
                                 de.compressedSize;
-            directorySize += 46 + de.name.length +
+            directorySize += centralFileHeaderLength + de.name.length +
                                 de.extra.length +
                                 de.comment.length;
         }
 
         if (!isZip64 && _directory.length > ushort.max)
             _isZip64 = true;
-        uint dataSize = archiveSize + directorySize + 22 + cast(uint) comment.length;
+        uint dataSize = archiveSize + directorySize + endOfCentralDirLength + cast(uint) comment.length;
         if (isZip64)
-            dataSize += eocd64LocLength + eocd64Length;
+            dataSize += zip64EndOfCentralDirLocatorLength + zip64EndOfCentralDirLength;
 
-        _data = new ubyte[dataSize];
+        _data = uninitializedArray!(ubyte[])(dataSize);
 
         // Populate the data[]
 
@@ -453,7 +687,7 @@ final class ZipArchive
         foreach (ArchiveMember de; directory)
         {
             de.offset = i;
-            _data[i .. i + 4] = cast(ubyte[])"PK\x03\x04";
+            _data[i .. i + 4] = localFileHeaderSignature;
             putUshort(i + 4,  de.extractVersion);
             putUshort(i + 6,  de.flags);
             putUshort(i + 8,  de._compressionMethod);
@@ -463,9 +697,9 @@ final class ZipArchive
             putUint  (i + 22, to!uint(de.expandedSize));
             putUshort(i + 26, cast(ushort) de.name.length);
             putUshort(i + 28, cast(ushort) de.extra.length);
-            i += 30;
+            i += localFileHeaderLength;
 
-            _data[i .. i + de.name.length] = (cast(ubyte[]) de.name)[];
+            _data[i .. i + de.name.length] = (de.name.representation)[];
             i += de.name.length;
             _data[i .. i + de.extra.length] = (cast(ubyte[]) de.extra)[];
             i += de.extra.length;
@@ -475,10 +709,9 @@ final class ZipArchive
 
         // Write directory
         directoryOffset = i;
-        _numEntries = 0;
         foreach (ArchiveMember de; directory)
         {
-            _data[i .. i + 4] = cast(ubyte[])"PK\x01\x02";
+            _data[i .. i + 4] = centralFileHeaderSignature;
             putUshort(i + 4,  de._madeVersion);
             putUshort(i + 6,  de.extractVersion);
             putUshort(i + 8,  de.flags);
@@ -490,180 +723,175 @@ final class ZipArchive
             putUshort(i + 28, cast(ushort) de.name.length);
             putUshort(i + 30, cast(ushort) de.extra.length);
             putUshort(i + 32, cast(ushort) de.comment.length);
-            putUshort(i + 34, de.diskNumber);
+            putUshort(i + 34, cast(ushort) 0);
             putUshort(i + 36, de.internalAttributes);
             putUint  (i + 38, de._externalAttributes);
             putUint  (i + 42, de.offset);
-            i += 46;
+            i += centralFileHeaderLength;
 
-            _data[i .. i + de.name.length] = (cast(ubyte[]) de.name)[];
+            _data[i .. i + de.name.length] = (de.name.representation)[];
             i += de.name.length;
             _data[i .. i + de.extra.length] = (cast(ubyte[]) de.extra)[];
             i += de.extra.length;
-            _data[i .. i + de.comment.length] = (cast(ubyte[]) de.comment)[];
+            _data[i .. i + de.comment.length] = (de.comment.representation)[];
             i += de.comment.length;
-            _numEntries++;
         }
-        _totalEntries = numEntries;
 
         if (isZip64)
         {
             // Write zip64 end of central directory record
             uint eocd64Offset = i;
-            _data[i .. i + 4] = cast(ubyte[])"PK\x06\x06";
-            putUlong (i + 4,  eocd64Length - 12);
+            _data[i .. i + 4] = zip64EndOfCentralDirSignature;
+            putUlong (i + 4,  zip64EndOfCentralDirLength - 12);
             putUshort(i + 12, zip64ExtractVersion);
             putUshort(i + 14, zip64ExtractVersion);
-            putUint  (i + 16, diskNumber);
-            putUint  (i + 20, diskStartDir);
-            putUlong (i + 24, numEntries);
-            putUlong (i + 32, totalEntries);
+            putUint  (i + 16, cast(ushort) 0);
+            putUint  (i + 20, cast(ushort) 0);
+            putUlong (i + 24, directory.length);
+            putUlong (i + 32, directory.length);
             putUlong (i + 40, directorySize);
             putUlong (i + 48, directoryOffset);
-            i += eocd64Length;
+            i += zip64EndOfCentralDirLength;
 
             // Write zip64 end of central directory record locator
-            _data[i .. i + 4] = cast(ubyte[])"PK\x06\x07";
-            putUint  (i + 4,  diskNumber);
+            _data[i .. i + 4] = zip64EndOfCentralDirLocatorSignature;
+            putUint  (i + 4,  cast(ushort) 0);
             putUlong (i + 8,  eocd64Offset);
             putUint  (i + 16, 1);
-            i += eocd64LocLength;
+            i += zip64EndOfCentralDirLocatorLength;
         }
 
         // Write end record
-        endrecOffset = i;
-        _data[i .. i + 4] = cast(ubyte[])"PK\x05\x06";
-        putUshort(i + 4,  cast(ushort) diskNumber);
-        putUshort(i + 6,  cast(ushort) diskStartDir);
-        putUshort(i + 8,  (numEntries > ushort.max ? ushort.max : cast(ushort) numEntries));
+        _data[i .. i + 4] = endOfCentralDirSignature;
+        putUshort(i + 4,  cast(ushort) 0);
+        putUshort(i + 6,  cast(ushort) 0);
+        putUshort(i + 8,  (totalEntries > ushort.max ? ushort.max : cast(ushort) totalEntries));
         putUshort(i + 10, (totalEntries > ushort.max ? ushort.max : cast(ushort) totalEntries));
         putUint  (i + 12, directorySize);
         putUint  (i + 16, directoryOffset);
         putUshort(i + 20, cast(ushort) comment.length);
-        i += 22;
+        i += endOfCentralDirLength;
 
         // Write archive comment
-        assert(i + comment.length == data.length);
-        _data[i .. data.length] = (cast(ubyte[]) comment)[];
+        assert(i + comment.length == data.length, "Writing the archive comment failed.");
+        _data[i .. data.length] = (comment.representation)[];
 
         return cast(void[]) data;
     }
 
+    @safe pure unittest
+    {
+        import std.exception : assertNotThrown;
+
+        ZipArchive zip = new ZipArchive();
+        zip.comment = "A";
+        assertNotThrown(zip.build());
+    }
+
+    @safe pure unittest
+    {
+        import std.range : repeat, array;
+        import std.exception : assertThrown;
+
+        ZipArchive zip = new ZipArchive();
+        zip.comment = 'A'.repeat(70_000).array;
+        assertThrown!ZipException(zip.build());
+    }
+
     /* ============ Reading an existing archive =================== */
 
     /**
      * Constructor to use when reading an existing archive.
      *
-     * Fills in the properties data[], diskNumber, diskStartDir, numEntries,
-     * totalEntries, comment[], and directory[].
+     * Fills in the properties data[], totalEntries, comment[], and directory[].
      * For each ArchiveMember, fills in
      * properties madeVersion, extractVersion, flags, compressionMethod, time,
-     * crc32, compressedSize, expandedSize, compressedData[], diskNumber,
+     * crc32, compressedSize, expandedSize, compressedData[],
      * internalAttributes, externalAttributes, name[], extra[], comment[].
      * Use expand() to get the expanded data for each ArchiveMember.
      *
      * Params:
-     *  buffer = the entire contents of the archive.
+     *     buffer = The entire contents of the archive.
+     *
+     * Throws: ZipException when the archive was invalid or when malware was detected.
      */
-
     this(void[] buffer)
-    {   uint iend;
-        uint i;
-        int endcommentlength;
-        uint directorySize;
-        uint directoryOffset;
-
+    {
         this._data = cast(ubyte[]) buffer;
 
-        if (data.length > uint.max - 2)
-            throw new ZipException("zip files bigger than 4 GB are unsupported");
+        enforce!ZipException(data.length <= uint.max - 2, "zip files bigger than 4 GB are unsupported");
+
+        _segs = [Segment(0, cast(uint) data.length)];
+
+        uint i = findEndOfCentralDirRecord();
 
-        // Find 'end record index' by searching backwards for signature
-        iend = (data.length > 66_000 ? to!uint(data.length - 66_000) : 0);
-        for (i = to!uint(data.length) - 22; 1; i--)
+        int endCommentLength = getUshort(i + 20);
+        comment = cast(string)(_data[i + endOfCentralDirLength .. i + endOfCentralDirLength + endCommentLength]);
+
+        // end of central dir record
+        removeSegment(i, i + endOfCentralDirLength + endCommentLength);
+
+        uint k = i - zip64EndOfCentralDirLocatorLength;
+        if (k < i && _data[k .. k + 4] == zip64EndOfCentralDirLocatorSignature)
         {
-            if (i < iend || i >= data.length)
-                throw new ZipException("no end record");
+            _isZip64 = true;
+            i = k;
 
-            if (_data[i .. i + 4] == cast(ubyte[])"PK\x05\x06")
-            {
-                endcommentlength = getUshort(i + 20);
-                if (i + 22 + endcommentlength > data.length
-                        || i + 22 + endcommentlength < i)
-                    continue;
-                comment = cast(string)(_data[i + 22 .. i + 22 + endcommentlength]);
-                endrecOffset = i;
-
-                uint k = i - eocd64LocLength;
-                if (k < i && _data[k .. k + 4] == cast(ubyte[])"PK\x06\x07")
-                {
-                    _isZip64 = true;
-                    i = k;
-                }
-
-                break;
-            }
+            // zip64 end of central dir record locator
+            removeSegment(k, k + zip64EndOfCentralDirLocatorLength);
         }
 
+        uint directorySize;
+        uint directoryOffset;
+        uint directoryCount;
+
         if (isZip64)
         {
             // Read Zip64 record data
             ulong eocdOffset = getUlong(i + 8);
-            if (eocdOffset + eocd64Length > _data.length)
-                throw new ZipException("corrupted directory");
+            enforce!ZipException(eocdOffset + zip64EndOfCentralDirLength <= _data.length,
+                                 "corrupted directory");
 
             i = to!uint(eocdOffset);
-            if (_data[i .. i + 4] != cast(ubyte[])"PK\x06\x06")
-                throw new ZipException("invalid Zip EOCD64 signature");
+            enforce!ZipException(_data[i .. i + 4] == zip64EndOfCentralDirSignature,
+                                 "invalid Zip EOCD64 signature");
 
             ulong eocd64Size = getUlong(i + 4);
-            if (eocd64Size + i - 12 > data.length)
-                throw new ZipException("invalid Zip EOCD64 size");
+            enforce!ZipException(eocd64Size + i - 12 <= data.length,
+                                 "invalid Zip EOCD64 size");
 
-            _diskNumber = getUint(i + 16);
-            _diskStartDir = getUint(i + 20);
+            // zip64 end of central dir record
+            removeSegment(i, cast(uint) (i + 12 + eocd64Size));
 
             ulong numEntriesUlong = getUlong(i + 24);
             ulong totalEntriesUlong = getUlong(i + 32);
             ulong directorySizeUlong = getUlong(i + 40);
             ulong directoryOffsetUlong = getUlong(i + 48);
 
-            if (numEntriesUlong > uint.max)
-                throw new ZipException("supposedly more than 4294967296 files in archive");
+            enforce!ZipException(numEntriesUlong <= uint.max,
+                                 "supposedly more than 4294967296 files in archive");
 
-            if (numEntriesUlong != totalEntriesUlong)
-                throw new ZipException("multiple disk zips not supported");
+            enforce!ZipException(numEntriesUlong == totalEntriesUlong,
+                                 "multiple disk zips not supported");
 
-            if (directorySizeUlong > i || directoryOffsetUlong > i
-                    || directorySizeUlong + directoryOffsetUlong > i)
-                throw new ZipException("corrupted directory");
+            enforce!ZipException(directorySizeUlong <= i && directoryOffsetUlong <= i
+                                 && directorySizeUlong + directoryOffsetUlong <= i,
+                                 "corrupted directory");
 
-            _numEntries = to!uint(numEntriesUlong);
-            _totalEntries = to!uint(totalEntriesUlong);
+            directoryCount = to!uint(totalEntriesUlong);
             directorySize = to!uint(directorySizeUlong);
             directoryOffset = to!uint(directoryOffsetUlong);
         }
         else
         {
-        // Read end record data
-        _diskNumber = getUshort(i + 4);
-        _diskStartDir = getUshort(i + 6);
-
-        _numEntries = getUshort(i + 8);
-        _totalEntries = getUshort(i + 10);
-
-        if (numEntries != totalEntries)
-            throw new ZipException("multiple disk zips not supported");
-
-        directorySize = getUint(i + 12);
-        directoryOffset = getUint(i + 16);
-
-        if (directoryOffset + directorySize > i)
-            throw new ZipException("corrupted directory");
+            // Read end record data
+            directoryCount = getUshort(i + 10);
+            directorySize = getUint(i + 12);
+            directoryOffset = getUint(i + 16);
         }
 
         i = directoryOffset;
-        for (int n = 0; n < numEntries; n++)
+        for (int n = 0; n < directoryCount; n++)
         {
             /* The format of an entry is:
              *  'PK' 1, 2
@@ -677,8 +905,8 @@ final class ZipArchive
             uint extralen;
             uint commentlen;
 
-            if (_data[i .. i + 4] != cast(ubyte[])"PK\x01\x02")
-                throw new ZipException("invalid directory entry 1");
+            enforce!ZipException(_data[i .. i + 4] == centralFileHeaderSignature,
+                                 "wrong central file header signature found");
             ArchiveMember de = new ArchiveMember();
             de._index = n;
             de._madeVersion = getUshort(i + 4);
@@ -692,14 +920,17 @@ final class ZipArchive
             namelen = getUshort(i + 28);
             extralen = getUshort(i + 30);
             commentlen = getUshort(i + 32);
-            de._diskNumber = getUshort(i + 34);
             de.internalAttributes = getUshort(i + 36);
             de._externalAttributes = getUint(i + 38);
             de.offset = getUint(i + 42);
-            i += 46;
 
-            if (i + namelen + extralen + commentlen > directoryOffset + directorySize)
-                throw new ZipException("invalid directory entry 2");
+            // central file header
+            removeSegment(i, i + centralFileHeaderLength + namelen + extralen + commentlen);
+
+            i += centralFileHeaderLength;
+
+            enforce!ZipException(i + namelen + extralen + commentlen <= directoryOffset + directorySize,
+                                 "invalid field lengths in file header found");
 
             de.name = cast(string)(_data[i .. i + namelen]);
             i += namelen;
@@ -708,31 +939,288 @@ final class ZipArchive
             de.comment = cast(string)(_data[i .. i + commentlen]);
             i += commentlen;
 
-            immutable uint dataOffset = de.offset + 30 + namelen + extralen;
-            if (dataOffset + de.compressedSize > endrecOffset)
-                throw new ZipException("Invalid directory entry offset or size.");
+            auto localFileHeaderNamelen = getUshort(de.offset + 26);
+            auto localFileHeaderExtralen = getUshort(de.offset + 28);
+
+            // file data
+            removeSegment(de.offset, de.offset + localFileHeaderLength + localFileHeaderNamelen
+                                     + localFileHeaderExtralen + de._compressedSize);
+
+            immutable uint dataOffset = de.offset + localFileHeaderLength
+                                        + localFileHeaderNamelen + localFileHeaderExtralen;
             de._compressedData = _data[dataOffset .. dataOffset + de.compressedSize];
 
             _directory[de.name] = de;
+        }
+
+        enforce!ZipException(i == directoryOffset + directorySize, "invalid directory entry 3");
+    }
+
+    @system unittest
+    {
+        import std.exception : assertThrown;
+
+        // contains wrong directorySize (extra byte 0xff)
+        auto file =
+            "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~
+            "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~
+            "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
+            "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
+            "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
+            "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
+            "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
+            "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
+            "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\xff\x50\x4b\x05"~
+            "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4b\x00\x00\x00\x43\x00\x00"~
+            "\x00\x00\x00";
+
+        assertThrown!ZipException(new ZipArchive(cast(void[]) file));
+    }
+
+    @system unittest
+    {
+        import std.exception : assertThrown;
+
+        // wrong eocdOffset
+        auto file =
+            "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
+            "\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
+            "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
+            "\x00\x00";
+
+        assertThrown!ZipException(new ZipArchive(cast(void[]) file));
+    }
+
+    @system unittest
+    {
+        import std.exception : assertThrown;
+
+        // wrong signature of zip64 end of central directory
+        auto file =
+            "\x50\x4b\x06\x07\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
+            "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
+            "\x00\x00";
+
+        assertThrown!ZipException(new ZipArchive(cast(void[]) file));
+    }
+
+    @system unittest
+    {
+        import std.exception : assertThrown;
+
+        // wrong size of zip64 end of central directory
+        auto file =
+            "\x50\x4b\x06\x06\xff\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
+            "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
+            "\x00\x00";
+
+        assertThrown!ZipException(new ZipArchive(cast(void[]) file));
+    }
+
+    @system unittest
+    {
+        import std.exception : assertThrown;
+
+        // too many entries in zip64 end of central directory
+        auto file =
+            "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
+            "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
+            "\x00\x00";
+
+        assertThrown!ZipException(new ZipArchive(cast(void[]) file));
+    }
+
+    @system unittest
+    {
+        import std.exception : assertThrown;
+
+        // zip64: numEntries and totalEntries differ
+        auto file =
+            "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
+            "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
+            "\x00\x00";
+
+        assertThrown!ZipException(new ZipArchive(cast(void[]) file));
+    }
+
+    @system unittest
+    {
+        import std.exception : assertThrown;
+
+        // zip64: directorySize too large
+        auto file =
+            "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
+            "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
+            "\x00\x00";
+
+        assertThrown!ZipException(new ZipArchive(cast(void[]) file));
+
+        // zip64: directoryOffset too large
+        file =
+            "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
+            "\xff\xff\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
+            "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
+            "\x00\x00";
+
+        assertThrown!ZipException(new ZipArchive(cast(void[]) file));
+
+        // zip64: directorySize + directoryOffset too large
+        // we need to add a useless byte at the beginning to avoid that one of the other two checks allready fires
+        file =
+            "\x00\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"~
+            "\x01\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
+            "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
+            "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
+            "\x00\x00";
+
+        assertThrown!ZipException(new ZipArchive(cast(void[]) file));
+    }
+
+    @system unittest
+    {
+        import std.exception : assertThrown;
+
+        // wrong central file header signature
+        auto file =
+            "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~
+            "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~
+            "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
+            "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
+            "\x6c\x6c\x6f\x50\x4b\x01\x03\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
+            "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
+            "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
+            "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
+            "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~
+            "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~
+            "\x00\x00\x00";
+
+        assertThrown!ZipException(new ZipArchive(cast(void[]) file));
+    }
+
+    @system unittest
+    {
+        import std.exception : assertThrown;
+
+        // invalid field lengths in file header
+        auto file =
+            "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~
+            "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~
+            "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
+            "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
+            "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
+            "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
+            "\x00\x18\x00\x01\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
+            "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
+            "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\xff\x50\x4b\x05"~
+            "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~
+            "\x00\x00\x00";
+
+        assertThrown!ZipException(new ZipArchive(cast(void[]) file));
+    }
+
+    private uint findEndOfCentralDirRecord()
+    {
+        // end of central dir record can be followed by a comment of up to 2^^16-1 bytes
+        // therefore we have to scan 2^^16 positions
+
+        uint endrecOffset = to!uint(data.length);
+        foreach (i; 0 .. 2 ^^ 16)
+        {
+            if (endOfCentralDirLength + i > data.length) break;
+            uint start = to!uint(data.length) - endOfCentralDirLength - i;
+
+            if (data[start .. start + 4] != endOfCentralDirSignature) continue;
+
+            auto numberOfThisDisc = getUshort(start + 4);
+            if (numberOfThisDisc != 0) continue; // no support for multiple volumes yet
+
+            auto numberOfStartOfCentralDirectory = getUshort(start + 6);
+            if (numberOfStartOfCentralDirectory != 0) continue; // dito
+
+            if (numberOfThisDisc < numberOfStartOfCentralDirectory) continue;
+
+            uint k = start - zip64EndOfCentralDirLocatorLength;
+            auto maybeZip64 = k < start && _data[k .. k + 4] == zip64EndOfCentralDirLocatorSignature;
+
+            auto totalNumberOfEntriesOnThisDisk = getUshort(start + 8);
+            auto totalNumberOfEntriesInCentralDir = getUshort(start + 10);
+
+            if (totalNumberOfEntriesOnThisDisk > totalNumberOfEntriesInCentralDir &&
+               (!maybeZip64 || totalNumberOfEntriesOnThisDisk < 0xffff)) continue;
+
+            auto sizeOfCentralDirectory = getUint(start + 12);
+            if (sizeOfCentralDirectory > start &&
+               (!maybeZip64 || sizeOfCentralDirectory < 0xffff)) continue;
 
+            auto offsetOfCentralDirectory = getUint(start + 16);
+            if (offsetOfCentralDirectory > start - sizeOfCentralDirectory &&
+               (!maybeZip64 || offsetOfCentralDirectory < 0xffff)) continue;
+
+            auto zipfileCommentLength = getUshort(start + 20);
+            if (start + zipfileCommentLength + endOfCentralDirLength != data.length) continue;
+
+            enforce!ZipException(endrecOffset == to!uint(data.length),
+                                 "found more than one valid 'end of central dir record'");
+
+            endrecOffset = start;
         }
-        if (i != directoryOffset + directorySize)
-            throw new ZipException("invalid directory entry 3");
+
+        enforce!ZipException(endrecOffset != to!uint(data.length),
+                             "found no valid 'end of central dir record'");
+
+        return endrecOffset;
     }
 
-    /*****
-     * Decompress the contents of archive member de and return the expanded
-     * data.
+    /**
+     * Decompress the contents of a member.
      *
      * Fills in properties extractVersion, flags, compressionMethod, time,
      * crc32, compressedSize, expandedSize, expandedData[], name[], extra[].
+     *
+     * Params:
+     *     de = Member to be decompressed.
+     *
+     * Returns: The expanded data.
+     *
+     * Throws: ZipException when the entry is invalid or the compression method is not supported.
      */
     ubyte[] expand(ArchiveMember de)
-    {   uint namelen;
+    {
+        import std.string : representation;
+
+        uint namelen;
         uint extralen;
 
-        if (_data[de.offset .. de.offset + 4] != cast(ubyte[])"PK\x03\x04")
-            throw new ZipException("invalid directory entry 4");
+        enforce!ZipException(_data[de.offset .. de.offset + 4] == localFileHeaderSignature,
+                             "wrong local file header signature found");
 
         // These values should match what is in the main zip archive directory
         de._extractVersion = getUshort(de.offset + 4);
@@ -753,16 +1241,7 @@ final class ZipArchive
             printf("\t\textralen = %d\n", extralen);
         }
 
-        if (de.flags & 1)
-            throw new ZipException("encryption not supported");
-
-        int i;
-        i = de.offset + 30 + namelen + extralen;
-        if (i + de.compressedSize > endrecOffset)
-            throw new ZipException("invalid directory entry 5");
-
-        de._compressedData = _data[i .. i + de.compressedSize];
-        debug(print) arrayPrint(de.compressedData);
+        enforce!ZipException((de.flags & 1) == 0, "encryption not supported");
 
         switch (de.compressionMethod)
         {
@@ -783,40 +1262,183 @@ final class ZipArchive
         }
     }
 
+    @system unittest
+    {
+        import std.exception : assertThrown;
+
+        // check for correct local file header signature
+        auto file =
+            "\x50\x4b\x04\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~
+            "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~
+            "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
+            "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
+            "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
+            "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
+            "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
+            "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
+            "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~
+            "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~
+            "\x00\x00\x00";
+
+        auto za = new ZipArchive(cast(void[]) file);
+
+        assertThrown!ZipException(za.expand(za._directory["file"]));
+    }
+
+    @system unittest
+    {
+        import std.exception : assertThrown;
+
+        // check for encryption flag
+        auto file =
+            "\x50\x4b\x03\x04\x0a\x00\x01\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~
+            "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~
+            "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
+            "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
+            "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
+            "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
+            "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
+            "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
+            "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~
+            "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~
+            "\x00\x00\x00";
+
+        auto za = new ZipArchive(cast(void[]) file);
+
+        assertThrown!ZipException(za.expand(za._directory["file"]));
+    }
+
+    @system unittest
+    {
+        import std.exception : assertThrown;
+
+        // check for invalid compression method
+        auto file =
+            "\x50\x4b\x03\x04\x0a\x00\x00\x00\x03\x00\x8f\x72\x4a\x4f\x86\xa6"~
+            "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~
+            "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
+            "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
+            "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
+            "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
+            "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
+            "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
+            "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~
+            "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~
+            "\x00\x00\x00";
+
+        auto za = new ZipArchive(cast(void[]) file);
+
+        assertThrown!ZipException(za.expand(za._directory["file"]));
+    }
+
     /* ============ Utility =================== */
 
-    @safe ushort getUshort(int i)
+    @safe @nogc pure nothrow ushort getUshort(uint i)
     {
         ubyte[2] result = data[i .. i + 2];
         return littleEndianToNative!ushort(result);
     }
 
-    @safe uint getUint(int i)
+    @safe @nogc pure nothrow uint getUint(uint i)
     {
         ubyte[4] result = data[i .. i + 4];
         return littleEndianToNative!uint(result);
     }
 
-    @safe ulong getUlong(int i)
+    @safe @nogc pure nothrow ulong getUlong(uint i)
     {
         ubyte[8] result = data[i .. i + 8];
         return littleEndianToNative!ulong(result);
     }
 
-    @safe void putUshort(int i, ushort us)
+    @safe @nogc pure nothrow void putUshort(uint i, ushort us)
     {
         data[i .. i + 2] = nativeToLittleEndian(us);
     }
 
-    @safe void putUint(int i, uint ui)
+    @safe @nogc pure nothrow void putUint(uint i, uint ui)
     {
         data[i .. i + 4] = nativeToLittleEndian(ui);
     }
 
-    @safe void putUlong(int i, ulong ul)
+    @safe @nogc pure nothrow void putUlong(uint i, ulong ul)
     {
         data[i .. i + 8] = nativeToLittleEndian(ul);
     }
+
+    /* ============== for detecting overlaps =============== */
+
+private:
+
+    // defines a segment of the zip file, including start, excluding end
+    struct Segment
+    {
+        uint start;
+        uint end;
+    }
+
+    // removes Segment start .. end from _segs
+    // throws zipException if start .. end is not completely available in _segs;
+    void removeSegment(uint start, uint end) pure @safe
+    in (start < end, "segment invalid")
+    {
+        auto found = false;
+        size_t pos;
+        foreach (i,seg;_segs)
+            if (seg.start <= start && seg.end >= end
+                && (!found || seg.start > _segs[pos].start))
+            {
+                found = true;
+                pos = i;
+            }
+
+        enforce!ZipException(found, "overlapping data detected");
+
+        if (start>_segs[pos].start)
+            _segs ~= Segment(_segs[pos].start, start);
+        if (end<_segs[pos].end)
+            _segs ~= Segment(end, _segs[pos].end);
+        _segs = _segs[0 .. pos] ~ _segs[pos + 1 .. $];
+    }
+
+    pure @safe unittest
+    {
+        with (new ZipArchive())
+        {
+            _segs = [Segment(0,100)];
+            removeSegment(10,20);
+            assert(_segs == [Segment(0,10),Segment(20,100)]);
+
+            _segs = [Segment(0,100)];
+            removeSegment(0,20);
+            assert(_segs == [Segment(20,100)]);
+
+            _segs = [Segment(0,100)];
+            removeSegment(10,100);
+            assert(_segs == [Segment(0,10)]);
+
+            _segs = [Segment(0,100), Segment(200,300), Segment(400,500)];
+            removeSegment(220,230);
+            assert(_segs == [Segment(0,100),Segment(400,500),Segment(200,220),Segment(230,300)]);
+
+            _segs = [Segment(200,300), Segment(0,100), Segment(400,500)];
+            removeSegment(20,30);
+            assert(_segs == [Segment(200,300),Segment(400,500),Segment(0,20),Segment(30,100)]);
+
+            import std.exception : assertThrown;
+
+            _segs = [Segment(0,100), Segment(200,300), Segment(400,500)];
+            assertThrown(removeSegment(120,230));
+
+            _segs = [Segment(0,100), Segment(200,300), Segment(400,500)];
+            removeSegment(0,100);
+            assertThrown(removeSegment(0,100));
+
+            _segs = [Segment(0,100)];
+            removeSegment(0,100);
+            assertThrown(removeSegment(0,100));
+        }
+    }
 }
 
 debug(print)
@@ -963,10 +1585,135 @@ the quick brown fox jumps over the lazy dog\r
     assert(amAfter.time == am.time);
 }
 
-// Non-Android Posix-only, because we can't rely on the unzip command being
-// available on Android or Windows
-version (Android) {} else
-version (Posix) @system unittest
+@system unittest
+{
+    // invalid format of end of central directory entry
+    import std.exception : assertThrown;
+    assertThrown!ZipException(new ZipArchive(cast(void[]) "\x50\x4B\x05\x06aaaaaaaaaaaaaaaaaaaa"));
+}
+
+@system unittest
+{
+    // minimum (empty) archive should pass
+    auto za = new ZipArchive(cast(void[]) "\x50\x4B\x05\x06\x00\x00\x00\x00\x00\x00\x00"~
+                                          "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
+    assert(za.directory.length == 0);
+
+    // one byte too short or too long should not pass
+    import std.exception : assertThrown;
+    assertThrown!ZipException(new ZipArchive(cast(void[]) "\x50\x4B\x05\x06\x00\x00\x00\x00\x00\x00\x00"~
+                                                          "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"));
+    assertThrown!ZipException(new ZipArchive(cast(void[]) "\x50\x4B\x05\x06\x00\x00\x00\x00\x00\x00\x00"~
+                                                          "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"));
+}
+
+@system unittest
+{
+    // https://issues.dlang.org/show_bug.cgi?id=20239
+    // chameleon file, containing two valid end of central directory entries
+    auto file =
+        "\x50\x4B\x03\x04\x0A\x00\x00\x00\x00\x00\x89\x36\x39\x4F\x04\x6A\xB3\xA3\x01\x00"~
+        "\x00\x00\x01\x00\x00\x00\x0D\x00\x1C\x00\x62\x65\x73\x74\x5F\x6C\x61\x6E\x67\x75"~
+        "\x61\x67\x65\x55\x54\x09\x00\x03\x82\xF2\x8A\x5D\x82\xF2\x8A\x5D\x75\x78\x0B\x00"~
+        "\x01\x04\xEB\x03\x00\x00\x04\xEB\x03\x00\x00\x44\x50\x4B\x01\x02\x1E\x03\x0A\x00"~
+        "\x00\x00\x00\x00\x89\x36\x39\x4F\x04\x6A\xB3\xA3\x01\x00\x00\x00\x01\x00\x00\x00"~
+        "\x0D\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xB0\x81\x00\x00\x00\x00\x62\x65"~
+        "\x73\x74\x5F\x6C\x61\x6E\x67\x75\x61\x67\x65\x55\x54\x05\x00\x03\x82\xF2\x8A\x5D"~
+        "\x75\x78\x0B\x00\x01\x04\xEB\x03\x00\x00\x04\xEB\x03\x00\x00\x50\x4B\x05\x06\x00"~
+        "\x00\x00\x00\x01\x00\x01\x00\x53\x00\x00\x00\x48\x00\x00\x00\xB7\x00\x50\x4B\x03"~
+        "\x04\x0A\x00\x00\x00\x00\x00\x94\x36\x39\x4F\xD7\xCB\x3B\x55\x07\x00\x00\x00\x07"~
+        "\x00\x00\x00\x0D\x00\x1C\x00\x62\x65\x73\x74\x5F\x6C\x61\x6E\x67\x75\x61\x67\x65"~
+        "\x55\x54\x09\x00\x03\x97\xF2\x8A\x5D\x8C\xF2\x8A\x5D\x75\x78\x0B\x00\x01\x04\xEB"~
+        "\x03\x00\x00\x04\xEB\x03\x00\x00\x46\x4F\x52\x54\x52\x41\x4E\x50\x4B\x01\x02\x1E"~
+        "\x03\x0A\x00\x00\x00\x00\x00\x94\x36\x39\x4F\xD7\xCB\x3B\x55\x07\x00\x00\x00\x07"~
+        "\x00\x00\x00\x0D\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xB0\x81\xB1\x00\x00"~
+        "\x00\x62\x65\x73\x74\x5F\x6C\x61\x6E\x67\x75\x61\x67\x65\x55\x54\x05\x00\x03\x97"~
+        "\xF2\x8A\x5D\x75\x78\x0B\x00\x01\x04\xEB\x03\x00\x00\x04\xEB\x03\x00\x00\x50\x4B"~
+        "\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00\x53\x00\x00\x00\xFF\x00\x00\x00\x00\x00";
+
+    import std.exception : assertThrown;
+    assertThrown!ZipException(new ZipArchive(cast(void[]) file));
+}
+
+@system unittest
+{
+    // https://issues.dlang.org/show_bug.cgi?id=20287
+    // check for correct compressed data
+    auto file =
+        "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~
+        "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~
+        "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
+        "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
+        "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
+        "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
+        "\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
+        "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
+        "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~
+        "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~
+        "\x00\x00\x00";
+
+    auto za = new ZipArchive(cast(void[]) file);
+    assert(za.directory["file"].compressedData == [104, 101, 108, 108, 111]);
+}
+
+// https://issues.dlang.org/show_bug.cgi?id=20027
+@system unittest
+{
+    // central file header overlaps end of central directory
+    auto file =
+        // lfh
+        "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~
+        "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1c\x00\x66\x69"~
+        "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
+        "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
+        "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
+        "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
+        "\x00\x18\x00\x04\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
+        "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
+        "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~
+        "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~
+        "\x00\x00\x00";
+
+    import std.exception : assertThrown;
+    assertThrown!ZipException(new ZipArchive(cast(void[]) file));
+
+    // local file header and file data overlap second local file header and file data
+    file =
+        "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x8f\x72\x4a\x4f\x86\xa6"~
+        "\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04\x00\x1e\x00\x66\x69"~
+        "\x6c\x65\x55\x54\x09\x00\x03\x0d\x22\x9f\x5d\x12\x22\x9f\x5d\x75"~
+        "\x78\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x68\x65"~
+        "\x6c\x6c\x6f\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\x8f"~
+        "\x72\x4a\x4f\x86\xa6\x10\x36\x05\x00\x00\x00\x05\x00\x00\x00\x04"~
+        "\x00\x18\x00\x04\x00\x00\x00\x01\x00\x00\x00\xb0\x81\x00\x00\x00"~
+        "\x00\x66\x69\x6c\x65\x55\x54\x05\x00\x03\x0d\x22\x9f\x5d\x75\x78"~
+        "\x0b\x00\x01\x04\xf0\x03\x00\x00\x04\xf0\x03\x00\x00\x50\x4b\x05"~
+        "\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4a\x00\x00\x00\x43\x00\x00"~
+        "\x00\x00\x00";
+
+    assertThrown!ZipException(new ZipArchive(cast(void[]) file));
+}
+
+@system unittest
+{
+    // https://issues.dlang.org/show_bug.cgi?id=20295
+    // zip64 with 0xff bytes in end of central dir record do not work
+    // minimum (empty zip64) archive should pass
+    auto file =
+        "\x50\x4b\x06\x06\x2c\x00\x00\x00\x00\x00\x00\x00\x1e\x03\x2d\x00"~
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"~
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x06\x07\x00\x00\x00\x00"~
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\x4B\x05\x06"~
+        "\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"~
+        "\x00\x00";
+
+    auto za = new ZipArchive(cast(void[]) file);
+    assert(za.directory.length == 0);
+}
+
+version (HasUnzip)
+@system unittest
 {
     import std.datetime, std.file, std.format, std.path, std.process, std.stdio;