summaryrefslogtreecommitdiff
path: root/src/main.zig
diff options
context:
space:
mode:
authorMartin Ashby <martin@ashbysoft.com>2023-09-18 09:00:46 +0100
committerMartin Ashby <martin@ashbysoft.com>2023-09-18 09:00:46 +0100
commit0babeb00d7c47ea6b59fb3863a88cca9e2afd709 (patch)
tree3dccf18b817b467129417dd16c92ff1d5188b23a /src/main.zig
parentd29b334ac905087a9938b273953578aad88eda3c (diff)
downloadzip-zig-0babeb00d7c47ea6b59fb3863a88cca9e2afd709.tar.gz
zip-zig-0babeb00d7c47ea6b59fb3863a88cca9e2afd709.tar.bz2
zip-zig-0babeb00d7c47ea6b59fb3863a88cca9e2afd709.tar.xz
zip-zig-0babeb00d7c47ea6b59fb3863a88cca9e2afd709.zip
More work on writing ZIPs
Diffstat (limited to 'src/main.zig')
-rw-r--r--src/main.zig143
1 files changed, 129 insertions, 14 deletions
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", .{});