231 lines
7.4 KiB
Lua
231 lines
7.4 KiB
Lua
|
|
module("luarocks.tools.zip", package.seeall)
|
|
|
|
local zlib = require "zlib"
|
|
|
|
local function number_to_bytestring(number, nbytes)
|
|
local out = {}
|
|
for i = 1, nbytes do
|
|
local byte = number % 256
|
|
table.insert(out, string.char(byte))
|
|
number = (number - byte) / 256
|
|
end
|
|
return table.concat(out)
|
|
end
|
|
|
|
--- Return a zip handle open for writing.
|
|
-- @param name filename of the zipfile to be created.
|
|
-- @return a zip handle, or nil in case of error.
|
|
function write_open(name)
|
|
|
|
local zf = {}
|
|
|
|
zf.ziphandle = io.open(name, "w")
|
|
if not zf.ziphandle then
|
|
return nil
|
|
end
|
|
zf.files = {}
|
|
zf.in_open_file = false
|
|
|
|
return zf
|
|
end
|
|
|
|
--- Begin a new file to be stored inside the zipfile.
|
|
-- @param zf handle of the zipfile being written.
|
|
-- @param filename filenome of the file to be added to the zipfile.
|
|
-- @return true if succeeded, nil in case of failure.
|
|
function write_open_new_file_in_zip(zf, filename)
|
|
if zf.in_open_file then
|
|
close_file_in_zip(zf)
|
|
return nil
|
|
end
|
|
local lfh = {}
|
|
zf.local_file_header = lfh
|
|
lfh.last_mod_file_time = 0 -- TODO
|
|
lfh.last_mod_file_date = 0 -- TODO
|
|
lfh.crc32 = 0 -- initial value
|
|
lfh.compressed_size = 0 -- unknown yet
|
|
lfh.uncompressed_size = 0 -- unknown yet
|
|
lfh.file_name_length = #filename
|
|
lfh.extra_field_length = 0
|
|
lfh.file_name = filename:gsub("\\", "/")
|
|
zf.in_open_file = true
|
|
zf.data = {}
|
|
return true
|
|
end
|
|
|
|
--- Write data to the file currently being stored in the zipfile.
|
|
-- @param zf handle of the zipfile being written.
|
|
-- @param buf string containing data to be written.
|
|
-- @return true if succeeded, nil in case of failure.
|
|
function write_in_file_in_zip(zf, buf)
|
|
if not zf.in_open_file then
|
|
return nil
|
|
end
|
|
local lfh = zf.local_file_header
|
|
local cbuf = zlib.compress(buf):sub(3, -5)
|
|
lfh.crc32 = zlib.crc32(lfh.crc32, buf)
|
|
lfh.compressed_size = lfh.compressed_size + #cbuf
|
|
lfh.uncompressed_size = lfh.uncompressed_size + #buf
|
|
table.insert(zf.data, cbuf)
|
|
return true
|
|
end
|
|
|
|
--- Complete the writing of a file stored in the zipfile.
|
|
-- @param zf handle of the zipfile being written.
|
|
-- @return true if succeeded, nil in case of failure.
|
|
function write_close_file_in_zip(zf)
|
|
local zh = zf.ziphandle
|
|
|
|
if not zf.in_open_file then
|
|
return nil
|
|
end
|
|
|
|
-- Local file header
|
|
local lfh = zf.local_file_header
|
|
lfh.offset = zh:seek()
|
|
zh:write(number_to_bytestring(0x04034b50, 4)) -- signature
|
|
zh:write(number_to_bytestring(20, 2)) -- version needed to extract: 2.0
|
|
zh:write(number_to_bytestring(0, 2)) -- general purpose bit flag
|
|
zh:write(number_to_bytestring(8, 2)) -- compression method: deflate
|
|
zh:write(number_to_bytestring(lfh.last_mod_file_time, 2))
|
|
zh:write(number_to_bytestring(lfh.last_mod_file_date, 2))
|
|
zh:write(number_to_bytestring(lfh.crc32, 4))
|
|
zh:write(number_to_bytestring(lfh.compressed_size, 4))
|
|
zh:write(number_to_bytestring(lfh.uncompressed_size, 4))
|
|
zh:write(number_to_bytestring(lfh.file_name_length, 2))
|
|
zh:write(number_to_bytestring(lfh.extra_field_length, 2))
|
|
zh:write(lfh.file_name)
|
|
|
|
-- File data
|
|
for _, cbuf in ipairs(zf.data) do
|
|
zh:write(cbuf)
|
|
end
|
|
|
|
-- Data descriptor
|
|
zh:write(number_to_bytestring(lfh.crc32, 4))
|
|
zh:write(number_to_bytestring(lfh.compressed_size, 4))
|
|
zh:write(number_to_bytestring(lfh.uncompressed_size, 4))
|
|
|
|
table.insert(zf.files, lfh)
|
|
zf.in_open_file = false
|
|
|
|
return true
|
|
end
|
|
|
|
--- Complete the writing of the zipfile.
|
|
-- @param zf handle of the zipfile being written.
|
|
-- @return true if succeeded, nil in case of failure.
|
|
function write_close(zf)
|
|
local zh = zf.ziphandle
|
|
|
|
local central_directory_offset = zh:seek()
|
|
|
|
local size_of_central_directory = 0
|
|
-- Central directory structure
|
|
for _, lfh in ipairs(zf.files) do
|
|
zh:write(number_to_bytestring(0x02014b50, 4)) -- signature
|
|
zh:write(number_to_bytestring(3, 2)) -- version made by: UNIX
|
|
zh:write(number_to_bytestring(20, 2)) -- version needed to extract: 2.0
|
|
zh:write(number_to_bytestring(0, 2)) -- general purpose bit flag
|
|
zh:write(number_to_bytestring(8, 2)) -- compression method: deflate
|
|
zh:write(number_to_bytestring(lfh.last_mod_file_time, 2))
|
|
zh:write(number_to_bytestring(lfh.last_mod_file_date, 2))
|
|
zh:write(number_to_bytestring(lfh.crc32, 4))
|
|
zh:write(number_to_bytestring(lfh.compressed_size, 4))
|
|
zh:write(number_to_bytestring(lfh.uncompressed_size, 4))
|
|
zh:write(number_to_bytestring(lfh.file_name_length, 2))
|
|
zh:write(number_to_bytestring(lfh.extra_field_length, 2))
|
|
zh:write(number_to_bytestring(0, 2)) -- file comment length
|
|
zh:write(number_to_bytestring(0, 2)) -- disk number start
|
|
zh:write(number_to_bytestring(0, 2)) -- internal file attributes
|
|
zh:write(number_to_bytestring(0, 4)) -- external file attributes
|
|
zh:write(number_to_bytestring(lfh.offset, 4)) -- relative offset of local header
|
|
zh:write(lfh.file_name)
|
|
size_of_central_directory = size_of_central_directory + 46 + lfh.file_name_length
|
|
end
|
|
|
|
-- End of central directory record
|
|
zh:write(number_to_bytestring(0x06054b50, 4)) -- signature
|
|
zh:write(number_to_bytestring(0, 2)) -- number of this disk
|
|
zh:write(number_to_bytestring(0, 2)) -- number of disk with start of central directory
|
|
zh:write(number_to_bytestring(#zf.files, 2)) -- total number of entries in the central dir on this disk
|
|
zh:write(number_to_bytestring(#zf.files, 2)) -- total number of entries in the central dir
|
|
zh:write(number_to_bytestring(size_of_central_directory, 4))
|
|
zh:write(number_to_bytestring(central_directory_offset, 4))
|
|
zh:write(number_to_bytestring(0, 2)) -- zip file comment length
|
|
zh:close()
|
|
|
|
return true
|
|
end
|
|
|
|
-- @return boolean or (boolean, string): true on success,
|
|
-- false and an error message on failure.
|
|
local function add_to_zip(zf, file)
|
|
local fin
|
|
local ok, err = write_open_new_file_in_zip(zf, file)
|
|
if not ok then
|
|
err = "error in opening "..file.." in zipfile"
|
|
else
|
|
fin = io.open(file, "rb")
|
|
if not fin then
|
|
ok = false
|
|
err = "error opening "..file.." for reading"
|
|
end
|
|
end
|
|
while ok do
|
|
local buf = fin:read(size_buf)
|
|
if not buf then
|
|
break
|
|
end
|
|
ok = write_in_file_in_zip(zf, buf)
|
|
if not ok then
|
|
err = "error in writing "..file.." in the zipfile"
|
|
end
|
|
end
|
|
if fin then
|
|
fin:close()
|
|
end
|
|
if ok then
|
|
ok = write_close_file_in_zip(zf)
|
|
if not ok then
|
|
err = "error in writing "..file.." in the zipfile"
|
|
end
|
|
end
|
|
return ok == true, err
|
|
end
|
|
|
|
--- Compress files in a .zip archive.
|
|
-- @param zipfile string: pathname of .zip archive to be created.
|
|
-- @param ... Filenames to be stored in the archive are given as
|
|
-- additional arguments.
|
|
-- @return boolean or (boolean, string): true on success,
|
|
-- false and an error message on failure.
|
|
function zip(zipfile, ...)
|
|
local zf = write_open(filename)
|
|
if not zf then
|
|
return nil, "error opening "..filename
|
|
end
|
|
|
|
local ok, err
|
|
for _, file in pairs({...}) do
|
|
if fs.is_dir(file) then
|
|
for _, file in pairs(fs.find(file)) do
|
|
if fs.is_file(file) then
|
|
ok, err = add_to_zip(file)
|
|
if not ok then break end
|
|
end
|
|
end
|
|
else
|
|
ok, err = add_to_zip(file)
|
|
if not ok then break end
|
|
end
|
|
end
|
|
|
|
local ok = write_close(zf)
|
|
if not ok then
|
|
return false, "error closing "..filename
|
|
end
|
|
return ok, err
|
|
end
|