diff options
author | Martin Ashby <martin@ashbysoft.com> | 2023-09-18 21:14:31 +0100 |
---|---|---|
committer | Martin Ashby <martin@ashbysoft.com> | 2023-09-18 21:14:31 +0100 |
commit | da5b272e4b1d2d235ae36f8c7757b822bbe3e17d (patch) | |
tree | 53790d566c981a4844bb97670817fc9d394ffae8 /src/main.zig | |
parent | 0babeb00d7c47ea6b59fb3863a88cca9e2afd709 (diff) | |
download | zip-zig-da5b272e4b1d2d235ae36f8c7757b822bbe3e17d.tar.gz zip-zig-da5b272e4b1d2d235ae36f8c7757b822bbe3e17d.tar.bz2 zip-zig-da5b272e4b1d2d235ae36f8c7757b822bbe3e17d.tar.xz zip-zig-da5b272e4b1d2d235ae36f8c7757b822bbe3e17d.zip |
More work on writing zip
but it doesn't work yet
Diffstat (limited to 'src/main.zig')
-rw-r--r-- | src/main.zig | 246 |
1 files changed, 146 insertions, 100 deletions
diff --git a/src/main.zig b/src/main.zig index 0d33600..71def8f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -171,30 +171,30 @@ fn pump(reader: anytype, writer: anytype, expected_size_written: usize, expected if (crc32.final() != expected_crc32) return error.WrongChecksum; } -fn pump_returning(reader: anytype, writer: anytype) !struct { written: usize, crc32: u32 } { +fn pump_returning(reader: anytype, writer: anytype) !struct { u64, u32 } { + var cw = std.io.countingWriter(writer); + var cww = cw.writer(); var buf = [_]u8{0} ** 1024; var crc32 = std.hash.Crc32.init(); - var written: usize = 0; while (true) { + std.log.err("reading...", .{}); const read = try reader.read(&buf); if (read == 0) break; const write = buf[0..read]; - try writer.writeAll(write); + try cww.writeAll(write); crc32.update(write); - written += read; } - return .{ - .written = written, - .crc32 = crc32.final(), - }; + const res = .{cw.bytes_written,crc32.final()}; + std.log.err("res... {any}", .{res}); + return res; } const CentralDirectoryHeader = struct { const SIG: u32 = @as(u32, 0x02014b50); version_made_by: u16, version_needed_to_extract: u16, - general_purpose_bit_flag: u16, + general_purpose_bit_flag: std.bit_set.IntegerBitSet(16), compression_method: CompressionMethod, last_mod_file_time: u16, last_mod_file_date: u16, @@ -224,12 +224,12 @@ const CentralDirectoryHeader = struct { // 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); + self.file_comment_length = @intCast(self.file_comment.len); 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, @bitCast(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); @@ -251,6 +251,7 @@ const CentralDirectoryHeader = struct { fn from(allocator: std.mem.Allocator, lfh: LocalFileHeader, offset: u32) !CentralDirectoryHeader { // TODO generics return .{ + .version_made_by = 0, .version_needed_to_extract = lfh.version_needed_to_extract, .general_purpose_bit_flag = lfh.general_purpose_bit_flag, .compression_method = lfh.compression_method, @@ -266,9 +267,9 @@ const CentralDirectoryHeader = struct { .internal_file_attributes = 0, .external_file_attributes = 0, .relative_offset_of_local_header = offset, - .file_name = try allocator.dupe(lfh.file_name), - .extra_field = try allocator.dupe(lfh.extra_field), - .file_comment = [_]u8{}, + .file_name = try allocator.dupe(u8, lfh.file_name), + .extra_field = try allocator.dupe(u8, lfh.extra_field), + .file_comment = &[_]u8{}, }; } @@ -350,14 +351,16 @@ const LocalFileHeader = struct { } fn from(allocator: std.mem.Allocator, path: []const u8) !LocalFileHeader { + std.log.err("lfh.from {s}", .{path}); const file = try std.fs.cwd().openFile(path, .{}); defer file.close(); const md = try file.metadata(); var rdr = file.reader(); var cw = std.io.countingWriter(std.io.null_writer); - var comp = try std.compress.deflate.compressor(allocator, cw, .{}); + var comp = try std.compress.deflate.compressor(allocator, cw.writer(), .{}); defer comp.deinit(); const written, const crc32 = try pump_returning(rdr, comp.writer()); + return .{ .version_needed_to_extract = 1, .general_purpose_bit_flag = std.bit_set.IntegerBitSet(16).initEmpty(), @@ -367,9 +370,9 @@ const LocalFileHeader = struct { .crc32 = crc32, .compressed_size = @intCast(written), .uncompressed_size = @intCast(md.size()), // TODO zip64 support - .file_name_length = path.len, + .file_name_length = @intCast(path.len), .extra_field_length = 0, - .file_name = allocator.dupe(u8, path), + .file_name = try allocator.dupe(u8, path), .extra_field = &[_]u8{}, }; } @@ -379,8 +382,8 @@ const LocalFileHeader = struct { 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, @bitCast(self.general_purpose_bit_flag.mask)); + 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); @@ -437,6 +440,35 @@ const LocalFileHeader = struct { }, } } + + fn write_data(self: *LocalFileHeader, allocator: std.mem.Allocator, reader: anytype, writer: anytype) !void { + const is_encrypted = self.general_purpose_bit_flag.isSet(0); + if (is_encrypted) return error.EncryptionNotSupported; + + if (self.is_dir()) { + if (self.compressed_size != 0) { + // directories can't have a size, this is very likely wrong. + return error.InvalidFileName; + } + return; // Do nothing here. If we were definitely writing to a filesystem we could make an empty dir I guess. + } + + switch (self.compression_method) { + .store => { + _ = try pump_returning(reader, writer); + }, + .deflate => { + var comp = try std.compress.deflate.compressor(allocator, writer, .{}); + defer comp.deinit(); + var decomp_writer = comp.writer(); + _ = try pump_returning(reader, decomp_writer); + }, + else => { + std.log.err("compression method {} not supported", .{self.compression_method}); + return error.CompressionMethodNotSupported; + }, + } + } }; const Dynamic = struct { @@ -535,43 +567,49 @@ pub const WriteOptions = struct {}; /// Write a whole zip file pub fn write_zip(allocator: std.mem.Allocator, path_iterator: anytype, writer: anytype) !void { var cw = std.io.countingWriter(writer); + var cww = cw.writer(); var zf = @This().empty(allocator); defer zf.deinit(); while (path_iterator.next()) |path| { - const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); var lfh = try LocalFileHeader.from(allocator, path); defer lfh.deinit(allocator); - var cdh = try CentralDirectoryHeader.from(allocator, lfh, cw.bytes_written); + const file = try std.fs.cwd().openFile(path, .{}); + defer file.close(); + var cdh = try CentralDirectoryHeader.from(allocator, lfh, @intCast(cw.bytes_written)); try zf.central_directory_headers.append(cdh); - try lfh.write(cw); - _ = pump_returning(file.reader(), cw); + std.log.err("lfh {any}", .{lfh}); + try lfh.write(cww); + try lfh.write_data(allocator, file.reader(), cww); } const central_dir_offset = cw.bytes_written; - for (zf.central_directory_headers.items) |cdh| { - try cdh.write(cw); + for (0..zf.central_directory_headers.items.len) |i| { + var cdh = zf.central_directory_headers.items[i]; + std.log.err("cdh {any}", .{cdh}); + try cdh.write(cww); } const size_of_central_dir = cw.bytes_written - central_dir_offset; var eocdr = EndOfCentralDirectoryRecord{ .disk_number_this = 0, .disk_number_central_dir_start = 0, - .total_central_dir_entries_on_this_disk = zf.central_directory_headers.items.len, - .total_central_dir_entries = zf.central_directory_headers.items.len, - .size_of_central_dir = size_of_central_dir, - .central_dir_offset = central_dir_offset, + .total_central_dir_entries_on_this_disk = @intCast(zf.central_directory_headers.items.len), + .total_central_dir_entries = @intCast(zf.central_directory_headers.items.len), + .size_of_central_dir = @intCast(size_of_central_dir), + .central_dir_offset = @intCast(central_dir_offset), // TODO zip64 support .comment_length = 0, .comment = &[_]u8{}, }; - try eocdr.write(cw); + std.log.err("eocdr {any}", .{eocdr}); + try eocdr.write(cww); } const TestFileIter = struct { files: [2][]const u8, ix: usize, - fn next(self: TestFileIter) ?[]const u8 { + fn next(self: *TestFileIter) ?[]const u8 { if (self.ix < 2) { - defer self.ix += 1; - return self.files[self.ix]; + const res = self.files[self.ix]; + self.ix += 1; + return res; } return null; } @@ -579,72 +617,80 @@ const TestFileIter = struct { test "write" { const allocator = std.testing.allocator; - const out = try std.fs.cwd().createFile("test_out.zip", .{}); - defer out.close(); - const tfi = TestFileIter{ - .ix = 0, - .files = [_][]const u8{ "src/foo.txt", "src/bar.txt" }, - }; - try write_zip(allocator, tfi, out.writer()); -} - -test "open stream" { - const test_zip = @embedFile("hello.zip"); - var fbs = std.io.fixedBufferStream(test_zip); - 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 "open file" { - const test_zip = try std.fs.cwd().openFile("src/hello.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"); - try std.testing.expectEqualStrings(zf.file_name(1), "foo.txt"); -} + { + const out = try std.fs.cwd().createFile("test_out.zip", .{}); + defer out.close(); + var tfi = TestFileIter{ + .ix = 0, + .files = [_][]const u8{ "src/foo.txt", "src/bar.txt" }, + }; + try write_zip(allocator, &tfi, out.writer()); + } -test "extract stored" { - const test_zip = @embedFile("hello.zip"); - var fbs = std.io.fixedBufferStream(test_zip); - var zf = try @This().from(std.testing.allocator, &fbs); - defer zf.deinit(); - var out = [_]u8{0} ** 1024; - var fbs_out = std.io.fixedBufferStream(&out); - var writer = fbs_out.writer(); - try zf.extract(0, &fbs, writer); - try std.testing.expectEqualStrings("Hello, Zip!", fbs_out.getWritten()); - fbs_out.reset(); - try zf.extract(1, &fbs, writer); - try std.testing.expectEqualStrings("hi there\n", fbs_out.getWritten()); + { + const out = try std.fs.cwd().openFile("test_out.zip", .{}); + defer out.close(); + try pipeToFileSystem(try std.fs.cwd().makeOpenPath("test_out", .{}), out.reader(), .{.allocator = allocator}); + } } -test "extract deflate" { - const test_zip = @embedFile("deflate.zip"); - var fbs = std.io.fixedBufferStream(test_zip); - var zf = try @This().from(std.testing.allocator, &fbs); - defer zf.deinit(); - var out = [_]u8{0} ** 1024; - 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.items[0].compression_method); - try zf.extract(0, &fbs, writer); - try std.testing.expectEqualStrings(@embedFile("foo.txt"), fbs_out.getWritten()); -} -test "subdir" { - const test_zip = @embedFile("subfolder.zip"); - var fbs = std.io.fixedBufferStream(test_zip); - var zf = try @This().from(std.testing.allocator, &fbs); - defer zf.deinit(); - try std.testing.expectEqual(true, zf.is_dir(0)); - try std.testing.expectEqual(false, zf.is_dir(1)); -} -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 }); -} +// test "open stream" { +// const test_zip = @embedFile("hello.zip"); +// var fbs = std.io.fixedBufferStream(test_zip); +// 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 "open file" { +// const test_zip = try std.fs.cwd().openFile("src/hello.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"); +// try std.testing.expectEqualStrings(zf.file_name(1), "foo.txt"); +// } + +// test "extract stored" { +// const test_zip = @embedFile("hello.zip"); +// var fbs = std.io.fixedBufferStream(test_zip); +// var zf = try @This().from(std.testing.allocator, &fbs); +// defer zf.deinit(); +// var out = [_]u8{0} ** 1024; +// var fbs_out = std.io.fixedBufferStream(&out); +// var writer = fbs_out.writer(); +// try zf.extract(0, &fbs, writer); +// try std.testing.expectEqualStrings("Hello, Zip!", fbs_out.getWritten()); +// fbs_out.reset(); +// try zf.extract(1, &fbs, writer); +// try std.testing.expectEqualStrings("hi there\n", fbs_out.getWritten()); +// } + +// test "extract deflate" { +// const test_zip = @embedFile("deflate.zip"); +// var fbs = std.io.fixedBufferStream(test_zip); +// var zf = try @This().from(std.testing.allocator, &fbs); +// defer zf.deinit(); +// var out = [_]u8{0} ** 1024; +// 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.items[0].compression_method); +// try zf.extract(0, &fbs, writer); +// try std.testing.expectEqualStrings(@embedFile("foo.txt"), fbs_out.getWritten()); +// } +// test "subdir" { +// const test_zip = @embedFile("subfolder.zip"); +// var fbs = std.io.fixedBufferStream(test_zip); +// var zf = try @This().from(std.testing.allocator, &fbs); +// defer zf.deinit(); +// try std.testing.expectEqual(true, zf.is_dir(0)); +// try std.testing.expectEqual(false, zf.is_dir(1)); +// } +// 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 }); +// } |