- Notifications
You must be signed in to change notification settings - Fork19
[Lib][Version 3.0.1][Functional] C++14 wrapper around minizip compression library
License
Lecrapouille/zipper
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Zipper is a C++14 wrapper around the minizip compression library. Its goal is to bring the power and simplicity of minizip to a more object-oriented and C++ user-friendly library.
This project is a continuation of the originalproject. The original project was created out of the need for a compression library that would be reliable, simple, and flexible. By flexibility, we mean supporting various types of inputs and outputs, specifically the ability to compress into memory instead of being restricted to file compression only, and using data from memory instead of just files.
This current fork repository was created because the original project was no longer maintained by its authors, and I, Lecrapouille, encountered issues due to missing administrative rights (needed for CI, branch management, API breaking changes, etc.).
- Create zip files in memory.
- Support for files, vectors, and generic streams as input for zipping.
- File mappings for replacement strategies (overwrite if exists or use alternative names from mapping).
- Password-protected zip (AES).
- Multi-platform support.
- Project compiles as both static and dynamic libraries.
- Protection flags against overwriting existing files during extraction.
- Protection against theZip Slip attack.
- API to detectZip Bomb attacks. Extraction is not recursive.
- Non-regression tests.
- Zipper currently uses an outdated (and potentially vulnerable) version ofminizip from 2017 (SHA1 0bb5afeb0d3f23149b086ccda7e4fee7d48f4fdf) with some custom modifications.
There are two ways to compile the project:
- Makefile: This is the official compilation method but only supports Linux and macOS
- CMake: Recently added, it supports all operating systems and was primarily added for Windows support
This is the official way to download the project and compile it:
git clone https://github.com/lecrapouille/zipper.git --recursivecd zippermake download-external-libsmake compile-external-libsmake -j8
Explanations of compilation commands:
- Git cloning requires the recursive option to install the Makefile helper and third-party libs (
zlib
andminizip
) in theexternal
folder. They are based on fixed SHA1. They are installed in the folderexternal
. - Optionally
make download-external-libs
will git clone HEADs of third-party libs (zlib
andminizip
) in theexternal
folder. It is optional since it was initially used instead of git submodule. make compile-external-libs
will compile third-party libs (zlib
andminizip
) but not install them on your operating system. They are compiled as static libraries and merged into this library inside thebuild
folder.make
will compile this library against the third-party libs (zlib
andminizip
). Abuild
folder is created with two demos inside. Note:-j8
should be adapted to your number of CPU cores.
See theREADME file for using the demos. To run demos, you can run them:
cd build./unzipper-demo -h./zipper-demo -h
To install C++ header files, shared and static libraries on your operating system, type:
sudo make install
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
For developers, you can run non regression tests. They depend on:
- googletest framework
- lcov for code coverage
make tests -j8
As an alternative, you can also build the project using CMake:
git clone https://github.com/lecrapouille/zipper.git --recursivecd zippermkdir buildcd buildcmake .. -DZIPPER_SHARED_LIB=ON -DZIPPER_BUILD_DEMOS=ON -DZIPPER_BUILD_TESTS=ON# Either:cmake --build. --config Release# Or: make -j8
Optional options:
-DZIPPER_SHARED_LIB=ON
allows creating a shared lib instead of static lib.-DZIPPER_BUILD_DEMOS=ON
allows compiling zipper and unzipper "hello world" demos.-DZIPPER_BUILD_TESTS=ON
allows compiling unit tests (if you are a developer).
- In your project, add the needed headers in your C++ files:
#include<Zipper/Unzipper.hpp>#include<Zipper/Zipper.hpp>
- To compile your project "as it" against Zipper, the simplest way is to use the
pkg-config
command:
g++ -W -Wall --std=c++14 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:
find_package(zipperREQUIRED)target_link_libraries(your_applicationzipper::zipper)
You have an exampledoc/demos/CMakeHelloWorld.
There are two classes available:Zipper
andUnzipper
. They behave in the same manner regarding constructors and storage parameters.
#include<Zipper/Zipper.hpp>usingnamespacezipper;
- Constructor without password and replace
ziptest.zip
if already present. The new zip archive is empty. The flagZipper::OpenFlags::Overwrite
is optional.
Zipperzipper("ziptest.zip", Zipper::OpenFlags::Overwrite);
- Constructor without password and preserve
ziptest.zip
if already present. The flagZipper::OpenFlags::Append
is mandatory!
Zipperzipper("ziptest.zip", Zipper::OpenFlags::Append);
- Constructor with password (using AES algorithm) and replace
ziptest.zip
if already present. The new zip archive is empty. The flagZipper::OpenFlags::Overwrite
is optional.
Zipperzipper("ziptest.zip","my_password", Zipper::OpenFlags::Overwrite);
- Constructor with a password and preserve
ziptest.zip
if already present. The flagZipper::OpenFlags::Append
is mandatory!
Zipperzipper("ziptest.zip","my_password", Zipper::OpenFlags::Append);
- Constructor for in-memory zip compression (storage inside std::iostream):
std::stringstream zipStream;Zipperzipper(zipStream);Zipperzipper(zipStream, Zipper::OpenFlags::Overwrite);Zipperzipper(zipStream, Zipper::OpenFlags::Append);Zipperzipper(zipStream,"my_password");Zipperzipper(zipStream,"my_password", Zipper::OpenFlags::Overwrite);Zipperzipper(zipStream,"my_password", Zipper::OpenFlags::Append);
- Constructor for in-memory zip compression (storage inside std::vector):
std::vector<unsignedchar> zipVector;Zipperzipper(zipVector);Zipperzipper(zipVector, Zipper::OpenFlags::Overwrite);Zipperzipper(zipVector, Zipper::OpenFlags::Append);Zipperzipper(zipVector,"my_password");Zipperzipper(zipVector,"my_password", Zipper::OpenFlags::Overwrite);Zipperzipper(zipVector,"my_password", Zipper::OpenFlags::Append);
- Note: all constructors will throw a
std::runtime_error
exception in case of failure.
try{ Zipperzipper("ziptest.zip", ...); ...}catch (std::runtime_errorconst& e){ std::cerr << e.what() << std::endl;}
- If this is not a desired behavior, you can choose the alternative dummy constructor followed by the
open
method which takes the same arguments as constructors. This method does not throw but will returnfalse
in case of error, you can get the reason by callingerror()
.
// Dummy constructorZipper zipper;// Same arguments than seen previously with constructors.if (!zipper.open(...)){ std::cerr << zipper.error() << std::endl;}
Do not forget to callclose()
explicitly (it's called implicitly from the destructor) otherwisethe zip will not be well-formed andUnzipper
(or any unzipper application) will fail to open it, for example.
Zipperzipper("ziptest.zip", ...);...zipper.close();// Now Unzipper unzipper("ziptest.zip") can work
Afterclose()
you can reopen the zip archive withopen()
without arguments. You can pass the same arguments than seen previously with constructors to open with new password or flags. Note: that any open method will call implicitly the close() method.
Zipperzipper("ziptest.zip", ...);...zipper.close();...zipper.open();...zipper.close();
Theadd()
method allows appending files or folders. TheZipper::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
. - To preserve directory hierarchy add
| Zipper::ZipFlags::SaveHierarchy
else files are only stored.
In case of success, theadd()
will returntrue
; otherwise it will returnfalse
anderror()
should be used for getting the std::error_code.
- Adding an entire folder to a zip:
Zipperzipper("ziptest.zip");zipper.add("myFolder/");zipper.close();
- Adding a file by name:
Zipperzipper("ziptest.zip");zipper.add("myFolder/somefile.txt");zipper.close();
- You can change their name in the archive:
Zipperzipper("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::ifstreaminput1("first_file");std::ifstreaminput2("second_file");Zipperzipper("ziptest.zip");zipper.add(input1,"Test1");zipper.add(input2,"Test2");zipper.close();
Note that:
the
zipper::close()
updatesstd::ifstream
and makes the in-memory zip well formed.do not use std::ifstream before closed() was called.
be sure the std::ifstream is not deleted before closed() was called.
Add a file with a specific timestamp:
std::ifstreaminput("somefile.txt");std::tm timestamp;timestamp.tm_year =2024;timestamp.tm_mon =0;timestamp.tm_mday =1;timestamp.tm_hour =12;timestamp.tm_min =1;timestamp.tm_sec =2;Zipperzipper("ziptest.zip");zipper.add(input, timestamp,"somefile.txt");zipper.close();
- Zipper has security againstZip Slip vulnerability.
zipper.add(input1,"../Test1");
Will always returnfalse
becauseTest1
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 /pwdcd ../../../../../../../../../../../../../../..pwd
- The Zipper lib forces canonical paths in the archive. The following code works (will return
true
):
zipper.add(input1,"foo/../Test1");
becausefoo/../Test1
is replaced byTest1
(even if the folderfoo
is not present in thezip archive).
- Do not forget that the close() finalized the in-memory zip file: you cannot use Unzipper on the same memory until the Zipper::close() has been closed (meaning: even if you have called Zipper::add, the zip is not well formed).
- Creating a zip file using the awesome streams from theboost 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);Zipperzipper("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::ifstreaminput1("some file");Zipperzipper(zip_in_memory);// You can pass passwordzipper.add(input1,"Test1");zipper.close();Unzipperunzipper(zip_in_memory);unzipper.extract(...
Or:
#include<vector>std::vector<unsignedchar> zip_vector;std::ifstreaminput1("some file");Zipperzipper(zip_vector);// You can pass passwordzipper.add(input1,"Test1");zipper.close();```- Creating a zip in-memory stream with files:```c++// Example of using stringstreamstd::stringstream zipStream;std::stringstreaminputStream("content to zip");Zipperzipper(zipStream);// You can pass passwordzipper.add(inputStream,"Test1");zipper.close();// Example of extractingzipper::Unzipperunzipper(zipData);// or unzipper(zipStream) for stringstreamunzipper.extract(...
#include<Zipper/Unzipper.hpp>usingnamespacezipper;
- Constructor without password and opening
ziptest.zip
(should already be present).
Unzipperunzipper("ziptest.zip");...unzipper.close();
- Constructor with a password and opening
ziptest.zip
(should already be present).
Unzipperunzipper("ziptest.zip","my_password");...unzipper.close();
- Constructor for in-memory zip extraction (from std::iostream):
std::stringstream zipStream;// Without passwordUnzipperunzipper(zipStream);// Or with password:Unzipperunzipper(zipStream,"my_password");
- Constructor for in-memory zip extraction (from std::vector):
// Without passwordstd::vector<unsignedchar> zipVector;Unzipperunzipper(zipVector);// Or with password:Unzipperunzipper(zipVector,"my_password");
- Note: all constructors will throw a
std::runtime_error
exception in case of failure.
try{ Unzipperunzipper("ziptest.zip", ...); ...}catch (std::runtime_errorconst& e){ std::cerr << e.what() << std::endl;}
Unzipperunzipper("zipfile.zip");std::vector<ZipEntry> entries = unzipper.entries();for (auto& it: unzipper.entries()){ std::cout << it.name <<":" << it.timestamp << std::endl;}unzipper.close();
Unzipperunzipper("zipfile.zip");size_t total_size = unzipper.sizeOnDisk();if (total_size > MAX_ALLOWED_SIZE) {// Prevent zip bomb attack std::cerr <<"Zip file too large!" << std::endl;return;}unzipper.close();
Two methods are available:extractAll()
for the whole archive andextract()
fora single element in the archive. They returntrue
in case of successorfalse
in case of failure. In case of failure, useunzipper.error();
to get thestd::error_code
.
If you are scared ofZip bomb attack you cancheck the total uncompressed size of all entries in the zip archive by callingunzipper.sizeOnDisk()
beforeunzipper.extractAll(...)
.
// 1 gigabyteconstsize_tMAX_TOTAL_UNCOMPRESSED_BYTES (1 *1024 *1024 *1024)Unzipper unzipper("zipfile.zip");if (unzipper.sizeOnDisk() <= MAX_TOTAL_UNCOMPRESSED_BYTES){ unzipper.extractAll(Unzipper::OverwriteMode::Overwrite);}else{ std::cerr <<"Zip bomb attack prevented" << std::endl;}
- If you do not care about replacing existing files or folders:
Unzipperunzipper("zipfile.zip");unzipper.extractAll(Unzipper::OverwriteMode::Overwrite);unzipper.close();
- If you care about replacing existing files or folders. The method will fail (return
false
)if a file would be replaced and the methoderror()
will give you information.
Unzipperunzipper("zipfile.zip");unzipper.extractAll();// equivalent to unzipper.extractAll(Unzipper::OverwriteMode::DoNotOverwrite);unzipper.close();
- Extracting all entries from the zip file to the desired destination:
Unzipperunzipper("zipfile.zip");unzipper.extractAll("/the/destination/path");// Fail if a file exists (DoNotOverwrite is implicit)unzipper.extractAll("/the/destination/path", Unzipper::OverwriteMode::Overwrite);// Replace existing filesunzipper.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"} };Unzipperunzipper("zipfile.zip");unzipper.extractAll(".", alternativeNames);unzipper.close();
- Extracting a single entry from the zip file:
Unzipperunzipper("zipfile.zip");unzipper.extract("entry name");unzipper.close();
Returnstrue
in case of success orfalse
in case of failure.In case of failure, useunzipper.error();
to get thestd::error_code
.
- Extracting a single entry from the zip file to destination:
Unzipperunzipper("zipfile.zip");unzipper.extract("entry name","/the/destination/path");// Fail if a file exists (DoNotOverwrite is implicit)unzipper.extract("entry name","/the/destination/path", Unzipper::OverwriteMode::Overwrite);// Replace existing fileunzipper.close();
Returnstrue
in case of success orfalse
in case of failure.In case of failure, useunzipper.error();
to get thestd::error_code
.
- Extracting a single entry from the zip file to memory (stream):
std::stringstream output;Unzipperunzipper("zipfile.zip");unzipper.extract("entry name", output);unzipper.close();
- Extracting a single entry from the zip file to memory (vector):
std::vector<unsignedchar> unzipped_entry;Unzipperunzipper("zipfile.zip");unzipper.extract("entry name", unzipped_entry);unzipper.close();
Returnstrue
in case of success orfalse
in case of failure.In case of failure, useunzipper.error();
to get thestd::error_code
.
- Extracting from a vector:
std::vector<unsignedchar> zip_vector;// Populated with Zipper zipper(zip_vect);Unzipperunzipper(zip_vector);unzipper.extract("Test1");
- Zipper has security againstZip Slip vulnerability: if an entry has a path outside the extraction folder (like
../foo.txt
) itwill returnfalse
even if the replace option is set.
You can monitor the extraction progress by setting a callback function. The callback provides information about:
- Current status (OK, KO, InProgress)
- Current file being extracted
- Number of bytes read
- Total bytes to extract
- Number of files extracted
- Total number of files
Example:
Unzipperunzipper("zipfile.zip");// Set the progress callbackunzipper.setProgressCallback([](const Unzipper::Progress& progress) {switch (progress.status) {case Unzipper::Progress::Status::InProgress: std::cout <<"Extracting:" << progress.current_file <<" (" << progress.files_extracted <<"/" << progress.total_files <<" files)" << (progress.bytes_read *100 / progress.total_bytes) <<"%" << std::endl;break;case Unzipper::Progress::Status::OK: std::cout <<"Extraction completed successfully" << std::endl;break;case Unzipper::Progress::Status::KO: std::cout <<"Extraction failed" << std::endl;break; }});// Start extractionunzipper.extractAll();unzipper.close();
Same idea for Zipper.
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 uses the AES encryption algorithm, which is not the default encryption method for zip files. Your operating system's zip tools may not support AES. You can extract it using the 7-Zip tool:
7za e your.zip
. If you want to use the default zip encryption (at your own risk since the password can be cracked), you can remove the AES option in the following files:Make andexternal/compile-external-libs.sh and recompile the Zipper project.Q: Why are there two classes: one for zipping and one for unzipping? Couldn't we have made a single class to handle both operations?
A: AFAIK this is a constraint imposed by the minizip API: No simultaneous operations are allowed - you cannot read from and write to a ZIP archive at the same time.
Q: How do I remove a file from the zip?
A: To modify an existing archive:
- You generally need to create a temporary copy.
- Copy the unchanged files.
- Add the new files.
- Replace the original.
About
[Lib][Version 3.0.1][Functional] C++14 wrapper around minizip compression library
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors3
Uh oh!
There was an error while loading.Please reload this page.