A portable, simple zip library written in C
This is done by hacking awesome miniz library and layering functions on top of the miniz v3.1.2 API.
... Some day, I was looking for zip library written in C for my project, but I could not find anything simple enough and lightweight. Everything what I tried required 'crazy mental gymnastics' to integrate or had some limitations or was too heavy. I hate frameworks, factories and adding new dependencies. If I must to install all those dependencies and link new library, I'm getting almost sick. I wanted something powerful and small enough, so I could add just a few files and compile them into my project. And finally I found miniz. Miniz is a lossless, high performance data compression library in a single source file. I only needed simple interface to append buffers or files to the current zip-entry. Thanks to this feature I'm able to merge many files/buffers and compress them on-the-fly.
It was the reason, why I decided to write zip module on top of the miniz. It required a little bit hacking and wrapping some functions, but I kept simplicity. So, you can grab these 3 files and compile them into your project. I hope that interface is also extremely simple, so you will not have any problems to understand it.
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);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);int on_extract_entry(const char *filename, void *arg) {
static int i = 0;
int n = *(int *)arg;
printf("Extracted: %s (%d of %d)\n", filename, ++i, n);
return 0;
}
// From "foo.zip" on disk
int arg = 2;
zip_extract("foo.zip", "/tmp", on_extract_entry, &arg);
// Or from memory
arg = 2;
zip_stream_extract(zipstream, zipstreamsize, "/tmp", on_extract_entry, &arg);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);unsigned char *buf;
size_t bufsize;
struct zip_t *zip = zip_open("foo.zip", 0, 'r');
{
zip_entry_open(zip, "foo-1.txt");
{
bufsize = zip_entry_size(zip);
buf = calloc(sizeof(unsigned char), bufsize);
zip_entry_noallocread(zip, (void *)buf, bufsize);
}
zip_entry_close(zip);
}
zip_close(zip);
free(buf);…struct zip_t *zip = zip_open("foo.zip", 0, 'r');
{
zip_entry_open(zip, "foo-2.txt");
{
zip_entry_fread(zip, "foo-2.txt");
}
zip_entry_close(zip);
}
zip_close(zip);char *outbuf = NULL;
size_t outbufsize = 0;
const char *inbuf = "Append some data here...\0";
struct zip_t *zip = zip_stream_open(NULL, 0, ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
{
zip_entry_open(zip, "foo-1.txt");
{
zip_entry_write(zip, inbuf, strlen(inbuf));
}
zip_entry_close(zip);
/* copy compressed stream into outbuf */
zip_stream_copy(zip, (void **)&outbuf, &outbufsize);
}
zip_stream_close(zip);
free(outbuf);char *buf = NULL;
size_t bufsize = 0;
struct zip_t *zip = zip_stream_open(zipstream, zipstreamsize, 0, 'r');
{
zip_entry_open(zip, "foo-1.txt");
{
zip_entry_read(zip, (void **)&buf, &bufsize);
}
zip_entry_close(zip);
}
zip_stream_close(zip);
free(buf);Reads up to size bytes of the entry starting at offset into a caller-owned
buffer (no internal allocation). size is clamped to the bytes remaining after
offset, so it is safe to pass a size larger than what is left in the entry. The
call returns the number of bytes actually written, or a negative error code (for
example when offset is past the end of the entry).
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);struct zip_t *zip = zip_open("foo.zip", 0, 'r');
int i, n = zip_entries_total(zip);
for (i = 0; i < n; ++i) {
zip_entry_openbyindex(zip, i);
{
const char *name = zip_entry_name(zip);
int isdir = zip_entry_isdir(zip);
int issymlink = zip_entry_issymlink(zip);
unsigned long long size = zip_entry_size(zip);
unsigned int crc32 = zip_entry_crc32(zip);
}
zip_entry_close(zip);
}
zip_close(zip);…char *entries[] = {"unused.txt", "remove.ini", "delete.me"};
// size_t indices[] = {0, 1, 2};
struct zip_t *zip = zip_open("foo.zip", 0, 'd');
{
zip_entries_delete(zip, entries, 3);
// you can also delete by index, instead of by name
// zip_entries_deletebyindex(zip, indices, 3);
}
zip_close(zip);struct zip_t *zip = zip_open_with_password("secret.zip", ZIP_DEFAULT_COMPRESSION_LEVEL, 'w', "password");
{
zip_entry_open(zip, "secret-1.txt");
{
const char *buf = "Classified data...\0";
zip_entry_write(zip, buf, strlen(buf));
}
zip_entry_close(zip);
}
zip_close(zip);void *buf = NULL;
size_t bufsize;
struct zip_t *zip = zip_open_with_password("secret.zip", 0, 'r', "password");
{
zip_entry_open(zip, "secret-1.txt");
{
zip_entry_read(zip, &buf, &bufsize);
}
zip_entry_close(zip);
}
zip_close(zip);
free(buf);…char *entries[] = {"obsolete.txt", "remove-me.dat"};
struct zip_t *zip = zip_open_with_password("secret.zip", 0, 'd', "password");
{
zip_entries_delete(zip, entries, 2);
}
zip_close(zip);Compile zip library as a dynamic library.
$ mkdir build
$ cd build
$ cmake -DBUILD_SHARED_LIBS=true ..
$ cmake --build .Third party binding: kuba--/c-go-zip
package main
/*
#cgo CFLAGS: -I../src
#cgo LDFLAGS: -L. -lzip
#include <zip.h>
*/
import "C"
import "unsafe"
func main() {
path := C.CString("/tmp/go.zip")
zip := C.zip_open(path, 6, 'w')
entryname := C.CString("test")
C.zip_entry_open(zip, entryname)
content := "test content"
buf := unsafe.Pointer(C.CString(content))
bufsize := C.size_t(len(content))
C.zip_entry_write(zip, buf, bufsize)
C.zip_entry_close(zip)
C.zip_close(zip)
}…Install ffi gem.
$ gem install ffiBind in your module.
…Install cffi package
$ pip install cffiBind in your package.
……The language comes with RingZip based on this library
load "ziplib.ring"
new Zip {
setFileName("myfile.zip")
open("w")
newEntry() {
open("test.c")
writefile("test.c")
close()
}
close()
}$ zig build-exe main.zig -lc -lzipconst c = @cImport({
@cInclude("zip.h");
});
pub fn main() void {
var zip = c.zip_open("/tmp/zig.zip", 6, 'w');
defer c.zip_close(zip);
_ = c.zip_entry_open(zip, "test");
defer _ = c.zip_entry_close(zip);
const content = "test content";
_ = c.zip_entry_write(zip, content, content.len);
}Third party binding: thechampagne/zip-odin
…Third party binding: thechampagne/nimzip
$ nim c --passL:-lzip main.nimproc zip_open(zipname: cstring, level: cint, mode: char): pointer {.importc.}
proc zip_close(zip: pointer) {.importc.}
proc zip_entry_open(zip: pointer, entryname: cstring): cint {.importc.}
proc zip_entry_close(zip: pointer): cint {.importc.}
proc zip_entry_write(zip: pointer, buf: pointer, bufsize: csize_t): cint {.importc.}
when isMainModule:
var zip = zip_open("/tmp/nim.zip", 6, 'w')
discard zip_entry_open(zip, "test")
let content: cstring = "test content";
discard zip_entry_write(zip, content, csize_t(len(content)))
discard zip_entry_close(zip)
zip_close(zip)Third party binding: thechampagne/zip-d
$ dmd -L-lzip main.dextern(C) void* zip_open(const(char)* zipname, int level, char mode);
extern(C) void zip_close(void* zip);
extern(C) int zip_entry_open(void* zip, const(char)* entryname);
extern(C) int zip_entry_close(void* zip);
extern(C) int zip_entry_write(void* zip, const(void)* buf, size_t bufsize);
void main()
{
void* zip = zip_open("/tmp/d.zip", 6, 'w');
scope(exit) zip_close(zip);
zip_entry_open(zip, "test");
scope(exit) zip_entry_close(zip);
string content = "test content";
zip_entry_write(zip, content.ptr, content.length);
}Third party binding: thechampagne/zip-pascal
…Third party binding: gynt/luamemzip
-- Loads the luamemzip.dll
zip = require("luamemzip")
content = "Test content"
z = zip.zip_stream_open(nil, 6, 'w')
zip.zip_entry_open(z, "test.content")
zip.zip_entry_write(z, content)
zip.zip_entry_close(z)
data = zip.zip_stream_copy(z)
zip.zip_close(z)The library supports three compile-time flags to strip unused functionality and reduce binary size. All features are enabled by default -- if you just drop the source files into your project and compile, everything works as before.
| Flag | Default | Effect when disabled (=0) |
|---|---|---|
ZIP_ENABLE_DEFLATE |
1 |
Removes compression / archive-writing APIs (zip_entry_write, zip_entry_fwrite, zip_create, zip_entries_delete, …). Write and append modes ('w', 'a', 'd') return an error. |
ZIP_ENABLE_INFLATE |
1 |
Removes decompression / extraction APIs (zip_entry_read, zip_entry_fread, `zip_entry_ex |
No open issues yet, or sync has not completed.