summaryrefslogtreecommitdiff
path: root/src/main.zig
diff options
context:
space:
mode:
authorMartin Ashby <martin@ashbysoft.com>2023-09-15 08:12:57 +0100
committerMartin Ashby <martin@ashbysoft.com>2023-09-15 08:12:57 +0100
commit145be0de76f0cef3b4c6b954d1a540cfebb20019 (patch)
tree36781f931234fb6e988a4db80085a444da1f236f /src/main.zig
parentd20ca2ef83fd4e137207c8425080147352997457 (diff)
downloadzip-zig-145be0de76f0cef3b4c6b954d1a540cfebb20019.tar.gz
zip-zig-145be0de76f0cef3b4c6b954d1a540cfebb20019.tar.bz2
zip-zig-145be0de76f0cef3b4c6b954d1a540cfebb20019.tar.xz
zip-zig-145be0de76f0cef3b4c6b954d1a540cfebb20019.zip
Rearrange code
Add some convenience methods to ZipFile
Diffstat (limited to 'src/main.zig')
-rw-r--r--src/main.zig162
1 files changed, 89 insertions, 73 deletions
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");
}