diff options
-rw-r--r-- | src/bar.txt | 1 | ||||
-rw-r--r-- | src/main.zig | 143 |
2 files changed, 130 insertions, 14 deletions
diff --git a/src/bar.txt b/src/bar.txt new file mode 100644 index 0000000..adabbec --- /dev/null +++ b/src/bar.txt @@ -0,0 +1 @@ +hello there
\ No newline at end of file diff --git a/src/main.zig b/src/main.zig index cfcac3e..0d33600 100644 --- a/src/main.zig +++ b/src/main.zig @@ -58,7 +58,7 @@ allocator: std.mem.Allocator, end_of_central_directory_record: EndOfCentralDirectoryRecord, central_directory_headers: std.ArrayList(CentralDirectoryHeader), -pub fn empty(allocator: std.mem.Allocator) !@This() { +pub fn empty(allocator: std.mem.Allocator) @This() { return .{ .allocator = allocator, .end_of_central_directory_record = EndOfCentralDirectoryRecord.empty(), @@ -117,7 +117,6 @@ pub fn deinit(self: *@This()) void { 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); @@ -172,6 +171,25 @@ 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 } { + var buf = [_]u8{0} ** 1024; + var crc32 = std.hash.Crc32.init(); + var written: usize = 0; + while (true) { + const read = try reader.read(&buf); + if (read == 0) break; + const write = buf[0..read]; + try writer.writeAll(write); + + crc32.update(write); + written += read; + } + return .{ + .written = written, + .crc32 = crc32.final(), + }; +} + const CentralDirectoryHeader = struct { const SIG: u32 = @as(u32, 0x02014b50); version_made_by: u16, @@ -206,7 +224,7 @@ 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); try writer.writeIntLittle(u32, SIG); try writer.writeIntLittle(u16, self.version_made_by); @@ -230,6 +248,30 @@ const CentralDirectoryHeader = struct { try writer.writeAll(self.file_comment); } + fn from(allocator: std.mem.Allocator, lfh: LocalFileHeader, offset: u32) !CentralDirectoryHeader { + // TODO generics + return .{ + .version_needed_to_extract = lfh.version_needed_to_extract, + .general_purpose_bit_flag = lfh.general_purpose_bit_flag, + .compression_method = lfh.compression_method, + .last_mod_file_time = lfh.last_mod_file_time, + .last_mod_file_date = lfh.last_mod_file_date, + .crc32 = lfh.crc32, + .compressed_size = lfh.compressed_size, + .uncompressed_size = lfh.uncompressed_size, + .file_name_length = lfh.file_name_length, + .extra_field_length = lfh.extra_field_length, + .file_comment_length = 0, + .disk_number_start = 0, + .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{}, + }; + } + fn deinit(self: *CentralDirectoryHeader, allocator: std.mem.Allocator) void { allocator.free(self.file_name); allocator.free(self.extra_field); @@ -254,7 +296,7 @@ const EndOfCentralDirectoryRecord = struct { }); } - fn empty() !EndOfCentralDirectoryRecord { + fn empty() EndOfCentralDirectoryRecord { return .{ .disk_number_this = 0, .disk_number_central_dir_start = 0, @@ -307,6 +349,31 @@ const LocalFileHeader = struct { }); } + fn from(allocator: std.mem.Allocator, path: []const u8) !LocalFileHeader { + 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, .{}); + 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(), + .compression_method = .deflate, + .last_mod_file_time = 0, // TODO + .last_mod_file_date = 0, // TODO + .crc32 = crc32, + .compressed_size = @intCast(written), + .uncompressed_size = @intCast(md.size()), // TODO zip64 support + .file_name_length = path.len, + .extra_field_length = 0, + .file_name = allocator.dupe(u8, path), + .extra_field = &[_]u8{}, + }; + } + 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); @@ -436,15 +503,15 @@ pub const Options = struct { allocator: std.mem.Allocator, }; -/// tar.zig compatibility, ish. It does a forwards only pass of a zipfile +/// 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); // var peek_reader = peek_stream.reader(); - var i: u32 = 0; + var i: u32 = 0; while (true) { std.log.err("reading file {}", .{i}); - var lfh = LocalFileHeader.read(allocator, reader) catch |e| switch (e) { + var lfh = LocalFileHeader.read(allocator, reader) catch |e| switch (e) { error.InvalidSignature => return, // done else => return e, }; @@ -459,17 +526,66 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi try lfh.extract(allocator, reader, writer); } // TODO skip data descriptor - i+=1; + i += 1; } } pub const WriteOptions = struct {}; -/// Write a whole zip file. file_iterator -pub fn write_zip(file_iterator: anytype, writer: anytype) !void { - _ = writer; - _ = file_iterator; - +/// 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 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); + try zf.central_directory_headers.append(cdh); + try lfh.write(cw); + _ = pump_returning(file.reader(), cw); + } + const central_dir_offset = cw.bytes_written; + for (zf.central_directory_headers.items) |cdh| { + try cdh.write(cw); + } + 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, + .comment_length = 0, + .comment = &[_]u8{}, + }; + try eocdr.write(cw); +} + +const TestFileIter = struct { + files: [2][]const u8, + ix: usize, + fn next(self: TestFileIter) ?[]const u8 { + if (self.ix < 2) { + defer self.ix += 1; + return self.files[self.ix]; + } + return null; + } +}; + +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" { @@ -526,7 +642,6 @@ test "subdir" { 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", .{}); |