Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- use build.xml to import the zip and json libraries directly
- local zip = setmetatable({}, {__index=getfenv()})
- local json = setmetatable({}, {__index=getfenv()})
- local base64 = setmetatable({}, {__index=getfenv()})
- local argparse = setmetatable({}, {__index=getfenv()})
- do
- local function zip_api_make()
- --[[
- LUA MODULE
- compress.deflatelua - deflate (and gunzip/zlib) implemented in Lua.
- SYNOPSIS
- local DEFLATE = require 'compress.deflatelua'
- -- uncompress gzip file
- local fh = assert(io.open'foo.txt.gz', 'rb')
- local ofh = assert(io.open'foo.txt', 'wb')
- DEFLATE.gunzip {input=fh, output=ofh}
- fh:close(); ofh:close()
- -- can also uncompress from string including zlib and raw DEFLATE formats.
- DESCRIPTION
- This is a pure Lua implementation of decompressing the DEFLATE format,
- including the related zlib and gzip formats.
- Note: This library only supports decompression.
- Compression is not currently implemented.
- API
- Note: in the following functions, input stream `fh` may be
- a file handle, string, or an iterator function that returns strings.
- Output stream `ofh` may be a file handle or a function that
- consumes one byte (number 0..255) per call.
- DEFLATE.inflate {input=fh, output=ofh}
- Decompresses input stream `fh` in the DEFLATE format
- while writing to output stream `ofh`.
- DEFLATE is detailed in http://tools.ietf.org/html/rfc1951 .
- DEFLATE.gunzip {input=fh, output=ofh, disable_crc=disable_crc}
- Decompresses input stream `fh` with the gzip format
- while writing to output stream `ofh`.
- `disable_crc` (defaults to `false`) will disable CRC-32 checking
- to increase speed.
- gzip is detailed in http://tools.ietf.org/html/rfc1952 .
- DEFLATE.inflate_zlib {input=fh, output=ofh, disable_crc=disable_crc}
- Decompresses input stream `fh` with the zlib format
- while writing to output stream `ofh`.
- `disable_crc` (defaults to `false`) will disable CRC-32 checking
- to increase speed.
- zlib is detailed in http://tools.ietf.org/html/rfc1950 .
- DEFLATE.adler32(byte, crc) --> rcrc
- Returns adler32 checksum of byte `byte` (number 0..255) appended
- to string with adler32 checksum `crc`. This is internally used by
- `inflate_zlib`.
- ADLER32 in detailed in http://tools.ietf.org/html/rfc1950 .
- COMMAND LINE UTILITY
- A `gunziplua` command line utility (in folder `bin`) is also provided.
- This mimicks the *nix `gunzip` utility but is a pure Lua implementation
- that invokes this library. For help do
- gunziplua -h
- DEPENDENCIES
- Requires 'digest.crc32lua' (used for optional CRC-32 checksum checks).
- https://github.com/davidm/lua-digest-crc32lua
- Will use a bit library ('bit', 'bit32', 'bit.numberlua') if available. This
- is not that critical for this library but is required by digest.crc32lua.
- 'pythonic.optparse' is only required by the optional `gunziplua`
- command-line utilty for command line parsing.
- https://github.com/davidm/lua-pythonic-optparse
- INSTALLATION
- Copy the `compress` directory into your LUA_PATH.
- REFERENCES
- [1] DEFLATE Compressed Data Format Specification version 1.3
- http://tools.ietf.org/html/rfc1951
- [2] GZIP file format specification version 4.3
- http://tools.ietf.org/html/rfc1952
- [3] http://en.wikipedia.org/wiki/DEFLATE
- [4] pyflate, by Paul Sladen
- http://www.paul.sladen.org/projects/pyflate/
- [5] Compress::Zlib::Perl - partial pure Perl implementation of
- Compress::Zlib
- http://search.cpan.org/~nwclark/Compress-Zlib-Perl/Perl.pm
- LICENSE
- (c) 2008-2011 David Manura. Licensed under the same terms as Lua (MIT).
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
- (end license)
- --]]
- local assert = assert
- local error = error
- local ipairs = ipairs
- local pairs = pairs
- local print = print
- local require = require
- local tostring = tostring
- local type = type
- local setmetatable = setmetatable
- local io = io
- local math = math
- local table_sort = table.sort
- local math_max = math.max
- local string_char = string.char
- --[[
- Requires the first module listed that exists, else raises like `require`.
- If a non-string is encountered, it is returned.
- Second return value is module name loaded (or '').
- --]]
- local function requireany(...)
- local errs = {}
- for i = 1, select('#', ...) do local name = select(i, ...)
- if type(name) ~= 'string' then return name, '' end
- local ok, mod = pcall(require, name)
- if ok then return mod, name end
- errs[#errs+1] = mod
- end
- error(table.concat(errs, '\n'), 2)
- end
- --local crc32 = require "digest.crc32lua" . crc32_byte
- --local bit, name_ = requireany('bit', 'bit32', 'bit.numberlua', nil)
- local bit = bit
- local DEBUG = false
- -- Whether to use `bit` library functions in current module.
- -- Unlike the crc32 library, it doesn't make much difference in this module.
- local NATIVE_BITOPS = false--(bit ~= nil) -- not sure why, but this code didn't work with the bit library
- local band, lshift, rshift
- if NATIVE_BITOPS then
- band = bit.band
- lshift = bit.blshift
- rshift = bit.brshift
- end
- local function warn(s)
- io.stderr:write(s, '\n')
- end
- local function debug(...)
- print('DEBUG', ...)
- end
- local function runtime_error(s, level)
- level = level or 1
- error(s, level+1)
- end
- local function make_outstate(outbs)
- local outstate = {}
- outstate.outbs = outbs
- outstate.window = {}
- outstate.window_pos = 1
- return outstate
- end
- local function output(outstate, byte)
- -- debug('OUTPUT:', s)
- local window_pos = outstate.window_pos
- outstate.outbs(byte)
- outstate.window[window_pos] = byte
- outstate.window_pos = window_pos % 32768 + 1 -- 32K
- end
- local function noeof(val)
- return assert(val, 'unexpected end of file')
- end
- local function hasbit(bits, bit)
- return bits % (bit + bit) >= bit
- end
- local function memoize(f)
- local mt = {}
- local t = setmetatable({}, mt)
- function mt:__index(k)
- local v = f(k)
- t[k] = v
- return v
- end
- return t
- end
- -- small optimization (lookup table for powers of 2)
- local pow2 = memoize(function(n) return 2^n end)
- --local tbits = memoize(
- -- function(bits)
- -- return memoize( function(bit) return getbit(bits, bit) end )
- -- end )
- -- weak metatable marking objects as bitstream type
- local is_bitstream = setmetatable({}, {__mode='k'})
- -- DEBUG
- -- prints LSB first
- --[[
- local function bits_tostring(bits, nbits)
- local s = ''
- local tmp = bits
- local function f()
- local b = tmp % 2 == 1 and 1 or 0
- s = s .. b
- tmp = (tmp - b) / 2
- end
- if nbits then
- for i=1,nbits do f() end
- else
- while tmp ~= 0 do f() end
- end
- return s
- end
- --]]
- local function bytestream_from_file(fh)
- local o = {}
- function o:read()
- local sb = fh:read(1)
- if sb then return sb:byte() end
- end
- return o
- end
- local function bytestream_from_string(s)
- local i = 1
- local o = {}
- function o:read()
- local by
- if i <= #s then
- by = s:byte(i)
- i = i + 1
- end
- return by
- end
- return o
- end
- local function bytestream_from_function(f)
- local i = 0
- local buffer = ''
- local o = {}
- function o:read()
- i = i + 1
- if i > #buffer then
- buffer = f()
- if not buffer then return end
- i = 1
- end
- return buffer:byte(i,i)
- end
- return o
- end
- local function bitstream_from_bytestream(bys)
- local buf_byte = 0
- local buf_nbit = 0
- local o = {}
- function o:nbits_left_in_byte()
- return buf_nbit
- end
- if NATIVE_BITOPS then
- function o:read(nbits)
- nbits = nbits or 1
- while buf_nbit < nbits do
- local byte = bys:read()
- if not byte then return end -- note: more calls also return nil
- buf_byte = buf_byte + lshift(byte, buf_nbit)
- buf_nbit = buf_nbit + 8
- end
- local bits
- if nbits == 0 then
- bits = 0
- elseif nbits == 32 then
- bits = buf_byte
- buf_byte = 0
- else
- bits = band(buf_byte, rshift(0xffffffff, 32 - nbits))
- buf_byte = rshift(buf_byte, nbits)
- end
- buf_nbit = buf_nbit - nbits
- return bits
- end
- else
- function o:read(nbits)
- nbits = nbits or 1
- while buf_nbit < nbits do
- local byte = bys:read()
- if not byte then return end -- note: more calls also return nil
- buf_byte = buf_byte + pow2[buf_nbit] * byte
- buf_nbit = buf_nbit + 8
- end
- local m = pow2[nbits]
- local bits = buf_byte % m
- buf_byte = (buf_byte - bits) / m
- buf_nbit = buf_nbit - nbits
- return bits
- end
- end
- is_bitstream[o] = true
- return o
- end
- local function get_bitstream(o)
- local bs
- if is_bitstream[o] then
- return o
- elseif io.type(o) == 'file' then
- bs = bitstream_from_bytestream(bytestream_from_file(o))
- elseif type(o) == 'string' then
- bs = bitstream_from_bytestream(bytestream_from_string(o))
- elseif type(o) == 'function' then
- bs = bitstream_from_bytestream(bytestream_from_function(o))
- else
- runtime_error 'unrecognized type'
- end
- return bs
- end
- local function get_obytestream(o)
- local bs
- if io.type(o) == 'file' then
- bs = function(sbyte) o:write(string_char(sbyte)) end
- elseif type(o) == 'function' then
- bs = o
- else
- runtime_error('unrecognized type: ' .. tostring(o))
- end
- return bs
- end
- local function HuffmanTable(init, is_full)
- local t = {}
- if is_full then
- for val,nbits in pairs(init) do
- if nbits ~= 0 then
- t[#t+1] = {val=val, nbits=nbits}
- --debug('*',val,nbits)
- end
- end
- else
- for i=1,#init-2,2 do
- local firstval, nbits, nextval = init[i], init[i+1], init[i+2]
- --debug(val, nextval, nbits)
- if nbits ~= 0 then
- for val=firstval,nextval-1 do
- t[#t+1] = {val=val, nbits=nbits}
- end
- end
- end
- end
- table_sort(t, function(a,b)
- return a.nbits == b.nbits and a.val < b.val or a.nbits < b.nbits
- end)
- -- assign codes
- local code = 1 -- leading 1 marker
- local nbits = 0
- for i,s in ipairs(t) do
- if s.nbits ~= nbits then
- code = code * pow2[s.nbits - nbits]
- nbits = s.nbits
- end
- s.code = code
- --debug('huffman code:', i, s.nbits, s.val, code, bits_tostring(code))
- code = code + 1
- end
- local minbits = math.huge
- local look = {}
- for i,s in ipairs(t) do
- minbits = math.min(minbits, s.nbits)
- look[s.code] = s.val
- end
- --for _,o in ipairs(t) do
- -- debug(':', o.nbits, o.val)
- --end
- -- function t:lookup(bits) return look[bits] end
- local msb = NATIVE_BITOPS and function(bits, nbits)
- local res = 0
- for i=1,nbits do
- res = lshift(res, 1) + band(bits, 1)
- bits = rshift(bits, 1)
- end
- return res
- end or function(bits, nbits)
- local res = 0
- for i=1,nbits do
- local b = bits % 2
- bits = (bits - b) / 2
- res = res * 2 + b
- end
- return res
- end
- local tfirstcode = memoize(
- function(bits) return pow2[minbits] + msb(bits, minbits) end)
- function t:read(bs)
- local code = 1 -- leading 1 marker
- local nbits = 0
- while 1 do
- if nbits == 0 then -- small optimization (optional)
- code = tfirstcode[noeof(bs:read(minbits))]
- nbits = nbits + minbits
- else
- local b = noeof(bs:read())
- nbits = nbits + 1
- code = code * 2 + b -- MSB first
- --[[NATIVE_BITOPS
- code = lshift(code, 1) + b -- MSB first
- --]]
- end
- --debug('code?', code, bits_tostring(code))
- local val = look[code]
- if val then
- --debug('FOUND', val)
- return val
- end
- end
- end
- return t
- end
- local function parse_gzip_header(bs)
- -- local FLG_FTEXT = 2^0
- local FLG_FHCRC = 2^1
- local FLG_FEXTRA = 2^2
- local FLG_FNAME = 2^3
- local FLG_FCOMMENT = 2^4
- local id1 = bs:read(8)
- local id2 = bs:read(8)
- if id1 ~= 31 or id2 ~= 139 then
- runtime_error 'not in gzip format'
- end
- local cm = bs:read(8) -- compression method
- local flg = bs:read(8) -- FLaGs
- local mtime = bs:read(32) -- Modification TIME
- local xfl = bs:read(8) -- eXtra FLags
- local os = bs:read(8) -- Operating System
- if DEBUG then
- debug("CM=", cm)
- debug("FLG=", flg)
- debug("MTIME=", mtime)
- -- debug("MTIME_str=",os.date("%Y-%m-%d %H:%M:%S",mtime)) -- non-portable
- debug("XFL=", xfl)
- debug("OS=", os)
- end
- if not os then runtime_error 'invalid header' end
- if hasbit(flg, FLG_FEXTRA) then
- local xlen = bs:read(16)
- local extra = 0
- for i=1,xlen do
- extra = bs:read(8)
- end
- if not extra then runtime_error 'invalid header' end
- end
- local function parse_zstring(bs)
- repeat
- local by = bs:read(8)
- if not by then runtime_error 'invalid header' end
- until by == 0
- end
- if hasbit(flg, FLG_FNAME) then
- parse_zstring(bs)
- end
- if hasbit(flg, FLG_FCOMMENT) then
- parse_zstring(bs)
- end
- if hasbit(flg, FLG_FHCRC) then
- local crc16 = bs:read(16)
- if not crc16 then runtime_error 'invalid header' end
- -- IMPROVE: check CRC. where is an example .gz file that
- -- has this set?
- if DEBUG then
- debug("CRC16=", crc16)
- end
- end
- end
- local function parse_zlib_header(bs)
- local cm = bs:read(4) -- Compression Method
- local cinfo = bs:read(4) -- Compression info
- local fcheck = bs:read(5) -- FLaGs: FCHECK (check bits for CMF and FLG)
- local fdict = bs:read(1) -- FLaGs: FDICT (present dictionary)
- local flevel = bs:read(2) -- FLaGs: FLEVEL (compression level)
- local cmf = cinfo * 16 + cm -- CMF (Compresion Method and flags)
- local flg = fcheck + fdict * 32 + flevel * 64 -- FLaGs
- if cm ~= 8 then -- not "deflate"
- runtime_error("unrecognized zlib compression method: " + cm)
- end
- if cinfo > 7 then
- runtime_error("invalid zlib window size: cinfo=" + cinfo)
- end
- local window_size = 2^(cinfo + 8)
- if (cmf*256 + flg) % 31 ~= 0 then
- runtime_error("invalid zlib header (bad fcheck sum)")
- end
- if fdict == 1 then
- runtime_error("FIX:TODO - FDICT not currently implemented")
- local dictid_ = bs:read(32)
- end
- return window_size
- end
- local function parse_huffmantables(bs)
- local hlit = bs:read(5) -- # of literal/length codes - 257
- local hdist = bs:read(5) -- # of distance codes - 1
- local hclen = noeof(bs:read(4)) -- # of code length codes - 4
- local ncodelen_codes = hclen + 4
- local codelen_init = {}
- local codelen_vals = {
- 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}
- for i=1,ncodelen_codes do
- local nbits = bs:read(3)
- local val = codelen_vals[i]
- codelen_init[val] = nbits
- end
- local codelentable = HuffmanTable(codelen_init, true)
- local function decode(ncodes)
- local init = {}
- local nbits
- local val = 0
- while val < ncodes do
- local codelen = codelentable:read(bs)
- --FIX:check nil?
- local nrepeat
- if codelen <= 15 then
- nrepeat = 1
- nbits = codelen
- --debug('w', nbits)
- elseif codelen == 16 then
- nrepeat = 3 + noeof(bs:read(2))
- -- nbits unchanged
- elseif codelen == 17 then
- nrepeat = 3 + noeof(bs:read(3))
- nbits = 0
- elseif codelen == 18 then
- nrepeat = 11 + noeof(bs:read(7))
- nbits = 0
- else
- error 'ASSERT'
- end
- for i=1,nrepeat do
- init[val] = nbits
- val = val + 1
- end
- end
- local huffmantable = HuffmanTable(init, true)
- return huffmantable
- end
- local nlit_codes = hlit + 257
- local ndist_codes = hdist + 1
- local littable = decode(nlit_codes)
- local disttable = decode(ndist_codes)
- return littable, disttable
- end
- local tdecode_len_base
- local tdecode_len_nextrabits
- local tdecode_dist_base
- local tdecode_dist_nextrabits
- local function parse_compressed_item(bs, outstate, littable, disttable)
- local val = littable:read(bs)
- --debug(val, val < 256 and string_char(val))
- if val < 256 then -- literal
- output(outstate, val)
- elseif val == 256 then -- end of block
- return true
- else
- if not tdecode_len_base then
- local t = {[257]=3}
- local skip = 1
- for i=258,285,4 do
- for j=i,i+3 do t[j] = t[j-1] + skip end
- if i ~= 258 then skip = skip * 2 end
- end
- t[285] = 258
- tdecode_len_base = t
- --for i=257,285 do debug('T1',i,t[i]) end
- end
- if not tdecode_len_nextrabits then
- local t = {}
- if NATIVE_BITOPS then
- for i=257,285 do
- local j = math_max(i - 261, 0)
- t[i] = rshift(j, 2)
- end
- else
- for i=257,285 do
- local j = math_max(i - 261, 0)
- t[i] = (j - (j % 4)) / 4
- end
- end
- t[285] = 0
- tdecode_len_nextrabits = t
- --for i=257,285 do debug('T2',i,t[i]) end
- end
- local len_base = tdecode_len_base[val]
- local nextrabits = tdecode_len_nextrabits[val]
- local extrabits = bs:read(nextrabits)
- local len = len_base + extrabits
- if not tdecode_dist_base then
- local t = {[0]=1}
- local skip = 1
- for i=1,29,2 do
- for j=i,i+1 do t[j] = t[j-1] + skip end
- if i ~= 1 then skip = skip * 2 end
- end
- tdecode_dist_base = t
- --for i=0,29 do debug('T3',i,t[i]) end
- end
- if not tdecode_dist_nextrabits then
- local t = {}
- if NATIVE_BITOPS then
- for i=0,29 do
- local j = math_max(i - 2, 0)
- t[i] = rshift(j, 1)
- end
- else
- for i=0,29 do
- local j = math_max(i - 2, 0)
- t[i] = (j - (j % 2)) / 2
- end
- end
- tdecode_dist_nextrabits = t
- --for i=0,29 do debug('T4',i,t[i]) end
- end
- local dist_val = disttable:read(bs)
- local dist_base = tdecode_dist_base[dist_val]
- local dist_nextrabits = tdecode_dist_nextrabits[dist_val]
- local dist_extrabits = bs:read(dist_nextrabits)
- local dist = dist_base + dist_extrabits
- --debug('BACK', len, dist)
- for i=1,len do
- local pos = (outstate.window_pos - 1 - dist) % 32768 + 1 -- 32K
- output(outstate, assert(outstate.window[pos], 'invalid distance'))
- end
- end
- return false
- end
- local function parse_block(bs, outstate)
- local bfinal = bs:read(1)
- local btype = bs:read(2)
- local BTYPE_NO_COMPRESSION = 0
- local BTYPE_FIXED_HUFFMAN = 1
- local BTYPE_DYNAMIC_HUFFMAN = 2
- local BTYPE_RESERVED_ = 3
- if DEBUG then
- debug('bfinal=', bfinal)
- debug('btype=', btype)
- end
- if btype == BTYPE_NO_COMPRESSION then
- bs:read(bs:nbits_left_in_byte())
- local len = bs:read(16)
- local nlen_ = noeof(bs:read(16))
- for i=1,len do
- local by = noeof(bs:read(8))
- output(outstate, by)
- end
- elseif btype == BTYPE_FIXED_HUFFMAN or btype == BTYPE_DYNAMIC_HUFFMAN then
- local littable, disttable
- if btype == BTYPE_DYNAMIC_HUFFMAN then
- littable, disttable = parse_huffmantables(bs)
- else
- littable = HuffmanTable {0,8, 144,9, 256,7, 280,8, 288,nil}
- disttable = HuffmanTable {0,5, 32,nil}
- end
- repeat
- local is_done = parse_compressed_item(
- bs, outstate, littable, disttable)
- until is_done
- else
- runtime_error('unrecognized compression type '..btype)
- end
- return bfinal ~= 0
- end
- function inflate(t)
- local bs = get_bitstream(t.input)
- local outbs = get_obytestream(t.output)
- local outstate = make_outstate(outbs)
- repeat
- local is_final = parse_block(bs, outstate)
- until is_final
- end
- local inflate = inflate
- function gunzip(t)
- local bs = get_bitstream(t.input)
- local outbs = get_obytestream(t.output)
- local disable_crc = t.disable_crc
- if disable_crc == nil then disable_crc = false end
- parse_gzip_header(bs)
- local data_crc32 = 0
- inflate{input=bs, output=
- disable_crc and outbs or
- function(byte)
- data_crc32 = crc32(byte, data_crc32)
- outbs(byte)
- end
- }
- bs:read(bs:nbits_left_in_byte())
- local expected_crc32 = bs:read(32)
- local isize = bs:read(32) -- ignored
- if DEBUG then
- debug('crc32=', expected_crc32)
- debug('isize=', isize)
- end
- if not disable_crc and data_crc32 then
- if data_crc32 ~= expected_crc32 then
- runtime_error('invalid compressed data--crc error')
- end
- end
- if bs:read() then
- warn 'trailing garbage ignored'
- end
- end
- function adler32(byte, crc)
- local s1 = crc % 65536
- local s2 = (crc - s1) / 65536
- s1 = (s1 + byte) % 65521
- s2 = (s2 + s1) % 65521
- return s2*65536 + s1
- end -- 65521 is the largest prime smaller than 2^16
- function inflate_zlib(t)
- local bs = get_bitstream(t.input)
- local outbs = get_obytestream(t.output)
- local disable_crc = t.disable_crc
- if disable_crc == nil then disable_crc = false end
- local window_size_ = parse_zlib_header(bs)
- local data_adler32 = 1
- inflate{input=bs, output=
- disable_crc and outbs or
- function(byte)
- data_adler32 = M.adler32(byte, data_adler32)
- outbs(byte)
- end
- }
- bs:read(bs:nbits_left_in_byte())
- local b3 = bs:read(8)
- local b2 = bs:read(8)
- local b1 = bs:read(8)
- local b0 = bs:read(8)
- local expected_adler32 = ((b3*256 + b2)*256 + b1)*256 + b0
- if DEBUG then
- debug('alder32=', expected_adler32)
- end
- if not disable_crc then
- if data_adler32 ~= expected_adler32 then
- runtime_error('invalid compressed data--crc error')
- end
- end
- if bs:read() then
- warn 'trailing garbage ignored'
- end
- end
- --------------------------------------------------------------------------------------------------------
- -- END DEFLATE
- -- BEGIN ZIP
- --------------------------------------------------------------------------------------------------------
- local function noCompression(t)
- local fIn
- if type(t.input) == "function" then
- fIn = t.input
- elseif type(t.input) == "table" and t.input.read then
- fIn = t.input.read
- end
- local fOut
- if type(t.output) == "function" then
- fOut = t.output
- elseif type(t.output) == "table" and t.output.write then
- fOut = t.output.write
- end
- for c in fIn do
- fOut(c:byte())
- end
- end
- local function assert(condition, errMsg, level)
- if condition then return condition end
- if type(level) ~= "number" then
- level = 2
- elseif level <= 0 then
- level = 0
- else
- level = level + 1
- end
- error(errMsg or "Assertion failed!", level)
- end
- function open(fileHandle)
- local obj = {}
- local data = {}
- local cursor = 1
- local endOfCentralDirectoryRecord
- local centralDirectoryHeaders = {}
- local function ux(x)
- local n = 0
- for i=1, x do
- n = n + data[cursor] * 2^((i-1) * 8)
- cursor = cursor + 1
- end
- return n
- end
- local function u1()
- return ux(1)
- end
- local function u2()
- return ux(2)
- end
- local function u4()
- return ux(4)
- end
- local function str(len)
- local s = ""
- for i=1,len or 1 do
- s = s .. string.char(u1())
- end
- return s
- end
- local function dataDescriptor()
- local dataDescriptor = {}
- local header = u4() -- optional header
- if header == 0x08074b50 then
- dataDescriptor.CRC_32 = u4()
- else
- dataDescriptor.CRC_32 = header
- end
- dataDescriptor.compressedSize = u4()
- dataDescriptor.unCompressedSize = u4()
- end
- local function parseFileHeader() -- 0x04034b50
- local fileHeader = {}
- assert(u4() == 0x04034b50, "Invalid local file header")
- fileHeader.version = u2()
- fileHeader.generalPurposeBitFlag = u2()
- fileHeader.compressionMethod = u2()
- fileHeader.fileLastModificationTime = u2()
- fileHeader.fileLastModificationDate = u2()
- fileHeader.CRC_32 = u4()
- fileHeader.compressedSize = u4()
- fileHeader.unCompressedSize = u4()
- fileHeader.fileNameLength = u2()
- fileHeader.extraFieldLength = u2()
- fileHeader.fileName = str(fileHeader.fileNameLength)
- fileHeader.extraField = str(fileHeader.extraFieldLength)
- fileHeader.compressedData = str(fileHeader.compressedSize)
- if bit.band(fileHeader.generalPurposeBitFlag, 8) > 0 then
- fileHeader.dataDescriptor = dataDescriptor()
- end
- return fileHeader
- end
- local function parseCentralDirectoryHeader()
- local header = {}
- header.versionMadeBy = u2()
- header.versionNeededToExtract = u2()
- header.generalPurposeBitFlag = u2()
- header.compressionMethod = u2()
- header.fileLastModificationTime = u2()
- header.fileLastModificationDate = u2()
- header.CRC_32 = u4()
- header.compressedSize = u4()
- header.unCompressedSize = u4()
- header.fileNameLength = u2()
- header.extraFieldLength = u2()
- header.commentLength = u2()
- header.diskNumber = u2()
- header.internalFileAttributes = u2()
- header.externalFileAttributes = u4()
- header.offset = u4()
- header.fileName = str(header.fileNameLength)
- header.extraField = str(header.extraFieldLength)
- header.comment = str(header.commentLength)
- return header
- end
- local function parseEndOfCentralDirectoryRecord()
- local record = {}
- record.numberOfThisDisk = u2()
- record.centralDirectoryDisk = u2()
- record.numRecordsOnThisDisk = u2()
- record.numRecords = u2()
- record.sizeOfCentralDirectory = u4()
- record.offsetOfCentralDirectory = u4()
- record.commentLength = u2()
- record.comment = str(record.commentLength)
- return record
- end
- local function findCentralDirectoryHeader(path)
- path = fs.combine(path, "")
- for i,v in ipairs(centralDirectoryHeaders) do
- if fs.combine(v.fileName, "") == path then
- return v
- end
- end
- end
- for b in fileHandle.read do
- table.insert(data, b)
- end
- cursor = #data - 3
- repeat
- local header = u4()
- cursor = cursor - 5
- until header == 0x06054b50
- cursor = cursor + 5
- endOfCentralDirectoryRecord = parseEndOfCentralDirectoryRecord()
- cursor = endOfCentralDirectoryRecord.offsetOfCentralDirectory + 1
- while u4() == 0x02014b50 do
- table.insert(centralDirectoryHeaders, parseCentralDirectoryHeader())
- end
- local zfs = {}
- function zfs.exists(path)
- return findCentralDirectoryHeader(fs.combine(path, "")) and true or false
- end
- function zfs.isDir(path)
- assert(type(path) == "string", "Expected string, got " .. type(path), 2)
- if path == "" or path == "/" then
- return true
- end
- local header = findCentralDirectoryHeader(path)
- return (header ~= nil) and header.fileName:sub(-1) == "/"
- end
- function zfs.list(path)
- path = fs.combine(path, "")
- assert(not zfs.exists(path) or zfs.isDir(path), "Not a directory", 2)
- local list = {}
- for i,v in ipairs(centralDirectoryHeaders) do
- local vpath = fs.combine(v.fileName, "")
- if path ~= vpath then
- if fs.getDir(vpath) == path then
- table.insert(list, fs.combine(vpath:sub(#path + 1), ""))
- end
- end
- end
- return list
- end
- function zfs.open(path, mode)
- assert(mode == "r" or mode == "rb", "Invalid open mode: " .. tostring(mode), 2)
- if not zfs.exists(path) then
- return
- end
- local outData = {}
- local cdHeader = findCentralDirectoryHeader(fs.combine(path, ""))
- cursor = cdHeader.offset + 1
- local localFileHeader = parseFileHeader()
- assert(localFileHeader.compressionMethod == 8 or localFileHeader.compressionMethod == 0,
- "Unsupported compression method", 2)
- local compressedDataCursor = 1
- local decompressor
- if localFileHeader.compressionMethod == 8 then
- decompressor = inflate
- elseif localFileHeader.compressionMethod == 0 then
- decompressor = noCompression
- end
- decompressor{input=function()
- if compressedDataCursor > localFileHeader.compressedSize then
- return
- end
- compressedDataCursor = compressedDataCursor + 1
- return localFileHeader.compressedData:sub(compressedDataCursor-1,compressedDataCursor-1)
- end, output=function(byte)
- if mode == "rb" then
- table.insert(outData, byte)
- elseif mode == "r" then
- table.insert(outData, string.char(byte))
- end
- end}
- local fh = {}
- if mode == "rb" then
- function fh.read()
- return table.remove(outData, 1)
- end
- elseif mode == "r" then
- function fh.readLine()
- local s = ""
- for c in function() return table.remove(outData, 1) end do
- if c == "\n" then
- break
- end
- s = s .. c
- end
- return s
- end
- function fh.readAll()
- local s = table.concat(outData)
- outData = {}
- return s
- end
- end
- function fh.close()
- outData = {}
- end
- return fh
- end
- return zfs
- end
- end
- setfenv(zip_api_make, zip)
- zip_api_make()
- local function json_api_make()
- ------------------------------------------------------------------ utils
- local controls = {["\n"]="\\n", ["\r"]="\\r", ["\t"]="\\t", ["\b"]="\\b", ["\f"]="\\f", ["\""]="\\\"", ["\\"]="\\\\"}
- local function isArray(t)
- local max = 0
- for k,v in pairs(t) do
- if type(k) ~= "number" then
- return false
- elseif k > max then
- max = k
- end
- end
- return max == #t
- end
- local whites = {['\n']=true; ['\r']=true; ['\t']=true; [' ']=true; [',']=true; [':']=true}
- function removeWhite(str)
- while whites[str:sub(1, 1)] do
- str = str:sub(2)
- end
- return str
- end
- ------------------------------------------------------------------ encoding
- local function encodeCommon(val, pretty, tabLevel, tTracking)
- local str = ""
- -- Tabbing util
- local function tab(s)
- str = str .. ("\t"):rep(tabLevel) .. s
- end
- local function arrEncoding(val, bracket, closeBracket, iterator, loopFunc)
- str = str .. bracket
- if pretty then
- str = str .. "\n"
- tabLevel = tabLevel + 1
- end
- for k,v in iterator(val) do
- tab("")
- loopFunc(k,v)
- str = str .. ","
- if pretty then str = str .. "\n" end
- end
- if pretty then
- tabLevel = tabLevel - 1
- end
- if str:sub(-2) == ",\n" then
- str = str:sub(1, -3) .. "\n"
- elseif str:sub(-1) == "," then
- str = str:sub(1, -2)
- end
- tab(closeBracket)
- end
- -- Table encoding
- if type(val) == "table" then
- assert(not tTracking[val], "Cannot encode a table holding itself recursively")
- tTracking[val] = true
- if isArray(val) then
- arrEncoding(val, "[", "]", ipairs, function(k,v)
- str = str .. encodeCommon(v, pretty, tabLevel, tTracking)
- end)
- else
- arrEncoding(val, "{", "}", pairs, function(k,v)
- assert(type(k) == "string", "JSON object keys must be strings", 2)
- str = str .. encodeCommon(k, pretty, tabLevel, tTracking)
- str = str .. (pretty and ": " or ":") .. encodeCommon(v, pretty, tabLevel, tTracking)
- end)
- end
- -- String encoding
- elseif type(val) == "string" then
- str = '"' .. val:gsub("[%c\"\\]", controls) .. '"'
- -- Number encoding
- elseif type(val) == "number" or type(val) == "boolean" then
- str = tostring(val)
- else
- error("JSON only supports arrays, objects, numbers, booleans, and strings", 2)
- end
- return str
- end
- function encode(val)
- return encodeCommon(val, false, 0, {})
- end
- function encodePretty(val)
- return encodeCommon(val, true, 0, {})
- end
- ------------------------------------------------------------------ decoding
- local decodeControls = {}
- for k,v in pairs(controls) do
- decodeControls[v] = k
- end
- function parseBoolean(str)
- if str:sub(1, 4) == "true" then
- return true, removeWhite(str:sub(5))
- else
- return false, removeWhite(str:sub(6))
- end
- end
- function parseNull(str)
- return nil, removeWhite(str:sub(5))
- end
- local numChars = {['e']=true; ['E']=true; ['+']=true; ['-']=true; ['.']=true}
- function parseNumber(str)
- local i = 1
- while numChars[str:sub(i, i)] or tonumber(str:sub(i, i)) do
- i = i + 1
- end
- local val = tonumber(str:sub(1, i - 1))
- str = removeWhite(str:sub(i))
- return val, str
- end
- function parseString(str)
- str = str:sub(2)
- local s = ""
- while str:sub(1,1) ~= "\"" do
- local next = str:sub(1,1)
- str = str:sub(2)
- assert(next ~= "\n", "Unclosed string")
- if next == "\\" then
- local escape = str:sub(1,1)
- str = str:sub(2)
- next = assert(decodeControls[next..escape], "Invalid escape character")
- end
- s = s .. next
- end
- return s, removeWhite(str:sub(2))
- end
- function parseArray(str)
- str = removeWhite(str:sub(2))
- local val = {}
- local i = 1
- while str:sub(1, 1) ~= "]" do
- local v = nil
- v, str = parseValue(str)
- val[i] = v
- i = i + 1
- str = removeWhite(str)
- end
- str = removeWhite(str:sub(2))
- return val, str
- end
- function parseObject(str)
- str = removeWhite(str:sub(2))
- local val = {}
- while str:sub(1, 1) ~= "}" do
- local k, v = nil, nil
- k, v, str = parseMember(str)
- val[k] = v
- str = removeWhite(str)
- end
- str = removeWhite(str:sub(2))
- return val, str
- end
- function parseMember(str)
- local k = nil
- k, str = parseValue(str)
- local val = nil
- val, str = parseValue(str)
- return k, val, str
- end
- function parseValue(str)
- local fchar = str:sub(1, 1)
- if fchar == "{" then
- return parseObject(str)
- elseif fchar == "[" then
- return parseArray(str)
- elseif tonumber(fchar) ~= nil or numChars[fchar] then
- return parseNumber(str)
- elseif str:sub(1, 4) == "true" or str:sub(1, 5) == "false" then
- return parseBoolean(str)
- elseif fchar == "\"" then
- return parseString(str)
- elseif str:sub(1, 4) == "null" then
- return parseNull(str)
- end
- return nil
- end
- function decode(str)
- str = removeWhite(str)
- t = parseValue(str)
- return t
- end
- function decodeFromFile(path)
- local file = assert(fs.open(path, "r"))
- local decoded = decode(file.readAll())
- file.close()
- return decoded
- end
- end
- setfenv(json_api_make, json)
- json_api_make()
- local function base64_api_make()
- local lookup_V2C = {}
- for ixChar = 65, 90 do lookup_V2C[ixChar - 65] = string.char(ixChar) end
- for ixChar = 97, 122 do lookup_V2C[ixChar - 71] = string.char(ixChar) end
- for ixChar = 48, 57 do lookup_V2C[ixChar + 4] = string.char(ixChar) end
- lookup_V2C[62] = "+"
- lookup_V2C[63] = "/"
- local lookup_C2V = {}
- for key, value in pairs(lookup_V2C) do lookup_C2V[value] = key end
- lookup_C2V["="] = -1
- function decode(str)
- local data = {}
- for i=1,#str do
- data[i] = lookup_C2V[str:sub(i,i)]
- end
- local result = {}
- local oldTime = os.time()
- for i=1, #str, 4 do
- local newTime = os.time()
- if newTime - oldTime >= (0.020 * 3) then
- oldTime = newTime
- sleep(0)
- end
- if data[i + 3] == -1 then
- if data[i + 2] == -1 then
- table.insert(result, bit.blshift(data[i], 2) + bit.brshift(data[i + 1], 4))
- else
- table.insert(result, bit.blshift(data[i], 2) + bit.brshift(data[i + 1], 4))
- table.insert(result, bit.band(0xff, bit.blshift(data[i + 1], 4)) + bit.brshift(data[i + 2], 2))
- end
- else
- table.insert(result, bit.blshift(data[i], 2) + bit.brshift(data[i + 1], 4))
- table.insert(result, bit.band(0xff, bit.blshift(data[i + 1], 4)) + bit.brshift(data[i + 2], 2))
- table.insert(result, bit.band(0xff, bit.blshift(data[i + 2], 6)) + data[i + 3])
- end
- end
- return result
- end
- end
- setfenv(base64_api_make, base64)
- base64_api_make()
- local function argparse_api_make()
- ------------------------------------------------------------------
- -- Parameter
- ------------------------------------------------------------------
- local Parameter = {}
- function Parameter:matches(arg, options, tArgs)
- if arg:sub(1,1) ~= "-" then
- return false
- end
- arg = arg:sub(2)
- if not (arg:find("^"..self.name.."$") or arg:find("^"..self.sShortcut.."$")) then
- return false
- end
- local val = table.remove(tArgs, 1)
- if self.isMulti then
- options[self.name] = options[self.name] or {}
- table.insert(options[self.name], val)
- else
- options[self.name] = val
- end
- return true
- end
- function Parameter:shortcut(shortcut)
- self.sShortcut = shortcut
- return self
- end
- function Parameter:multi()
- self.isMulti = true
- return self
- end
- ------------------------------------------------------------------
- -- Switch
- ------------------------------------------------------------------
- local Switch = {}
- function Switch:matches(arg, options, tArgs)
- if arg:sub(1,1) ~= "-" then
- return false
- end
- arg = arg:sub(2)
- if not (arg:find("^"..self.name.."$") or arg:find("^"..self.sShortcut.."$")) then
- return false
- end
- options[self.name] = true
- return true
- end
- function Switch:shortcut(shortcut)
- self.sShortcut = shortcut
- return self
- end
- ------------------------------------------------------------------
- -- Argument
- ------------------------------------------------------------------
- local Argument = {}
- function Argument:matches(arg, options, tArgs)
- if self.matched then
- return false
- end
- if self.nCount == 1 then
- options[self.name] = arg
- else
- local count = self.nCount
- if count == "*" then
- count = #tArgs
- else
- count = count - 1
- end
- local args = {arg}
- for i=1, count do
- table.insert(args, table.remove(tArgs, 1))
- end
- options[self.name] = args
- end
- self.matched = true
- return true
- end
- function Argument:count(count)
- assert(type(count) == "number" or count == "*", "Bad argument to Argument:count. Expected number, got " .. count)
- self.nCount = count
- return self
- end
- ------------------------------------------------------------------
- -- Parser
- ------------------------------------------------------------------
- local Parser = {}
- function Parser:parameter(name)
- local param = setmetatable({name=name,sShortcut=name}, {__index=Parameter})
- table.insert(self.parameters, param)
- return param
- end
- function Parser:switch(name)
- local switch = setmetatable({name=name,sShortcut=name}, {__index=Switch})
- table.insert(self.switches, switch)
- return switch
- end
- function Parser:argument(name)
- local arg = setmetatable({name=name,nCount=1}, {__index=Argument})
- table.insert(self.arguments, arg)
- return arg
- end
- function Parser:usage(str)
- self.sUsage = str
- end
- function Parser:printUsage()
- print(self.sUsage)
- end
- function Parser:parseArg(arg, options, tArgs)
- for i,v in ipairs(self.parameters) do
- if v:matches(arg, options, tArgs) then
- return true
- end
- end
- for i,v in ipairs(self.switches) do
- if v:matches(arg, options, tArgs) then
- return true
- end
- end
- for i,v in ipairs(self.arguments) do
- if v:matches(arg, options, tArgs) then
- return true
- end
- end
- return false
- end
- function Parser:parse(options, ...)
- local tArgs = {...}
- for arg in function() return table.remove(tArgs, 1) end do
- if not self:parseArg(arg, options, tArgs) then
- print(tArgs.error or ("Unknown argument: "..arg))
- self:printUsage()
- return false
- end
- end
- return options
- end
- function new()
- local parser = setmetatable({parameters={},switches={},arguments={}}, {__index=Parser})
- return parser
- end
- end
- setfenv(argparse_api_make, argparse)
- argparse_api_make()
- end
- local oldTime = os.time()
- local function sleepCheckin()
- local newTime = os.time()
- if newTime - oldTime >= (0.020 * 1.5) then
- oldTime = newTime
- sleep(0)
- end
- end
- local function combine(path, ...)
- if not path then
- return ""
- end
- return fs.combine(path, combine(...))
- end
- -- Arguments
- local parser = argparse.new()
- parser
- :parameter"user"
- :shortcut"u"
- parser
- :parameter"repo"
- :shortcut"r"
- parser
- :parameter"tag"
- :shortcut"t"
- parser
- :switch"emit-events"
- :shortcut"e"
- parser
- :argument"dir"
- local options = parser:parse({}, ...)
- assert(options and options.user and options.repo and options.dir, "Usage: grin -user <user> -repo <repo> [-tag tag_name] <dir>")
- local print = print
- if options["emit-events"] then
- function print(...)
- local s = ""
- for i,v in ipairs({...}) do
- s = s .. tostring(v)
- end
- os.queueEvent("grin_install_status", s)
- end
- end
- -- Begin installation
- local githubApiResponse = assert(http.get("https://api.github.com/repos/"..options.user.."/"..options.repo.."/releases"))
- assert(githubApiResponse.getResponseCode() == 200, "Failed github response")
- print("Got github response")
- local githubApiJSON = json.decode(githubApiResponse.readAll())
- assert(type(githubApiJSON) == "table", "Malformed response")
- local release
- if options.tag then
- for i,v in ipairs(githubApiJSON) do
- if v.tag_name == options.tag then
- release = v
- break
- end
- end
- assert(release, "Release " .. options.tag .. " not found")
- else
- release = assert(githubApiJSON[1], "Latest release not found")
- end
- local assetUrl = assert(release.assets and release.assets[1] and release.assets[1].url, "Malformed response")
- print("Got JSON")
- local zipResponse = assert(http.get(assetUrl, {["Accept"]="application/octet-stream"}))
- assert(zipResponse.getResponseCode() == 200 or zipResponse.getResponseCode() == 302, "Failed zip response")
- local base64Str = zipResponse.readAll()
- print("Decoding base64")
- sleep(0)
- local zipTbl = assert(base64.decode(base64Str), "Failed to decode base 64")
- print("Zip scanned. Unarchiving...")
- sleep(0)
- local i = 0
- local zfs = zip.open({read=function()
- sleepCheckin()
- i = i + 1
- return zipTbl[i]
- end})
- local function copyFilesFromDir(dir)
- for i,v in ipairs(zfs.list(dir)) do
- sleepCheckin()
- local fullPath = fs.combine(dir, v)
- if zfs.isDir(fullPath) then
- copyFilesFromDir(fullPath)
- else
- print("Copying file: " .. fullPath)
- local fh = fs.open(combine(shell.resolve(options.dir), fullPath), "wb")
- local zfh = zfs.open(fullPath, "rb")
- for b in zfh.read do
- sleepCheckin()
- fh.write(b)
- end
- fh.close()
- zfh.close()
- end
- end
- end
- copyFilesFromDir("")
- print("grin installation complete")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement