-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathzip.lua
270 lines (241 loc) · 8.25 KB
/
zip.lua
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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
local ffi = require('ffi')
local Zlib = require('zlib_native')
ffi.cdef[[
struct zip_LFH {
uint32_t signature;
uint16_t version_needed;
uint16_t flags;
uint16_t compression_method;
uint16_t last_mod_file_time;
uint16_t last_mod_file_date;
uint32_t crc_32;
uint32_t compressed_size;
uint32_t uncompressed_size;
uint16_t file_name_length;
uint16_t extra_field_length;
} __attribute ((packed));
struct zip_CDFH {
uint32_t signature;
uint16_t version;
uint16_t version_needed;
uint16_t flags;
uint16_t compression_method;
uint16_t last_mod_file_time;
uint16_t last_mod_file_date;
uint32_t crc_32;
uint32_t compressed_size;
uint32_t uncompressed_size;
uint16_t file_name_length;
uint16_t extra_field_length;
uint16_t file_comment_length;
uint16_t disk_number;
uint16_t internal_file_attributes;
uint32_t external_file_attributes;
uint32_t local_file_header_offset;
} __attribute__ ((packed));
struct zip_EoCD {
uint32_t signature;
uint16_t disk_number;
uint16_t central_dir_disk_number;
uint16_t central_dir_disk_records;
uint16_t central_dir_total_records;
uint32_t central_dir_size;
uint32_t central_dir_offset;
uint16_t file_comment_length;
} __attribute__ ((packed));
]]
-- Local File Header
local LFH = ffi.typeof("struct zip_LFH")
-- Central Directory File Header
local CDFH = ffi.typeof("struct zip_CDFH")
-- End of Central Directory
local EoCD = ffi.typeof("struct zip_EoCD")
-- Given a path like /foo/bar and foo//bar/ return foo/bar.bar
-- This removes leading and trailing slashes as well as multiple internal slashes.
local function normalizePath(path)
local parts = {}
for part in string.gmatch(path, "([^/]+)") do
table.insert(parts, part)
end
return table.concat(parts, "/")
end
-- Please provide with I/O functions.
-- fsFstat(fd) takes a fd and returns a table with a .size property for file length
-- fs.read(fd, length, offset) reads from a file at offset and for a set number of bytes
-- return a string of all data
return function (fd, fs)
local cd = {}
-- Scan from the end of a file to find the start position of
-- the EoCD (end of central directory) entry.
local function findEoCD()
local stat = fs.fstat(fd)
-- Theoretically, the comment at the end can be 0x10000 bytes long
-- though there is no sense reading more than that.
local maxSize = 391 + 22
local start = stat.size - maxSize
local tail = fs.read(fd, maxSize, start)
local position = #tail
-- Scan backwards looking for the EoCD signature 0x06054b50
while position > 0 do
if string.byte(tail, position) == 0x06 and
string.byte(tail, position - 1) == 0x05 and
string.byte(tail, position - 2) == 0x4b and
string.byte(tail, position - 3) == 0x50 then
return start + position - 4
end
position = position - 1
end
end
-- Once you know the EoCD position, you can read and parse it.
local function readEoCD(position)
local eocd = EoCD()
local size = ffi.sizeof(eocd)
local data = fs.read(fd, size, position)
ffi.copy(eocd, data, size)
if eocd.signature ~= 0x06054b50 then
error "Invalid EoCD position"
end
local comment = fs.read(fd, eocd.file_comment_length, position + size)
return {
disk_number = eocd.disk_number,
central_dir_disk_number = eocd.central_dir_disk_number,
central_dir_disk_records = eocd.central_dir_disk_records,
central_dir_total_records = eocd.central_dir_total_records,
central_dir_size = eocd.central_dir_size,
central_dir_offset = eocd.central_dir_offset,
file_comment = comment,
}
end
local function readCDFH(position, start)
local cdfh = CDFH()
local size = ffi.sizeof(cdfh)
local data = fs.read(fd, size, position)
ffi.copy(cdfh, data, size)
if cdfh.signature ~= 0x02014b50 then
error "Invalid CDFH position"
end
local n, m, k = cdfh.file_name_length, cdfh.extra_field_length, cdfh.file_comment_length
local more = fs.read(fd, n + m + k, position + size)
return {
version = cdfh.version,
version_needed = cdfh.version_needed,
flags = cdfh.flags,
compression_method = cdfh.compression_method,
last_mod_file_time = cdfh.last_mod_file_time,
last_mod_file_date = cdfh.last_mod_file_date,
crc_32 = cdfh.crc_32,
compressed_size = cdfh.compressed_size,
uncompressed_size = cdfh.uncompressed_size,
file_name = string.sub(more, 1, n),
-- extra_field = string.sub(more, n + 1, n + m),
comment = string.sub(more, n + m + 1),
disk_number = cdfh.disk_number,
internal_file_attributes = cdfh.internal_file_attributes,
external_file_attributes = cdfh.external_file_attributes,
local_file_header_offset = cdfh.local_file_header_offset,
local_file_header_position = cdfh.local_file_header_offset + start,
header_size = size + n + m + k
}
end
local function readLFH(position)
local lfh = LFH()
local size = ffi.sizeof(lfh)
local data = fs.read(fd, size, position)
ffi.copy(lfh, data, size)
if lfh.signature ~= 0x04034b50 then
error "Invalid LFH position"
end
local n, m = lfh.file_name_length, lfh.extra_field_length
local more = fs.read(fd, n + m, position + size)
return {
version_needed = lfh.version_needed,
flags = lfh.flags,
compression_method = lfh.compression_method,
last_mod_file_time = lfh.last_mod_file_time,
last_mod_file_date = lfh.last_mod_file_date,
crc_32 = lfh.crc_32,
compressed_size = lfh.compressed_size,
uncompressed_size = lfh.uncompressed_size,
file_name = string.sub(more, 1, n),
-- extra_field = string.sub(more, n + 1, n + m),
header_size = size + n + m,
}
end
local function stat(path)
path = normalizePath(path)
local entry = cd[path]
if entry then return entry end
return nil, "No such entry '" .. path .. "'"
end
local function readdir(path)
path = normalizePath(path)
if path and not cd[path] then
return nil, "No such directory '" .. path .. "'"
end
local entries = {}
local pattern
if #path > 0 then
pattern = "^" .. path .. "/([^/]+)$"
else
pattern = "^([^/]+)$"
end
for name, entry in pairs(cd) do
local a, b, match = string.find(name, pattern)
if match then
entries[match] = entry
end
end
return entries
end
local function readfile(path)
path = normalizePath(path)
local entry = cd[path]
if entry == nil then return nil, "No such file '" .. path .. "'" end
local lfh = readLFH(entry.local_file_header_position)
if entry.crc_32 ~= lfh.crc_32 or
entry.file_name ~= lfh.file_name or
entry.compression_method ~= lfh.compression_method or
entry.compressed_size ~= lfh.compressed_size or
entry.uncompressed_size ~= lfh.uncompressed_size then
error "Local file header doesn't match entry in central directory"
end
p(lfh)
local start = entry.local_file_header_position + lfh.header_size
local compressed = fs.read(fd, lfh.compressed_size, start)
if #compressed ~= entry.compressed_size then
error "compressed size mismatch"
end
local uncompressed
-- Store
if lfh.compression_method == 0 then
uncompressed = compressed
-- Inflate
elseif lfh.compression_method == 8 then
uncompressed = assert(Zlib.new('inflate',-15):write(compressed, 'finish'))
else
error("Unknown compression method: " .. lfh.compression_method)
end
if #uncompressed ~= lfh.uncompressed_size then
error "uncompressed size mismatch"
end
return uncompressed
end
local function load()
local position = findEoCD()
if position == nil then return nil, "Can't find End of Central Directory" end
local eocd = readEoCD(position)
position = position - eocd.central_dir_size
local start = position - eocd.central_dir_offset
for i = 1, eocd.central_dir_disk_records do
local cdfh = readCDFH(position, start)
cd[normalizePath(cdfh.file_name)] = cdfh
position = position + cdfh.header_size
end
return {
stat = stat,
readdir = readdir,
readfile = readfile,
}
end
return load()
end