1if not modules then modules = { } end modules ['util-zip'] = {
2 version = 1.001,
3 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
4 copyright = "PRAGMA ADE / ConTeXt Development Team",
5 license = "see context related readme files"
6}
7
8
9
10
11
12
13
14
15local type, tostring, tonumber = type, tostring, tonumber
16local sort, concat = table.sort, table.concat
17
18local find, format, sub, gsub = string.find, string.format, string.sub, string.gsub
19local osdate, ostime, osclock = os.date, os.time, os.clock
20local ioopen = io.open
21local loaddata, savedata = io.loaddata, io.savedata
22local filejoin, isdir, dirname, mkdirs = file.join, lfs.isdir, file.dirname, dir.mkdirs
23local suffix, suffixes = file.suffix, file.suffixes
24local openfile = io.open
25
26gzip = gzip or { }
27
28if not zlib then
29 zlib = xzip
30elseif not xzip then
31 xzip = zlib
32end
33
34local files = utilities.files
35local openfile = files.open
36local closefile = files.close
37local readstring = files.readstring
38local readcardinal2 = files.readcardinal2le
39local readcardinal4 = files.readcardinal4le
40local setposition = files.setposition
41local getposition = files.getposition
42
43local band = bit32.band
44local rshift = bit32.rshift
45local lshift = bit32.lshift
46
47local zlibdecompress = zlib.decompress
48local zlibdecompresssize = zlib.decompresssize
49local zlibchecksum = zlib.crc32
50
51local decompress = function(source) return zlibdecompress (source,-15) end
52local decompresssize = function(source,targetsize) return zlibdecompresssize(source,targetsize,-15) end
53local calculatecrc = function(buffer,initial) return zlibchecksum (initial or 0,buffer) end
54
55local zipfiles = { }
56utilities.zipfiles = zipfiles
57
58local openzipfile, closezipfile, unzipfile, foundzipfile, getziphash, getziplist do
59
60 function openzipfile(name)
61 return {
62 name = name,
63 handle = openfile(name,0),
64 }
65 end
66
67 local function collect(z)
68 if not z.list then
69 local list = { }
70 local hash = { }
71 local position = 0
72 local index = 0
73 local handle = z.handle
74 while true do
75 setposition(handle,position)
76 local signature = readstring(handle,4)
77 if signature == "PK\3\4" then
78
79
80
81
82 local version = readcardinal2(handle)
83 local flag = readcardinal2(handle)
84 local method = readcardinal2(handle)
85 local filetime = readcardinal2(handle)
86 local filedate = readcardinal2(handle)
87 local crc32 = readcardinal4(handle)
88 local compressed = readcardinal4(handle)
89 local uncompressed = readcardinal4(handle)
90 local namelength = readcardinal2(handle)
91 local extralength = readcardinal2(handle)
92 local filename = readstring(handle,namelength)
93 local descriptor = band(flag,8) ~= 0
94 local encrypted = band(flag,1) ~= 0
95 local acceptable = method == 0 or method == 8
96
97 local skipped = 0
98 local size = 0
99 if encrypted then
100 size = readcardinal2(handle)
101 skipbytes(size)
102 skipped = skipped + size + 2
103 skipbytes(8)
104 skipped = skipped + 8
105 size = readcardinal2(handle)
106 skipbytes(size)
107 skipped = skipped + size + 2
108 size = readcardinal4(handle)
109 skipbytes(size)
110 skipped = skipped + size + 4
111 size = readcardinal2(handle)
112 skipbytes(size)
113 skipped = skipped + size + 2
114 end
115 position = position + 30 + namelength + extralength + skipped
116 if descriptor then
117 setposition(handle,position + compressed)
118 crc32 = readcardinal4(handle)
119 compressed = readcardinal4(handle)
120 uncompressed = readcardinal4(handle)
121 end
122 if acceptable then
123 index = index + 1
124 local data = {
125 filename = filename,
126 index = index,
127 position = position,
128 method = method,
129 compressed = compressed,
130 uncompressed = uncompressed,
131 crc32 = crc32,
132 encrypted = encrypted,
133 }
134 hash[filename] = data
135 list[index] = data
136 else
137
138 end
139 position = position + compressed
140 else
141 break
142 end
143 z.list = list
144 z.hash = hash
145 end
146 end
147 end
148
149 function getziplist(z)
150 local list = z.list
151 if not list then
152 collect(z)
153 end
154 return z.list
155 end
156
157 function getziphash(z)
158 local hash = z.hash
159 if not hash then
160 collect(z)
161 end
162 return z.hash
163 end
164
165 function foundzipfile(z,name)
166 return getziphash(z)[name]
167 end
168
169 function closezipfile(z)
170 local f = z.handle
171 if f then
172 closefile(f)
173 z.handle = nil
174 end
175 end
176
177 function unzipfile(z,filename,check)
178 local hash = z.hash
179 if not hash then
180 hash = zipfiles.hash(z)
181 end
182 local data = hash[filename]
183 if not data then
184
185
186 end
187 if data then
188 local handle = z.handle
189 local position = data.position
190 local compressed = data.compressed
191 if compressed > 0 then
192 setposition(handle,position)
193 local result = readstring(handle,compressed)
194 if data.method == 8 then
195 if decompresssize then
196 result = decompresssize(result,data.uncompressed)
197 else
198 result = decompress(result)
199 end
200 end
201 if check and data.crc32 ~= calculatecrc(result) then
202 print("checksum mismatch")
203 return ""
204 end
205 return result
206 else
207 return ""
208 end
209 end
210 end
211
212 zipfiles.open = openzipfile
213 zipfiles.close = closezipfile
214 zipfiles.unzip = unzipfile
215 zipfiles.hash = getziphash
216 zipfiles.list = getziplist
217 zipfiles.found = foundzipfile
218
219end
220
221if xzip then
222
223 local writecardinal1 = files.writebyte
224 local writecardinal2 = files.writecardinal2le
225 local writecardinal4 = files.writecardinal4le
226
227 local logwriter = logs.writer
228
229 local globpattern = dir.globpattern
230
231
232 local compress = xzip.compress
233 local checksum = xzip.crc32
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253 local function fromdostime(dostime,dosdate)
254 return ostime {
255 year = rshift(dosdate, 9) + 1980,
256 month = band(rshift(dosdate, 5), 0x0F),
257 day = band( (dosdate ), 0x1F),
258 hour = band(rshift(dostime,11) ),
259 min = band(rshift(dostime, 5), 0x3F),
260 sec = band( (dostime ), 0x1F),
261 }
262 end
263
264 local function todostime(time)
265 local t = osdate("*t",time)
266 return
267 lshift(t.year - 1980, 9) + lshift(t.month,5) + t.day,
268 lshift(t.hour ,11) + lshift(t.min ,5) + rshift(t.sec,1)
269 end
270
271 local function openzip(filename,level,comment,verbose)
272 local f = ioopen(filename,"wb")
273 if f then
274 return {
275 filename = filename,
276 handle = f,
277 list = { },
278 level = tonumber(level) or 3,
279 comment = tostring(comment),
280 verbose = verbose,
281 uncompressed = 0,
282 compressed = 0,
283 }
284 end
285 end
286
287 local function writezip(z,name,data,level,time)
288 local f = z.handle
289 local list = z.list
290 local level = tonumber(level) or z.level or 3
291 local method = 8
292 local zipped = compress(data,level)
293 local checksum = checksum(data)
294 local verbose = z.verbose
295
296 if not zipped then
297 method = 0
298 zipped = data
299 end
300
301 local start = f:seek()
302 local compressed = #zipped
303 local uncompressed = #data
304
305 z.compressed = z.compressed + compressed
306 z.uncompressed = z.uncompressed + uncompressed
307
308 if verbose then
309 local pct = 100 * compressed/uncompressed
310 if pct >= 100 then
311 logwriter(format("%10i %s",uncompressed,name))
312 else
313 logwriter(format("%10i %02.1f %s",uncompressed,pct,name))
314 end
315 end
316
317 f:write("\x50\x4b\x03\x04")
318
319 writecardinal2(f,0)
320 writecardinal2(f,0)
321 writecardinal2(f,method)
322 writecardinal2(f,0)
323 writecardinal2(f,0)
324 writecardinal4(f,checksum)
325 writecardinal4(f,compressed)
326 writecardinal4(f,uncompressed)
327 writecardinal2(f,#name)
328 writecardinal2(f,0)
329
330 f:write(name)
331 f:write(zipped)
332
333 list[#list+1] = { #zipped, #data, name, checksum, start, time or 0 }
334 end
335
336 local function closezip(z)
337 local f = z.handle
338 local list = z.list
339 local comment = z.comment
340 local verbose = z.verbose
341 local count = #list
342 local start = f:seek()
343
344 for i=1,count do
345 local l = list[i]
346 local compressed = l[1]
347 local uncompressed = l[2]
348 local name = l[3]
349 local checksum = l[4]
350 local start = l[5]
351 local time = l[6]
352 local date, time = todostime(time)
353 f:write('\x50\x4b\x01\x02')
354 writecardinal2(f,0)
355 writecardinal2(f,0)
356 writecardinal2(f,0)
357 writecardinal2(f,8)
358 writecardinal2(f,time)
359 writecardinal2(f,date)
360 writecardinal4(f,checksum)
361 writecardinal4(f,compressed)
362 writecardinal4(f,uncompressed)
363 writecardinal2(f,#name)
364 writecardinal2(f,0)
365 writecardinal2(f,0)
366 writecardinal2(f,0)
367 writecardinal2(f,0)
368 writecardinal4(f,0)
369 writecardinal4(f,start)
370 f:write(name)
371 end
372
373 local stop = f:seek()
374 local size = stop - start
375
376 f:write('\x50\x4b\x05\x06')
377 writecardinal2(f,0)
378 writecardinal2(f,0)
379 writecardinal2(f,count)
380 writecardinal2(f,count)
381 writecardinal4(f,size)
382 writecardinal4(f,start)
383 if type(comment) == "string" and comment ~= "" then
384 writecardinal2(f,#comment)
385 f:write(comment)
386 else
387 writecardinal2(f,0)
388 end
389
390 if verbose then
391 local compressed = z.compressed
392 local uncompressed = z.uncompressed
393 local filename = z.filename
394
395 local pct = 100 * compressed/uncompressed
396 logwriter("")
397 if pct >= 100 then
398 logwriter(format("%10i %s",uncompressed,filename))
399 else
400 logwriter(format("%10i %02.1f %s",uncompressed,pct,filename))
401 end
402 end
403
404 f:close()
405 end
406
407 local function zipdir(zipname,path,level,verbose)
408 if type(zipname) == "table" then
409 verbose = zipname.verbose
410 level = zipname.level
411 path = zipname.path
412 zipname = zipname.zipname
413 end
414 if not zipname or zipname == "" then
415 return
416 end
417 if not path or path == "" then
418 path = "."
419 end
420 if not isdir(path) then
421 return
422 end
423 path = gsub(path,"\\+","/")
424 path = gsub(path,"/+","/")
425 local list = { }
426 local count = 0
427 globpattern(path,"",true,function(name,size,time)
428 count = count + 1
429 list[count] = { name, time }
430 end)
431 sort(list,function(a,b)
432 return a[1] < b[1]
433 end)
434 local zipf = openzip(zipname,level,comment,verbose)
435 if zipf then
436 local p = #path + 2
437 for i=1,count do
438 local li = list[i]
439 local name = li[1]
440 local time = li[2]
441 local data = loaddata(name)
442 local name = sub(name,p,#name)
443 writezip(zipf,name,data,level,time,verbose)
444 end
445 closezip(zipf)
446 end
447 end
448
449 local function unzipdir(zipname,path,verbose)
450 if type(zipname) == "table" then
451 verbose = zipname.verbose
452 path = zipname.path
453 zipname = zipname.zipname
454 end
455 if not zipname or zipname == "" then
456 return
457 end
458 if not path or path == "" then
459 path = "."
460 end
461 local z = openzipfile(zipname)
462 if z then
463 local list = getziplist(z)
464 if list then
465 local total = 0
466 local count = #list
467 local step = number.idiv(count,10)
468 local done = 0
469 local steps = verbose == "steps"
470 local time = steps and osclock()
471 for i=1,count do
472 local l = list[i]
473 local n = l.filename
474 local d = unzipfile(z,n)
475 if d then
476 local p = filejoin(path,n)
477 if mkdirs(dirname(p)) then
478 if steps then
479 total = total + #d
480 done = done + 1
481 if done >= step then
482 done = 0
483 logwriter(format("%4i files of %4i done, %10i bytes, %0.3f seconds",i,count,total,osclock()-time))
484 end
485 elseif verbose then
486 logwriter(n)
487 end
488 savedata(p,d)
489 end
490 else
491 logwriter(format("problem with file %s",n))
492 end
493 end
494 if steps then
495 logwriter(format("%4i files of %4i done, %10i bytes, %0.3f seconds",count,count,total,osclock()-time))
496 end
497 closezipfile(z)
498 return true
499 else
500 closezipfile(z)
501 end
502 end
503 end
504
505 zipfiles.zipdir = zipdir
506 zipfiles.unzipdir = unzipdir
507
508end
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528local pattern = "^\x1F\x8B\x08"
529local gziplevel = 3
530
531function gzip.suffix(filename)
532 local suffix, extra = suffixes(filename)
533 local gzipped = extra == "gz"
534 return suffix, gzipped
535end
536
537function gzip.compressed(s)
538 return s and find(s,pattern)
539end
540
541local getdecompressed
542local putcompressed
543
544if gzip.compress then
545
546 local gzipwindow = 15 + 16
547
548 local compress = zlib.compress
549 local decompress = zlib.decompress
550
551 getdecompressed = function(str)
552 return decompress(str,gzipwindow)
553 end
554
555 putcompressed = function(str,level)
556 return compress(str,level or gziplevel,nil,gzipwindow)
557 end
558
559else
560
561
562
563 local gzipwindow = -15
564 local identifier = "\x1F\x8B"
565
566 local compress = zlib.compress
567 local decompress = zlib.decompress
568 local crc32 = zlib.crc32
569
570 local streams = utilities.streams
571 local openstream = streams.openstring
572 local closestream = streams.close
573 local getposition = streams.getposition
574 local readbyte = streams.readbyte
575 local readcardinal4 = streams.readcardinal4le
576 local readcardinal2 = streams.readcardinal2le
577 local readstring = streams.readstring
578 local readcstring = streams.readcstring
579 local skipbytes = streams.skip
580
581 local tocardinal1 = streams.tocardinal1
582 local tocardinal4 = streams.tocardinal4le
583
584 getdecompressed = function(str)
585 local s = openstream(str)
586 local identifier = readstring(s,2)
587 local method = readbyte(s,1)
588 local flags = readbyte(s,1)
589 local timestamp = readcardinal4(s)
590 local compression = readbyte(s,1)
591 local operating = readbyte(s,1)
592
593
594
595
596
597 local isjusttext = band(flags,0x01) ~= 0 and true or false
598 local extrasize = band(flags,0x04) ~= 0 and readcardinal2(s) or 0
599 local filename = band(flags,0x08) ~= 0 and readcstring(s) or ""
600 local comment = band(flags,0x10) ~= 0 and readcstring(s) or ""
601 local checksum = band(flags,0x02) ~= 0 and readcardinal2(s) or 0
602 local compressed = readstring(s,#str)
603 local data = decompress(compressed,gzipwindow)
604 return data
605 end
606
607 putcompressed = function(str,level,originalname)
608 return concat {
609 identifier,
610 tocardinal1(0x08),
611 tocardinal1(0x08),
612 tocardinal4(os.time()),
613 tocardinal1(0x02),
614 tocardinal1(0xFF),
615 (originalname or "unknownname") .. "\0",
616 compress(str,level,nil,gzipwindow),
617 tocardinal4(crc32(str)),
618 tocardinal4(#str),
619 }
620 end
621
622end
623
624function gzip.load(filename)
625 local f = openfile(filename,"rb")
626 if not f then
627
628 else
629 local data = f:read("*all")
630 f:close()
631 if data and data ~= "" then
632 if suffix(filename) == "gz" then
633 data = getdecompressed(data)
634 end
635 return data
636 end
637 end
638end
639
640function gzip.save(filename,data,level,originalname)
641 if suffix(filename) ~= "gz" then
642 filename = filename .. ".gz"
643 end
644 local f = openfile(filename,"wb")
645 if f then
646 data = putcompressed(data or "",level or gziplevel,originalname)
647 f:write(data)
648 f:close()
649 return #data
650 end
651end
652
653function gzip.compress(s,level)
654 if s and not find(s,pattern) then
655 if not level then
656 level = gziplevel
657 elseif level <= 0 then
658 return s
659 elseif level > 9 then
660 level = 9
661 end
662 return putcompressed(s,level or gziplevel) or s
663 end
664end
665
666function gzip.decompress(s)
667 if s and find(s,pattern) then
668 return getdecompressed(s)
669 else
670 return s
671 end
672end
673
674zipfiles.gunzipfile = gzip.load
675
676return zipfiles
677 |