1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
|
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)});
}
|