# 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](#module-structure) - [Extracting ZIP Archives](#extracting-zip-archives) - [Iterating Over Entries](#iterating-over-entries) - [Entry Extraction](#entry-extraction) - [Diagnostics](#diagnostics) - [Low-Level Structures](#low-level-structures) - [Common Patterns](#common-patterns) ## Module Structure ```zig 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: ```zig 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 ```zig 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 ```zig 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: ```zig 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 ```text // 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 ```zig 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: ```zig 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: ```zig 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 ```zig 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: ```zig // 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 ```zig // 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 ```zig 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 ```zig 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 ```zig 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 ```zig 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 ```zig 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)