const std = @import("std"); const testing = std.testing; // https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.10.TXT const ZipFile = struct { // [local file header 1] // [encryption header 1] // [file data 1] // [data descriptor 1] // . // . // . // [local file header n] // [encryption header n] // [file data n] // [data descriptor n] // [archive decryption header] // [archive extra data record] // [central directory header 1] // . // . // . // [central directory header n] // [zip64 end of central directory record] // [zip64 end of central directory locator] // [end of central directory record] }; const LocalFileHeader = packed struct { const GPBF = packed struct(u16) { encrypted: bool = false, }; const SIG: u32 = 0x04034b50; sig: u32 = SIG, // version needed to extract 2 bytes general_purpose_bit_flag: GPBF, // compression method 2 bytes // last mod file time 2 bytes // last mod file date 2 bytes // crc-32 4 bytes // compressed size 4 bytes // uncompressed size 4 bytes // file name length 2 bytes // extra field length 2 bytes // file name (variable size) // extra field (variable size) }; const DataDescriptor = struct { const SIG: u32 = 0x08074b50; sig: u32 = SIG, // crc-32 4 bytes // compressed size 4 bytes // uncompressed size 4 bytes }; const ArchiveExtraDataRecord = struct { const SIG: u32 = 0x08064b50; sig: u32 = SIG, // extra field length 4 bytes // extra field data (variable size) }; const CentralDirectoryHeader = packed struct { const SIG: u32 = 0x02014b50; // central file header signature 4 bytes () sig: u32 = SIG, // version made by 2 bytes version_made_by: u16, // version needed to extract 2 bytes version_needed_to_extract: u16, // general purpose bit flag 2 bytes general_purpose_bit_flag: u16, // compression method 2 bytes compression_method: u16, // last mod file time 2 bytes last_mod_file_time: u16, // last mod file date 2 bytes last_mod_file_date: u16, // crc-32 4 bytes crc32: u32, // compressed size 4 bytes compressed_size: u32, // uncompressed size 4 bytes uncompressed_size: u32, // file name length 2 bytes file_name_length: u16, // extra field length 2 bytes extra_field_length: u16, // file comment length 2 bytes file_comment_length: u16, // disk number start 2 bytes disk_number_start: u16, // internal file attributes 2 bytes internal_file_attributes: u16, // external file attributes 4 bytes external_file_attributes: u32, // relative offset of local header 4 bytes relative_offset_of_local_header: u16, // file name (variable size) // extra field (variable size) // file comment (variable size) }; const DigitalSignature = struct { const SIG: u32 = 0x05054b50; sig: u32 = SIG, // size of data 2 bytes // signature data (variable size) }; const Zip64EndOfCentralDirectoryRecord = struct { const SIG: u32 = 0x06064b50; sig: u32 = SIG, // size of zip64 end of central // directory record 8 bytes // version made by 2 bytes // version needed to extract 2 bytes // number of this disk 4 bytes // number of the disk with the // start of the central directory 4 bytes // total number of entries in the // central directory on this disk 8 bytes // total number of entries in the // central directory 8 bytes // size of the central directory 8 bytes // offset of start of central // directory with respect to // the starting disk number 8 bytes // zip64 extensible data sector (variable size) }; const Zip64EndOfCentralDirectoryLocator = struct { const SIG: u32 = 0x07064b50; sig: u32 = SIG, // number of the disk with the // start of the zip64 end of // central directory 4 bytes // relative offset of the zip64 // end of central directory record 8 bytes // total number of disks 4 bytes }; const EndOfCentralDirectoryRecord = packed struct { const SIG: u32 = 0x06054b50; // // end of central dir signa SIG: u32 = 0x06054b50;ure 4 bytes (0x06054b50) sig: u32 = SIG, // 504b0506 // number of this disk 2 bytes disk_number_this: u16, // 0000 // number of the disk with the // start of the central directory 2 bytes disk_number_central_dir_start: u16, // 0000 // total number of entries in the // central directory on this disk 2 bytes total_central_dir_entries_on_this_disk: u16, // 0100 // total number of entries in // the central directory 2 bytes total_central_dir_entries: u16, // 0100 // size of the central directory 4 bytes size_of_central_dir: u32, // 4f000000 // offset of start of central // directory with respect to // the starting disk number 4 bytes central_dir_offset: u32, // 4e000000 // .ZIP file comment length 2 bytes comment_length: u16, // 0000 // .ZIP file comment (variable size) // comment: [*]u8, fn from(bytes: []const u8) !EndOfCentralDirectoryRecord { var fbs = std.io.fixedBufferStream(bytes); var rr = fbs.reader(); return EndOfCentralDirectoryRecord{ .sig = try rr.readIntLittle(u32), .disk_number_this = try rr.readIntLittle(u16), .disk_number_central_dir_start = try rr.readIntLittle(u16), .total_central_dir_entries_on_this_disk = try rr.readIntLittle(u16), .total_central_dir_entries = try rr.readIntLittle(u16), .size_of_central_dir = try rr.readIntLittle(u32), .central_dir_offset = try rr.readIntLittle(u32), .comment_length = try rr.readIntLittle(u16), // .comment = rr.read() }; } }; test "foo" { const test_zip = @embedFile("hello.zip"); var fbs = std.io.fixedBufferStream(test_zip); const eocdr_search_width_max: usize = 64_000; var eocdr_search_buf: [eocdr_search_width_max]u8 = undefined; const epos = try fbs.getEndPos(); const eocdr_search_width: usize = @min(epos, eocdr_search_width_max); const eocdr_seek_start: usize = epos - eocdr_search_width; std.log.err("epos {}", .{epos}); std.log.err("eocdr_search_width {}", .{eocdr_search_width}); std.log.err("eocdr_seek_start {}", .{eocdr_seek_start}); try fbs.seekTo(eocdr_seek_start); const eocdr_did_read = try fbs.read(&eocdr_search_buf); std.log.err("eocdr_did_read {}", .{eocdr_did_read}); const needle: [4]u8 = @bitCast(EndOfCentralDirectoryRecord.SIG); const eocdr_start = std.mem.indexOf(u8, eocdr_search_buf[0..eocdr_search_width], &needle) orelse return error.NoEndOfCentralDirectoryRecord; std.log.err("eocdr_start {}", .{eocdr_start}); try fbs.seekTo(eocdr_start); const eocdr_pos = try fbs.getPos(); std.log.err("eocdr_pos {}", .{eocdr_pos}); // const eocdr = try fbs.reader().readStruct(EndOfCentralDirectoryRecord); // _ = eocdr; // _ = eocdr; var rr = fbs.reader(); const eocdrb = try rr.readAllAlloc(std.testing.allocator, 10_000_000); defer std.testing.allocator.free(eocdrb); const eocdr = try EndOfCentralDirectoryRecord.from(eocdrb); _ = eocdr; // const comment = try rr.readAllAlloc(std.testing.allocator, eocdr.comment_length); // defer std.testing.allocator.free(comment); std.log.err("eocdrb {} len {}", .{ std.fmt.fmtSliceHexLower(eocdrb), eocdrb.len }); std.log.err("sz {}", .{@sizeOf(EndOfCentralDirectoryRecord)}); }