From 145be0de76f0cef3b4c6b954d1a540cfebb20019 Mon Sep 17 00:00:00 2001 From: Martin Ashby Date: Fri, 15 Sep 2023 08:12:57 +0100 Subject: Rearrange code Add some convenience methods to ZipFile --- src/main.zig | 162 ++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 89 insertions(+), 73 deletions(-) (limited to 'src/main.zig') diff --git a/src/main.zig b/src/main.zig index 6d78709..d123e7f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,6 +4,27 @@ const std = @import("std"); // See spec.txt. const ZipFile = struct { + // [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, is_zip_64: bool = false, end_of_central_directory_record: EndOfCentralDirectoryRecord, @@ -54,77 +75,14 @@ const ZipFile = struct { } self.allocator.free(self.central_directory_headers); } - // [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] -}; - -const Dynamic = struct { - field_name: []const u8, - length_field_name: []const u8, -}; - -fn read2( - allocator: std.mem.Allocator, - stream_or_file: anytype, - comptime T: type, - comptime sig: u32, - comptime dynamics: []const Dynamic, -) !T { - const ti = @typeInfo(T); - if (ti != .Struct) @compileError("read2 expects type parameter T to be a struct, but it was a " ++ @typeName(T)); - const si = ti.Struct; - var reader = stream_or_file.reader(); - const sig_actual = try reader.readIntLittle(u32); - if (sig_actual != sig) { - std.log.err("invalid signature expected {x} got {x}", .{ sig, sig_actual }); - return error.InvalidSignature; + fn count_files(self: ZipFile) u16 { + return self.end_of_central_directory_record.total_central_dir_entries; } - var t: T = undefined; - inline for (si.fields) |field| { - const fti = @typeInfo(field.type); - dynamic: inline for (dynamics) |dyn| { - if (comptime std.mem.eql(u8, dyn.field_name, field.name)) { - if (fti != .Pointer) @compileError("field " ++ field.name ++ " is marked dynamic but isn't a pointer. Instead it's a " ++ @typeName(field.type)); - const pi = fti.Pointer; - if (pi.size != .Slice) @compileError("field " ++ field.name ++ " is marked dynamic, but isn't a slice, instead it's sized " ++ @tagName(pi.size)); - const len = @field(t, dyn.length_field_name); - var buf = try allocator.alloc(pi.child, len); - // TODO how to errdefer in a loop, not sure where the scope ends. - _ = try reader.readAll(buf); - @field(t, field.name) = buf; - break :dynamic; - } - } else { - switch (fti) { - .Int => { - @field(t, field.name) = try reader.readIntLittle(field.type); - }, - else => @compileError("don't know how to handle field " ++ field.name ++ " of type " ++ @tagName(fti)), - } - } + fn file_name(self: ZipFile, index: u16) []const u8 { + return self.central_directory_headers[index].file_name; } - return t; -} +}; const CentralDirectoryHeader = struct { const SIG: u32 = @as(u32, 0x02014b50); @@ -149,7 +107,7 @@ const CentralDirectoryHeader = struct { file_comment: []const u8, fn read(allocator: std.mem.Allocator, stream_or_file: anytype) !CentralDirectoryHeader { - return read2(allocator, stream_or_file, CentralDirectoryHeader, CentralDirectoryHeader.SIG, &[_]Dynamic{ + return read2(allocator, stream_or_file, CentralDirectoryHeader, &[_]Dynamic{ .{ .field_name = "file_name", .length_field_name = "file_name_length" }, .{ .field_name = "extra_field", .length_field_name = "extra_field_length" }, .{ .field_name = "file_comment", .length_field_name = "file_comment_length" }, @@ -175,7 +133,7 @@ const EndOfCentralDirectoryRecord = struct { comment: []const u8, fn read(allocator: std.mem.Allocator, file_or_stream: anytype) !EndOfCentralDirectoryRecord { - return read2(allocator, file_or_stream, EndOfCentralDirectoryRecord, EndOfCentralDirectoryRecord.SIG, &[_]Dynamic{ + return read2(allocator, file_or_stream, EndOfCentralDirectoryRecord, &[_]Dynamic{ .{ .field_name = "comment", .length_field_name = "comment_length" }, }); } @@ -185,12 +143,70 @@ const EndOfCentralDirectoryRecord = struct { } }; +const Dynamic = struct { + field_name: []const u8, + length_field_name: []const u8, +}; + +fn read2( + allocator: std.mem.Allocator, + stream_or_file: anytype, + comptime T: type, + comptime dynamics: []const Dynamic, +) !T { + if (!@hasDecl(T, "SIG")) @compileError("Expected decl SIG:u32 on type " ++ @typeName(T)); + const ti = @typeInfo(T); + if (ti != .Struct) @compileError("read2 expects type parameter T to be a struct, but it was a " ++ @typeName(T)); + const si = ti.Struct; + + var reader = stream_or_file.reader(); + const sig_actual = try reader.readIntLittle(u32); + if (sig_actual != T.SIG) { + std.log.err("invalid signature expected {x} got {x}", .{ T.SIG, sig_actual }); + return error.InvalidSignature; + } + var t: T = undefined; + inline for (si.fields) |field| { + const fti = @typeInfo(field.type); + dynamic: inline for (dynamics) |dyn| { + if (comptime std.mem.eql(u8, dyn.field_name, field.name)) { + if (fti != .Pointer) @compileError("field " ++ field.name ++ " is marked dynamic but isn't a pointer. Instead it's a " ++ @typeName(field.type)); + const pi = fti.Pointer; + if (pi.size != .Slice) @compileError("field " ++ field.name ++ " is marked dynamic, but isn't a slice, instead it's sized " ++ @tagName(pi.size)); + const len = @field(t, dyn.length_field_name); + var buf = try allocator.alloc(pi.child, len); + // TODO how to errdefer in a loop, not sure where the scope ends. + _ = try reader.readAll(buf); + @field(t, field.name) = buf; + break :dynamic; + } + } else { + switch (fti) { + .Int => { + @field(t, field.name) = try reader.readIntLittle(field.type); + }, + else => @compileError("don't know how to handle field " ++ field.name ++ " of type " ++ @tagName(fti)), + } + } + } + return t; +} + test "foo" { const test_zip = @embedFile("hello.zip"); var fbs = std.io.fixedBufferStream(test_zip); var zf = try ZipFile.from(std.testing.allocator, &fbs); defer zf.deinit(); - try std.testing.expectEqual(zf.central_directory_headers.len, 2); - try std.testing.expectEqualStrings(zf.central_directory_headers[0].file_name, "hello.txt"); - try std.testing.expectEqualStrings(zf.central_directory_headers[1].file_name, "foo.txt"); + 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" { + const test_zip = try std.fs.cwd().openFile("src/hello.zip", .{}); + var zf = try ZipFile.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"); + try std.testing.expectEqualStrings(zf.file_name(1), "foo.txt"); } -- cgit v1.2.3-ZIG