summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main.zig328
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());
}