aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbinary/all_in_one1
-rw-r--r--spec/fixtures/abc.bz2bin0 -> 66 bytes
-rw-r--r--spec/fs_spec.lua16
-rw-r--r--src/luarocks/fs/lua.lua148
-rw-r--r--src/luarocks/fs/unix/tools.lua60
-rw-r--r--src/luarocks/fs/win32/tools.lua102
-rw-r--r--src/luarocks/tools/zip.lua368
7 files changed, 504 insertions, 191 deletions
diff --git a/binary/all_in_one b/binary/all_in_one
index 882289f5..dcef0fbf 100755
--- a/binary/all_in_one
+++ b/binary/all_in_one
@@ -463,7 +463,6 @@ local function main()
463 463
464 local dependencies = { 464 local dependencies = {
465 md5 = "md5", 465 md5 = "md5",
466 luazip = if_platform("unix", "luazip"),
467 luasec = "./binary/luasec-0.7alpha-2.rockspec", 466 luasec = "./binary/luasec-0.7alpha-2.rockspec",
468 luaposix = if_platform("unix", "./binary/luaposix-34.0.4-1.rockspec"), 467 luaposix = if_platform("unix", "./binary/luaposix-34.0.4-1.rockspec"),
469 luasocket = "luasocket", 468 luasocket = "luasocket",
diff --git a/spec/fixtures/abc.bz2 b/spec/fixtures/abc.bz2
new file mode 100644
index 00000000..ee786715
--- /dev/null
+++ b/spec/fixtures/abc.bz2
Binary files differ
diff --git a/spec/fs_spec.lua b/spec/fs_spec.lua
index 66453404..5bec6168 100644
--- a/spec/fs_spec.lua
+++ b/spec/fs_spec.lua
@@ -1293,6 +1293,22 @@ describe("Luarocks fs test #unit", function()
1293 assert.falsy(exists_file("nonexistent")) 1293 assert.falsy(exists_file("nonexistent"))
1294 end) 1294 end)
1295 end) 1295 end)
1296
1297 describe("fs.bunzip2", function()
1298
1299 it("uncompresses a .bz2 file", function()
1300 local input = testing_paths.fixtures_dir .. "/abc.bz2"
1301 local output = os.tmpname()
1302 assert.truthy(fs.bunzip2(input, output))
1303 local fd = io.open(output, "r")
1304 local content = fd:read("*a")
1305 fd:close()
1306 assert.same(300000, #content)
1307 local abc = ("a"):rep(100000)..("b"):rep(100000)..("c"):rep(100000)
1308 assert.same(abc, content)
1309 end)
1310
1311 end)
1296 1312
1297 describe("fs.unzip", function() 1313 describe("fs.unzip", function()
1298 local tmpdir 1314 local tmpdir
diff --git a/src/luarocks/fs/lua.lua b/src/luarocks/fs/lua.lua
index f2fa50b6..375cdee3 100644
--- a/src/luarocks/fs/lua.lua
+++ b/src/luarocks/fs/lua.lua
@@ -1,6 +1,6 @@
1 1
2--- Native Lua implementation of filesystem and platform abstractions, 2--- Native Lua implementation of filesystem and platform abstractions,
3-- using LuaFileSystem, LZLib, MD5 and LuaCurl. 3-- using LuaFileSystem, LuaSocket, LuaSec, lua-zlib, LuaPosix, MD5.
4-- module("luarocks.fs.lua") 4-- module("luarocks.fs.lua")
5local fs_lua = {} 5local fs_lua = {}
6 6
@@ -10,20 +10,21 @@ local cfg = require("luarocks.core.cfg")
10local dir = require("luarocks.dir") 10local dir = require("luarocks.dir")
11local util = require("luarocks.util") 11local util = require("luarocks.util")
12 12
13local socket_ok, zip_ok, unzip_ok, lfs_ok, md5_ok, posix_ok, _ 13local socket_ok, zip_ok, lfs_ok, md5_ok, posix_ok, bz2_ok, _
14local http, ftp, lrzip, luazip, lfs, md5, posix 14local http, ftp, zip, lfs, md5, posix, bz2
15 15
16if cfg.fs_use_modules then 16if cfg.fs_use_modules then
17 socket_ok, http = pcall(require, "socket.http") 17 socket_ok, http = pcall(require, "socket.http")
18 _, ftp = pcall(require, "socket.ftp") 18 _, ftp = pcall(require, "socket.ftp")
19 zip_ok, lrzip = pcall(require, "luarocks.tools.zip") 19 zip_ok, zip = pcall(require, "luarocks.tools.zip")
20 unzip_ok, luazip = pcall(require, "zip"); _G.zip = nil 20 bz2_ok, bz2 = pcall(require, "luarocks.tools.bzip2")
21 lfs_ok, lfs = pcall(require, "lfs") 21 lfs_ok, lfs = pcall(require, "lfs")
22 md5_ok, md5 = pcall(require, "md5") 22 md5_ok, md5 = pcall(require, "md5")
23 posix_ok, posix = pcall(require, "posix") 23 posix_ok, posix = pcall(require, "posix")
24end 24end
25 25
26local patch = require("luarocks.tools.patch") 26local patch = require("luarocks.tools.patch")
27local tar = require("luarocks.tools.tar")
27 28
28local dir_stack = {} 29local dir_stack = {}
29 30
@@ -589,52 +590,54 @@ end
589end 590end
590 591
591--------------------------------------------------------------------- 592---------------------------------------------------------------------
592-- LuaZip functions 593-- lua-bz2 functions
594---------------------------------------------------------------------
595
596if bz2_ok then
597
598local function bunzip2_string(data)
599 local decompressor = bz2.initDecompress()
600 local output, err = decompressor:update(data)
601 if not output then
602 return nil, err
603 end
604 decompressor:close()
605 return output
606end
607
608--- Uncompresses a .bz2 file.
609-- @param infile string: pathname of .bz2 file to be extracted.
610-- @param outfile string or nil: pathname of output file to be produced.
611-- If not given, name is derived from input file.
612-- @return boolean: true on success; nil and error message on failure.
613function fs_lua.bunzip2(infile, outfile)
614 assert(type(infile) == "string")
615 assert(outfile == nil or type(outfile) == "string")
616 if not outfile then
617 outfile = infile:gsub("%.bz2$", "")
618 end
619
620 return fs.filter_file(bunzip2_string, infile, outfile)
621end
622
623end
624
625---------------------------------------------------------------------
626-- luarocks.tools.zip functions
593--------------------------------------------------------------------- 627---------------------------------------------------------------------
594 628
595if zip_ok then 629if zip_ok then
596 630
597function fs_lua.zip(zipfile, ...) 631function fs_lua.zip(zipfile, ...)
598 return lrzip.zip(zipfile, ...) 632 return zip.zip(zipfile, ...)
599end 633end
600 634
635function fs_lua.unzip(zipfile)
636 return zip.unzip(zipfile)
601end 637end
602 638
603if unzip_ok then 639function fs_lua.gunzip(infile, outfile)
604--- Uncompress files from a .zip archive. 640 return zip.gunzip(infile, outfile)
605-- @param filename string: pathname of .zip archive to be extracted.
606-- @return boolean: true on success, false on failure.
607function fs_lua.unzip(filename)
608 local zipfile, err = luazip.open(filename)
609 if not zipfile then return nil, err end
610 local files = zipfile:files()
611 local file = files()
612 repeat
613 if file.filename:sub(#file.filename) == "/" then
614 local ok, err = fs.make_dir(dir.path(fs.current_dir(), file.filename))
615 if not ok then return nil, err end
616 else
617 local base = dir.dir_name(file.filename)
618 if base ~= "" then
619 base = dir.path(fs.current_dir(), base)
620 if not fs.is_dir(base) then
621 local ok, err = fs.make_dir(base)
622 if not ok then return nil, err end
623 end
624 end
625 local rf, err = zipfile:open(file.filename)
626 if not rf then zipfile:close(); return nil, err end
627 local contents = rf:read("*a")
628 rf:close()
629 local wf, err = io.open(dir.path(fs.current_dir(), file.filename), "wb")
630 if not wf then zipfile:close(); return nil, err end
631 wf:write(contents)
632 wf:close()
633 end
634 file = files()
635 until not file
636 zipfile:close()
637 return true
638end 641end
639 642
640end 643end
@@ -878,6 +881,16 @@ local octal_to_rwx = {
878 ["7"] = "rwx", 881 ["7"] = "rwx",
879} 882}
880 883
884function fs_lua._unix_rwx_to_number(rwx)
885 local num = 0
886 for i = 1, 9 do
887 if rwx:sub(10 - i, 10 - i) == "-" then
888 num = num + 2^i
889 end
890 end
891 return num
892end
893
881do 894do
882 local umask_cache 895 local umask_cache
883 function fs_lua._unix_umask() 896 function fs_lua._unix_umask()
@@ -886,13 +899,8 @@ do
886 end 899 end
887 -- LuaPosix (as of 34.0.4) only returns the umask as rwx 900 -- LuaPosix (as of 34.0.4) only returns the umask as rwx
888 local rwx = posix.umask() 901 local rwx = posix.umask()
889 local oct = 0 902 local num = fs_lua._unix_rwx_to_number(rwx)
890 for i = 1, 9 do 903 umask_cache = ("%03o"):format(num)
891 if rwx:sub(10 - i, 10 - i) == "-" then
892 oct = oct + 2^i
893 end
894 end
895 umask_cache = ("%03o"):format(oct)
896 return umask_cache 904 return umask_cache
897 end 905 end
898end 906end
@@ -1070,4 +1078,46 @@ function fs_lua.is_lua(filename)
1070 return (result == true) 1078 return (result == true)
1071end 1079end
1072 1080
1081--- Unpack an archive.
1082-- Extract the contents of an archive, detecting its format by
1083-- filename extension.
1084-- @param archive string: Filename of archive.
1085-- @return boolean or (boolean, string): true on success, false and an error message on failure.
1086function fs_lua.unpack_archive(archive)
1087 assert(type(archive) == "string")
1088
1089 local ok, err
1090 archive = fs.absolute_name(archive)
1091 if archive:match("%.tar%.gz$") then
1092 local tar_filename = archive:gsub("%.gz$", "")
1093 ok, err = fs.gunzip(archive, tar_filename)
1094 if ok then
1095 ok, err = tar.untar(tar_filename, ".")
1096 end
1097 elseif archive:match("%.tgz$") then
1098 local tar_filename = archive:gsub("%.tgz$", ".tar")
1099 ok, err = fs.gunzip(archive, tar_filename)
1100 if ok then
1101 ok, err = tar.untar(tar_filename, ".")
1102 end
1103 elseif archive:match("%.tar%.bz2$") then
1104 local tar_filename = archive:gsub("%.bz2$", "")
1105 ok, err = fs.bunzip2(archive, tar_filename)
1106 if ok then
1107 ok, err = tar.untar(tar_filename, ".")
1108 end
1109 elseif archive:match("%.zip$") then
1110 ok, err = fs.unzip(archive)
1111 elseif archive:match("%.lua$") or archive:match("%.c$") then
1112 -- Ignore .lua and .c files; they don't need to be extracted.
1113 return true
1114 else
1115 return false, "Couldn't extract archive "..archive..": unrecognized filename extension"
1116 end
1117 if not ok then
1118 return false, "Failed extracting "..archive..": "..err
1119 end
1120 return true
1121end
1122
1073return fs_lua 1123return fs_lua
diff --git a/src/luarocks/fs/unix/tools.lua b/src/luarocks/fs/unix/tools.lua
index de63eb81..d6277119 100644
--- a/src/luarocks/fs/unix/tools.lua
+++ b/src/luarocks/fs/unix/tools.lua
@@ -136,6 +136,33 @@ function tools.unzip(zipfile)
136 return fs.execute_quiet(vars.UNZIP, zipfile) 136 return fs.execute_quiet(vars.UNZIP, zipfile)
137end 137end
138 138
139local function uncompress(default_ext, program, infile, outfile)
140 assert(type(infile) == "string")
141 assert(outfile == nil or type(outfile) == "string")
142 if not outfile then
143 outfile = infile:gsub("%."..default_ext.."$", "")
144 end
145 return fs.execute(fs.Q(program).." -c "..fs.Q(infile).." > "..fs.Q(outfile))
146end
147
148--- Uncompresses a .gz file.
149-- @param infile string: pathname of .gz file to be extracted.
150-- @param outfile string or nil: pathname of output file to be produced.
151-- If not given, name is derived from input file.
152-- @return boolean: true on success; nil and error message on failure.
153function tools.gunzip(infile, outfile)
154 return uncompress("gz", "gunzip", infile, outfile)
155end
156
157--- Uncompresses a .bz2 file.
158-- @param infile string: pathname of .bz2 file to be extracted.
159-- @param outfile string or nil: pathname of output file to be produced.
160-- If not given, name is derived from input file.
161-- @return boolean: true on success; nil and error message on failure.
162function tools.bunzip2(infile, outfile)
163 return uncompress("bz2", "bunzip2", infile, outfile)
164end
165
139--- Test is file/directory exists 166--- Test is file/directory exists
140-- @param file string: filename to test 167-- @param file string: filename to test
141-- @return boolean: true if file exists, false otherwise. 168-- @return boolean: true if file exists, false otherwise.
@@ -198,39 +225,6 @@ function tools.set_permissions(filename, mode, scope)
198 return fs.execute(vars.CHMOD, perms, filename) 225 return fs.execute(vars.CHMOD, perms, filename)
199end 226end
200 227
201--- Unpack an archive.
202-- Extract the contents of an archive, detecting its format by
203-- filename extension.
204-- @param archive string: Filename of archive.
205-- @return boolean or (boolean, string): true on success, false and an error message on failure.
206function tools.unpack_archive(archive)
207 assert(type(archive) == "string")
208
209 local pipe_to_tar = " | "..vars.TAR.." -xf -"
210
211 if not cfg.verbose then
212 pipe_to_tar = " 2> /dev/null"..fs.quiet(pipe_to_tar)
213 end
214
215 local ok
216 if archive:match("%.tar%.gz$") or archive:match("%.tgz$") then
217 ok = fs.execute_string(vars.GUNZIP.." -c "..fs.Q(archive)..pipe_to_tar)
218 elseif archive:match("%.tar%.bz2$") then
219 ok = fs.execute_string(vars.BUNZIP2.." -c "..fs.Q(archive)..pipe_to_tar)
220 elseif archive:match("%.zip$") then
221 ok = fs.execute_quiet(vars.UNZIP, archive)
222 elseif archive:match("%.lua$") or archive:match("%.c$") then
223 -- Ignore .lua and .c files; they don't need to be extracted.
224 return true
225 else
226 return false, "Couldn't extract archive "..archive..": unrecognized filename extension"
227 end
228 if not ok then
229 return false, "Failed extracting "..archive
230 end
231 return true
232end
233
234function tools.attributes(filename, attrtype) 228function tools.attributes(filename, attrtype)
235 local flag = ((attrtype == "permissions") and vars.STATPERMFLAG) 229 local flag = ((attrtype == "permissions") and vars.STATPERMFLAG)
236 or ((attrtype == "owner") and vars.STATOWNERFLAG) 230 or ((attrtype == "owner") and vars.STATOWNERFLAG)
diff --git a/src/luarocks/fs/win32/tools.lua b/src/luarocks/fs/win32/tools.lua
index 0a27cbe3..633aae5e 100644
--- a/src/luarocks/fs/win32/tools.lua
+++ b/src/luarocks/fs/win32/tools.lua
@@ -148,6 +148,51 @@ function tools.unzip(zipfile)
148 return fs.execute_quiet(fs.Q(vars.SEVENZ).." -aoa x", zipfile) 148 return fs.execute_quiet(fs.Q(vars.SEVENZ).." -aoa x", zipfile)
149end 149end
150 150
151local function sevenz(default_ext, infile, outfile)
152 assert(type(infile) == "string")
153 assert(outfile == nil or type(outfile) == "string")
154
155 local dropext = infile:gsub("%."..default_ext.."$", "")
156 local outdir = dir.dir_name(dropext)
157
158 infile = fs.absolute_name(infile)
159
160 local cmdline = fs.Q(vars.SEVENZ).." -aoa -t* -o"..fs.Q(outdir).." x "..fs.Q(infile)
161 local ok, err = fs.execute_quiet(cmdline)
162 if not ok then
163 return nil, err
164 end
165
166 if outfile then
167 outfile = fs.absolute_name(outfile)
168 dropext = fs.absolute_name(dropext)
169 ok, err = os.rename(dropext, outfile)
170 if not ok then
171 return nil, err
172 end
173 end
174
175 return true
176end
177
178--- Uncompresses a .gz file.
179-- @param infile string: pathname of .gz file to be extracted.
180-- @param outfile string or nil: pathname of output file to be produced.
181-- If not given, name is derived from input file.
182-- @return boolean: true on success; nil and error message on failure.
183function tools.gunzip(infile, outfile)
184 return sevenz("gz", infile, outfile)
185end
186
187--- Uncompresses a .bz2 file.
188-- @param infile string: pathname of .bz2 file to be extracted.
189-- @param outfile string or nil: pathname of output file to be produced.
190-- If not given, name is derived from input file.
191-- @return boolean: true on success; nil and error message on failure.
192function tools.bunzip2(infile, outfile)
193 return sevenz("bz2", infile, outfile)
194end
195
151--- Test is pathname is a directory. 196--- Test is pathname is a directory.
152-- @param file string: pathname to test 197-- @param file string: pathname to test
153-- @return boolean: true if it is a directory, false otherwise. 198-- @return boolean: true if it is a directory, false otherwise.
@@ -241,63 +286,6 @@ function tools.set_permissions(filename, mode, scope)
241 return true 286 return true
242end 287end
243 288
244
245--- Strip the last extension of a filename.
246-- Example: "foo.tar.gz" becomes "foo.tar".
247-- If filename has no dots, returns it unchanged.
248-- @param filename string: The file name to strip.
249-- @return string: The stripped name.
250local function strip_extension(filename)
251 assert(type(filename) == "string")
252 return (filename:gsub("%.[^.]+$", "")) or filename
253end
254
255--- Uncompress gzip file.
256-- @param archive string: Filename of archive.
257-- @return boolean : success status
258local function gunzip(archive)
259 return fs.execute_quiet(fs.Q(vars.SEVENZ).." -aoa x", archive)
260end
261
262--- Unpack an archive.
263-- Extract the contents of an archive, detecting its format by
264-- filename extension.
265-- @param archive string: Filename of archive.
266-- @return boolean or (boolean, string): true on success, false and an error message on failure.
267function tools.unpack_archive(archive)
268 assert(type(archive) == "string")
269
270 local ok
271 local sevenzx = fs.Q(vars.SEVENZ).." -aoa x"
272 if archive:match("%.tar%.gz$") then
273 ok = gunzip(archive)
274 if ok then
275 ok = fs.execute_quiet(sevenzx, strip_extension(archive))
276 end
277 elseif archive:match("%.tgz$") then
278 ok = gunzip(archive)
279 if ok then
280 ok = fs.execute_quiet(sevenzx, strip_extension(archive)..".tar")
281 end
282 elseif archive:match("%.tar%.bz2$") then
283 ok = fs.execute_quiet(sevenzx, archive)
284 if ok then
285 ok = fs.execute_quiet(sevenzx, strip_extension(archive))
286 end
287 elseif archive:match("%.zip$") then
288 ok = fs.execute_quiet(sevenzx, archive)
289 elseif archive:match("%.lua$") or archive:match("%.c$") then
290 -- Ignore .lua and .c files; they don't need to be extracted.
291 return true
292 else
293 return false, "Couldn't extract archive "..archive..": unrecognized filename extension"
294 end
295 if not ok then
296 return false, "Failed extracting "..archive
297 end
298 return true
299end
300
301--- Test for existance of a file. 289--- Test for existance of a file.
302-- @param file string: filename to test 290-- @param file string: filename to test
303-- @return boolean: true if file exists, false otherwise. 291-- @return boolean: true if file exists, false otherwise.
diff --git a/src/luarocks/tools/zip.lua b/src/luarocks/tools/zip.lua
index e6d9e36a..5974c7bf 100644
--- a/src/luarocks/tools/zip.lua
+++ b/src/luarocks/tools/zip.lua
@@ -1,26 +1,58 @@
1 1
2--- A Lua implementation of .zip file archiving (used for creating .rock files), 2--- A Lua implementation of .zip and .gz file compression and decompression,
3-- using only lzlib or lua-lzib. 3-- using only lzlib or lua-lzib.
4local zip = {} 4local zip = {}
5 5
6local zlib = require("zlib") 6local zlib = require("zlib")
7local fs = require("luarocks.fs") 7local fs = require("luarocks.fs")
8local fun = require("luarocks.fun")
8local dir = require("luarocks.dir") 9local dir = require("luarocks.dir")
9 10
11local stat_ok, stat = pcall(require, "posix.sys.stat")
12
13local function shr(n, m)
14 return math.floor(n / 2^m)
15end
16
17local function shl(n, m)
18 return n * 2^m
19end
20local function lowbits(n, m)
21 return n % 2^m
22end
23
24local function mode_to_windowbits(mode)
25 if mode == "gzip" then
26 return 31
27 elseif mode == "zlib" then
28 return 0
29 elseif mode == "raw" then
30 return -15
31 end
32end
33
10-- zlib module can be provided by both lzlib and lua-lzib packages. 34-- zlib module can be provided by both lzlib and lua-lzib packages.
11-- Create a compatibility layer. 35-- Create a compatibility layer.
12local zlib_compress, zlib_crc32 36local zlib_compress, zlib_uncompress, zlib_crc32
13if zlib._VERSION:match "^lua%-zlib" then 37if zlib._VERSION:match "^lua%-zlib" then
14 function zlib_compress(data) 38 function zlib_compress(data, mode)
15 return (zlib.deflate()(data, "finish")) 39 return (zlib.deflate(6, mode_to_windowbits(mode))(data, "finish"))
40 end
41
42 function zlib_uncompress(data, mode)
43 return (zlib.inflate(mode_to_windowbits(mode))(data))
16 end 44 end
17 45
18 function zlib_crc32(data) 46 function zlib_crc32(data)
19 return zlib.crc32()(data) 47 return zlib.crc32()(data)
20 end 48 end
21elseif zlib._VERSION:match "^lzlib" then 49elseif zlib._VERSION:match "^lzlib" then
22 function zlib_compress(data) 50 function zlib_compress(data, mode)
23 return zlib.compress(data) 51 return zlib.compress(data, -1, nil, mode_to_windowbits(mode))
52 end
53
54 function zlib_uncompress(data, mode)
55 return zlib.decompress(data, mode_to_windowbits(mode))
24 end 56 end
25 57
26 function zlib_crc32(data) 58 function zlib_crc32(data)
@@ -30,7 +62,7 @@ else
30 error("unknown zlib library", 0) 62 error("unknown zlib library", 0)
31end 63end
32 64
33local function number_to_bytestring(number, nbytes) 65local function number_to_lestring(number, nbytes)
34 local out = {} 66 local out = {}
35 for _ = 1, nbytes do 67 for _ = 1, nbytes do
36 local byte = number % 256 68 local byte = number % 256
@@ -40,6 +72,20 @@ local function number_to_bytestring(number, nbytes)
40 return table.concat(out) 72 return table.concat(out)
41end 73end
42 74
75local function lestring_to_number(str)
76 local n = 0
77 local bytes = { string.byte(str, 1, #str) }
78 for b = 1, #str do
79 n = n + shl(bytes[b], (b-1)*8)
80 end
81 return math.floor(n)
82end
83
84local LOCAL_FILE_HEADER_SIGNATURE = number_to_lestring(0x04034b50, 4)
85local DATA_DESCRIPTOR_SIGNATURE = number_to_lestring(0x08074b50, 4)
86local CENTRAL_DIRECTORY_SIGNATURE = number_to_lestring(0x02014b50, 4)
87local END_OF_CENTRAL_DIR_SIGNATURE = number_to_lestring(0x06054b50, 4)
88
43--- Begin a new file to be stored inside the zipfile. 89--- Begin a new file to be stored inside the zipfile.
44-- @param self handle of the zipfile being written. 90-- @param self handle of the zipfile being written.
45-- @param filename filenome of the file to be added to the zipfile. 91-- @param filename filenome of the file to be added to the zipfile.
@@ -56,7 +102,7 @@ local function zipwriter_open_new_file_in_zip(self, filename)
56 lfh.file_name_length = #filename 102 lfh.file_name_length = #filename
57 lfh.extra_field_length = 0 103 lfh.extra_field_length = 0
58 lfh.file_name = filename:gsub("\\", "/") 104 lfh.file_name = filename:gsub("\\", "/")
59 lfh.external_attr = 0 -- TODO properly store permissions 105 lfh.external_attr = shl(493, 16) -- TODO proper permissions
60 self.in_open_file = true 106 self.in_open_file = true
61 return true 107 return true
62end 108end
@@ -70,7 +116,7 @@ local function zipwriter_write_file_in_zip(self, data)
70 return nil 116 return nil
71 end 117 end
72 local lfh = self.local_file_header 118 local lfh = self.local_file_header
73 local compressed = zlib_compress(data):sub(3, -5) 119 local compressed = zlib_compress(data, "raw")
74 lfh.crc32 = zlib_crc32(data) 120 lfh.crc32 = zlib_crc32(data)
75 lfh.compressed_size = #compressed 121 lfh.compressed_size = #compressed
76 lfh.uncompressed_size = #data 122 lfh.uncompressed_size = #data
@@ -91,26 +137,27 @@ local function zipwriter_close_file_in_zip(self)
91 -- Local file header 137 -- Local file header
92 local lfh = self.local_file_header 138 local lfh = self.local_file_header
93 lfh.offset = zh:seek() 139 lfh.offset = zh:seek()
94 zh:write(number_to_bytestring(0x04034b50, 4)) -- signature 140 zh:write(LOCAL_FILE_HEADER_SIGNATURE)
95 zh:write(number_to_bytestring(20, 2)) -- version needed to extract: 2.0 141 zh:write(number_to_lestring(20, 2)) -- version needed to extract: 2.0
96 zh:write(number_to_bytestring(0, 2)) -- general purpose bit flag 142 zh:write(number_to_lestring(4, 2)) -- general purpose bit flag
97 zh:write(number_to_bytestring(8, 2)) -- compression method: deflate 143 zh:write(number_to_lestring(8, 2)) -- compression method: deflate
98 zh:write(number_to_bytestring(lfh.last_mod_file_time, 2)) 144 zh:write(number_to_lestring(lfh.last_mod_file_time, 2))
99 zh:write(number_to_bytestring(lfh.last_mod_file_date, 2)) 145 zh:write(number_to_lestring(lfh.last_mod_file_date, 2))
100 zh:write(number_to_bytestring(lfh.crc32, 4)) 146 zh:write(number_to_lestring(lfh.crc32, 4))
101 zh:write(number_to_bytestring(lfh.compressed_size, 4)) 147 zh:write(number_to_lestring(lfh.compressed_size, 4))
102 zh:write(number_to_bytestring(lfh.uncompressed_size, 4)) 148 zh:write(number_to_lestring(lfh.uncompressed_size, 4))
103 zh:write(number_to_bytestring(lfh.file_name_length, 2)) 149 zh:write(number_to_lestring(lfh.file_name_length, 2))
104 zh:write(number_to_bytestring(lfh.extra_field_length, 2)) 150 zh:write(number_to_lestring(lfh.extra_field_length, 2))
105 zh:write(lfh.file_name) 151 zh:write(lfh.file_name)
106 152
107 -- File data 153 -- File data
108 zh:write(self.data) 154 zh:write(self.data)
109 155
110 -- Data descriptor 156 -- Data descriptor
111 zh:write(number_to_bytestring(lfh.crc32, 4)) 157 zh:write(DATA_DESCRIPTOR_SIGNATURE)
112 zh:write(number_to_bytestring(lfh.compressed_size, 4)) 158 zh:write(number_to_lestring(lfh.crc32, 4))
113 zh:write(number_to_bytestring(lfh.uncompressed_size, 4)) 159 zh:write(number_to_lestring(lfh.compressed_size, 4))
160 zh:write(number_to_lestring(lfh.uncompressed_size, 4))
114 161
115 table.insert(self.files, lfh) 162 table.insert(self.files, lfh)
116 self.in_open_file = false 163 self.in_open_file = false
@@ -167,36 +214,36 @@ local function zipwriter_close(self)
167 local size_of_central_directory = 0 214 local size_of_central_directory = 0
168 -- Central directory structure 215 -- Central directory structure
169 for _, lfh in ipairs(self.files) do 216 for _, lfh in ipairs(self.files) do
170 zh:write(number_to_bytestring(0x02014b50, 4)) -- signature 217 zh:write(CENTRAL_DIRECTORY_SIGNATURE) -- signature
171 zh:write(number_to_bytestring(3, 2)) -- version made by: UNIX 218 zh:write(number_to_lestring(3, 2)) -- version made by: UNIX
172 zh:write(number_to_bytestring(20, 2)) -- version needed to extract: 2.0 219 zh:write(number_to_lestring(20, 2)) -- version needed to extract: 2.0
173 zh:write(number_to_bytestring(0, 2)) -- general purpose bit flag 220 zh:write(number_to_lestring(0, 2)) -- general purpose bit flag
174 zh:write(number_to_bytestring(8, 2)) -- compression method: deflate 221 zh:write(number_to_lestring(8, 2)) -- compression method: deflate
175 zh:write(number_to_bytestring(lfh.last_mod_file_time, 2)) 222 zh:write(number_to_lestring(lfh.last_mod_file_time, 2))
176 zh:write(number_to_bytestring(lfh.last_mod_file_date, 2)) 223 zh:write(number_to_lestring(lfh.last_mod_file_date, 2))
177 zh:write(number_to_bytestring(lfh.crc32, 4)) 224 zh:write(number_to_lestring(lfh.crc32, 4))
178 zh:write(number_to_bytestring(lfh.compressed_size, 4)) 225 zh:write(number_to_lestring(lfh.compressed_size, 4))
179 zh:write(number_to_bytestring(lfh.uncompressed_size, 4)) 226 zh:write(number_to_lestring(lfh.uncompressed_size, 4))
180 zh:write(number_to_bytestring(lfh.file_name_length, 2)) 227 zh:write(number_to_lestring(lfh.file_name_length, 2))
181 zh:write(number_to_bytestring(lfh.extra_field_length, 2)) 228 zh:write(number_to_lestring(lfh.extra_field_length, 2))
182 zh:write(number_to_bytestring(0, 2)) -- file comment length 229 zh:write(number_to_lestring(0, 2)) -- file comment length
183 zh:write(number_to_bytestring(0, 2)) -- disk number start 230 zh:write(number_to_lestring(0, 2)) -- disk number start
184 zh:write(number_to_bytestring(0, 2)) -- internal file attributes 231 zh:write(number_to_lestring(0, 2)) -- internal file attributes
185 zh:write(number_to_bytestring(lfh.external_attr, 4)) -- external file attributes 232 zh:write(number_to_lestring(lfh.external_attr, 4)) -- external file attributes
186 zh:write(number_to_bytestring(lfh.offset, 4)) -- relative offset of local header 233 zh:write(number_to_lestring(lfh.offset, 4)) -- relative offset of local header
187 zh:write(lfh.file_name) 234 zh:write(lfh.file_name)
188 size_of_central_directory = size_of_central_directory + 46 + lfh.file_name_length 235 size_of_central_directory = size_of_central_directory + 46 + lfh.file_name_length
189 end 236 end
190 237
191 -- End of central directory record 238 -- End of central directory record
192 zh:write(number_to_bytestring(0x06054b50, 4)) -- signature 239 zh:write(END_OF_CENTRAL_DIR_SIGNATURE) -- signature
193 zh:write(number_to_bytestring(0, 2)) -- number of this disk 240 zh:write(number_to_lestring(0, 2)) -- number of this disk
194 zh:write(number_to_bytestring(0, 2)) -- number of disk with start of central directory 241 zh:write(number_to_lestring(0, 2)) -- number of disk with start of central directory
195 zh:write(number_to_bytestring(#self.files, 2)) -- total number of entries in the central dir on this disk 242 zh:write(number_to_lestring(#self.files, 2)) -- total number of entries in the central dir on this disk
196 zh:write(number_to_bytestring(#self.files, 2)) -- total number of entries in the central dir 243 zh:write(number_to_lestring(#self.files, 2)) -- total number of entries in the central dir
197 zh:write(number_to_bytestring(size_of_central_directory, 4)) 244 zh:write(number_to_lestring(size_of_central_directory, 4))
198 zh:write(number_to_bytestring(central_directory_offset, 4)) 245 zh:write(number_to_lestring(central_directory_offset, 4))
199 zh:write(number_to_bytestring(0, 2)) -- zip file comment length 246 zh:write(number_to_lestring(0, 2)) -- zip file comment length
200 zh:close() 247 zh:close()
201 248
202 return true 249 return true
@@ -253,12 +300,231 @@ function zip.zip(zipfile, ...)
253 end 300 end
254 end 301 end
255 302
256 ok = zw:close() 303 zw:close()
304 return ok, err
305end
306
307
308local function ziptime_to_luatime(ztime, zdate)
309 return {
310 year = shr(zdate, 9) + 1980,
311 month = shr(lowbits(zdate, 9), 5),
312 day = lowbits(zdate, 5),
313 hour = shr(ztime, 11),
314 min = shr(lowbits(ztime, 11), 5),
315 sec = lowbits(ztime, 5) * 2,
316 }
317end
318
319local function read_file_in_zip(zh, cdr)
320 local sig = zh:read(4)
321 if sig ~= LOCAL_FILE_HEADER_SIGNATURE then
322 return nil, "failed reading Local File Header signature"
323 end
324
325 local lfh = {}
326 lfh.version_needed = lestring_to_number(zh:read(2))
327 lfh.bitflag = lestring_to_number(zh:read(2))
328 lfh.compression_method = lestring_to_number(zh:read(2))
329 lfh.last_mod_file_time = lestring_to_number(zh:read(2))
330 lfh.last_mod_file_date = lestring_to_number(zh:read(2))
331 lfh.crc32 = lestring_to_number(zh:read(4))
332 lfh.compressed_size = lestring_to_number(zh:read(4))
333 lfh.uncompressed_size = lestring_to_number(zh:read(4))
334 lfh.file_name_length = lestring_to_number(zh:read(2))
335 lfh.extra_field_length = lestring_to_number(zh:read(2))
336 lfh.file_name = zh:read(lfh.file_name_length)
337 lfh.extra_field = zh:read(lfh.extra_field_length)
338
339 local data = zh:read(cdr.compressed_size)
340
341 local uncompressed
342 if cdr.compression_method == 8 then
343 uncompressed = zlib_uncompress(data, "raw")
344 elseif cdr.compression_method == 0 then
345 uncompressed = data
346 else
347 return nil, "unknown compression method " .. cdr.compression_method
348 end
349
350 if #uncompressed ~= cdr.uncompressed_size then
351 return nil, "uncompressed size doesn't match"
352 end
353 if cdr.crc32 ~= zlib_crc32(uncompressed) then
354 return nil, "crc32 failed (expected " .. cdr.crc32 .. ") - data: " .. uncompressed
355 end
356
357 return uncompressed
358end
359
360local function process_end_of_central_dir(zh)
361 local at, err = zh:seek("end", -22)
362 if not at then
363 return nil, err
364 end
365
366 while true do
367 local sig = zh:read(4)
368 if sig == END_OF_CENTRAL_DIR_SIGNATURE then
369 break
370 end
371 at = at - 1
372 local at1, err = zh:seek("set", at)
373 if at1 ~= at then
374 return nil, "Could not find End of Central Directory signature"
375 end
376 end
377
378 -- number of this disk (2 bytes)
379 -- number of the disk with the start of the central directory (2 bytes)
380 -- total number of entries in the central directory on this disk (2 bytes)
381 -- total number of entries in the central directory (2 bytes)
382 zh:seek("cur", 6)
383
384 local central_directory_entries = lestring_to_number(zh:read(2))
385
386 -- central directory size (4 bytes)
387 zh:seek("cur", 4)
388
389 local central_directory_offset = lestring_to_number(zh:read(4))
390
391 return central_directory_entries, central_directory_offset
392end
393
394local function process_central_dir(zh, cd_entries)
395
396 local files = {}
397
398 for i = 1, cd_entries do
399 local sig = zh:read(4)
400 if sig ~= CENTRAL_DIRECTORY_SIGNATURE then
401 return nil, "failed reading Central Directory signature"
402 end
403
404 local cdr = {}
405 files[i] = cdr
406
407 cdr.version_made_by = lestring_to_number(zh:read(2))
408 cdr.version_needed = lestring_to_number(zh:read(2))
409 cdr.bitflag = lestring_to_number(zh:read(2))
410 cdr.compression_method = lestring_to_number(zh:read(2))
411 cdr.last_mod_file_time = lestring_to_number(zh:read(2))
412 cdr.last_mod_file_date = lestring_to_number(zh:read(2))
413 cdr.last_mod_luatime = ziptime_to_luatime(cdr.last_mod_file_time, cdr.last_mod_file_date)
414 cdr.crc32 = lestring_to_number(zh:read(4))
415 cdr.compressed_size = lestring_to_number(zh:read(4))
416 cdr.uncompressed_size = lestring_to_number(zh:read(4))
417 cdr.file_name_length = lestring_to_number(zh:read(2))
418 cdr.extra_field_length = lestring_to_number(zh:read(2))
419 cdr.file_comment_length = lestring_to_number(zh:read(2))
420 cdr.disk_number_start = lestring_to_number(zh:read(2))
421 cdr.internal_attr = lestring_to_number(zh:read(2))
422 cdr.external_attr = lestring_to_number(zh:read(4))
423 cdr.offset = lestring_to_number(zh:read(4))
424 cdr.file_name = zh:read(cdr.file_name_length)
425 cdr.extra_field = zh:read(cdr.extra_field_length)
426 cdr.file_comment = zh:read(cdr.file_comment_length)
427 end
428 return files
429end
430
431--- Uncompress files from a .zip archive.
432-- @param zipfile string: pathname of .zip archive to be created.
433-- @return boolean or (boolean, string): true on success,
434-- false and an error message on failure.
435function zip.unzip(zipfile)
436 zipfile = fs.absolute_name(zipfile)
437 local zh, err = io.open(zipfile, "rb")
438 if not zh then
439 return nil, err
440 end
441
442 local cd_entries, cd_offset = process_end_of_central_dir(zh)
443 if not cd_entries then
444 return nil, cd_offset
445 end
446
447 local ok, err = zh:seek("set", cd_offset)
257 if not ok then 448 if not ok then
258 return false, "error closing "..zipfile 449 return nil, err
259 end 450 end
260 return ok, err 451
452 local files, err = process_central_dir(zh, cd_entries)
453 if not files then
454 return nil, err
455 end
456
457 for _, cdr in ipairs(files) do
458 local file = cdr.file_name
459 if file:sub(#file) == "/" then
460 local ok, err = fs.make_dir(dir.path(fs.current_dir(), file))
461 if not ok then
462 return nil, err
463 end
464 else
465 local base = dir.dir_name(file)
466 if base ~= "" then
467 base = dir.path(fs.current_dir(), base)
468 if not fs.is_dir(base) then
469 local ok, err = fs.make_dir(base)
470 if not ok then
471 return nil, err
472 end
473 end
474 end
475
476 local ok, err = zh:seek("set", cdr.offset)
477 if not ok then
478 return nil, err
479 end
480
481 local contents, err = read_file_in_zip(zh, cdr)
482 if not contents then
483 return nil, err
484 end
485 local pathname = dir.path(fs.current_dir(), file)
486 local wf, err = io.open(pathname, "wb")
487 if not wf then
488 zh:close()
489 return nil, err
490 end
491 wf:write(contents)
492 wf:close()
493
494 if cdr.external_attr > 0 then
495 fs.set_permissions(pathname, "exec", "all")
496 else
497 fs.set_permissions(pathname, "read", "all")
498 end
499 fs.set_time(pathname, cdr.last_mod_luatime)
500 end
501 end
502 zh:close()
503 return true
261end 504end
262 505
506function zip.gzip(input_filename, output_filename)
507 assert(type(input_filename) == "string")
508 assert(output_filename == nil or type(output_filename) == "string")
509
510 if not output_filename then
511 output_filename = input_filename .. ".gz"
512 end
513
514 local fn = fun.partial(fun.flip(zlib_compress), "gzip")
515 return fs.filter_file(fn, input_filename, output_filename)
516end
517
518function zip.gunzip(input_filename, output_filename)
519 assert(type(input_filename) == "string")
520 assert(output_filename == nil or type(output_filename) == "string")
521
522 if not output_filename then
523 output_filename = input_filename:gsub("%.gz$", "")
524 end
525
526 local fn = fun.partial(fun.flip(zlib_uncompress), "gzip")
527 return fs.filter_file(fn, input_filename, output_filename)
528end
263 529
264return zip 530return zip