summaryrefslogtreecommitdiff
path: root/src/main.zig
diff options
context:
space:
mode:
authorMartin Ashby <martin@ashbysoft.com>2023-09-18 21:14:31 +0100
committerMartin Ashby <martin@ashbysoft.com>2023-09-18 21:14:31 +0100
commitda5b272e4b1d2d235ae36f8c7757b822bbe3e17d (patch)
tree53790d566c981a4844bb97670817fc9d394ffae8 /src/main.zig
parent0babeb00d7c47ea6b59fb3863a88cca9e2afd709 (diff)
downloadzip-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.zig246
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 });
+// }