ZipWriter v1.2

ZipWriter is a simple C++11 class for writing Zip archives. It's fully usable out-of-the-box, but for more serious use you will probably want to take it apart and hack it to suit your needs.

Features

Not supported

How to compile it

GCC compilation and library creation example:

g++ -s -O2 -Wall -o zipwriter.o -c zipwriter.cpp -std=c++11
ar rs libzipwriter.a zipwriter.o

Then just place libzipwriter.a and zipwriter.h into the appropriate directories where the compiler can find them.

A word about character encodings

Traditionally ZIP files haven't supported anything but single-byte character sets. There is no Unicode support. The old DOS PkZip/PkUnZip could reliably handle only the local codepage (437, 850, etc.) and Windows archivers used Windows-1252 (I think; I'm not an authoritative expert on this, but this is what I've seen) and converted between these encodings. However, more modern implementations can use UTF-8 encoded filenames, because there is a specific flag for this encoding. It won't help with the file/archive comments which are still expected to be encoded with legacy encodings.

ZipWriter expects that all filenames will be UTF-8 encoded and it will store them as-is, but comments should be either kept in legacy encodings, or use UTF-8 and just hope it works. Info-ZIP project has defined some extra fields for UTF-8 encoded comments, but ZipWriter does not support these fields at the moment.

API

ZipWriter::open

bool open(const std::string &name);

Opens an archive for writing. Fails if you already have a file open in this instance (call finish() or cancel() to get rid of it).

Under Windows, you have to convert wide strings to UTF-8 first.

ZipWriter::addFile

bool addFile(const std::string &name,
             const std::string &internalName = "",
             const std::string &fileComment = "")

Adds a new file into the archive. name is the real name of the file you want to add and internalName is the name used inside the archive (the name can include directory names). This way you can splice together neat-looking archives that in reality contain files from everywhere in your system. Both names must be UTF-8 encoded. If you pass in an empty string for internalName, ZipWriter automatically uses name instead.

The fileComment is the file comment. It can be empty.

Because the ZIP format uses 16-bit integers to store the filename length, the name is limited to 65536 characters. Because the names are UTF-8, that means 65536 bytes.

Note that if you use directory names, you must use forward slashes! Backward slashes (\) won't work correctly. ZipWriter converts backslashes to forward slashes, but you still need to be aware of this.

ZipWriter::addDirectory

bool addDirectory(const std::string &name,
                  const std::string &internalName = "")

Adds a directory into the archive. You don't have to explicitly add directories, because most unzipping tools can recreate them by simply analyzing the filenames. But if you want to preserve the directory date and time stamps, access rights, etc. then you have to add them manually. I'm not sure if you have to add directories before or after the files, but in my tests both have worked.

Like addFile, name is the real directory name and internalName is the name inside the archive. If internalName is empty, name will be automatically used. Directories cannot have comments (at least not yet).

Note that you must use forward slashes in directory names! Backward slashes (\) won't work correctly. ZipWriter converts backslashes to forward slashes, but you still need to be aware of this.

In some future version ZipWriter might keep track of the directories by itself.

ZipWriter::finish

bool finish();

Writes the central directory and closes the output file, finishing the archive. After this, calls to addFile() and addDirectory() will fail. You can reuse the class instance by calling open(). Returns true if ok, false if failed. This will fail if you hadn't opened any archive yet.

You don't have to call this by yourself, the class destructor calls this. Of course it's always good to play safe.

ZipWriter::cancel

void cancel();

"Cancels" the ZIP writing. Closes the archive without writing the central directory. If no files had been added yet, it results in an empty ZIP file. Not all programs can handle empty ZIP files. For example, WinRAR deletes the ZIP when you delete the last file. Use this if you encounter errors and want to bail out.

Some archivers might be able to "repair" the half-finished archive and construct a working central directory for it, so if you cancel ZIP writing, it is advisable that you remove the file too.

ZipWriter::ok

bool ok() const;

Returns true if a Zip archive has been successfully opened for writing. If this returns false, all calls to addFile(), addDirectory() and finish() will fail.

Archive comments

void setArchiveComment(const std::string &comment);
const std::string &getArchiveComment() const;

Set and get the main archive comment. This must be set before calling finish or letting the class go out of scope. The comment is truncated to 65536 characters/bytes.

Error codes and strings

const char *errorString(const int error = -1);
int getError() const;

Retrieve the previous error code. If a function call fails, the numeric code tells you why. Use errorString to convert the number to a textual explanation.

Controlling the compression level

bool compressionEnabled;
int compressionLevel;

Controls the compression. If compressionEnabled is false, then the files added into the archive will not be compressed. compressionLevel controls the compression level. It can be any value between 0 and 9, or Z_NO_COMPRESSION, Z_BEST_SPEED, Z_BEST_COMPRESSION or Z_DEFAULT_COMPRESSION.

These two survive calls to finish() and cancel(). You can change them freely at any time and they take effect on the next file you add into the archive. By default, compressionEnabled is true and compressionLevel is Z_BEST_COMPRESSION.

Miscellaneous

static const char *getVersion();
static const char *getCopyright();

Returns UTF-8 encoded version and copyright strings. These are static functions, so you can call them at any time, even without declaring a class instance first.

Error codes

The following table lists the error codes and their meanings.

Symbolic valueExplanation
kError_NoneNo errors
kError_ArchiveNotOpenedYou didn't open a ZIP file and called a function that needs it, like addFile() or addDirectory().
kError_ArchiveAlreadyOpenYou tried to open another ZIP without finishing the previous first.
kError_CouldNotOpenThe specified file could not be opened. Check the name and/or permissions.
kError_EmptyNameThe filename was empty.
kError_UnicodeConversionFailedUnicode conversion from UTF-8 to UCS-2 failed. This can happen only under Windows.
kError_DeflateInit2_FailedA call to deflateInit2() failed, meaning, the file could not be compressed.
kError_CompressionFailedFile compression failed for some reason. Could be a bug in ZipWriter.
kError_CouldNotReadFileZipWriter could not read from the source file.
kError_CouldNotWriteFileZipWriter could not write to the output file.
kError_CouldNotGetInfoZipWriter could not get the external file information (timestamps, attributes, etc.).

Examples

testzipwriter

The primary example is the testzipwriter.cpp file. Compile and run it. writer_list.txt is an example of the list file the program wants.

Creating a ZIP file

#include <cstdio>
#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 a 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
    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 will go in 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 notes.pdf");
    zip.addFile("backup/Generating artificial gravity.odt");

    if (!zip.finish()) {
        printf("Could not finish the ZIP file: %s (%d)\n", zip.errorString(), zip.getError());
        return 1;
    }

    return 0;
}

Changelog

Future TODO

Things I want to do at some point:

Some more features that I might implement, and you probably will implement by yourself if you seriously use ZipWriter for something:

Of course, anyone who seriously will use ZipWriter will probably implement these on their own, so I don't know if I'll ever make these "officially" part of the library. I know, I've created and used extremely customized versions of this library.

License

ZipWriter is licensed under the zlib/libpng license:

Copyright © 2013-2014 Jarmo Pietiläinen

This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.

Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
  2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
  3. This notice may not be removed or altered from any source distribution.

This manual is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0).

Contact information

You can reach me by email. The email address is on my homepage.