diff options
-rw-r--r-- | src/main.zig | 328 |
1 files changed, 165 insertions, 163 deletions
diff --git a/src/main.zig b/src/main.zig index 417f10b..f8e2b99 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3,177 +3,177 @@ const std = @import("std"); // ZIP file implementation // See spec.txt. -const ZipFile = struct { - const CompressionMethod = enum(u16) { - store = 0, - shrink = 1, - reduce_1 = 2, - reduce_2 = 3, - reduce_3 = 4, - reduce_4 = 5, - implode = 6, - reserved_1 = 7, - deflate = 8, - deflate64 = 9, - pkware_implode = 10, - reserved_2 = 11, - bzip2 = 12, - reserved_3 = 13, - lzma = 14, - reserved_4 = 15, - ibm_zos_zmpsc = 16, - reserved_5 = 17, - ibm_terse = 18, - ibm_lz77_z = 19, - zstd_deprecated = 20, - zstd = 93, - mp3 = 94, - xz = 95, - jpeg = 96, - wavpack = 97, - ppmd_version_i_rev1 = 98, - aex_encryption_marker = 99, - }; - // [local file header 1] - // [encryption header 1] - // [file data 1] - // [data descriptor 1] - // . - // . - // . - // [local file header n] - // [encryption header n] - // [file data n] - // [data descriptor n] - // [archive decryption header] - // [archive extra data record] - // [central directory header 1] - // . - // . - // . - // [central directory header n] - // [zip64 end of central directory record] - // [zip64 end of central directory locator] - // [end of central directory record] - allocator: std.mem.Allocator, - end_of_central_directory_record: EndOfCentralDirectoryRecord, - central_directory_headers: []CentralDirectoryHeader, - fn from(allocator: std.mem.Allocator, file_or_stream: anytype) !ZipFile { - // Find the EndOfCentralDirectoryRecord. It must be in the last 64k of the file - const eocdr_search_width_max: usize = 64_000; - const epos = try file_or_stream.getEndPos(); - const eocdr_search_width: usize = @min(epos, eocdr_search_width_max); - const eocdr_seek_start: usize = epos - eocdr_search_width; - try file_or_stream.seekTo(eocdr_seek_start); - var reader = file_or_stream.reader(); - const needle = @byteSwap(EndOfCentralDirectoryRecord.SIG); - var window: u32 = try reader.readIntLittle(u32); - while (true) { - if (window == needle) { - try file_or_stream.seekBy(-4); - break; - } - const nb = try reader.readByte(); - window <<= 8; - window |= nb; - } else { - return error.EndOfCentralDirectoryRecordNotFound; - } - var eocdr = try EndOfCentralDirectoryRecord.read(allocator, file_or_stream); - errdefer eocdr.deinit(allocator); - if (eocdr.disk_number_this != 0 or eocdr.disk_number_central_dir_start != 0) return error.SpansNotSupported; - if (eocdr.total_central_dir_entries != eocdr.total_central_dir_entries_on_this_disk) return error.SpansNotSupported; - - var central_directory_headers = try allocator.alloc(CentralDirectoryHeader, eocdr.total_central_dir_entries); - errdefer allocator.free(central_directory_headers); - try file_or_stream.seekTo(eocdr.central_dir_offset); - for (0..eocdr.total_central_dir_entries) |i| { - central_directory_headers[i] = try CentralDirectoryHeader.read(allocator, file_or_stream); - } +const CompressionMethod = enum(u16) { + store = 0, + shrink = 1, + reduce_1 = 2, + reduce_2 = 3, + reduce_3 = 4, + reduce_4 = 5, + implode = 6, + reserved_1 = 7, + deflate = 8, + deflate64 = 9, + pkware_implode = 10, + reserved_2 = 11, + bzip2 = 12, + reserved_3 = 13, + lzma = 14, + reserved_4 = 15, + ibm_zos_zmpsc = 16, + reserved_5 = 17, + ibm_terse = 18, + ibm_lz77_z = 19, + zstd_deprecated = 20, + zstd = 93, + mp3 = 94, + xz = 95, + jpeg = 96, + wavpack = 97, + ppmd_version_i_rev1 = 98, + aex_encryption_marker = 99, +}; +// [local file header 1] +// [encryption header 1] +// [file data 1] +// [data descriptor 1] +// . +// . +// . +// [local file header n] +// [encryption header n] +// [file data n] +// [data descriptor n] +// [archive decryption header] +// [archive extra data record] +// [central directory header 1] +// . +// . +// . +// [central directory header n] +// [zip64 end of central directory record] +// [zip64 end of central directory locator] +// [end of central directory record] +allocator: std.mem.Allocator, +end_of_central_directory_record: EndOfCentralDirectoryRecord, +central_directory_headers: []CentralDirectoryHeader, - return ZipFile{ - .allocator = allocator, - .end_of_central_directory_record = eocdr, - .central_directory_headers = central_directory_headers, - }; - } - fn deinit(self: *ZipFile) void { - self.end_of_central_directory_record.deinit(self.allocator); - for (0..self.central_directory_headers.len) |i| { - self.central_directory_headers[i].deinit(self.allocator); +fn from(allocator: std.mem.Allocator, file_or_stream: anytype) !@This() { + // Find the EndOfCentralDirectoryRecord. It must be in the last 64k of the file + const eocdr_search_width_max: usize = 64_000; + const epos = try file_or_stream.getEndPos(); + const eocdr_search_width: usize = @min(epos, eocdr_search_width_max); + const eocdr_seek_start: usize = epos - eocdr_search_width; + try file_or_stream.seekTo(eocdr_seek_start); + var reader = file_or_stream.reader(); + const needle = @byteSwap(EndOfCentralDirectoryRecord.SIG); + var window: u32 = try reader.readIntLittle(u32); + while (true) { + if (window == needle) { + try file_or_stream.seekBy(-4); + break; } - self.allocator.free(self.central_directory_headers); + const nb = try reader.readByte(); + window <<= 8; + window |= nb; + } else { + return error.EndOfCentralDirectoryRecordNotFound; } + var eocdr = try EndOfCentralDirectoryRecord.read(allocator, file_or_stream); + errdefer eocdr.deinit(allocator); + if (eocdr.disk_number_this != 0 or eocdr.disk_number_central_dir_start != 0) return error.SpansNotSupported; + if (eocdr.total_central_dir_entries != eocdr.total_central_dir_entries_on_this_disk) return error.SpansNotSupported; - fn count_files(self: ZipFile) u16 { - return self.end_of_central_directory_record.total_central_dir_entries; - } - fn file_name(self: ZipFile, index: u16) []const u8 { - return self.central_directory_headers[index].file_name; + var central_directory_headers = try allocator.alloc(CentralDirectoryHeader, eocdr.total_central_dir_entries); + errdefer allocator.free(central_directory_headers); + try file_or_stream.seekTo(eocdr.central_dir_offset); + for (0..eocdr.total_central_dir_entries) |i| { + central_directory_headers[i] = try CentralDirectoryHeader.read(allocator, file_or_stream); } - fn file_comment(self: ZipFile, index: u16) []const u8 { - return self.central_directory_headers[index].file_comment; + + return .{ + .allocator = allocator, + .end_of_central_directory_record = eocdr, + .central_directory_headers = central_directory_headers, + }; +} +fn deinit(self: *@This()) void { + self.end_of_central_directory_record.deinit(self.allocator); + for (0..self.central_directory_headers.len) |i| { + self.central_directory_headers[i].deinit(self.allocator); } - fn extract(self: ZipFile, index: u16, stream_or_file_in: anytype, stream_or_file_out: anytype) !void { - const cdh = self.central_directory_headers[index]; - try stream_or_file_in.seekTo(cdh.relative_offset_of_local_header); - var lfh = try LocalFileHeader.read(self.allocator, stream_or_file_in); - defer lfh.deinit(self.allocator); - const is_encrypted = lfh.general_purpose_bit_flag.isSet(0); - if (is_encrypted) return error.EncryptionNotSupported; + self.allocator.free(self.central_directory_headers); +} - var reader = stream_or_file_in.reader(); - var lr = std.io.limitedReader(reader, lfh.compressed_size); - var limited_reader = lr.reader(); - switch (lfh.compression_method) { - .store => { - var writer = stream_or_file_out.writer(); - try pump(limited_reader, writer, lfh.uncompressed_size, lfh.crc32); - }, - .deflate => { - var decomp = try std.compress.deflate.decompressor(self.allocator, limited_reader, null); - defer decomp.deinit(); - var decomp_reader = decomp.reader(); - var writer = stream_or_file_out.writer(); - try pump(decomp_reader, writer, lfh.uncompressed_size, lfh.crc32); - }, - .lzma => { - var decomp = try std.compress.lzma.decompress(self.allocator, limited_reader); - defer decomp.deinit(); - var decomp_reader = decomp.reader(); - var writer = stream_or_file_out.writer(); - try pump(decomp_reader, writer, lfh.uncompressed_size, lfh.crc32); - }, - else => { - std.log.err("compression method {} not supported", .{lfh.compression_method}); - return error.CompressionMethodNotSupported; - }, - } +fn count_files(self: @This()) u16 { + return self.end_of_central_directory_record.total_central_dir_entries; +} +fn file_name(self: @This(), index: u16) []const u8 { + return self.central_directory_headers[index].file_name; +} +fn file_comment(self: @This(), index: u16) []const u8 { + return self.central_directory_headers[index].file_comment; +} +fn extract(self: @This(), index: u16, stream_or_file_in: anytype, stream_or_file_out: anytype) !void { + const cdh = self.central_directory_headers[index]; + try stream_or_file_in.seekTo(cdh.relative_offset_of_local_header); + var lfh = try LocalFileHeader.read(self.allocator, stream_or_file_in); + defer lfh.deinit(self.allocator); + const is_encrypted = lfh.general_purpose_bit_flag.isSet(0); + if (is_encrypted) return error.EncryptionNotSupported; + + var reader = stream_or_file_in.reader(); + var lr = std.io.limitedReader(reader, lfh.compressed_size); + var limited_reader = lr.reader(); + switch (lfh.compression_method) { + .store => { + var writer = stream_or_file_out.writer(); + try pump(limited_reader, writer, lfh.uncompressed_size, lfh.crc32); + }, + .deflate => { + var decomp = try std.compress.deflate.decompressor(self.allocator, limited_reader, null); + defer decomp.deinit(); + var decomp_reader = decomp.reader(); + var writer = stream_or_file_out.writer(); + try pump(decomp_reader, writer, lfh.uncompressed_size, lfh.crc32); + }, + .lzma => { + var decomp = try std.compress.lzma.decompress(self.allocator, limited_reader); + defer decomp.deinit(); + var decomp_reader = decomp.reader(); + var writer = stream_or_file_out.writer(); + try pump(decomp_reader, writer, lfh.uncompressed_size, lfh.crc32); + }, + else => { + std.log.err("compression method {} not supported", .{lfh.compression_method}); + return error.CompressionMethodNotSupported; + }, } - fn pump(reader: anytype, writer: anytype, expected_size_written: usize, expected_crc32: u32) !void { - var buf = [_]u8{0} ** 1024; - var crc32 = std.hash.Crc32.init(); - var written: usize = 0; - while (true) { - const read = try reader.read(&buf); - if (read == 0) break; - const write = buf[0..read]; - try writer.writeAll(write); +} - crc32.update(write); - written += read; - } - if (written != expected_size_written) return error.WrongUncompressedSize; - if (crc32.final() != expected_crc32) return error.WrongChecksum; +fn pump(reader: anytype, writer: anytype, expected_size_written: usize, expected_crc32: u32) !void { + var buf = [_]u8{0} ** 1024; + var crc32 = std.hash.Crc32.init(); + var written: usize = 0; + while (true) { + const read = try reader.read(&buf); + if (read == 0) break; + const write = buf[0..read]; + try writer.writeAll(write); + + crc32.update(write); + written += read; } -}; + if (written != expected_size_written) return error.WrongUncompressedSize; + if (crc32.final() != expected_crc32) return error.WrongChecksum; +} const CentralDirectoryHeader = struct { const SIG: u32 = @as(u32, 0x02014b50); version_made_by: u16, version_needed_to_extract: u16, general_purpose_bit_flag: u16, - compression_method: ZipFile.CompressionMethod, + compression_method: CompressionMethod, last_mod_file_time: u16, last_mod_file_date: u16, crc32: u32, @@ -231,7 +231,7 @@ const LocalFileHeader = struct { const SIG: u32 = @as(u32, 0x04034b50); version_needed_to_extract: u16, general_purpose_bit_flag: std.bit_set.IntegerBitSet(16), - compression_method: ZipFile.CompressionMethod, + compression_method: CompressionMethod, last_mod_file_time: u16, last_mod_file_date: u16, crc32: u32, @@ -241,12 +241,14 @@ const LocalFileHeader = struct { extra_field_length: u16, file_name: []const u8, extra_field: []const u8, + fn read(allocator: std.mem.Allocator, stream_or_file: anytype) !LocalFileHeader { return read2(allocator, stream_or_file, LocalFileHeader, &[_]Dynamic{ .{ .field_name = "file_name", .length_field_name = "file_name_length" }, .{ .field_name = "extra_field", .length_field_name = "extra_field_length" }, }); } + fn deinit(self: *LocalFileHeader, allocator: std.mem.Allocator) void { allocator.free(self.file_name); allocator.free(self.extra_field); @@ -314,19 +316,19 @@ fn read2( return t; } -test "foo" { +test "open stream" { const test_zip = @embedFile("hello.zip"); var fbs = std.io.fixedBufferStream(test_zip); - var zf = try ZipFile.from(std.testing.allocator, &fbs); + var zf = try @This().from(std.testing.allocator, &fbs); defer zf.deinit(); try std.testing.expectEqual(zf.count_files(), 2); try std.testing.expectEqualStrings(zf.file_name(0), "hello.txt"); try std.testing.expectEqualStrings(zf.file_name(1), "foo.txt"); } -test "foo2" { +test "open file" { const test_zip = try std.fs.cwd().openFile("src/hello.zip", .{}); - var zf = try ZipFile.from(std.testing.allocator, &test_zip); + var zf = try @This().from(std.testing.allocator, &test_zip); defer zf.deinit(); try std.testing.expectEqual(zf.count_files(), 2); try std.testing.expectEqualStrings(zf.file_name(0), "hello.txt"); @@ -336,7 +338,7 @@ test "foo2" { test "extract stored" { const test_zip = @embedFile("hello.zip"); var fbs = std.io.fixedBufferStream(test_zip); - var zf = try ZipFile.from(std.testing.allocator, &fbs); + var zf = try @This().from(std.testing.allocator, &fbs); defer zf.deinit(); var out = [_]u8{0} ** 1024; var fbs_out = std.io.fixedBufferStream(&out); @@ -350,12 +352,12 @@ test "extract stored" { test "extract deflate" { const test_zip = @embedFile("deflate.zip"); var fbs = std.io.fixedBufferStream(test_zip); - var zf = try ZipFile.from(std.testing.allocator, &fbs); + var zf = try @This().from(std.testing.allocator, &fbs); defer zf.deinit(); var out = [_]u8{0} ** 1024; var fbs_out = std.io.fixedBufferStream(&out); try std.testing.expectEqualStrings("Here is a comment :)", zf.file_comment(0)); - try std.testing.expectEqual(ZipFile.CompressionMethod.deflate, zf.central_directory_headers[0].compression_method); + try std.testing.expectEqual(@This().CompressionMethod.deflate, zf.central_directory_headers[0].compression_method); try zf.extract(0, &fbs, &fbs_out); try std.testing.expectEqualStrings(@embedFile("foo.txt"), fbs_out.getWritten()); } |