From d29b334ac905087a9938b273953578aad88eda3c Mon Sep 17 00:00:00 2001 From: Martin Ashby Date: Sun, 17 Sep 2023 20:59:41 +0100 Subject: Switch slice -> ArrayList for central_directory_headers Will help support writing new zip files or modifying existing ones. Add initial write methods for structures, TODO make it generic --- src/main.zig | 153 ++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 126 insertions(+), 27 deletions(-) diff --git a/src/main.zig b/src/main.zig index d154c8c..cfcac3e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -56,7 +56,15 @@ const CompressionMethod = enum(u16) { // [end of central directory record] allocator: std.mem.Allocator, end_of_central_directory_record: EndOfCentralDirectoryRecord, -central_directory_headers: []CentralDirectoryHeader, +central_directory_headers: std.ArrayList(CentralDirectoryHeader), + +pub fn empty(allocator: std.mem.Allocator) !@This() { + return .{ + .allocator = allocator, + .end_of_central_directory_record = EndOfCentralDirectoryRecord.empty(), + .central_directory_headers = std.ArrayList(CentralDirectoryHeader).init(allocator), + }; +} pub fn from(allocator: std.mem.Allocator, file_or_stream: anytype) !@This() { // Find the EndOfCentralDirectoryRecord. It must be in the last 64k of the file @@ -74,11 +82,22 @@ pub fn from(allocator: std.mem.Allocator, file_or_stream: anytype) !@This() { 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, reader); + + var central_directory_headers = try std.ArrayList(CentralDirectoryHeader).initCapacity(allocator, eocdr.total_central_dir_entries); + errdefer { + // while (central_directory_headers.popOrNull()) |cdh| { + // cdh.deinit(allocator); + // } + var cdh: ?CentralDirectoryHeader = central_directory_headers.popOrNull(); + while (cdh != null) { + cdh.?.deinit(allocator); + cdh = central_directory_headers.popOrNull(); + } + central_directory_headers.deinit(); + } + for (0..eocdr.total_central_dir_entries) |_| { + central_directory_headers.appendAssumeCapacity(try CentralDirectoryHeader.read(allocator, reader)); } return .{ @@ -88,7 +107,18 @@ pub fn from(allocator: std.mem.Allocator, file_or_stream: anytype) !@This() { }; } -/// returns how much to seekBy after the signature is found (becuase we'll now have read over it.) +pub fn deinit(self: *@This()) void { + self.end_of_central_directory_record.deinit(self.allocator); + var cdh: ?CentralDirectoryHeader = self.central_directory_headers.popOrNull(); + while (cdh != null) { + cdh.?.deinit(self.allocator); + cdh = self.central_directory_headers.popOrNull(); + } + self.central_directory_headers.deinit(); +} + + +/// returns how much to seekBy after the signature is found (becuase we'll now have read past it.) fn read_to_sig(reader: anytype, sig: u32) !i32 { const needle = @byteSwap(sig); var window: u32 = try reader.readIntLittle(u32); @@ -104,28 +134,20 @@ fn read_to_sig(reader: anytype, sig: u32) !i32 { } } -pub 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); - } - self.allocator.free(self.central_directory_headers); -} - pub fn count_files(self: @This()) u16 { return self.end_of_central_directory_record.total_central_dir_entries; } pub fn file_name(self: @This(), index: usize) []const u8 { - return self.central_directory_headers[index].file_name; + return self.central_directory_headers.items[index].file_name; } pub fn file_comment(self: @This(), index: usize) []const u8 { - return self.central_directory_headers[index].file_comment; + return self.central_directory_headers.items[index].file_comment; } pub fn is_dir(self: *@This(), index: usize) bool { - return std.mem.endsWith(u8, self.central_directory_headers[index].file_name, "/"); // This is what the java stdlib does + return std.mem.endsWith(u8, self.central_directory_headers.items[index].file_name, "/"); // This is what the java stdlib does } pub fn extract(self: @This(), index: usize, stream_or_file_in: anytype, writer: anytype) !void { - const cdh = self.central_directory_headers[index]; + const cdh = self.central_directory_headers.items[index]; try stream_or_file_in.seekTo(cdh.relative_offset_of_local_header); var reader = stream_or_file_in.reader(); var lfh = try LocalFileHeader.read(self.allocator, reader); @@ -180,6 +202,34 @@ const CentralDirectoryHeader = struct { }); } + fn write(self: *CentralDirectoryHeader, writer: anytype) !void { + // TODO generics + self.file_name_length = @intCast(self.file_name.len); + self.extra_field_length = @intCast(self.extra_field.len); + self.file_comment_length = @intCast(self.file_comment); + + try writer.writeIntLittle(u32, SIG); + try writer.writeIntLittle(u16, self.version_made_by); + try writer.writeIntLittle(u16, self.version_needed_to_extract); + try writer.writeIntLittle(u16, self.general_purpose_bit_flag); + try writer.writeIntLittle(u16, @intFromEnum(self.compression_method)); + try writer.writeIntLittle(u16, self.last_mod_file_time); + try writer.writeIntLittle(u16, self.last_mod_file_date); + try writer.writeIntLittle(u32, self.crc32); + try writer.writeIntLittle(u32, self.compressed_size); + try writer.writeIntLittle(u32, self.uncompressed_size); + try writer.writeIntLittle(u16, self.file_name_length); + try writer.writeIntLittle(u16, self.extra_field_length); + try writer.writeIntLittle(u16, self.file_comment_length); + try writer.writeIntLittle(u16, self.disk_number_start); + try writer.writeIntLittle(u16, self.internal_file_attributes); + try writer.writeIntLittle(u32, self.external_file_attributes); + try writer.writeIntLittle(u32, self.relative_offset_of_local_header); + try writer.writeAll(self.file_name); + try writer.writeAll(self.extra_field); + try writer.writeAll(self.file_comment); + } + fn deinit(self: *CentralDirectoryHeader, allocator: std.mem.Allocator) void { allocator.free(self.file_name); allocator.free(self.extra_field); @@ -204,6 +254,32 @@ const EndOfCentralDirectoryRecord = struct { }); } + fn empty() !EndOfCentralDirectoryRecord { + return .{ + .disk_number_this = 0, + .disk_number_central_dir_start = 0, + .total_central_dir_entries_on_this_disk = 0, + .total_central_dir_entries = 0, + .size_of_central_dir = 0, + .central_dir_offset = 0, + .comment_length = 0, + .comment = &[_]u8{}, + }; + } + + fn write(self: *EndOfCentralDirectoryRecord, writer: anytype) !void { + self.comment_length = @intCast(self.comment.len); + try writer.writeIntLittle(u32, SIG); + try writer.writeIntLittle(u16, self.disk_number_this); + try writer.writeIntLittle(u16, self.disk_number_central_dir_start); + try writer.writeIntLittle(u16, self.total_central_dir_entries_on_this_disk); + try writer.writeIntLittle(u16, self.total_central_dir_entries); + try writer.writeIntLittle(u32, self.size_of_central_dir); + try writer.writeIntLittle(u32, self.central_dir_offset); + try writer.writeIntLittle(u16, self.comment_length); + try writer.writeAll(self.comment); + } + fn deinit(self: *EndOfCentralDirectoryRecord, allocator: std.mem.Allocator) void { allocator.free(self.comment); } @@ -231,6 +307,24 @@ const LocalFileHeader = struct { }); } + fn write(self: *LocalFileHeader, writer: anytype) !void { + self.file_name_length = @intCast(self.file_name.len); + self.extra_field_length = @intCast(self.extra_field.len); + try writer.writeIntLittle(u32, SIG); + try writer.writeIntLittle(u16, self.version_needed_to_extract); + try writer.writeIntLittle(u16, self.general_purpose_bit_flag); + try writer.writeIntLittle(u16, self.compression_method); + try writer.writeIntLittle(u16, self.last_mod_file_time); + try writer.writeIntLittle(u16, self.last_mod_file_date); + try writer.writeIntLittle(u32, self.crc32); + try writer.writeIntLittle(u32, self.compressed_size); + try writer.writeIntLittle(u32, self.uncompressed_size); + try writer.writeIntLittle(u16, self.file_name_length); + try writer.writeIntLittle(u16, self.extra_field_length); + try writer.writeAll(self.file_name); + try writer.writeAll(self.extra_field); + } + fn deinit(self: *LocalFileHeader, allocator: std.mem.Allocator) void { allocator.free(self.file_name); allocator.free(self.extra_field); @@ -341,7 +435,8 @@ fn read2( pub const Options = struct { allocator: std.mem.Allocator, }; -// tar.zig compatibility, ish. It manages a forwards-only read of the filesystem anyway + +/// tar.zig compatibility, ish. It does a forwards only pass of a zipfile pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !void { const allocator = options.allocator; // var peek_stream = std.io.peekStream(4, reader); @@ -363,16 +458,20 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi var writer = f.writer(); try lfh.extract(allocator, reader, writer); } + // TODO skip data descriptor i+=1; - // try reader.skipBytes(12, .{}); // Data descriptor signature. - // _ = read_to_sig(peek_reader, LocalFileHeader.SIG) catch |e| switch (e) { - // error.EndOfStream => return, - // else => return e, - // }; - // try peek_stream.putBack(&@as([4]u8, @bitCast(LocalFileHeader.SIG))); } } +pub const WriteOptions = struct {}; + +/// Write a whole zip file. file_iterator +pub fn write_zip(file_iterator: anytype, writer: anytype) !void { + _ = writer; + _ = file_iterator; + +} + test "open stream" { const test_zip = @embedFile("hello.zip"); var fbs = std.io.fixedBufferStream(test_zip); @@ -416,7 +515,7 @@ test "extract deflate" { var fbs_out = std.io.fixedBufferStream(&out); var writer = fbs_out.writer(); try std.testing.expectEqualStrings("Here is a comment :)", zf.file_comment(0)); - try std.testing.expectEqual(@This().CompressionMethod.deflate, zf.central_directory_headers[0].compression_method); + try std.testing.expectEqual(@This().CompressionMethod.deflate, zf.central_directory_headers.items[0].compression_method); try zf.extract(0, &fbs, writer); try std.testing.expectEqualStrings(@embedFile("foo.txt"), fbs_out.getWritten()); } @@ -429,7 +528,7 @@ test "subdir" { try std.testing.expectEqual(false, zf.is_dir(1)); } -test "filesystem" { +test "pipe to filesystem" { var f = try std.fs.cwd().openFile("src/subfolder.zip", .{}); defer f.close(); try pipeToFileSystem(try std.fs.cwd().makeOpenPath("test", .{}), f.reader(), .{ .allocator = std.testing.allocator }); -- cgit v1.2.3-ZIG