Zipper is a C++11 wrapper around the minizip compression library. Its goal is to bring the power and simplicity of minizip to a more object-oriented/C++ user-friendly library.
This project is the continuation of the original project. The original project was born out of the necessity for a compression library that would be reliable, simple, and flexible. By flexibility we mean supporting all kinds of inputs and outputs, but specifically being able to compress into memory instead of being restricted to file compression only, and using data from memory instead of just files as well.
This current fork repo has been made because the original project was no longer maintained by the original authors and I, Lecrapouille, have some issues due to missing administration rights (needed for CI, branch management, API breaking, etc.).
- Create zip in memory.
- Allow files, vectors, and generic streams as input to zip.
- File mappings for replacement strategies (overwrite if exists or use alternative names from mapping).
- Password-protected zip (EAS).
- Multi-platform.
- Project compiled as static and dynamic libraries.
- Protection against the Zip Slip attack.
- Non-regression tests.
- Zipper currently follows an outdated (and probably vulnerable) version of the https://github.com/Lecrapouille/minizip library: SHA1 0bb5afeb0d3f23149b086ccda7e4fee7d48f4fdf of https://github.com/zlib-ng/minizip-ng which dated from 2017.
- While some fixes have been added, this lib may still be vulnerable to ZipSlip attacks and mitigations should be put in place by Zipper's users.
To download the project and compile it:
git clone https://github.com/lecrapouille/zipper.git --recursive
cd zipper
# Optionally: make download-external-libs
make compile-external-libs
make -j`nproc --all`
# make -j`sysctl -n hw.logicalcpu` for MacOS X
Notes:
- Git cloning needs the recursivity option to install the Makefile helper and third-party libs (
zlib
andminizip
). They are based on fixed SHA1. - Optionally
make download-external-libs
will git clone HEADs of zlib and minizip to theexternal
folder. This replaces git submodules. make compile-external-libs
will compile zlib and minizip but not install them on your operating system. They are compiled as static libraries and merged into this library.
To install C++ header files and compiled libraries, type:
sudo make install
For Linux, you will see a message like:
*** Installing: doc => /usr/share/Zipper/2.0.0/doc
*** Installing: libs => /usr/lib
*** Installing: pkg-config => /usr/lib/pkgconfig
*** Installing: headers => /usr/include/Zipper-2.0.0
*** Installing: Zipper => /usr/include/Zipper-2.0.0
Optionally, you can compile demos present in the folder doc/demos:
make demos -j8
See their README files for their usage.
- In your project, add the needed headers in your C++ files:
#include <Zipper/Unzipper.hpp>
#include <Zipper/Zipper.hpp>
- To compile your project against Zipper, use pkg-config:
g++ -W -Wall --std=c++11 main.cpp -o prog `pkg-config zipper --cflags --libs`
-
For Makefile:
- set
LDFLAGS
topkg-config zipper --libs
- set
CPPFLAGS
topkg-config zipper --cflags
- set
-
For CMake:
include(FindPkgConfig)
find_package(zipper)
There are two classes available: Zipper
and Unzipper
. They behave in the same manner regarding constructors and storage parameters.
#include <Zipper/Zipper.hpp>
using namespace zipper;
- Constructor without password and replace
ziptest.zip
if already present. The new zip archive is empty.
Zipper zipper("ziptest.zip", Zipper::openFlags::Overwrite);
// Or simply: zipper("ziptest.zip");
- Constructor without password and preserve
ziptest.zip
if already present.
Zipper zipper("ziptest.zip", Zipper::openFlags::Append);
- Constructor with password (using AES algorithm) and replace
ziptest.zip
if already present. The new zip archive is empty.
Zipper zipper("ziptest.zip", "my_password", Zipper::openFlags::Overwrite);
// Or simply: zipper("ziptest.zip");
- Constructor with a password and preserve
ziptest.zip
if already present.
Zipper zipper("ziptest.zip", "my_password", Zipper::openFlags::Append);
- Note: all constructors will throw a
std::runtime_error
exception in case of failure.
try
{
Zipper zipper("ziptest.zip", ...);
...
}
catch (std::runtime_error const& e)
{
std::cerr << e.what() << std::endl;
}
Do not forget to call close()
explicitly (it's called implicitly from the destructor) otherwise
the zip will not be well-formed and Unzipper
will fail to open it, for example.
Zipper zipper("ziptest.zip", ...);
...
zipper.close(); // Now Unzipper unzipper("ziptest.zip") can work
After close()
you can reopen the zip archive with open()
. The implicit option
Zipper::openFlags::Append
is to preserve the zip content. To replace the zip
file, use Zipper::openFlags::Overwrite
.
Zipper zipper("ziptest.zip", ...);
...
zipper.close();
...
zipper.open(); // equivalent to zipper.open(Zipper::openFlags::Append);
// or zipper.open(Zipper::openFlags::Overwrite);
...
zipper.close();
In case of success, open()
will return true
; otherwise it will return false
and error()
should be used for getting the std::error_code.
if (!zipper.open())
{
std::cerr << zipper.error().message() << std::endl;
}
The add()
method allows appending files or folders. The Zipper::zipFlags::Better
is set implicitly. Other options are (as the last argument):
- Store only:
Zipper::zipFlags::Store
. - Compress faster, less compressed:
Zipper::zipFlags::Faster
. - Compress intermediate time/compression:
Zipper::zipFlags::Medium
. - Compress better:
Zipper::zipFlags::Better
.
In case of success, the add()
will return true
; otherwise it will return false
and error()
should be used for getting the std::error_code.
- Adding an entire folder to a zip:
Zipper zipper("ziptest.zip");
zipper.add("myFolder/");
zipper.close();
- Adding a file by name:
Zipper zipper("ziptest.zip");
zipper.add("somefile.txt");
zipper.close();
- You can change their name in the archive:
Zipper zipper("ziptest.zip");
zipper.add("somefile.txt", "new_name_in_archive");
zipper.close();
- Create a zip file with 2 files referred by their
std::ifstream
and change their name in the archive:
std::ifstream input1("first_file");
std::ifstream input2("second_file");
Zipper zipper("ziptest.zip");
zipper.add(input1, "Test1");
zipper.add(input2, "Test2");
zipper.close();
- Zipper has security against Zip Slip vulnerability.
zipper.add(input1, "../Test1");
Will always return false
because Test1
would be extracted outside the destination folder. This prevents malicious attacks from replacing your system files:
zipper.add(malicious_passwd, "../../../../../../../../../../../../../../../etc/passwd");
Because in Unix, trying to go outside the root folder /
will stay in the root folder. Example:
cd /
pwd
cd ../../../../../../../../../../../../../../..
pwd
- The Zipper lib forces canonical paths in the archive. The following code works (will return
true
):
zipper.add(input1, "foo/../Test1");
because foo/../Test1
is replaced by Test1
(even if the folder foo
is not present in the
zip archive).
- Creating a zip file using the awesome streams from the boost library that lets us use a vector as a stream:
#include <boost/interprocess/streams/vectorstream.hpp>
...
boost::interprocess::basic_vectorstream<std::vector<char>> input_data(some_vector);
Zipper zipper("ziptest.zip");
zipper.add(input_data, "Test1");
zipper.close();
- Creating a zip in a vector with files:
#include <boost/interprocess/streams/vectorstream.hpp>
...
boost::interprocess::basic_vectorstream<std::vector<char>> zip_in_memory;
std::ifstream input1("some file");
Zipper zipper(zip_in_memory); // You can pass password
zipper.add(input1, "Test1");
zipper.close();
zipper::Unzipper unzipper(zip_in_memory);
unzipper.extractEntry(...
Or:
#include <vector>
std::vector<unsigned char> zip_vect;
std::ifstream input1("some file");
Zipper zipper(zip_vect); // You can pass password
zipper.add(input1, "Test1");
zipper.close();
- Creating a zip in-memory stream with files:
std::stringstream ss;
std::ifstream input1("some file");
Zipper zipper(ss); // You can pass password
zipper.add(input1, "Test1");
zipper.close();
zipper::Unzipper unzipper(ss);
unzipper.extractEntry(...
#include <Zipper/Unzipper.hpp>
using namespace zipper;
- Constructor without password and opening
ziptest.zip
(shall be already present).
Unzipper unzipper("ziptest.zip");
...
unzipper.close();
- Constructor with a password and opening
ziptest.zip
(shall be already present).
Unzipper unzipper("ziptest.zip", "my_password");
...
unzipper.close();
- Note: all constructors will throw a
std::runtime_error
exception in case of failure.
try
{
Unzipper unzipper("ziptest.zip", ...);
...
}
catch (std::runtime_error const& e)
{
std::cerr << e.what() << std::endl;
}
Unzipper unzipper("zipfile.zip");
std::vector<ZipEntry> entries = unzipper.entries();
for (auto& it: unzipper.entries())
{
std::cout << it.name << ": "
<< it.timestamp
<< std::endl;
}
unzipper.close();
Two methods are available: extract()
for the whole archive and extractEntry()
for
a single element in the archive. They return true
in case of success
or false
in case of failure. In case of failure, use unzipper.error();
to get the std::error_code
.
- If you do not care about replacing existing files or folders:
Unzipper unzipper("zipfile.zip");
unzipper.extractAll(true);
unzipper.close();
- If you care about replacing existing files or folders. The method will fail (return
false
) if a file would be replaced.
Unzipper unzipper("zipfile.zip");
unzipper.extractAll(); // equivalent to unzipper.extractAll(false);
unzipper.close();
- Extracting all entries from the zip file to the desired destination:
Unzipper unzipper("zipfile.zip");
unzipper.extractAll("/the/destination/path"); // Fail if a file exists (false argument is implicit)
unzipper.extractAll("/the/destination/path", true); // Replace existing files
unzipper.close();
- Extracting all entries from the zip file using alternative names for existing files on disk:
std::map<std::string, std::string> alternativeNames = { {"Test1", "alternative_name_test1"} };
Unzipper unzipper("zipfile.zip");
unzipper.extractAll(".", alternativeNames);
unzipper.close();
- Extracting a single entry from the zip file:
Unzipper unzipper("zipfile.zip");
unzipper.extractEntry("entry name");
unzipper.close();
Returns true
in case of success or false
in case of failure.
In case of failure, use unzipper.error();
to get the std::error_code
.
- Extracting a single entry from the zip file to destination:
Unzipper unzipper("zipfile.zip");
unzipper.extractEntry("entry name", "/the/destination/path"); // Fail if a file exists (false argument is implicit)
unzipper.extractEntry("entry name", "/the/destination/path", true); // Replace existing file
unzipper.close();
Returns true
in case of success or false
in case of failure.
In case of failure, use unzipper.error();
to get the std::error_code
.
- Extracting a single entry from the zip file to memory:
std::vector<unsigned char> unzipped_entry;
Unzipper unzipper("zipfile.zip");
unzipper.extractEntryToMemory("entry name", unzipped_entry);
unzipper.close();
Returns true
in case of success or false
in case of failure.
In case of failure, use unzipper.error();
to get the std::error_code
.
- Extracting from a vector:
std::vector<unsigned char> zip_vect; // Populated with Zipper zipper(zip_vect);
Unzipper unzipper(zip_vect);
unzipper.extractEntry("Test1");
- Zipper has security against Zip Slip vulnerability: if an entry has a path outside the extraction folder (like
../foo.txt
) it will returnfalse
even if the replace option is set.
Basic unzipper standalone application given as demo in doc/demos.
Depends on:
- googletest framework
- lcov for code coverage
Two ways of running them:
- From the root folder:
make tests -j`nproc --all`
- From the tests/ folder:
cd tests
make coverage -j`nproc --all`
A coverage report is created and opened. If you do not wish to generate the report:
cd tests
make -j`nproc --all`
./build/Zipper-UnitTest
- Q: I used a password when zipping with the Zipper lib, but now when I want to extract data with my operating system's zip tool, I get an error.
- A: By default, Zipper encrypts with the EAS algorithm which is not the default encryption algorithm for zip files. Your operating system's zip tools may not understand EAS. You can extract it with the 7-Zip tool:
7za e your.zip
. If you want the default zip encryption (at your own risk since the password can be cracked) you can remove the EAS option in the following files: Make and external/compile-external-libs.sh and recompile the Zipper project.