main.zig (27902B)
1 const std = @import("std"); 2 3 // ZIP file implementation 4 // See spec.txt. 5 6 pub const CompressionMethod = enum(u16) { 7 store = 0, 8 shrink = 1, 9 reduce_1 = 2, 10 reduce_2 = 3, 11 reduce_3 = 4, 12 reduce_4 = 5, 13 implode = 6, 14 reserved_1 = 7, 15 deflate = 8, 16 deflate64 = 9, 17 pkware_implode = 10, 18 reserved_2 = 11, 19 bzip2 = 12, 20 reserved_3 = 13, 21 lzma = 14, 22 reserved_4 = 15, 23 ibm_zos_zmpsc = 16, 24 reserved_5 = 17, 25 ibm_terse = 18, 26 ibm_lz77_z = 19, 27 zstd_deprecated = 20, 28 zstd = 93, 29 mp3 = 94, 30 xz = 95, 31 jpeg = 96, 32 wavpack = 97, 33 ppmd_version_i_rev1 = 98, 34 aex_encryption_marker = 99, 35 }; 36 // [local file header 1] 37 // [encryption header 1] 38 // [file data 1] 39 // [data descriptor 1] 40 // . 41 // . 42 // . 43 // [local file header n] 44 // [encryption header n] 45 // [file data n] 46 // [data descriptor n] 47 // [archive decryption header] 48 // [archive extra data record] 49 // [central directory header 1] 50 // . 51 // . 52 // . 53 // [central directory header n] 54 // [zip64 end of central directory record] 55 // [zip64 end of central directory locator] 56 // [end of central directory record] 57 allocator: std.mem.Allocator, 58 end_of_central_directory_record: EndOfCentralDirectoryRecord, 59 central_directory_headers: std.ArrayList(CentralDirectoryHeader), 60 61 pub fn empty(allocator: std.mem.Allocator) @This() { 62 return .{ 63 .allocator = allocator, 64 .end_of_central_directory_record = EndOfCentralDirectoryRecord.empty(), 65 .central_directory_headers = std.ArrayList(CentralDirectoryHeader).init(allocator), 66 }; 67 } 68 69 pub fn from(allocator: std.mem.Allocator, file_or_stream: anytype) !@This() { 70 // Find the EndOfCentralDirectoryRecord. It must be in the last 64k of the file 71 const eocdr_search_width_max: usize = 64_000; 72 const epos = try file_or_stream.getEndPos(); 73 const eocdr_search_width: usize = @min(epos, eocdr_search_width_max); 74 const eocdr_seek_start: usize = epos - eocdr_search_width; 75 try file_or_stream.seekTo(eocdr_seek_start); 76 var reader = file_or_stream.reader(); 77 const sb = try read_to_sig(reader, EndOfCentralDirectoryRecord.SIG); 78 try file_or_stream.seekBy(sb); 79 80 var eocdr = try EndOfCentralDirectoryRecord.read(allocator, reader); 81 errdefer eocdr.deinit(allocator); 82 if (eocdr.disk_number_this != 0 or eocdr.disk_number_central_dir_start != 0) return error.SpansNotSupported; 83 if (eocdr.total_central_dir_entries != eocdr.total_central_dir_entries_on_this_disk) return error.SpansNotSupported; 84 85 try file_or_stream.seekTo(eocdr.central_dir_offset); 86 87 var central_directory_headers = try std.ArrayList(CentralDirectoryHeader).initCapacity(allocator, eocdr.total_central_dir_entries); 88 errdefer { 89 // while (central_directory_headers.popOrNull()) |cdh| { 90 // cdh.deinit(allocator); 91 // } 92 var cdh: ?CentralDirectoryHeader = central_directory_headers.popOrNull(); 93 while (cdh != null) { 94 cdh.?.deinit(allocator); 95 cdh = central_directory_headers.popOrNull(); 96 } 97 central_directory_headers.deinit(); 98 } 99 for (0..eocdr.total_central_dir_entries) |_| { 100 central_directory_headers.appendAssumeCapacity(try CentralDirectoryHeader.read(allocator, reader)); 101 } 102 103 return .{ 104 .allocator = allocator, 105 .end_of_central_directory_record = eocdr, 106 .central_directory_headers = central_directory_headers, 107 }; 108 } 109 110 pub fn deinit(self: *@This()) void { 111 self.end_of_central_directory_record.deinit(self.allocator); 112 var cdh: ?CentralDirectoryHeader = self.central_directory_headers.popOrNull(); 113 while (cdh != null) { 114 cdh.?.deinit(self.allocator); 115 cdh = self.central_directory_headers.popOrNull(); 116 } 117 self.central_directory_headers.deinit(); 118 } 119 120 /// returns how much to seekBy after the signature is found (becuase we'll now have read past it.) 121 fn read_to_sig(reader: anytype, sig: u32) !i32 { 122 const needle = @byteSwap(sig); 123 var window: u32 = try reader.readIntLittle(u32); 124 while (true) { 125 if (window == needle) { 126 return -4; 127 } 128 const nb = try reader.readByte(); 129 window <<= 8; 130 window |= nb; 131 } else { 132 return error.SignatureNotFound; 133 } 134 } 135 136 pub fn count_files(self: @This()) u16 { 137 return self.end_of_central_directory_record.total_central_dir_entries; 138 } 139 pub fn file_name(self: @This(), index: usize) []const u8 { 140 return self.central_directory_headers.items[index].file_name; 141 } 142 pub fn file_comment(self: @This(), index: usize) []const u8 { 143 return self.central_directory_headers.items[index].file_comment; 144 } 145 pub fn is_dir(self: *@This(), index: usize) bool { 146 return std.mem.endsWith(u8, self.central_directory_headers.items[index].file_name, "/"); // This is what the java stdlib does 147 } 148 pub fn extract(self: @This(), index: usize, stream_or_file_in: anytype, writer: anytype) !void { 149 const cdh = self.central_directory_headers.items[index]; 150 try stream_or_file_in.seekTo(cdh.relative_offset_of_local_header); 151 var reader = stream_or_file_in.reader(); 152 var lfh = try LocalFileHeader.read(self.allocator, reader); 153 defer lfh.deinit(self.allocator); 154 return try lfh.extract(self.allocator, reader, writer); 155 } 156 157 fn pump(reader: anytype, writer: anytype, expected_size_written: usize, expected_crc32: u32) !void { 158 var buf = [_]u8{0} ** 1024; 159 var crc32 = std.hash.Crc32.init(); 160 var written: usize = 0; 161 while (true) { 162 const read = try reader.read(&buf); 163 if (read == 0) break; 164 const write = buf[0..read]; 165 try writer.writeAll(write); 166 167 crc32.update(write); 168 written += read; 169 } 170 if (written != expected_size_written) return error.WrongUncompressedSize; 171 if (crc32.final() != expected_crc32) return error.WrongChecksum; 172 } 173 174 fn pump_returning(reader: anytype, writer: anytype) !struct { u64, u32 } { 175 var cw = std.io.countingWriter(writer); 176 var cww = cw.writer(); 177 var buf = [_]u8{0} ** 1024; 178 var crc32 = std.hash.Crc32.init(); 179 while (true) { 180 std.log.err("reading...", .{}); 181 const read = try reader.read(&buf); 182 if (read == 0) break; 183 const write = buf[0..read]; 184 try cww.writeAll(write); 185 186 crc32.update(write); 187 } 188 const res = .{ cw.bytes_written, crc32.final() }; 189 std.log.err("res... {any}", .{res}); 190 return res; 191 } 192 193 pub const CentralDirectoryHeader = struct { 194 const SIG: u32 = @as(u32, 0x02014b50); 195 version_made_by: u16, 196 version_needed_to_extract: u16, 197 general_purpose_bit_flag: std.bit_set.IntegerBitSet(16), 198 compression_method: CompressionMethod, 199 last_mod_file_time: u16, 200 last_mod_file_date: u16, 201 crc32: u32, 202 compressed_size: u32, 203 uncompressed_size: u32, 204 file_name_length: u16, 205 extra_field_length: u16, 206 file_comment_length: u16, 207 disk_number_start: u16, 208 internal_file_attributes: u16, 209 external_file_attributes: u32, 210 relative_offset_of_local_header: u32, 211 file_name: []const u8, 212 extra_field: []const u8, 213 file_comment: []const u8, 214 215 pub fn read(allocator: std.mem.Allocator, reader: anytype) !CentralDirectoryHeader { 216 return read2(allocator, reader, CentralDirectoryHeader, &[_]Dynamic{ 217 .{ .field_name = "file_name", .length_field_name = "file_name_length" }, 218 .{ .field_name = "extra_field", .length_field_name = "extra_field_length" }, 219 .{ .field_name = "file_comment", .length_field_name = "file_comment_length" }, 220 }); 221 } 222 223 pub fn write(self: *CentralDirectoryHeader, writer: anytype) !void { 224 // TODO generics 225 self.file_name_length = @intCast(self.file_name.len); 226 self.extra_field_length = @intCast(self.extra_field.len); 227 self.file_comment_length = @intCast(self.file_comment.len); 228 229 try writer.writeIntLittle(u32, SIG); 230 try writer.writeIntLittle(u16, self.version_made_by); 231 try writer.writeIntLittle(u16, self.version_needed_to_extract); 232 try writer.writeIntLittle(u16, @bitCast(self.general_purpose_bit_flag)); 233 try writer.writeIntLittle(u16, @intFromEnum(self.compression_method)); 234 try writer.writeIntLittle(u16, self.last_mod_file_time); 235 try writer.writeIntLittle(u16, self.last_mod_file_date); 236 try writer.writeIntLittle(u32, self.crc32); 237 try writer.writeIntLittle(u32, self.compressed_size); 238 try writer.writeIntLittle(u32, self.uncompressed_size); 239 try writer.writeIntLittle(u16, self.file_name_length); 240 try writer.writeIntLittle(u16, self.extra_field_length); 241 try writer.writeIntLittle(u16, self.file_comment_length); 242 try writer.writeIntLittle(u16, self.disk_number_start); 243 try writer.writeIntLittle(u16, self.internal_file_attributes); 244 try writer.writeIntLittle(u32, self.external_file_attributes); 245 try writer.writeIntLittle(u32, self.relative_offset_of_local_header); 246 try writer.writeAll(self.file_name); 247 try writer.writeAll(self.extra_field); 248 try writer.writeAll(self.file_comment); 249 } 250 251 pub fn from(allocator: std.mem.Allocator, lfh: LocalFileHeader, offset: u32) !CentralDirectoryHeader { 252 // TODO generics 253 return .{ 254 .version_made_by = 0, 255 .version_needed_to_extract = lfh.version_needed_to_extract, 256 .general_purpose_bit_flag = lfh.general_purpose_bit_flag, 257 .compression_method = lfh.compression_method, 258 .last_mod_file_time = lfh.last_mod_file_time, 259 .last_mod_file_date = lfh.last_mod_file_date, 260 .crc32 = lfh.crc32, 261 .compressed_size = lfh.compressed_size, 262 .uncompressed_size = lfh.uncompressed_size, 263 .file_name_length = lfh.file_name_length, 264 .extra_field_length = lfh.extra_field_length, 265 .file_comment_length = 0, 266 .disk_number_start = 0, 267 .internal_file_attributes = 0, 268 .external_file_attributes = 0, 269 .relative_offset_of_local_header = offset, 270 .file_name = try allocator.dupe(u8, lfh.file_name), 271 .extra_field = try allocator.dupe(u8, lfh.extra_field), 272 .file_comment = &[_]u8{}, 273 }; 274 } 275 276 pub fn deinit(self: *CentralDirectoryHeader, allocator: std.mem.Allocator) void { 277 allocator.free(self.file_name); 278 allocator.free(self.extra_field); 279 allocator.free(self.file_comment); 280 } 281 }; 282 283 pub const EndOfCentralDirectoryRecord = struct { 284 const SIG: u32 = @as(u32, 0x06054b50); 285 disk_number_this: u16, 286 disk_number_central_dir_start: u16, 287 total_central_dir_entries_on_this_disk: u16, 288 total_central_dir_entries: u16, 289 size_of_central_dir: u32, 290 central_dir_offset: u32, 291 comment_length: u16, 292 comment: []const u8, 293 294 pub fn read(allocator: std.mem.Allocator, reader: anytype) !EndOfCentralDirectoryRecord { 295 return read2(allocator, reader, EndOfCentralDirectoryRecord, &[_]Dynamic{ 296 .{ .field_name = "comment", .length_field_name = "comment_length" }, 297 }); 298 } 299 300 pub fn empty() EndOfCentralDirectoryRecord { 301 return .{ 302 .disk_number_this = 0, 303 .disk_number_central_dir_start = 0, 304 .total_central_dir_entries_on_this_disk = 0, 305 .total_central_dir_entries = 0, 306 .size_of_central_dir = 0, 307 .central_dir_offset = 0, 308 .comment_length = 0, 309 .comment = &[_]u8{}, 310 }; 311 } 312 313 pub fn write(self: *EndOfCentralDirectoryRecord, writer: anytype) !void { 314 self.comment_length = @intCast(self.comment.len); 315 try writer.writeIntLittle(u32, SIG); 316 try writer.writeIntLittle(u16, self.disk_number_this); 317 try writer.writeIntLittle(u16, self.disk_number_central_dir_start); 318 try writer.writeIntLittle(u16, self.total_central_dir_entries_on_this_disk); 319 try writer.writeIntLittle(u16, self.total_central_dir_entries); 320 try writer.writeIntLittle(u32, self.size_of_central_dir); 321 try writer.writeIntLittle(u32, self.central_dir_offset); 322 try writer.writeIntLittle(u16, self.comment_length); 323 try writer.writeAll(self.comment); 324 } 325 326 pub fn deinit(self: *EndOfCentralDirectoryRecord, allocator: std.mem.Allocator) void { 327 allocator.free(self.comment); 328 } 329 }; 330 331 pub const LocalFileHeader = struct { 332 const SIG: u32 = @as(u32, 0x04034b50); 333 version_needed_to_extract: u16, 334 general_purpose_bit_flag: std.bit_set.IntegerBitSet(16), 335 compression_method: CompressionMethod, 336 last_mod_file_time: u16, 337 last_mod_file_date: u16, 338 crc32: u32, 339 compressed_size: u32, 340 uncompressed_size: u32, 341 file_name_length: u16, 342 extra_field_length: u16, 343 file_name: []const u8, 344 extra_field: []const u8, 345 346 pub fn read(allocator: std.mem.Allocator, reader: anytype) !LocalFileHeader { 347 return read2(allocator, reader, LocalFileHeader, &[_]Dynamic{ 348 .{ .field_name = "file_name", .length_field_name = "file_name_length" }, 349 .{ .field_name = "extra_field", .length_field_name = "extra_field_length" }, 350 }); 351 } 352 353 pub fn from(allocator: std.mem.Allocator, path: []const u8) !LocalFileHeader { 354 std.log.err("lfh.from {s}", .{path}); 355 const file = try std.fs.cwd().openFile(path, .{}); 356 defer file.close(); 357 const md = try file.metadata(); 358 var rdr = file.reader(); 359 var cw = std.io.countingWriter(std.io.null_writer); 360 var comp = try std.compress.deflate.compressor(allocator, cw.writer(), .{}); 361 defer comp.deinit(); 362 const written, const crc32 = try pump_returning(rdr, comp.writer()); 363 364 return .{ 365 .version_needed_to_extract = 1, 366 .general_purpose_bit_flag = std.bit_set.IntegerBitSet(16).initEmpty(), 367 .compression_method = .deflate, 368 .last_mod_file_time = 0, // TODO 369 .last_mod_file_date = 0, // TODO 370 .crc32 = crc32, 371 .compressed_size = @intCast(written), 372 .uncompressed_size = @intCast(md.size()), // TODO zip64 support 373 .file_name_length = @intCast(path.len), 374 .extra_field_length = 0, 375 .file_name = try allocator.dupe(u8, path), 376 .extra_field = &[_]u8{}, 377 }; 378 } 379 380 pub fn write(self: *LocalFileHeader, writer: anytype) !void { 381 self.file_name_length = @intCast(self.file_name.len); 382 self.extra_field_length = @intCast(self.extra_field.len); 383 try writer.writeIntLittle(u32, SIG); 384 try writer.writeIntLittle(u16, self.version_needed_to_extract); 385 try writer.writeIntLittle(u16, @bitCast(self.general_purpose_bit_flag.mask)); 386 try writer.writeIntLittle(u16, @intFromEnum(self.compression_method)); 387 try writer.writeIntLittle(u16, self.last_mod_file_time); 388 try writer.writeIntLittle(u16, self.last_mod_file_date); 389 try writer.writeIntLittle(u32, self.crc32); 390 try writer.writeIntLittle(u32, self.compressed_size); 391 try writer.writeIntLittle(u32, self.uncompressed_size); 392 try writer.writeIntLittle(u16, self.file_name_length); 393 try writer.writeIntLittle(u16, self.extra_field_length); 394 try writer.writeAll(self.file_name); 395 try writer.writeAll(self.extra_field); 396 } 397 398 pub fn deinit(self: *LocalFileHeader, allocator: std.mem.Allocator) void { 399 allocator.free(self.file_name); 400 allocator.free(self.extra_field); 401 } 402 403 pub fn is_dir(self: *LocalFileHeader) bool { 404 return std.mem.endsWith(u8, self.file_name, "/"); // This is what the java stdlib does 405 } 406 407 pub fn extract(self: *LocalFileHeader, allocator: std.mem.Allocator, reader: anytype, writer: anytype) !void { 408 const is_encrypted = self.general_purpose_bit_flag.isSet(0); 409 if (is_encrypted) return error.EncryptionNotSupported; 410 411 if (self.is_dir()) { 412 if (self.compressed_size != 0) { 413 // directories can't have a size, this is very likely wrong. 414 return error.InvalidFileName; 415 } 416 return; // Do nothing here. If we were definitely writing to a filesystem we could make an empty dir I guess. 417 } 418 419 var lr = std.io.limitedReader(reader, self.compressed_size); 420 var limited_reader = lr.reader(); 421 switch (self.compression_method) { 422 .store => { 423 try pump(limited_reader, writer, self.uncompressed_size, self.crc32); 424 }, 425 .deflate => { 426 var decomp = try std.compress.deflate.decompressor(allocator, limited_reader, null); 427 defer decomp.deinit(); 428 var decomp_reader = decomp.reader(); 429 try pump(decomp_reader, writer, self.uncompressed_size, self.crc32); 430 }, 431 .lzma => { 432 var decomp = try std.compress.lzma.decompress(allocator, limited_reader); 433 defer decomp.deinit(); 434 var decomp_reader = decomp.reader(); 435 try pump(decomp_reader, writer, self.uncompressed_size, self.crc32); 436 }, 437 else => { 438 std.log.err("compression method {} not supported", .{self.compression_method}); 439 return error.CompressionMethodNotSupported; 440 }, 441 } 442 } 443 444 pub fn write_data(self: *LocalFileHeader, allocator: std.mem.Allocator, reader: anytype, writer: anytype) !void { 445 const is_encrypted = self.general_purpose_bit_flag.isSet(0); 446 if (is_encrypted) return error.EncryptionNotSupported; 447 448 if (self.is_dir()) { 449 if (self.compressed_size != 0) { 450 // directories can't have a size, this is very likely wrong. 451 return error.InvalidFileName; 452 } 453 return; // Do nothing here. If we were definitely writing to a filesystem we could make an empty dir I guess. 454 } 455 456 switch (self.compression_method) { 457 .store => { 458 _ = try pump_returning(reader, writer); 459 }, 460 .deflate => { 461 var comp = try std.compress.deflate.compressor(allocator, writer, .{}); 462 defer comp.deinit(); 463 var decomp_writer = comp.writer(); 464 _ = try pump_returning(reader, decomp_writer); 465 }, 466 else => { 467 std.log.err("compression method {} not supported", .{self.compression_method}); 468 return error.CompressionMethodNotSupported; 469 }, 470 } 471 } 472 }; 473 474 const Dynamic = struct { 475 field_name: []const u8, 476 length_field_name: []const u8, 477 }; 478 479 fn read2( 480 allocator: std.mem.Allocator, 481 reader: anytype, 482 comptime T: type, 483 comptime dynamics: []const Dynamic, 484 ) !T { 485 if (!@hasDecl(T, "SIG")) @compileError("Expected decl SIG:u32 on type " ++ @typeName(T)); 486 const ti = @typeInfo(T); 487 if (ti != .Struct) @compileError("read2 expects type parameter T to be a struct, but it was a " ++ @typeName(T)); 488 const si = ti.Struct; 489 490 const sig_actual = try reader.readIntLittle(u32); 491 if (sig_actual != T.SIG) { 492 std.log.err("invalid signature expected {x} got {x}", .{ T.SIG, sig_actual }); 493 return error.InvalidSignature; 494 } 495 var t: T = undefined; 496 inline for (si.fields) |field| { 497 const fti = @typeInfo(field.type); 498 dynamic: inline for (dynamics) |dyn| { 499 if (comptime std.mem.eql(u8, dyn.field_name, field.name)) { 500 if (fti != .Pointer) @compileError("field " ++ field.name ++ " is marked dynamic but isn't a pointer. Instead it's a " ++ @typeName(field.type)); 501 const pi = fti.Pointer; 502 if (pi.size != .Slice) @compileError("field " ++ field.name ++ " is marked dynamic, but isn't a slice, instead it's sized " ++ @tagName(pi.size)); 503 const len = @field(t, dyn.length_field_name); 504 var buf = try allocator.alloc(pi.child, len); 505 // TODO how to errdefer in a loop, not sure where the scope ends. 506 _ = try reader.readAll(buf); 507 @field(t, field.name) = buf; 508 break :dynamic; 509 } 510 } else { 511 switch (fti) { 512 .Int => { 513 @field(t, field.name) = try reader.readIntLittle(field.type); 514 }, 515 .Struct => |fsi| { 516 if (fsi.backing_integer) |bi| { 517 const int = try reader.readIntLittle(bi); 518 @field(t, field.name) = @bitCast(int); 519 } else @compileError("only packed struct with backing integer are supported, field " ++ field.name ++ " type " ++ @typeName(field.type) ++ "is not such a struct"); 520 }, 521 .Enum => |fei| { 522 if (@typeInfo(fei.tag_type) == .Int) { 523 const int = try reader.readIntLittle(fei.tag_type); 524 @field(t, field.name) = @enumFromInt(int); 525 } else @compileError("only enum with integer tag type are supported field " ++ field.name ++ " type " ++ @typeName(field.type) ++ "is not such a struct"); 526 }, 527 else => @compileError("don't know how to handle field " ++ field.name ++ " of type " ++ @tagName(fti)), 528 } 529 } 530 } 531 return t; 532 } 533 534 pub const Options = struct { 535 allocator: std.mem.Allocator, 536 }; 537 538 /// tar.zig compatibility, ish. It does a forwards only pass of a zipfile 539 pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !void { 540 const allocator = options.allocator; 541 // var peek_stream = std.io.peekStream(4, reader); 542 // var peek_reader = peek_stream.reader(); 543 var i: u32 = 0; 544 while (true) { 545 std.log.err("reading file {}", .{i}); 546 var lfh = LocalFileHeader.read(allocator, reader) catch |e| switch (e) { 547 error.InvalidSignature => return, // done 548 else => return e, 549 }; 550 defer lfh.deinit(allocator); 551 if (std.fs.path.dirname(lfh.file_name)) |dn| { 552 try dir.makePath(dn); 553 } 554 if (!lfh.is_dir()) { 555 var f = try dir.createFile(lfh.file_name, .{}); 556 defer f.close(); 557 var writer = f.writer(); 558 try lfh.extract(allocator, reader, writer); 559 } 560 // TODO skip data descriptor 561 i += 1; 562 } 563 } 564 565 pub const WriteOptions = struct {}; 566 567 /// Write a whole zip file 568 pub fn write_zip(allocator: std.mem.Allocator, path_iterator: anytype, writer: anytype) !void { 569 var cw = std.io.countingWriter(writer); 570 var cww = cw.writer(); 571 var zf = @This().empty(allocator); 572 defer zf.deinit(); 573 while (path_iterator.next()) |path| { 574 var lfh = try LocalFileHeader.from(allocator, path); 575 defer lfh.deinit(allocator); 576 const file = try std.fs.cwd().openFile(path, .{}); 577 defer file.close(); 578 var cdh = try CentralDirectoryHeader.from(allocator, lfh, @intCast(cw.bytes_written)); 579 try zf.central_directory_headers.append(cdh); 580 std.log.err("lfh {any}", .{lfh}); 581 try lfh.write(cww); 582 try lfh.write_data(allocator, file.reader(), cww); 583 } 584 const central_dir_offset = cw.bytes_written; 585 for (0..zf.central_directory_headers.items.len) |i| { 586 var cdh = zf.central_directory_headers.items[i]; 587 std.log.err("cdh {any}", .{cdh}); 588 try cdh.write(cww); 589 } 590 const size_of_central_dir = cw.bytes_written - central_dir_offset; 591 var eocdr = EndOfCentralDirectoryRecord{ 592 .disk_number_this = 0, 593 .disk_number_central_dir_start = 0, 594 .total_central_dir_entries_on_this_disk = @intCast(zf.central_directory_headers.items.len), 595 .total_central_dir_entries = @intCast(zf.central_directory_headers.items.len), 596 .size_of_central_dir = @intCast(size_of_central_dir), 597 .central_dir_offset = @intCast(central_dir_offset), // TODO zip64 support 598 .comment_length = 0, 599 .comment = &[_]u8{}, 600 }; 601 std.log.err("eocdr {any}", .{eocdr}); 602 try eocdr.write(cww); 603 } 604 605 const TestFileIter = struct { 606 files: [2][]const u8, 607 ix: usize, 608 fn next(self: *TestFileIter) ?[]const u8 { 609 if (self.ix < 2) { 610 const res = self.files[self.ix]; 611 self.ix += 1; 612 return res; 613 } 614 return null; 615 } 616 }; 617 618 test "write" { 619 // TODO this isn't finished. 620 return error.SkipZigTest; 621 // const allocator = std.testing.allocator; 622 // { 623 // const out = try std.fs.cwd().createFile("test_out.zip", .{}); 624 // defer out.close(); 625 // var tfi = TestFileIter{ 626 // .ix = 0, 627 // .files = [_][]const u8{ "src/foo.txt", "src/bar.txt" }, 628 // }; 629 // try write_zip(allocator, &tfi, out.writer()); 630 // } 631 632 // { 633 // const out = try std.fs.cwd().openFile("test_out.zip", .{}); 634 // defer out.close(); 635 // try pipeToFileSystem(try std.fs.cwd().makeOpenPath("test_out", .{}), out.reader(), .{ .allocator = allocator }); 636 // } 637 } 638 639 // test "open stream" { 640 // const test_zip = @embedFile("hello.zip"); 641 // var fbs = std.io.fixedBufferStream(test_zip); 642 // var zf = try @This().from(std.testing.allocator, &fbs); 643 // defer zf.deinit(); 644 // try std.testing.expectEqual(zf.count_files(), 2); 645 // try std.testing.expectEqualStrings(zf.file_name(0), "hello.txt"); 646 // try std.testing.expectEqualStrings(zf.file_name(1), "foo.txt"); 647 // } 648 649 // test "open file" { 650 // const test_zip = try std.fs.cwd().openFile("src/hello.zip", .{}); 651 // var zf = try @This().from(std.testing.allocator, &test_zip); 652 // defer zf.deinit(); 653 // try std.testing.expectEqual(zf.count_files(), 2); 654 // try std.testing.expectEqualStrings(zf.file_name(0), "hello.txt"); 655 // try std.testing.expectEqualStrings(zf.file_name(1), "foo.txt"); 656 // } 657 658 // test "extract stored" { 659 // const test_zip = @embedFile("hello.zip"); 660 // var fbs = std.io.fixedBufferStream(test_zip); 661 // var zf = try @This().from(std.testing.allocator, &fbs); 662 // defer zf.deinit(); 663 // var out = [_]u8{0} ** 1024; 664 // var fbs_out = std.io.fixedBufferStream(&out); 665 // var writer = fbs_out.writer(); 666 // try zf.extract(0, &fbs, writer); 667 // try std.testing.expectEqualStrings("Hello, Zip!", fbs_out.getWritten()); 668 // fbs_out.reset(); 669 // try zf.extract(1, &fbs, writer); 670 // try std.testing.expectEqualStrings("hi there\n", fbs_out.getWritten()); 671 // } 672 673 // test "extract deflate" { 674 // const test_zip = @embedFile("deflate.zip"); 675 // var fbs = std.io.fixedBufferStream(test_zip); 676 // var zf = try @This().from(std.testing.allocator, &fbs); 677 // defer zf.deinit(); 678 // var out = [_]u8{0} ** 1024; 679 // var fbs_out = std.io.fixedBufferStream(&out); 680 // var writer = fbs_out.writer(); 681 // try std.testing.expectEqualStrings("Here is a comment :)", zf.file_comment(0)); 682 // try std.testing.expectEqual(@This().CompressionMethod.deflate, zf.central_directory_headers.items[0].compression_method); 683 // try zf.extract(0, &fbs, writer); 684 // try std.testing.expectEqualStrings(@embedFile("foo.txt"), fbs_out.getWritten()); 685 // } 686 // test "subdir" { 687 // const test_zip = @embedFile("subfolder.zip"); 688 // var fbs = std.io.fixedBufferStream(test_zip); 689 // var zf = try @This().from(std.testing.allocator, &fbs); 690 // defer zf.deinit(); 691 // try std.testing.expectEqual(true, zf.is_dir(0)); 692 // try std.testing.expectEqual(false, zf.is_dir(1)); 693 // } 694 // test "pipe to filesystem" { 695 // var f = try std.fs.cwd().openFile("src/subfolder.zip", .{}); 696 // defer f.close(); 697 // try pipeToFileSystem(try std.fs.cwd().makeOpenPath("test", .{}), f.reader(), .{ .allocator = std.testing.allocator }); 698 // }