All formats

libarchive

LanguageC (usable from C++)
LicenseBSD 2-clause (new code), some files under other permissive licenses
Repositorygithub.com/libarchive/libarchive
Documentationlibarchive wiki, man pages archive_read(3), archive_write(3), archive_entry(3)
Depends onoptional zlib, bzip2, liblzma, zstd, lz4, OpenSSL / mbed TLS / Nettle (for AES)
Read / writeyes / yes, ZIP64, ZipCrypto and WinZip AES, stored, DEFLATE, bzip2, LZMA, XZ, Zstandard (writing bzip2, LZMA, XZ and Zstandard needs 3.8+)

libarchive is the library behind bsdtar, which ships as tar on Windows 10+, macOS and FreeBSD. It reads and writes many archive formats (ZIP, tar, pax, cpio, 7z, ISO 9660, ar, shar) and reads more (RAR, RAR5, CAB, LHA, XAR), all through the same API: open an archive, loop over entries, read or write the data.

The API is stream-oriented: entries come in order, and you can’t jump to a specific file without reading the ones before it. For ZIP, libarchive has a seekable reader that uses the central directory (archive_read_support_format_zip_seekable) and a streaming reader that uses local headers (..._zip_streamable); archive_read_support_format_zip enables both. It can’t modify an archive in place: to change one, read it and write a new one.

Installation#

Shell
# Debian / Ubuntu
sudo apt install libarchive-dev
# Fedora
sudo dnf install libarchive-devel
# macOS (the system has libarchive, but no headers)
brew install libarchive
# Windows (vcpkg)
vcpkg install libarchive

With CMake:

cmake
find_package(LibArchive REQUIRED)
target_link_libraries(myapp PRIVATE LibArchive::LibArchive)

Basic usage: list files in a ZIP#

C
#include <archive.h>
#include <archive_entry.h>
#include <stdio.h>

int main(void) {
    struct archive *a = archive_read_new();
    archive_read_support_format_zip(a);
    if (archive_read_open_filename(a, "in.zip", 10240) != ARCHIVE_OK) {
        fprintf(stderr, "%s\n", archive_error_string(a));
        archive_read_free(a);
        return 1;
    }
    struct archive_entry *entry;
    while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
        printf("%-40s %lld bytes\n", archive_entry_pathname(entry),
               (long long)archive_entry_size(entry));
        archive_read_data_skip(a);  // skip the data, go to the next entry
    }
    archive_read_free(a);
    return 0;
}

To read any supported format instead of only ZIP, call archive_read_support_format_all(a) and archive_read_support_filter_all(a).

How to extract a ZIP file to disk#

archive_write_disk creates files, directories and permissions. The SECURE_* flags reject .. paths, absolute paths and writing through symlinks:

C
#include <archive.h>
#include <archive_entry.h>
#include <stdio.h>

static int copy_data(struct archive *ar, struct archive *aw) {
    const void *buf;
    size_t size;
    la_int64_t offset;
    for (;;) {
        int r = archive_read_data_block(ar, &buf, &size, &offset);
        if (r == ARCHIVE_EOF) return ARCHIVE_OK;
        if (r < ARCHIVE_OK) return r;
        if (archive_write_data_block(aw, buf, size, offset) < ARCHIVE_OK) return ARCHIVE_FATAL;
    }
}

int extract(const char *path) {
    struct archive *a = archive_read_new();
    archive_read_support_format_zip(a);
    struct archive *ext = archive_write_disk_new();
    archive_write_disk_set_options(ext, ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM |
        ARCHIVE_EXTRACT_SECURE_NODOTDOT | ARCHIVE_EXTRACT_SECURE_SYMLINKS |
        ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS);
    archive_write_disk_set_standard_lookup(ext);

    if (archive_read_open_filename(a, path, 10240) != ARCHIVE_OK) return 1;
    struct archive_entry *entry;
    int r;
    while ((r = archive_read_next_header(a, &entry)) == ARCHIVE_OK) {
        if (archive_write_header(ext, entry) == ARCHIVE_OK)
            copy_data(a, ext);
        else
            fprintf(stderr, "%s\n", archive_error_string(ext));
        archive_write_finish_entry(ext);
    }
    archive_read_free(a);
    archive_write_free(ext);
    return r == ARCHIVE_EOF ? 0 : 1;
}

Call chdir to the target folder first, or prefix archive_entry_pathname with it using archive_entry_set_pathname.

How to create a ZIP file#

C
#include <archive.h>
#include <archive_entry.h>
#include <string.h>

int write_zip(void) {
    const char *text = "hello from libarchive\n";
    struct archive *a = archive_write_new();
    archive_write_set_format_zip(a);
    archive_write_set_options(a, "zip:compression=deflate");  // also store; bzip2, lzma, xz, zstd since 3.8
    if (archive_write_open_filename(a, "out.zip") != ARCHIVE_OK) return 1;

    struct archive_entry *entry = archive_entry_new();
    archive_entry_set_pathname(entry, "hello.txt");
    archive_entry_set_size(entry, strlen(text));
    archive_entry_set_filetype(entry, AE_IFREG);
    archive_entry_set_perm(entry, 0644);
    archive_write_header(a, entry);
    archive_write_data(a, text, strlen(text));
    archive_entry_free(entry);

    archive_write_close(a);
    archive_write_free(a);
    return 0;
}

How to create and read an AES-encrypted ZIP#

C
// writing
archive_write_set_format_zip(a);
archive_write_set_options(a, "zip:encryption=aes256");  // or aes128, zipcrypt
archive_write_set_passphrase(a, "my password");

// reading
archive_read_support_format_zip(a);
archive_read_add_passphrase(a, "my password");  // can be called several times

Tips#