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.
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.
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.
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.
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.
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.
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
.
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.
The following table lists the error codes and their meanings.
Symbolic value | Explanation |
---|---|
kError_None | No errors |
kError_ArchiveNotOpened | You didn't open a ZIP file and called a function that needs it, like addFile() or addDirectory() . |
kError_ArchiveAlreadyOpen | You tried to open another ZIP without finishing the previous first. |
kError_CouldNotOpen | The specified file could not be opened. Check the name and/or permissions. |
kError_EmptyName | The filename was empty. |
kError_UnicodeConversionFailed | Unicode conversion from UTF-8 to UCS-2 failed. This can happen only under Windows. |
kError_DeflateInit2_Failed | A call to deflateInit2() failed, meaning, the file could not be compressed. |
kError_CompressionFailed | File compression failed for some reason. Could be a bug in ZipWriter. |
kError_CouldNotReadFile | ZipWriter could not read from the source file. |
kError_CouldNotWriteFile | ZipWriter could not write to the output file. |
kError_CouldNotGetInfo | ZipWriter could not get the external file information (timestamps, attributes, etc.). |
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.
#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; }
internalName
arguments to addFile()
and addDirectory()
.kError_CompressionFailed
return value.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:
addFile()
so you can display a progress bar and even cancel the compressionaddFile()
that reads data from a memory buffer (or any arbitrary source). This way you could compress anything, not just files.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.
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:
This manual is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0).
You can reach me by email. The email address is on my homepage.