kuba--/zip: A compact C library for creating and extracting ZIP archives
A portable, simple zip library written in C
At a glance
- What is it?
- kuba--/zip wraps miniz 3.1.2 with a compact C interface for archive creation, appending, extraction, and partial reads from files or memory. It suits applications that want ZIP support without introducing a larger framework or multiple dependencies.
- Who is it for?
- Adopt kuba--/zip when a C application needs a small, direct interface for creating, appending, inspecting, or extracting ZIP archives through miniz. Do not choose it as a complete filesystem archiver when encryption, repair, path-safety policy, or advanced archive operations are mandatory.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 59 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Why kuba--/zip Wraps miniz for ZIP Work
kuba--/zip addresses a specific integration problem: a C application needs to create, append to, inspect, or extract ZIP archives, but the available dependency is an API rather than a higher-level archive interface. The project layers its interface over the miniz v3.1.2 API and describes itself as a portable library for macOS, Linux, Windows, Android, and iOS.
The intended users are C developers embedding archive functionality directly in an application. Its design favors a small integration footprint. The README says the project consists of three files that can be compiled into the target project, rather than requiring a framework, factories, or several separately linked libraries. That makes the library relevant to command-line programs, native applications, embedded software, and other projects where dependency size and interface complexity matter.
The main distinction from using miniz directly is the archive-oriented API. Applications work with a `struct zip_t`, open or append archives, open named entries, write data, close entries, and then close the archive. They can also read entries into caller-owned buffers, use callbacks during extraction, inspect metadata, or access compressed streams held in memory. The wrapper is not a separate compression implementation. Compression and archive handling are based on miniz v3.1.2.
Creating Entries and Combining Files During Compression
Archive creation starts with `zip_open`, which receives an output path, a compression level, and mode `'w'`. Each entry is opened with `zip_entry_open`, receives data through functions such as `zip_entry_write` or `zip_entry_fwrite`, and is finalized by `zip_entry_close`. Closing the `struct zip_t` with `zip_close` finishes the archive.
A useful design choice is the file-writing interface. Several source files can be merged into one archive entry, with compression occurring while those files are read. The following is the README's creation example. In this example, `foo-1.txt` receives a buffer, while `foo-2.1.txt`, `foo-2.2.txt`, and `foo-2.3.txt` are merged into `foo-2.txt` and compressed on the fly.
struct zip_t *zip = zip_open("foo.zip", ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
{
zip_entry_open(zip, "foo-1.txt");
{
const char *buf = "Some data here...\0";
zip_entry_write(zip, buf, strlen(buf));
}
zip_entry_close(zip);
zip_entry_open(zip, "foo-2.txt");
{
// merge 3 files into one entry and compress them on-the-fly.
zip_entry_fwrite(zip, "foo-2.1.txt");
zip_entry_fwrite(zip, "foo-2.2.txt");
zip_entry_fwrite(zip, "foo-2.3.txt");
}
zip_entry_close(zip);
}
zip_close(zip);This model is more specific than a generic file-tree packer. The caller decides exactly how source files and buffers map to archive entries. The trade-off is that directory traversal, error handling, and archive policy remain the application's responsibility rather than being hidden behind a higher-level object model.
Appending, Streaming, and Extracting kuba--/zip Archives
The same entry model supports append mode and in-memory streams. Append mode opens an existing archive with mode `'a'`, adds a named entry, and closes the archive after writing. The stream API follows the same sequence, but `zip_stream_open` accepts an input buffer and size. For archive creation, it can start with a null pointer and zero length, and `zip_stream_copy` copies the resulting compressed stream into an output buffer.
The interface also supports extraction at several levels. `zip_extract` extracts a disk archive into a directory and invokes a callback for each extracted entry. `zip_stream_extract` performs the corresponding operation when the archive is already in memory. A caller can instead open one entry, use `zip_entry_read` for library-managed allocation, use `zip_entry_noallocread` with a buffer sized through `zip_entry_size`, or write an entry directly to a file with `zip_entry_fread`.
Appending follows this documented pattern. It does not require rebuilding the archive through a separate import operation, although the README does not document transactional behavior or rollback if a write fails midway through the process.
struct zip_t *zip = zip_open("foo.zip", ZIP_DEFAULT_COMPRESSION_LEVEL, 'a');
{
zip_entry_open(zip, "foo-3.txt");
{
const char *buf = "Append some data here...\0";
zip_entry_write(zip, buf, strlen(buf));
}
zip_entry_close(zip);
}
zip_close(zip);The stream design matters for applications that receive archives through memory rather than a filesystem path. It removes the need to write the input archive to disk just to inspect it, but the caller remains responsible for the lifetime and ownership of the supplied stream and returned buffers.
Reading ZIP Entries Without Unbounded Allocation
kuba--/zip provides allocation-free entry reads for applications that need explicit buffer control. The README's basic pattern queries the entry size, allocates a buffer with `calloc`, calls `zip_entry_noallocread`, and later frees the buffer. A newer partial-read example uses `zip_entry_noallocreadwithoffset` to read from an offset and returns the number of bytes written.
The documented partial-read behavior is precise. The requested `size` is clamped to the number of bytes remaining after `offset`. Passing a size larger than the remaining data is therefore safe with respect to that bound. The function returns the actual number of bytes written, or a negative error code when the offset is past the end of the entry or another read error occurs.
unsigned char buf[16];
size_t bufsize = sizeof(buf);
struct zip_t *zip = zip_open("foo.zip", 0, 'r');
{
zip_entry_open(zip, "foo-1.txt");
{
size_t offset = 4;
ssize_t nread = zip_entry_noallocreadwithoffset(zip, offset, bufsize, (void *)buf);
if (nread < 0) {
// offset out of range or read error
}
}
zip_entry_close(zip);
}
zip_close(zip);For ordinary entry reads, the documented sequence is:
void *buf = NULL;
size_t bufsize;
struct zip_t *zip = zip_open("foo.zip", 0, 'r');
{
zip_entry_open(zip, "foo-1.txt");
{
zip_entry_read(zip, &buf, &bufsize);
}
zip_entry_close(zip);
}
zip_close(zip);
free(buf);The limitation is intentional. The no-allocation API requires the caller to allocate enough space and interpret negative return values. It is suitable for fixed-size processing paths, but it does not remove the need to validate sizes and handle allocation failures.
Metadata Inspection and Recursive Folder Compression
Archive contents can be inspected through an indexed entry API. `zip_entries_total` returns the number of entries, `zip_entry_openbyindex` selects one by index, and `zip_entry_name`, `zip_entry_isdir`, `zip_entry_issymlink`, `zip_entry_size`, and `zip_entry_crc32` expose the fields shown in the README example. This is useful when an application needs to present a file list or make decisions before extracting data.
The metadata API also exposes a boundary that callers should account for. The README documents symbolic-link and directory flags, but it does not provide a complete security policy for extraction, such as protection against archive paths that escape a destination directory. The extraction callback receives the filename, so applications may need to validate paths before accepting extracted content when archive sources are not trusted.
The README also includes a `zip_walk` example for recursive folder compression. Its implementation uses platform-style filesystem calls such as `DIR`, `dirent`, `stat`, and `MAX_PATH`, so portability depends on the surrounding project or the corresponding implementation files. The README excerpt does not show the complete walk function, its build instructions, or how platform-specific branches are selected. A new user should inspect `src/`, `CMakeLists.txt`, and the example or command files before assuming that the folder-walk snippet can be copied unchanged across every supported platform.
For selective extraction, open an entry by name and choose the destination yourself. This is preferable when an application should not extract every archive member. The library offers multiple output mechanisms, but it does not document a single policy layer for filtering, path validation, or duplicate entry handling.
Installation, Maintenance, and kuba--/zip Usage Limits
The README does not provide a package-manager installation command, a CMake invocation, or a complete copy-and-paste build tutorial. It says that the three source files can be compiled into a project and provides API examples. The repository includes `CMakeLists.txt`, `src/`, `test/`, and `cmd/`, so the build system is visible in the repository layout, but the README does not specify the exact build command to use.
For a first use, the smallest documented workflow is to include the library's headers in the project, compile the relevant source files from `src/`, link the resulting code, and adapt one of the README examples. Because the header include names and build target are not included in the README, they are left unspecified here rather than guessed. The example's expected result is a `foo.zip` containing `foo-1.txt` and `foo-2.txt`, with the latter receiving data from three source files.
The repository is not archived, and its last push was on 2026-08-25. The recent releases listed include `v0.3.15+miniz.3.1.2`, `v0.3.14+miniz.3.1.1`, and `v0.3.13+miniz.3.1.1`. The release naming records the wrapper version together with the miniz version, which is important when checking compatibility. The MIT license permits use under its terms, subject to the license text, but this review does not interpret those terms as legal advice.
A real alternative is to call miniz directly. That avoids the `struct zip_t` entry lifecycle and wrapper helpers, but it also requires the application to work with miniz's lower-level API and assemble archive operations itself. kuba--/zip is the better fit when the goal is a compact ZIP-oriented C interface. It is the wrong choice when an application needs an independently verified extraction security policy, advanced archive repair, encryption features, or a complete filesystem archiver without implementing missing policy around the low-level wrapper.
Editorial conclusion
Adopt kuba--/zip when a C application needs a small, direct interface for creating, appending, inspecting, or extracting ZIP archives through miniz. Do not choose it as a complete filesystem archiver when encryption, repair, path-safety policy, or advanced archive operations are mandatory. Before adoption, verify the exact build files and headers in `src/`, test error handling on the target platforms, and confirm that the documented entry and stream APIs cover the required archive workflow.
Frequently asked questions
What is kuba--/zip used for?
kuba--/zip is a C library for creating, appending to, inspecting, and extracting ZIP archives. It provides a small archive-oriented interface over the miniz v3.1.2 API.
Does kuba--/zip support reading ZIP archives from memory?
Yes. The stream API uses `zip_stream_open` with an input buffer and size, and the README documents extracting an entry with `zip_entry_read` after opening the stream.
Can kuba--/zip read part of a ZIP entry?
Yes. `zip_entry_noallocreadwithoffset` reads from an offset into a caller-owned buffer, clamps the requested size to the remaining entry data, and returns the number of bytes written or a negative error code.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/kuba-zip)Was this useful?