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 getsize = files.size
38local readstring = files.readstring
39local readcardinal2 = files.readcardinal2le
40local readcardinal4 = files.readcardinal4le
41local setposition = files.setposition
42local getposition = files.getposition
43local skipbytes = files.skip
44
45local band = bit32.band
46local rshift = bit32.rshift
47local lshift = bit32.lshift
48
49local zlibdecompress = zlib.decompress
50local zlibdecompresssize = zlib.decompresssize
51local zlibchecksum = zlib.crc32
52
53if not CONTEXTLMTXMODE or CONTEXTLMTXMODE == 0 then
54 local cs = zlibchecksum
55 zlibchecksum = function(str,n) return cs(n or 0, str) end
56end
57
58local decompress = function(source) return zlibdecompress (source,-15) end
59local decompresssize = function(source,targetsize) return zlibdecompresssize(source,targetsize,-15) end
60local calculatecrc = function(buffer,initial) return zlibchecksum (initial or 0,buffer) end
61
62local zipfiles = { }
63utilities.zipfiles = zipfiles
64
65local openzipfile, closezipfile, unzipfile, foundzipfile, getziphash, getziplist do
66
67 function openzipfile(name)
68 return {
69 name = name,
70 handle = openfile(name,0),
71 }
72 end
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 local function update(handle,data)
164 position = data.offset
165 setposition(handle,position)
166 local signature = readstring(handle,4)
167 if signature == "PK\3\4" then
168
169
170
171
172 local version = readcardinal2(handle)
173 local flag = readcardinal2(handle)
174 local method = readcardinal2(handle)
175 skipbytes(handle,4)
176
177
178 local crc32 = readcardinal4(handle)
179 local compressed = readcardinal4(handle)
180 local uncompressed = readcardinal4(handle)
181 local namelength = readcardinal2(handle)
182 local extralength = readcardinal2(handle)
183 local filename = readstring(handle,namelength)
184 local descriptor = band(flag,8) ~= 0
185 local encrypted = band(flag,1) ~= 0
186 local acceptable = method == 0 or method == 8
187
188 local skipped = 0
189 local size = 0
190 if encrypted then
191 size = readcardinal2(handle)
192 skipbytes(handle,size)
193 skipped = skipped + size + 2
194 skipbytes(8)
195 skipped = skipped + 8
196 size = readcardinal2(handle)
197 skipbytes(handle,size)
198 skipped = skipped + size + 2
199 size = readcardinal4(handle)
200 skipbytes(handle,size)
201 skipped = skipped + size + 4
202 size = readcardinal2(handle)
203 skipbytes(handle,size)
204 skipped = skipped + size + 2
205 end
206 if acceptable then
207 if filename ~= data.filename then
208
209
210
211
212
213 else
214 position = position + 30 + namelength + extralength + skipped
215 data.position = position
216 return position
217 end
218 else
219
220 end
221 end
222 data.position = false
223 return false
224 end
225
226 local function collect(z)
227 if not z.list then
228 local list = { }
229 local hash = { }
230 local position = 0
231 local index = 0
232 local handle = z.handle
233 local size = getsize(handle)
234
235
236
237
238 for i=size-4,size-64*1024,-1 do
239 setposition(handle,i)
240 local enddirsignature = readcardinal4(handle)
241 if enddirsignature == 0x06054B50 then
242 local thisdisknumber = readcardinal2(handle)
243 local centraldisknumber = readcardinal2(handle)
244 local thisnofentries = readcardinal2(handle)
245 local totalnofentries = readcardinal2(handle)
246 local centralsize = readcardinal4(handle)
247 local centraloffset = readcardinal4(handle)
248 local commentlength = readcardinal2(handle)
249 local comment = readstring(handle,length)
250 if size - i >= 22 then
251 if thisdisknumber == centraldisknumber then
252 setposition(handle,centraloffset)
253 while true do
254 if readcardinal4(handle) == 0x02014B50 then
255 skipbytes(handle,4)
256
257
258 local flag = readcardinal2(handle)
259 local method = readcardinal2(handle)
260 skipbytes(handle,4)
261
262
263 local crc32 = readcardinal4(handle)
264 local compressed = readcardinal4(handle)
265 local uncompressed = readcardinal4(handle)
266 local namelength = readcardinal2(handle)
267 local extralength = readcardinal2(handle)
268 local commentlength = readcardinal2(handle)
269 skipbytes(handle,8)
270
271
272
273 local headeroffset = readcardinal4(handle)
274 local filename = readstring(handle,namelength)
275 skipbytes(handle,extralength+commentlength)
276
277
278
279 local descriptor = band(flag,8) ~= 0
280 local encrypted = band(flag,1) ~= 0
281 local acceptable = method == 0 or method == 8
282 if acceptable then
283 index = index + 1
284 local data = {
285 filename = filename,
286 index = index,
287 position = nil,
288 method = method,
289 compressed = compressed,
290 uncompressed = uncompressed,
291 crc32 = crc32,
292 encrypted = encrypted,
293 offset = headeroffset,
294 }
295 hash[filename] = data
296 list[index] = data
297 end
298 else
299 break
300 end
301 end
302 end
303 break
304 end
305 end
306 end
307
308
309
310
311
312
313 z.list = list
314 z.hash = hash
315 end
316 end
317
318 function getziplist(z)
319 local list = z.list
320 if not list then
321 collect(z)
322 end
323
324 return z.list
325 end
326
327 function getziphash(z)
328 local hash = z.hash
329 if not hash then
330 collect(z)
331 end
332 return z.hash
333 end
334
335 function foundzipfile(z,name)
336 return getziphash(z)[name]
337 end
338
339 function closezipfile(z)
340 local f = z.handle
341 if f then
342 closefile(f)
343 z.handle = nil
344 end
345 end
346
347 function unzipfile(z,filename,check)
348 local hash = z.hash
349 if not hash then
350 hash = zipfiles.hash(z)
351 end
352 local data = hash[filename]
353 if not data then
354
355
356 end
357 if data then
358 local handle = z.handle
359 local position = data.position
360 local compressed = data.compressed
361 if position == nil then
362 position = update(handle,data)
363 end
364 if position and compressed > 0 then
365 setposition(handle,position)
366 local result = readstring(handle,compressed)
367 if data.method == 8 then
368 if decompresssize then
369 result = decompresssize(result,data.uncompressed)
370 else
371 result = decompress(result)
372 end
373 end
374 if check and data.crc32 ~= calculatecrc(result) then
375 print("checksum mismatch")
376 return ""
377 end
378 return result
379 else
380 return ""
381 end
382 end
383 end
384
385 zipfiles.open = openzipfile
386 zipfiles.close = closezipfile
387 zipfiles.unzip = unzipfile
388 zipfiles.hash = getziphash
389 zipfiles.list = getziplist
390 zipfiles.found = foundzipfile
391
392end
393
394if xzip then
395
396 local writecardinal1 = files.writebyte
397 local writecardinal2 = files.writecardinal2le
398 local writecardinal4 = files.writecardinal4le
399
400 local logwriter = logs.writer
401
402 local globpattern = dir.globpattern
403
404
405 local compress = xzip.compress
406 local checksum = xzip.crc32
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426 local function fromdostime(dostime,dosdate)
427 return ostime {
428 year = rshift(dosdate, 9) + 1980,
429 month = band(rshift(dosdate, 5), 0x0F),
430 day = band( (dosdate ), 0x1F),
431 hour = band(rshift(dostime,11) ),
432 min = band(rshift(dostime, 5), 0x3F),
433 sec = band( (dostime ), 0x1F),
434 }
435 end
436
437 local function todostime(time)
438 local t = osdate("*t",time)
439 return
440 lshift(t.year - 1980, 9) + lshift(t.month,5) + t.day,
441 lshift(t.hour ,11) + lshift(t.min ,5) + rshift(t.sec,1)
442 end
443
444 local function openzip(filename,level,comment,verbose)
445 local f = ioopen(filename,"wb")
446 if f then
447 return {
448 filename = filename,
449 handle = f,
450 list = { },
451 level = tonumber(level) or 3,
452 comment = tostring(comment),
453 verbose = verbose,
454 uncompressed = 0,
455 compressed = 0,
456 }
457 end
458 end
459
460 local function writezip(z,name,data,level,time)
461 local f = z.handle
462 local list = z.list
463 local level = tonumber(level) or z.level or 3
464 local method = 8
465 local zipped = compress(data,level)
466 local checksum = checksum(data)
467 local verbose = z.verbose
468
469 if not zipped then
470 method = 0
471 zipped = data
472 end
473
474 local start = f:seek()
475 local compressed = #zipped
476 local uncompressed = #data
477
478 z.compressed = z.compressed + compressed
479 z.uncompressed = z.uncompressed + uncompressed
480
481 if verbose then
482 local pct = 100 * compressed/uncompressed
483 if pct >= 100 then
484 logwriter(format("%10i %s",uncompressed,name))
485 else
486 logwriter(format("%10i %02.1f %s",uncompressed,pct,name))
487 end
488 end
489
490 f:write("\x50\x4b\x03\x04")
491
492 writecardinal2(f,0)
493 writecardinal2(f,0)
494 writecardinal2(f,method)
495 writecardinal2(f,0)
496 writecardinal2(f,0)
497 writecardinal4(f,checksum)
498 writecardinal4(f,compressed)
499 writecardinal4(f,uncompressed)
500 writecardinal2(f,#name)
501 writecardinal2(f,0)
502
503 f:write(name)
504 f:write(zipped)
505
506 list[#list+1] = { #zipped, #data, name, checksum, start, time or 0 }
507 end
508
509 local function closezip(z)
510 local f = z.handle
511 local list = z.list
512 local comment = z.comment
513 local verbose = z.verbose
514 local count = #list
515 local start = f:seek()
516
517 for i=1,count do
518 local l = list[i]
519 local compressed = l[1]
520 local uncompressed = l[2]
521 local name = l[3]
522 local checksum = l[4]
523 local start = l[5]
524 local time = l[6]
525 local date, time = todostime(time)
526 f:write('\x50\x4b\x01\x02')
527 writecardinal2(f,0)
528 writecardinal2(f,0)
529 writecardinal2(f,0)
530 writecardinal2(f,8)
531 writecardinal2(f,time)
532 writecardinal2(f,date)
533 writecardinal4(f,checksum)
534 writecardinal4(f,compressed)
535 writecardinal4(f,uncompressed)
536 writecardinal2(f,#name)
537 writecardinal2(f,0)
538 writecardinal2(f,0)
539 writecardinal2(f,0)
540 writecardinal2(f,0)
541 writecardinal4(f,0)
542 writecardinal4(f,start)
543 f:write(name)
544 end
545
546 local stop = f:seek()
547 local size = stop - start
548
549 f:write('\x50\x4b\x05\x06')
550 writecardinal2(f,0)
551 writecardinal2(f,0)
552 writecardinal2(f,count)
553 writecardinal2(f,count)
554 writecardinal4(f,size)
555 writecardinal4(f,start)
556 if type(comment) == "string" and comment ~= "" then
557 writecardinal2(f,#comment)
558 f:write(comment)
559 else
560 writecardinal2(f,0)
561 end
562
563 if verbose then
564 local compressed = z.compressed
565 local uncompressed = z.uncompressed
566 local filename = z.filename
567
568 local pct = 100 * compressed/uncompressed
569 logwriter("")
570 if pct >= 100 then
571 logwriter(format("%10i %s",uncompressed,filename))
572 else
573 logwriter(format("%10i %02.1f %s",uncompressed,pct,filename))
574 end
575 end
576
577 f:close()
578 end
579
580 local function zipdir(zipname,path,level,verbose)
581 if type(zipname) == "table" then
582 verbose = zipname.verbose
583 level = zipname.level
584 path = zipname.path
585 zipname = zipname.zipname
586 end
587 if not zipname or zipname == "" then
588 return
589 end
590 if not path or path == "" then
591 path = "."
592 end
593 if not isdir(path) then
594 return
595 end
596 path = gsub(path,"\\+","/")
597 path = gsub(path,"/+","/")
598 local list = { }
599 local count = 0
600 globpattern(path,"",true,function(name,size,time)
601 count = count + 1
602 list[count] = { name, time }
603 end)
604 sort(list,function(a,b)
605 return a[1] < b[1]
606 end)
607 local zipf = openzip(zipname,level,comment,verbose)
608 if zipf then
609 local p = #path + 2
610 for i=1,count do
611 local li = list[i]
612 local name = li[1]
613 local time = li[2]
614 local data = loaddata(name)
615 local name = sub(name,p,#name)
616 writezip(zipf,name,data,level,time,verbose)
617 end
618 closezip(zipf)
619 end
620 end
621
622 local function unzipdir(zipname,path,verbose,collect,validate)
623 if type(zipname) == "table" then
624 validate = zipname.validate
625 collect = zipname.collect
626 verbose = zipname.verbose
627 path = zipname.path
628 zipname = zipname.zipname
629 end
630 if not zipname or zipname == "" then
631 return
632 end
633 if not path or path == "" then
634 path = "."
635 end
636 local z = openzipfile(zipname)
637 if z then
638 local list = getziplist(z)
639 if list then
640 local total = 0
641 local count = #list
642 local step = number.idiv(count,10)
643 local done = 0
644 local steps = verbose == "steps"
645 local time = steps and osclock()
646
647 if collect then
648 collect = { }
649 else
650 collect = false
651 end
652 for i=1,count do
653 local l = list[i]
654 local n = l.filename
655 if not validate or validate(n) then
656 local d = unzipfile(z,n)
657 if d then
658 local p = filejoin(path,n)
659 if mkdirs(dirname(p)) then
660 if steps then
661 total = total + #d
662 done = done + 1
663 if done >= step then
664 done = 0
665 logwriter(format("%4i files of %4i done, %10i bytes, %0.3f seconds",i,count,total,osclock()-time))
666 end
667 elseif verbose then
668 logwriter(n)
669 end
670 savedata(p,d)
671 if collect then
672 collect[#collect+1] = p
673 end
674 end
675 else
676 logwriter(format("problem with file %s",n))
677 end
678 else
679
680 end
681 end
682 if steps then
683 logwriter(format("%4i files of %4i done, %10i bytes, %0.3f seconds",count,count,total,osclock()-time))
684 end
685 closezipfile(z)
686 if collect then
687 return collect
688 end
689 else
690 closezipfile(z)
691 end
692 end
693 end
694
695 zipfiles.zipdir = zipdir
696 zipfiles.unzipdir = unzipdir
697
698end
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718local pattern = "^\x1F\x8B\x08"
719local gziplevel = 3
720
721function gzip.suffix(filename)
722 local suffix, extra = suffixes(filename)
723 local gzipped = extra == "gz"
724 return suffix, gzipped
725end
726
727function gzip.compressed(s)
728 return s and find(s,pattern)
729end
730
731local getdecompressed
732local putcompressed
733
734if gzip.compress then
735
736 local gzipwindow = 15 + 16
737
738 local compress = zlib.compress
739 local decompress = zlib.decompress
740
741 getdecompressed = function(str)
742 return decompress(str,gzipwindow)
743 end
744
745 putcompressed = function(str,level)
746 return compress(str,level or gziplevel,nil,gzipwindow)
747 end
748
749else
750
751
752
753 local gzipwindow = -15
754 local identifier = "\x1F\x8B"
755
756 local compress = zlib.compress
757 local decompress = zlib.decompress
758 local zlibchecksum = zlib.crc32
759
760 if not CONTEXTLMTXMODE or CONTEXTLMTXMODE == 0 then
761 local cs = zlibchecksum
762 zlibchecksum = function(str,n) return cs(n or 0, str) end
763 end
764
765 local streams = utilities.streams
766 local openstream = streams.openstring
767 local closestream = streams.close
768 local getposition = streams.getposition
769 local readbyte = streams.readbyte
770 local readcardinal4 = streams.readcardinal4le
771 local readcardinal2 = streams.readcardinal2le
772 local readstring = streams.readstring
773 local readcstring = streams.readcstring
774 local skipbytes = streams.skip
775
776 local tocardinal1 = streams.tocardinal1
777 local tocardinal4 = streams.tocardinal4le
778
779 getdecompressed = function(str)
780 local s = openstream(str)
781 local identifier = readstring(s,2)
782 local method = readbyte(s,1)
783 local flags = readbyte(s,1)
784 local timestamp = readcardinal4(s)
785 local compression = readbyte(s,1)
786 local operating = readbyte(s,1)
787
788
789
790
791
792 local isjusttext = band(flags,0x01) ~= 0 and true or false
793 local extrasize = band(flags,0x04) ~= 0 and readcardinal2(s) or 0
794 local filename = band(flags,0x08) ~= 0 and readcstring(s) or ""
795 local comment = band(flags,0x10) ~= 0 and readcstring(s) or ""
796 local checksum = band(flags,0x02) ~= 0 and readcardinal2(s) or 0
797 local compressed = readstring(s,#str)
798 local data = decompress(compressed,gzipwindow)
799 return data
800 end
801
802 putcompressed = function(str,level,originalname)
803 return concat {
804 identifier,
805 tocardinal1(0x08),
806 tocardinal1(0x08),
807 tocardinal4(os.time()),
808 tocardinal1(0x02),
809 tocardinal1(0xFF),
810 (originalname or "unknownname") .. "\0",
811 compress(str,level,nil,gzipwindow),
812 tocardinal4(zlibchecksum(str)),
813 tocardinal4(#str),
814 }
815 end
816
817end
818
819function gzip.load(filename)
820 local f = openfile(filename,"rb")
821 if not f then
822
823 else
824 local data = f:read("*all")
825 f:close()
826 if data and data ~= "" then
827 if suffix(filename) == "gz" then
828 data = getdecompressed(data)
829 end
830 return data
831 end
832 end
833end
834
835function gzip.save(filename,data,level,originalname)
836 if suffix(filename) ~= "gz" then
837 filename = filename .. ".gz"
838 end
839 local f = openfile(filename,"wb")
840 if f then
841 data = putcompressed(data or "",level or gziplevel,originalname)
842 f:write(data)
843 f:close()
844 return #data
845 end
846end
847
848function gzip.compress(s,level)
849 if s and not find(s,pattern) then
850 if not level then
851 level = gziplevel
852 elseif level <= 0 then
853 return s
854 elseif level > 9 then
855 level = 9
856 end
857 return putcompressed(s,level or gziplevel) or s
858 end
859end
860
861function gzip.decompress(s)
862 if s and find(s,pattern) then
863 return getdecompressed(s)
864 else
865 return s
866 end
867end
868
869return zipfiles
870 |