z0b's realm

ZipReader and ZipWriter

These two small C++11 classes handle reading and writing ZIP files. They're designed to be simple and straightforward to be used. They don't support all the features of the ZIP format, but they support enough to be useful. ZipReader supports the Zip64 format and can read archives larger than 4 GiB (yes, it really can do this, I've tested it). ZipWriter currently does not support Zip64, but that might change in the future. ZipReader was created originally to replace the otherwise good, but overly complex PhysFS; and ZipWriter is "merely" a companion for it.

Both classes are standalone (the only external dependency is zlib) and don't need each other. They can be used simulatenously in the same project, if needed.

Both classes are designed to be modified. While they're perfectly usable as-is, it is rare that you'd use them without modifying them. I'm expecting that you tear them apart and modify them to suit your needs. If they work out-of-box box for you, that's good, but you're supposed to modify them!

Both are released under the zlib/libpng license.

Features

What they do?

ZipReader

  • Open normal and Zip64 archives
  • Get a list of files in the archive, including names, offsets, timestamp, CRC-32 and file sizes
  • Extract files from the archive
  • Get the archive comment, if it exists
  • Get read-only access to the file extra fields

ZipWriter

  • Create ordinary ZIP archives
  • Add files and directories into it
  • Compresses the files using the DEFLATE algorithm
  • Supports file and archive comments
  • Generated ZIP files have been tested with 7-Zip, WinRAR, WinZip, Linux command line unzip and even the old DOS PkUnZip 2.04g. (And with ZipReader, of course.)

What they don't do yet, but eventually might

  • ZipReader supports Zip64, but trying to extract files larger than 2 GiB from the archive will most likely fail. This is because the output is stored in std::vector. Changing that and the fread() call will fix this.
  • Zip64 support in ZipWriter. It's not hard, but it still takes a lot of work.
  • Streamed compression/decompression. Like opening a file inside the ZIP with fopen() and then calling fread()/fwrite().
    • Streamed decompression is more or less done. I just need an inspiration to test it some more, clean it up, and integrate it into ZipReader.
    • Streamed compression is in the prototype stage. Needs a lot more work.
  • Better support (both read and write) for the extended fields. These in particular are widely supported by various ZIP tools and would be handy in ZipRead/ZipWrite too:
    • NTFS timestamp support (field type 0x000A)
    • Unix extra field (type 0x000D)

What they'll never do

  • Encryption. The traditional ZIP encryption is too weak and the newer methods are too complex. Use proper tools like PGP/GPG.
  • Other compression methods than DEFLATE.
  • Multi-disk archives. Too much work for too little gain.
  • Handle non-UTF-8 encoded filenames properly. I'm in no authority position, but ZipReader and ZipWriter tend to enforce UTF-8 as much as possible, on purpose. ZipReader does not really care much about the encodings, it just stores the name as-is; but ZipWriter enforces UTF-8 names.

Notes

  • Tested under Windows 7 and several Linux distos
  • Tested compilers are GCC (4.6.x, 4.7.x and 4.9.x) and Clang 3.x. MSVC compatibility is unknown.
  • 64-bit compatibility is unknown, but I see no reason why it wouldn't work.
  • Let me know if you find any bugs! It's difficult to test something like this, because there is no "test suite" available.

Downloads

I eat my own dogfood: these two ZIPs are made with ZipWriter. So if you can't open them (and the downloads aren't corrupted), you know who to blame.

ZipReader 2.1
Filezipreader21.zip
Size18 KiB (17955 bytes)
ReleasedSep 3. 2014
SHA-256b2c014a0031a2e88f39df8563d6d5fe6c5eed185a5e9f214537ab1e362c4fc46

ZipWriter 1.2
Filezipwriter12.zip
Size17 KiB (17112 bytes)
ReleasedAug 11. 2014
SHA-256bb01aa1411289f87b12b4a7b4bb510bd3f91873b679df5ac74daf7d5457cc9b0

Examples

Displaying the contents of a ZIP file

#include <cstdio>
#include <string>
#include <zipreader.h>

int main()
{
    ZipReader zip;

    if (!zip.open("example.zip")) {
        printf("Can't open the archive: %s (%d)\n", zip.errorString(), zip.getError());
        return 1;
    }

    for (const auto &f : zip.getListOfFiles()) {
        const ZipReader::file &e = f.second;

        printf("Name: %s  CRC32: 0x%08X  Csize: %llu  Osize: %llu  Position: %llu %c%c%c%c\n",
            e.name.c_str(), e.crc32, e.csize, e.osize, e.offset,
            e.flags & ZipReader::file::kCompressed ? 'C' : '-',
            e.flags & ZipReader::file::kEncrypted ? 'E' : '-',
            e.flags & ZipReader::file::kUTF8 ? '8' : '-',
            e.flags & ZipReader::file::kUnsupported ? 'U' : '-'
        );
    }

    return 0;
}

Extracting a file from a ZIP

#include <cstdio>
#include <string>
#include <vector>
#include <zipreader.h>

int main()
{
    ZipReader zip;

    if (!zip.open("example.zip")) {
        printf("Can't open the archive: %s (%d)\n", zip.errorString(), zip.getError());
        return 1;
    }

    std::vector<uint8_t> out;

    // assuming the archive has "somefile.txt" in it...
    if (!zip.extractFile("somefile.txt", out))
        printf("Can't extract the file: %s (%d)\n", zip.errorString(), zip.getError());
    else {
        printf("File extracted, %u bytes\n", out.size());
        FILE *f = fopen("output.txt", "wb");

        if (f) {
            fwrite(&out[0], out.size(), 1, f);
            fclose(f);
        } else printf("Output file could not be opened for writing.\n");
    }

    return 0;
}

Creating a ZIP file

#include <cstdio>
#include <string>
#include <zipwriter.h>

int main()
{
    ZipWriter zip;

    if (!zip.open("test.zip")) {
        printf("Can't open the output file: %s (%d)\n", zip.errorString(), zip.getError());
        return 1;
    }

    zip.setArchiveComment("This is the main comment of the ZIP file.");

    zip.addFile("hello.txt");
    zip.addFile("world.txt", "", "This is the comment for the file 'world.txt'.");

    // You don't have to explicitly add directories, but if you want to preserve their
    // timestamps, access rights, etc. then you have no choice but to add them. Future
    // versions might do this automatically.
    zip.addDirectory("backup");

    // let's rename this file on-the-fly, with a comment
    zip.addFile("GLaDOS.cpp", "backup/friendly_ai.cpp", "CAUTION! Do not compile and execute.");

    // These are stored as-is
    zip.addFile("backup/faster_than_light_spaceship_blueprints.pdf");
    zip.addFile("backup/Practical Room-temperature Superconductors.tex");
    zip.addFile("backup/Mr Fusion design.pdf");
    zip.addFile("backup/Generating artificial gravity.odt");

    // Close the archive. You don't have to call this, the destructor takes care of it.
    // But it doesn't hurt to make sure.
    if (!zip.finish()) {
        printf("Could not finish the ZIP file: %s (%d)\n", zip.errorString(), zip.getError());
        return 1;
    }

    return 0;
}