1710 lines · 62.1 KB · D
RawDownload
// Written in the D programming language. /**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 archiveand storing files into a zip archive. These can be mixed though (e.g.read an archive, remove some files, add others and write the newarchive). 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]));     // 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);    }}--- Example for writing files into a zip archive:---import std.file : write;import std.string : representation;import std.zip; void main(){    // 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();     // 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 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) */ /*          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 Windowsversion (Android) {}else version (iOS) {}else version (TVOS) {}else version (WatchOS) {}else version (Posix)    version = HasUnzip; //debug=print; /// Thrown on error.class ZipException : Exception{    import std.exception : basicExceptionCtors;    ///    mixin basicExceptionCtors;} /// Compression method used by `ArchiveMember`.enum CompressionMethod : ushort{    none = 0,   /// No compression, just archiving.    deflate = 8 /// Deflate algorithm. Use zlib library to compress.} /// A single file or directory inside the archive.final class ArchiveMember{    import std.conv : to, octal;    import std.datetime.systime : DosFileTime, SysTime, SysTimeToDosFileTime;     /**     * 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;     /**     * 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;    private uint offset;    private uint _crc32;    private uint _compressedSize;    private uint _expandedSize;    private CompressionMethod _compressionMethod;    private ushort _madeVersion = 20;    private ushort _extractVersion = 20;    private uint _externalAttributes;    private DosFileTime _time;    // by default, no explicit order goes after explicit order    private uint _index = uint.max;     /**     * 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; }     /**     * 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; }     /**     * Size of data of member in uncompressed form.     *     * Returns: Size of uncompressed archive.     */    @property @safe pure nothrow @nogc uint expandedSize() const { return _expandedSize; }     /**     * Data of member in compressed form.     *     * Returns: The file data in compressed form.     */    @property @safe pure nothrow @nogc ubyte[] compressedData() { return _compressedData; }     /**     * 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; }     /// ditto    @property @safe void expandedData(ubyte[] ed)    {        _expandedData = ed;        _expandedSize  = to!uint(_expandedData.length);         // Clean old compressed data, if any        _compressedData.length = 0;        _compressedSize = 0;    }     /**     * 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)    {        version (Posix)        {            _externalAttributes = (attr & 0xFFFF) << 16;            _madeVersion &= 0x00FF;            _madeVersion |= 0x0300; // attributes are in UNIX format        }        else version (Windows)        {            _externalAttributes = attr;            _madeVersion &= 0x00FF; // attributes are in MS-DOS and OS/2 format        }        else        {            static assert(0, "Unimplemented platform");        }    }     version (Posix) @safe unittest    {        auto am = new ArchiveMember();        am.fileAttributes = octal!100644;        assert(am._externalAttributes == octal!100644 << 16);        assert((am._madeVersion & 0xFF00) == 0x0300);    }     /// ditto    @property @nogc nothrow uint fileAttributes() const    {        version (Posix)        {            if ((_madeVersion & 0xFF00) == 0x0300)                return _externalAttributes >> 16;            return 0;        }        else version (Windows)        {            if ((_madeVersion & 0xFF00) == 0x0000)                return _externalAttributes;            return 0;        }        else        {            static assert(0, "Unimplemented platform");        }    }     /**     * 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    {        return _time;    }     /// ditto    @property void time(SysTime time)    {        _time = SysTimeToDosFileTime(time);    }     /// ditto    @property void time(DosFileTime time) @safe pure nothrow @nogc    {        _time = time;    }     /**     * Get or set compression method used for this member.     *     * Params:     *     cm = Compression method.     *     * Returns: Compression method.     *     * See_Also:     *     $(LREF CompressionMethod)     **/    @property @safe @nogc pure nothrow CompressionMethod compressionMethod() const { return _compressionMethod; }     /// ditto    @property @safe pure void compressionMethod(CompressionMethod cm)    {        if (cm == _compressionMethod) return;         enforce!ZipException(_compressedSize == 0, "Can't change compression method for a compressed element");         _compressionMethod = cm;    }     /**     * 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)    {    void print()    {        printf("name = '%.*s'\n", cast(int) name.length, name.ptr);        printf("\tcomment = '%.*s'\n", cast(int) comment.length, comment.ptr);        printf("\tmadeVersion = x%04x\n", _madeVersion);        printf("\textractVersion = x%04x\n", extractVersion);        printf("\tflags = x%04x\n", flags);        printf("\tcompressionMethod = %d\n", compressionMethod);        printf("\ttime = %d\n", time);        printf("\tcrc32 = x%08x\n", crc32);        printf("\texpandedSize = %d\n", expandedSize);        printf("\tcompressedSize = %d\n", compressedSize);        printf("\tinternalAttributes = x%04x\n", internalAttributes);        printf("\texternalAttributes = x%08x\n", externalAttributes);        printf("\tindex = x%08x\n", index);    }    }} @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. */final class ZipArchive{    import std.algorithm.comparison : max;    import std.bitmanip : littleEndianToNative, nativeToLittleEndian;    import std.conv : to;    import std.datetime.systime : DosFileTime; 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 bool _isZip64;    static const ushort zip64ExtractVersion = 45;     private Segment[] _segs;     /**     * 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; }     /**     * Number of ArchiveMembers in the directory.     *     * Returns: The number of files in this archive.     */    @property @safe @nogc pure nothrow uint totalEntries() const { return cast(uint) _directory.length; }     /**     * 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);     * foreach (ArchiveMember am; archive.directory)     * {     *     writefln("member name is '%s'", am.name);     * }     * --------------------     *     * Returns: Associative array with all archive members.     */    @property @safe @nogc pure nothrow ArchiveMember[string] directory() { return _directory; }     private ArchiveMember[string] _directory;     debug (print)    {    @safe void print()    {        printf("\tdiskNumber = %u\n", diskNumber);

Lines 1–500 of 1710.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t