zig-skills/references/std-zip.md

9.8 KiB

std.zip - ZIP Archive API Reference (Zig 0.16.0)

ZIP archive reading and extraction. Zig 0.16 file and stream APIs use std.Io.Dir, std.Io.File, std.Io.Reader, and std.Io.Writer.

Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html

The examples below use Zig 0.16's explicit std.Io file and directory APIs.

Table of Contents

Module Structure

std.zip.extract()       // Extract entire archive to directory
std.zip.Iterator        // Iterate over archive entries
std.zip.Iterator.Entry  // Single archive entry
std.zip.Diagnostics     // Track extraction metadata
std.zip.ExtractOptions  // Extraction configuration
std.zip.CompressionMethod  // .store, .deflate

Extracting ZIP Archives

Basic Extraction

Extract all files from a ZIP archive to a directory:

const file = try std.Io.Dir.cwd().openFile(io, "archive.zip", .{});
defer file.close(io);

var buf: [4096]u8 = undefined;
var file_reader = file.reader(io, &buf);

try std.zip.extract(output_dir, &file_reader, .{});

With Options and Diagnostics

var diagnostics: std.zip.Diagnostics = .{ .allocator = allocator };
defer diagnostics.deinit();

try std.zip.extract(output_dir, &file_reader, .{
    .allow_backslashes = true,  // normalize \ to /
    .diagnostics = &diagnostics,
});

// Check common root directory
if (diagnostics.root_dir.len > 0) {
    std.debug.print("Archive root: {s}\n", .{diagnostics.root_dir});
}

ExtractOptions

pub const ExtractOptions = struct {
    allow_backslashes: bool = false,   // normalize \ to / in filenames
    diagnostics: ?*Diagnostics = null, // track extraction metadata
    verify_checksums: bool = false,    // true currently panics: TODO unimplemented
};

Leave verify_checksums false in Zig 0.16. Setting it true immediately panics, and normal extraction does not otherwise verify each entry's CRC-32 payload checksum.

Iterating Over Entries

Iterator API

For more control, iterate over entries individually:

const file = try std.Io.Dir.cwd().openFile(io, "archive.zip", .{});
defer file.close(io);

var buf: [4096]u8 = undefined;
var file_reader = file.reader(io, &buf);

var iter = try std.zip.Iterator.init(&file_reader);

var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
while (try iter.next()) |entry| {
    // Read filename from archive
    try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader));
    const filename = filename_buf[0..entry.filename_len];
    try file_reader.interface.readSliceAll(filename);

    std.debug.print("{s}: {d} bytes (compressed: {d})\n", .{
        filename,
        entry.uncompressed_size,
        entry.compressed_size,
    });
}

Iterator.Entry Fields

// Descriptive field inventory, not a declaration to copy: the concrete type
// of `flags` is private to std.zip.
struct {
    version_needed_to_extract: u16,
    flags: /* private general-purpose-flags type */,
    compression_method: CompressionMethod,  // .store or .deflate
    last_modification_time: u16,            // DOS time format
    last_modification_date: u16,            // DOS date format
    header_zip_offset: u64,                 // offset to central directory header
    crc32: u32,                             // CRC-32 checksum
    filename_len: u32,
    compressed_size: u64,
    uncompressed_size: u64,
    file_offset: u64,                       // offset to local file header
};

Entry Extraction

Extract Single Entry

var iter = try std.zip.Iterator.init(&file_reader);

var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
while (try iter.next()) |entry| {
    // Extract this entry to destination directory
    try entry.extract(&file_reader, .{}, &filename_buf, output_dir);
}

Selective Extraction

Extract only specific files:

var iter = try std.zip.Iterator.init(&file_reader);

var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
while (try iter.next()) |entry| {
    // Read filename first
    try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader));
    const filename = filename_buf[0..entry.filename_len];
    try file_reader.interface.readSliceAll(filename);

    // Only extract .zig files
    if (std.mem.endsWith(u8, filename, ".zig")) {
        try entry.extract(&file_reader, .{}, &filename_buf, output_dir);
    }
}

Diagnostics

Track metadata during extraction:

var diagnostics: std.zip.Diagnostics = .{ .allocator = allocator };
defer diagnostics.deinit();

try std.zip.extract(dest, &file_reader, .{
    .diagnostics = &diagnostics,
});

// root_dir is the common directory prefix for all files (if any)
// e.g., if all files are under "project/", root_dir will be "project"
if (diagnostics.root_dir.len > 0) {
    std.debug.print("Common root: {s}\n", .{diagnostics.root_dir});
}

Low-Level Structures

CompressionMethod

pub const CompressionMethod = enum(u16) {
    store = 0,    // no compression
    deflate = 8,  // DEFLATE algorithm
    _,            // other methods (unsupported)
};

EndRecord

Find and parse the end-of-central-directory record:

// From file
const end_record = try std.zip.EndRecord.findFile(&file_reader);

// From buffer
const end_record = try std.zip.EndRecord.findBuffer(zip_bytes);

// Check if ZIP64 extensions needed
if (end_record.need_zip64()) {
    // Parse ZIP64 end locator and record
}

Header Structures

// Central directory file header (46 bytes)
std.zip.CentralDirectoryFileHeader

// Local file header (30 bytes)
std.zip.LocalFileHeader

// End of central directory record (22 bytes)
std.zip.EndRecord

// ZIP64 end of central directory record
std.zip.EndRecord64

// ZIP64 end of central directory locator
std.zip.EndLocator64

Signature Constants

std.zip.central_file_header_sig  // "PK\x01\x02"
std.zip.local_file_header_sig    // "PK\x03\x04"
std.zip.end_record_sig           // "PK\x05\x06"
std.zip.end_record64_sig         // "PK\x06\x06"
std.zip.end_locator64_sig        // "PK\x06\x07"

Common Patterns

Extract ZIP to Directory

fn extractZip(io: std.Io, allocator: std.mem.Allocator, zip_path: []const u8, dest_path: []const u8) !void {
    const file = try std.Io.Dir.cwd().openFile(io, zip_path, .{});
    defer file.close(io);

    var buf: [4096]u8 = undefined;
    var file_reader = file.reader(io, &buf);

    var dest = try std.Io.Dir.cwd().createDirPathOpen(io, dest_path, .{});
    defer dest.close(io);

    var diagnostics: std.zip.Diagnostics = .{ .allocator = allocator };
    defer diagnostics.deinit();

    try std.zip.extract(dest, &file_reader, .{
        .allow_backslashes = true,
        .diagnostics = &diagnostics,
    });
}

List ZIP Contents

fn listZip(io: std.Io, zip_path: []const u8) !void {
    const file = try std.Io.Dir.cwd().openFile(io, zip_path, .{});
    defer file.close(io);

    var buf: [4096]u8 = undefined;
    var file_reader = file.reader(io, &buf);

    var iter = try std.zip.Iterator.init(&file_reader);

    var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
    var total_size: u64 = 0;
    var file_count: u64 = 0;

    while (try iter.next()) |entry| {
        try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader));
        const filename = filename_buf[0..entry.filename_len];
        try file_reader.interface.readSliceAll(filename);

        const method: []const u8 = switch (entry.compression_method) {
            .store => "stored",
            .deflate => "deflated",
            else => "unknown",
        };

        std.debug.print("{s:40} {d:>10} {s}\n", .{
            filename,
            entry.uncompressed_size,
            method,
        });

        total_size += entry.uncompressed_size;
        file_count += 1;
    }

    std.debug.print("\n{d} files, {d} bytes total\n", .{ file_count, total_size });
}

Extract Single File by Name

fn extractFile(
    file_reader: *std.Io.File.Reader,
    target_name: []const u8,
    dest: std.Io.Dir,
) !bool {
    var iter = try std.zip.Iterator.init(file_reader);

    var filename_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
    while (try iter.next()) |entry| {
        try file_reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader));
        const filename = filename_buf[0..entry.filename_len];
        try file_reader.interface.readSliceAll(filename);

        if (std.mem.eql(u8, filename, target_name)) {
            try entry.extract(file_reader, .{}, &filename_buf, dest);
            return true;
        }
    }
    return false;  // not found
}

Check if File is ZIP

fn isZipFile(io: std.Io, path: []const u8) !bool {
    const file = std.Io.Dir.cwd().openFile(io, path, .{}) catch return false;
    defer file.close(io);

    var buf: [4096]u8 = undefined;
    var file_reader = file.reader(io, &buf);

    _ = std.zip.EndRecord.findFile(&file_reader) catch return false;
    return true;
}

Supported Features

Formats: ZIP plus core single-disk ZIP64 records/extents. ZIP64 end-record extra data, unsupported versions, and multi-disk/locator variants are rejected, so this is not unrestricted ZIP64 compatibility.

Compression: Store (uncompressed), Deflate

Not supported:

  • Encryption (returns error.ZipEncryptionUnsupported)
  • Multi-disk archives (returns error.ZipMultiDiskUnsupported)
  • Other compression methods (LZMA, BZip2, etc.)
  • Writing ZIP archives (read-only API)

Path handling: Optional backslash normalization, directory traversal protection (rejects .. paths)