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
- Extracting ZIP Archives
- Iterating Over Entries
- Entry Extraction
- Diagnostics
- Low-Level Structures
- Common Patterns
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)