aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorV1K1NGbg <victor@ilchev.com>2024-08-03 12:31:21 +0300
committerV1K1NGbg <victor@ilchev.com>2024-08-05 20:51:31 +0300
commit3c2ab76e26c3cd4c863b7902cfd3f4424784d6b6 (patch)
treec4b67611ac3888133b08f34591644023775fca15
parent9a5c9fb314c8ec4d8876a424d12c17879d58db5f (diff)
downloadluarocks-3c2ab76e26c3cd4c863b7902cfd3f4424784d6b6.tar.gz
luarocks-3c2ab76e26c3cd4c863b7902cfd3f4424784d6b6.tar.bz2
luarocks-3c2ab76e26c3cd4c863b7902cfd3f4424784d6b6.zip
patch
-rw-r--r--src/luarocks/core/cfg.d.tl2
-rw-r--r--src/luarocks/tools/patch.tl80
-rw-r--r--src/luarocks/tools/tar.tl191
-rw-r--r--src/luarocks/tools/zip.tl531
-rw-r--r--src/luarocks/type_check.tl1
5 files changed, 773 insertions, 32 deletions
diff --git a/src/luarocks/core/cfg.d.tl b/src/luarocks/core/cfg.d.tl
index 5ad95b6d..7b74f258 100644
--- a/src/luarocks/core/cfg.d.tl
+++ b/src/luarocks/core/cfg.d.tl
@@ -86,6 +86,8 @@ local record cfg
86 record upload 86 record upload
87 server: string 87 server: string
88 version: string 88 version: string
89 tool_version: string
90 api_version: string
89 end 91 end
90end 92end
91 93
diff --git a/src/luarocks/tools/patch.tl b/src/luarocks/tools/patch.tl
index a3dbe0df..05b447da 100644
--- a/src/luarocks/tools/patch.tl
+++ b/src/luarocks/tools/patch.tl
@@ -9,17 +9,32 @@
9-- Version 0.1 9-- Version 0.1
10 10
11local record patch 11local record patch
12 12 record Lineends
13 lf: number
14 crlf: number
15 cr: number
16 end
17 record Hunks
18 startsrc: number
19 linessrc: number
20 starttgt: number
21 linestgt: number
22 invalid: boolean
23 text: {string}
24 end
13end 25end
14 26
15local fs = require("luarocks.fs") 27local fs = require("luarocks.fs")
16local fun = require("luarocks.fun") 28local fun = require("luarocks.fun")
17 29
18local io = io 30local type Lineends = patch.Lineends
19local os = os 31local type Hunks = patch.Hunks
20local string = string 32
21local table = table 33-- local io = io
22local format = string.format 34-- local os = os
35-- local string = string
36-- local table = table
37-- local format = string.format
23 38
24-- logging 39-- logging
25local debugmode = false 40local debugmode = false
@@ -38,7 +53,7 @@ local function endswith(s: string, s2: string): boolean
38end 53end
39 54
40-- Returns string s after filtering out any new-line characters from end. 55-- Returns string s after filtering out any new-line characters from end.
41local function endlstrip(s: string): string 56local function endlstrip(s: string): string, integer
42 return s:gsub('[\r\n]+$', '') 57 return s:gsub('[\r\n]+$', '')
43end 58end
44 59
@@ -57,13 +72,13 @@ local function exists(filename: string): boolean
57end 72end
58local function isfile(): boolean return true end --FIX? --! 73local function isfile(): boolean return true end --FIX? --!
59 74
60local function string_as_file(s: string) --! 75local function string_as_file(s: string): FILE --!
61 return { 76 return {
62 at = 0, 77 at = 0,
63 str = s, 78 str = s,
64 len = #s, 79 len = #s,
65 eof = false, 80 eof = false,
66 read = function(self, n) 81 read = function(self: FILE, n: number): string
67 if self.eof then return nil end 82 if self.eof then return nil end
68 local chunk = self.str:sub(self.at, self.at + n - 1) 83 local chunk = self.str:sub(self.at, self.at + n - 1)
69 self.at = self.at + n 84 self.at = self.at + n
@@ -72,10 +87,10 @@ local function string_as_file(s: string) --!
72 end 87 end
73 return chunk 88 return chunk
74 end, 89 end,
75 close = function(self) 90 close = function(self: FILE): boolean
76 self.eof = true 91 self.eof = true
77 end, 92 end,
78 } 93 } as FILE
79end 94end
80 95
81-- 96--
@@ -92,7 +107,7 @@ local function file_lines(f: FILE): function(): string
92 local buffer = "" 107 local buffer = ""
93 local pos_beg = 1 108 local pos_beg = 1
94 return function(): string 109 return function(): string
95 local pos, chars: number, number --? char represented as a number 110 local pos, chars: string, string
96 while 1 do 111 while 1 do
97 pos, chars = buffer:match('()([\r\n].)', pos_beg) 112 pos, chars = buffer:match('()([\r\n].)', pos_beg)
98 if pos or not f then 113 if pos or not f then
@@ -100,27 +115,28 @@ local function file_lines(f: FILE): function(): string
100 elseif f then 115 elseif f then
101 local chunk = f:read(CHUNK_SIZE) 116 local chunk = f:read(CHUNK_SIZE)
102 if chunk then 117 if chunk then
103 buffer = buffer:sub(pos_beg) .. chunk 118 buffer = buffer:sub(pos_beg) .. chunk --! funcy stuff with pos
104 pos_beg = 1 119 pos_beg = 1
105 else 120 else
106 f = nil 121 f = nil
107 end 122 end
108 end 123 end
109 end 124 end
110 if not pos then 125 local posi = math.tointeger(pos)
111 pos = #buffer 126 if not posi then
127 posi = #buffer
112 elseif chars == '\r\n' then 128 elseif chars == '\r\n' then
113 pos = pos + 1 129 posi = posi + 1
114 end 130 end
115 local line = buffer:sub(pos_beg, pos) 131 local line = buffer:sub(pos_beg, posi)
116 pos_beg = pos + 1 132 pos_beg = posi + 1
117 if #line > 0 then 133 if #line > 0 then
118 return line 134 return line
119 end 135 end
120 end 136 end
121end 137end
122 138
123local function match_linerange(line: string) 139local function match_linerange(line: string): string, string, string, string
124 local m1, m2, m3, m4 = line:match("^@@ %-(%d+),(%d+) %+(%d+),(%d+)") 140 local m1, m2, m3, m4 = line:match("^@@ %-(%d+),(%d+) %+(%d+),(%d+)")
125 if not m1 then m1, m3, m4 = line:match("^@@ %-(%d+) %+(%d+),(%d+)") end 141 if not m1 then m1, m3, m4 = line:match("^@@ %-(%d+) %+(%d+),(%d+)") end
126 if not m1 then m1, m2, m3 = line:match("^@@ %-(%d+),(%d+) %+(%d+)") end 142 if not m1 then m1, m2, m3 = line:match("^@@ %-(%d+),(%d+) %+(%d+)") end
@@ -128,11 +144,11 @@ local function match_linerange(line: string)
128 return m1, m2, m3, m4 144 return m1, m2, m3, m4
129end 145end
130 146
131local function match_epoch(str) 147local function match_epoch(str: string): string
132 return str:match("[^0-9]1969[^0-9]") or str:match("[^0-9]1970[^0-9]") 148 return str:match("[^0-9]1969[^0-9]") or str:match("[^0-9]1970[^0-9]")
133end 149end
134 150
135function patch.read_patch(filename, data) 151function patch.read_patch(filename: string, data: string)
136 -- define possible file regions that will direct the parser flow 152 -- define possible file regions that will direct the parser flow
137 local state = 'header' 153 local state = 'header'
138 -- 'header' - comments before the patch body 154 -- 'header' - comments before the patch body
@@ -142,26 +158,26 @@ function patch.read_patch(filename, data)
142 -- 'hunkskip' - skipping invalid hunk mode 158 -- 'hunkskip' - skipping invalid hunk mode
143 159
144 local all_ok = true 160 local all_ok = true
145 local lineends = {lf=0, crlf=0, cr=0} 161 local lineends: Lineends = {lf=0, crlf=0, cr=0}
146 local files = {source={}, target={}, epoch={}, hunks={}, fileends={}, hunkends={}} 162 local files = {source={}, target={}, epoch={}, hunks: {{Hunks}}={}, fileends={}, hunkends: {Lineends}={}}
147 local nextfileno = 0 163 local nextfileno = 0
148 local nexthunkno = 0 --: even if index starts with 0 user messages 164 local nexthunkno = 0 --: even if index starts with 0 user messages
149 -- number hunks from 1 165 -- number hunks from 1
150 166
151 -- hunkinfo holds parsed values, hunkactual - calculated 167 -- hunkinfo holds parsed values, hunkactual - calculated
152 local hunkinfo = { 168 local hunkinfo: Hunks = {
153 startsrc=nil, linessrc=nil, starttgt=nil, linestgt=nil, 169 startsrc=nil, linessrc=nil, starttgt=nil, linestgt=nil,
154 invalid=false, text={} 170 invalid=false, text={}
155 } 171 }
156 local hunkactual = {linessrc=nil, linestgt=nil} 172 local hunkactual: Hunks = {linessrc=nil, linestgt=nil}
157 173
158 info(format("reading patch %s", filename)) 174 info(string.format("reading patch %s", filename)) --!
159 175
160 local fp 176 local fp: FILE
161 if data then 177 if data then
162 fp = string_as_file(data) 178 fp = string_as_file(data)
163 else 179 else
164 fp = filename == '-' and io.stdin or assert(io.open(filename, "rb")) 180 fp = filename == '-' and io.stdin or assert(io.open(filename, "rb") as (FILE, string)) --! use of cast
165 end 181 end
166 local lineno = 0 182 local lineno = 0
167 183
@@ -205,10 +221,10 @@ function patch.read_patch(filename, data)
205 table.insert(hunkinfo.text, line) 221 table.insert(hunkinfo.text, line)
206 -- todo: handle \ No newline cases 222 -- todo: handle \ No newline cases
207 else 223 else
208 warning(format("invalid hunk no.%d at %d for target file %s", 224 warning(string.format("invalid hunk no.%d at %d for target file %s",
209 nexthunkno, lineno, files.target[nextfileno])) 225 nexthunkno, lineno, files.target[nextfileno]))
210 -- add hunk status node 226 -- add hunk status node
211 table.insert(files.hunks[nextfileno], table_copy(hunkinfo)) 227 table.insert(files.hunks[nextfileno], table_copy(hunkinfo)) --! cast
212 files.hunks[nextfileno][nexthunkno].invalid = true 228 files.hunks[nextfileno][nexthunkno].invalid = true
213 all_ok = false 229 all_ok = false
214 state = 'hunkskip' 230 state = 'hunkskip'
@@ -218,7 +234,7 @@ function patch.read_patch(filename, data)
218 if hunkactual.linessrc > hunkinfo.linessrc or 234 if hunkactual.linessrc > hunkinfo.linessrc or
219 hunkactual.linestgt > hunkinfo.linestgt 235 hunkactual.linestgt > hunkinfo.linestgt
220 then 236 then
221 warning(format("extra hunk no.%d lines at %d for target %s", 237 warning(string.format("extra hunk no.%d lines at %d for target %s",
222 nexthunkno, lineno, files.target[nextfileno])) 238 nexthunkno, lineno, files.target[nextfileno]))
223 -- add hunk status node 239 -- add hunk status node
224 table.insert(files.hunks[nextfileno], table_copy(hunkinfo)) 240 table.insert(files.hunks[nextfileno], table_copy(hunkinfo))
@@ -235,7 +251,7 @@ function patch.read_patch(filename, data)
235 if (ends.cr~=0 and 1 or 0) + (ends.crlf~=0 and 1 or 0) + 251 if (ends.cr~=0 and 1 or 0) + (ends.crlf~=0 and 1 or 0) +
236 (ends.lf~=0 and 1 or 0) > 1 252 (ends.lf~=0 and 1 or 0) > 1
237 then 253 then
238 warning(format("inconsistent line ends in patch hunks for %s", 254 warning(string.format("inconsistent line ends in patch hunks for %s",
239 files.source[nextfileno])) 255 files.source[nextfileno]))
240 end 256 end
241 end 257 end
diff --git a/src/luarocks/tools/tar.tl b/src/luarocks/tools/tar.tl
new file mode 100644
index 00000000..bac7b2a9
--- /dev/null
+++ b/src/luarocks/tools/tar.tl
@@ -0,0 +1,191 @@
1
2--- A pure-Lua implementation of untar (unpacking .tar archives)
3local tar = {}
4
5local fs = require("luarocks.fs")
6local dir = require("luarocks.dir")
7local fun = require("luarocks.fun")
8
9local blocksize = 512
10
11local function get_typeflag(flag)
12 if flag == "0" or flag == "\0" then return "file"
13 elseif flag == "1" then return "link"
14 elseif flag == "2" then return "symlink" -- "reserved" in POSIX, "symlink" in GNU
15 elseif flag == "3" then return "character"
16 elseif flag == "4" then return "block"
17 elseif flag == "5" then return "directory"
18 elseif flag == "6" then return "fifo"
19 elseif flag == "7" then return "contiguous" -- "reserved" in POSIX, "contiguous" in GNU
20 elseif flag == "x" then return "next file"
21 elseif flag == "g" then return "global extended header"
22 elseif flag == "L" then return "long name"
23 elseif flag == "K" then return "long link name"
24 end
25 return "unknown"
26end
27
28local function octal_to_number(octal)
29 local exp = 0
30 local number = 0
31 octal = octal:gsub("%s", "")
32 for i = #octal,1,-1 do
33 local digit = tonumber(octal:sub(i,i))
34 if not digit then
35 break
36 end
37 number = number + (digit * 8^exp)
38 exp = exp + 1
39 end
40 return number
41end
42
43local function checksum_header(block)
44 local sum = 256
45
46 if block:byte(1) == 0 then
47 return 0
48 end
49
50 for i = 1,148 do
51 local b = block:byte(i) or 0
52 sum = sum + b
53 end
54 for i = 157,500 do
55 local b = block:byte(i) or 0
56 sum = sum + b
57 end
58
59 return sum
60end
61
62local function nullterm(s)
63 return s:match("^[^%z]*")
64end
65
66local function read_header_block(block)
67 local header = {}
68 header.name = nullterm(block:sub(1,100))
69 header.mode = nullterm(block:sub(101,108)):gsub(" ", "")
70 header.uid = octal_to_number(nullterm(block:sub(109,116)))
71 header.gid = octal_to_number(nullterm(block:sub(117,124)))
72 header.size = octal_to_number(nullterm(block:sub(125,136)))
73 header.mtime = octal_to_number(nullterm(block:sub(137,148)))
74 header.chksum = octal_to_number(nullterm(block:sub(149,156)))
75 header.typeflag = get_typeflag(block:sub(157,157))
76 header.linkname = nullterm(block:sub(158,257))
77 header.magic = block:sub(258,263)
78 header.version = block:sub(264,265)
79 header.uname = nullterm(block:sub(266,297))
80 header.gname = nullterm(block:sub(298,329))
81 header.devmajor = octal_to_number(nullterm(block:sub(330,337)))
82 header.devminor = octal_to_number(nullterm(block:sub(338,345)))
83 header.prefix = block:sub(346,500)
84
85 -- if header.magic ~= "ustar " and header.magic ~= "ustar\0" then
86 -- return false, ("Invalid header magic %6x"):format(bestring_to_number(header.magic))
87 -- end
88 -- if header.version ~= "00" and header.version ~= " \0" then
89 -- return false, "Unknown version "..header.version
90 -- end
91 if header.typeflag == "unknown" then
92 if checksum_header(block) ~= header.chksum then
93 return false, "Failed header checksum"
94 end
95 end
96 return header
97end
98
99function tar.untar(filename, destdir)
100 assert(type(filename) == "string")
101 assert(type(destdir) == "string")
102
103 local tar_handle = io.open(filename, "rb")
104 if not tar_handle then return nil, "Error opening file "..filename end
105
106 local long_name, long_link_name
107 local ok, err
108 local make_dir = fun.memoize(fs.make_dir)
109 while true do
110 local block
111 repeat
112 block = tar_handle:read(blocksize)
113 until (not block) or block:byte(1) > 0
114 if not block then break end
115 if #block < blocksize then
116 ok, err = nil, "Invalid block size -- corrupted file?"
117 break
118 end
119
120 local header
121 header, err = read_header_block(block)
122 if not header then
123 ok = false
124 break
125 end
126
127 local file_data = ""
128 if header.size > 0 then
129 local nread = math.ceil(header.size / blocksize) * blocksize
130 file_data = tar_handle:read(header.size)
131 if nread > header.size then
132 tar_handle:seek("cur", nread - header.size)
133 end
134 end
135
136 if header.typeflag == "long name" then
137 long_name = nullterm(file_data)
138 elseif header.typeflag == "long link name" then
139 long_link_name = nullterm(file_data)
140 else
141 if long_name then
142 header.name = long_name
143 long_name = nil
144 end
145 if long_link_name then
146 header.name = long_link_name
147 long_link_name = nil
148 end
149 end
150 local pathname = dir.path(destdir, header.name)
151 pathname = fs.absolute_name(pathname)
152 if header.typeflag == "directory" then
153 ok, err = make_dir(pathname)
154 if not ok then
155 break
156 end
157 elseif header.typeflag == "file" then
158 local dirname = dir.dir_name(pathname)
159 if dirname ~= "" then
160 ok, err = make_dir(dirname)
161 if not ok then
162 break
163 end
164 end
165 local file_handle
166 file_handle, err = io.open(pathname, "wb")
167 if not file_handle then
168 ok = nil
169 break
170 end
171 file_handle:write(file_data)
172 file_handle:close()
173 fs.set_time(pathname, header.mtime)
174 if header.mode:match("[75]") then
175 fs.set_permissions(pathname, "exec", "all")
176 else
177 fs.set_permissions(pathname, "read", "all")
178 end
179 end
180 --[[
181 for k,v in pairs(header) do
182 util.printout("[\""..tostring(k).."\"] = "..(type(v)=="number" and v or "\""..v:gsub("%z", "\\0").."\""))
183 end
184 util.printout()
185 --]]
186 end
187 tar_handle:close()
188 return ok, err
189end
190
191return tar
diff --git a/src/luarocks/tools/zip.tl b/src/luarocks/tools/zip.tl
new file mode 100644
index 00000000..82d582fa
--- /dev/null
+++ b/src/luarocks/tools/zip.tl
@@ -0,0 +1,531 @@
1
2--- A Lua implementation of .zip and .gz file compression and decompression,
3-- using only lzlib or lua-lzib.
4local zip = {}
5
6local zlib = require("zlib")
7local fs = require("luarocks.fs")
8local fun = require("luarocks.fun")
9local dir = require("luarocks.dir")
10
11local pack = table.pack or function(...) return { n = select("#", ...), ... } end
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
34-- zlib module can be provided by both lzlib and lua-lzib packages.
35-- Create a compatibility layer.
36local zlib_compress, zlib_uncompress, zlib_crc32
37if zlib._VERSION:match "^lua%-zlib" then
38 function zlib_compress(data, mode)
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))
44 end
45
46 function zlib_crc32(data)
47 return zlib.crc32()(data)
48 end
49elseif zlib._VERSION:match "^lzlib" then
50 function zlib_compress(data, mode)
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))
56 end
57
58 function zlib_crc32(data)
59 return zlib.crc32(zlib.crc32(), data)
60 end
61else
62 error("unknown zlib library", 0)
63end
64
65local function number_to_lestring(number, nbytes)
66 local out = {}
67 for _ = 1, nbytes do
68 local byte = number % 256
69 table.insert(out, string.char(byte))
70 number = (number - byte) / 256
71 end
72 return table.concat(out)
73end
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
89--- Begin a new file to be stored inside the zipfile.
90-- @param self handle of the zipfile being written.
91-- @param filename filenome of the file to be added to the zipfile.
92-- @return true if succeeded, nil in case of failure.
93local function zipwriter_open_new_file_in_zip(self, filename)
94 if self.in_open_file then
95 self:close_file_in_zip()
96 return nil
97 end
98 local lfh = {}
99 self.local_file_header = lfh
100 lfh.last_mod_file_time = 0 -- TODO
101 lfh.last_mod_file_date = 0 -- TODO
102 lfh.file_name_length = #filename
103 lfh.extra_field_length = 0
104 lfh.file_name = filename:gsub("\\", "/")
105 lfh.external_attr = shl(493, 16) -- TODO proper permissions
106 self.in_open_file = true
107 return true
108end
109
110--- Write data to the file currently being stored in the zipfile.
111-- @param self handle of the zipfile being written.
112-- @param data string containing full contents of the file.
113-- @return true if succeeded, nil in case of failure.
114local function zipwriter_write_file_in_zip(self, data)
115 if not self.in_open_file then
116 return nil
117 end
118 local lfh = self.local_file_header
119 local compressed = zlib_compress(data, "raw")
120 lfh.crc32 = zlib_crc32(data)
121 lfh.compressed_size = #compressed
122 lfh.uncompressed_size = #data
123 self.data = compressed
124 return true
125end
126
127--- Complete the writing of a file stored in the zipfile.
128-- @param self handle of the zipfile being written.
129-- @return true if succeeded, nil in case of failure.
130local function zipwriter_close_file_in_zip(self)
131 local zh = self.ziphandle
132
133 if not self.in_open_file then
134 return nil
135 end
136
137 -- Local file header
138 local lfh = self.local_file_header
139 lfh.offset = zh:seek()
140 zh:write(LOCAL_FILE_HEADER_SIGNATURE)
141 zh:write(number_to_lestring(20, 2)) -- version needed to extract: 2.0
142 zh:write(number_to_lestring(4, 2)) -- general purpose bit flag
143 zh:write(number_to_lestring(8, 2)) -- compression method: deflate
144 zh:write(number_to_lestring(lfh.last_mod_file_time, 2))
145 zh:write(number_to_lestring(lfh.last_mod_file_date, 2))
146 zh:write(number_to_lestring(lfh.crc32, 4))
147 zh:write(number_to_lestring(lfh.compressed_size, 4))
148 zh:write(number_to_lestring(lfh.uncompressed_size, 4))
149 zh:write(number_to_lestring(lfh.file_name_length, 2))
150 zh:write(number_to_lestring(lfh.extra_field_length, 2))
151 zh:write(lfh.file_name)
152
153 -- File data
154 zh:write(self.data)
155
156 -- Data descriptor
157 zh:write(DATA_DESCRIPTOR_SIGNATURE)
158 zh:write(number_to_lestring(lfh.crc32, 4))
159 zh:write(number_to_lestring(lfh.compressed_size, 4))
160 zh:write(number_to_lestring(lfh.uncompressed_size, 4))
161
162 table.insert(self.files, lfh)
163 self.in_open_file = false
164
165 return true
166end
167
168-- @return boolean or (boolean, string): true on success,
169-- false and an error message on failure.
170local function zipwriter_add(self, file)
171 local fin
172 local ok, err = self:open_new_file_in_zip(file)
173 if not ok then
174 err = "error in opening "..file.." in zipfile"
175 else
176 fin = io.open(fs.absolute_name(file), "rb")
177 if not fin then
178 ok = false
179 err = "error opening "..file.." for reading"
180 end
181 end
182 if ok then
183 local data = fin:read("*a")
184 if not data then
185 err = "error reading "..file
186 ok = false
187 else
188 ok = self:write_file_in_zip(data)
189 if not ok then
190 err = "error in writing "..file.." in the zipfile"
191 end
192 end
193 end
194 if fin then
195 fin:close()
196 end
197 if ok then
198 ok = self:close_file_in_zip()
199 if not ok then
200 err = "error in writing "..file.." in the zipfile"
201 end
202 end
203 return ok == true, err
204end
205
206--- Complete the writing of the zipfile.
207-- @param self handle of the zipfile being written.
208-- @return true if succeeded, nil in case of failure.
209local function zipwriter_close(self)
210 local zh = self.ziphandle
211
212 local central_directory_offset = zh:seek()
213
214 local size_of_central_directory = 0
215 -- Central directory structure
216 for _, lfh in ipairs(self.files) do
217 zh:write(CENTRAL_DIRECTORY_SIGNATURE) -- signature
218 zh:write(number_to_lestring(3, 2)) -- version made by: UNIX
219 zh:write(number_to_lestring(20, 2)) -- version needed to extract: 2.0
220 zh:write(number_to_lestring(0, 2)) -- general purpose bit flag
221 zh:write(number_to_lestring(8, 2)) -- compression method: deflate
222 zh:write(number_to_lestring(lfh.last_mod_file_time, 2))
223 zh:write(number_to_lestring(lfh.last_mod_file_date, 2))
224 zh:write(number_to_lestring(lfh.crc32, 4))
225 zh:write(number_to_lestring(lfh.compressed_size, 4))
226 zh:write(number_to_lestring(lfh.uncompressed_size, 4))
227 zh:write(number_to_lestring(lfh.file_name_length, 2))
228 zh:write(number_to_lestring(lfh.extra_field_length, 2))
229 zh:write(number_to_lestring(0, 2)) -- file comment length
230 zh:write(number_to_lestring(0, 2)) -- disk number start
231 zh:write(number_to_lestring(0, 2)) -- internal file attributes
232 zh:write(number_to_lestring(lfh.external_attr, 4)) -- external file attributes
233 zh:write(number_to_lestring(lfh.offset, 4)) -- relative offset of local header
234 zh:write(lfh.file_name)
235 size_of_central_directory = size_of_central_directory + 46 + lfh.file_name_length
236 end
237
238 -- End of central directory record
239 zh:write(END_OF_CENTRAL_DIR_SIGNATURE) -- signature
240 zh:write(number_to_lestring(0, 2)) -- number of this disk
241 zh:write(number_to_lestring(0, 2)) -- number of disk with start of central directory
242 zh:write(number_to_lestring(#self.files, 2)) -- total number of entries in the central dir on this disk
243 zh:write(number_to_lestring(#self.files, 2)) -- total number of entries in the central dir
244 zh:write(number_to_lestring(size_of_central_directory, 4))
245 zh:write(number_to_lestring(central_directory_offset, 4))
246 zh:write(number_to_lestring(0, 2)) -- zip file comment length
247 zh:close()
248
249 return true
250end
251
252--- Return a zip handle open for writing.
253-- @param name filename of the zipfile to be created.
254-- @return a zip handle, or nil in case of error.
255function zip.new_zipwriter(name)
256
257 local zw = {}
258
259 zw.ziphandle = io.open(fs.absolute_name(name), "wb")
260 if not zw.ziphandle then
261 return nil
262 end
263 zw.files = {}
264 zw.in_open_file = false
265
266 zw.add = zipwriter_add
267 zw.close = zipwriter_close
268 zw.open_new_file_in_zip = zipwriter_open_new_file_in_zip
269 zw.write_file_in_zip = zipwriter_write_file_in_zip
270 zw.close_file_in_zip = zipwriter_close_file_in_zip
271
272 return zw
273end
274
275--- Compress files in a .zip archive.
276-- @param zipfile string: pathname of .zip archive to be created.
277-- @param ... Filenames to be stored in the archive are given as
278-- additional arguments.
279-- @return boolean or (boolean, string): true on success,
280-- false and an error message on failure.
281function zip.zip(zipfile, ...)
282 local zw = zip.new_zipwriter(zipfile)
283 if not zw then
284 return nil, "error opening "..zipfile
285 end
286
287 local args = pack(...)
288 local ok, err
289 for i=1, args.n do
290 local file = args[i]
291 if fs.is_dir(file) then
292 for _, entry in pairs(fs.find(file)) do
293 local fullname = dir.path(file, entry)
294 if fs.is_file(fullname) then
295 ok, err = zw:add(fullname)
296 if not ok then break end
297 end
298 end
299 else
300 ok, err = zw:add(file)
301 if not ok then break end
302 end
303 end
304
305 zw:close()
306 return ok, err
307end
308
309
310local function ziptime_to_luatime(ztime, zdate)
311 local date = {
312 year = shr(zdate, 9) + 1980,
313 month = shr(lowbits(zdate, 9), 5),
314 day = lowbits(zdate, 5),
315 hour = shr(ztime, 11),
316 min = shr(lowbits(ztime, 11), 5),
317 sec = lowbits(ztime, 5) * 2,
318 }
319
320 if date.month == 0 then date.month = 1 end
321 if date.day == 0 then date.day = 1 end
322
323 return date
324end
325
326local function read_file_in_zip(zh, cdr)
327 local sig = zh:read(4)
328 if sig ~= LOCAL_FILE_HEADER_SIGNATURE then
329 return nil, "failed reading Local File Header signature"
330 end
331
332 -- Skip over the rest of the zip file header. See
333 -- zipwriter_close_file_in_zip for the format.
334 zh:seek("cur", 22)
335 local file_name_length = lestring_to_number(zh:read(2))
336 local extra_field_length = lestring_to_number(zh:read(2))
337 zh:read(file_name_length)
338 zh:read(extra_field_length)
339
340 local data = zh:read(cdr.compressed_size)
341
342 local uncompressed
343 if cdr.compression_method == 8 then
344 uncompressed = zlib_uncompress(data, "raw")
345 elseif cdr.compression_method == 0 then
346 uncompressed = data
347 else
348 return nil, "unknown compression method " .. cdr.compression_method
349 end
350
351 if #uncompressed ~= cdr.uncompressed_size then
352 return nil, "uncompressed size doesn't match"
353 end
354 if cdr.crc32 ~= zlib_crc32(uncompressed) then
355 return nil, "crc32 failed (expected " .. cdr.crc32 .. ") - data: " .. uncompressed
356 end
357
358 return uncompressed
359end
360
361local function process_end_of_central_dir(zh)
362 local at, err = zh:seek("end", -22)
363 if not at then
364 return nil, err
365 end
366
367 while true do
368 local sig = zh:read(4)
369 if sig == END_OF_CENTRAL_DIR_SIGNATURE then
370 break
371 end
372 at = at - 1
373 local at1, err = zh:seek("set", at)
374 if at1 ~= at then
375 return nil, "Could not find End of Central Directory signature"
376 end
377 end
378
379 -- number of this disk (2 bytes)
380 -- number of the disk with the start of the central directory (2 bytes)
381 -- total number of entries in the central directory on this disk (2 bytes)
382 -- total number of entries in the central directory (2 bytes)
383 zh:seek("cur", 6)
384
385 local central_directory_entries = lestring_to_number(zh:read(2))
386
387 -- central directory size (4 bytes)
388 zh:seek("cur", 4)
389
390 local central_directory_offset = lestring_to_number(zh:read(4))
391
392 return central_directory_entries, central_directory_offset
393end
394
395local function process_central_dir(zh, cd_entries)
396
397 local files = {}
398
399 for i = 1, cd_entries do
400 local sig = zh:read(4)
401 if sig ~= CENTRAL_DIRECTORY_SIGNATURE then
402 return nil, "failed reading Central Directory signature"
403 end
404
405 local cdr = {}
406 files[i] = cdr
407
408 cdr.version_made_by = lestring_to_number(zh:read(2))
409 cdr.version_needed = lestring_to_number(zh:read(2))
410 cdr.bitflag = lestring_to_number(zh:read(2))
411 cdr.compression_method = lestring_to_number(zh:read(2))
412 cdr.last_mod_file_time = lestring_to_number(zh:read(2))
413 cdr.last_mod_file_date = lestring_to_number(zh:read(2))
414 cdr.last_mod_luatime = ziptime_to_luatime(cdr.last_mod_file_time, cdr.last_mod_file_date)
415 cdr.crc32 = lestring_to_number(zh:read(4))
416 cdr.compressed_size = lestring_to_number(zh:read(4))
417 cdr.uncompressed_size = lestring_to_number(zh:read(4))
418 cdr.file_name_length = lestring_to_number(zh:read(2))
419 cdr.extra_field_length = lestring_to_number(zh:read(2))
420 cdr.file_comment_length = lestring_to_number(zh:read(2))
421 cdr.disk_number_start = lestring_to_number(zh:read(2))
422 cdr.internal_attr = lestring_to_number(zh:read(2))
423 cdr.external_attr = lestring_to_number(zh:read(4))
424 cdr.offset = lestring_to_number(zh:read(4))
425 cdr.file_name = zh:read(cdr.file_name_length)
426 cdr.extra_field = zh:read(cdr.extra_field_length)
427 cdr.file_comment = zh:read(cdr.file_comment_length)
428 end
429 return files
430end
431
432--- Uncompress files from a .zip archive.
433-- @param zipfile string: pathname of .zip archive to be created.
434-- @return boolean or (boolean, string): true on success,
435-- false and an error message on failure.
436function zip.unzip(zipfile)
437 zipfile = fs.absolute_name(zipfile)
438 local zh, err = io.open(zipfile, "rb")
439 if not zh then
440 return nil, err
441 end
442
443 local cd_entries, cd_offset = process_end_of_central_dir(zh)
444 if not cd_entries then
445 return nil, cd_offset
446 end
447
448 local ok, err = zh:seek("set", cd_offset)
449 if not ok then
450 return nil, err
451 end
452
453 local files, err = process_central_dir(zh, cd_entries)
454 if not files then
455 return nil, err
456 end
457
458 for _, cdr in ipairs(files) do
459 local file = cdr.file_name
460 if file:sub(#file) == "/" then
461 local ok, err = fs.make_dir(dir.path(fs.current_dir(), file))
462 if not ok then
463 return nil, err
464 end
465 else
466 local base = dir.dir_name(file)
467 if base ~= "" then
468 base = dir.path(fs.current_dir(), base)
469 if not fs.is_dir(base) then
470 local ok, err = fs.make_dir(base)
471 if not ok then
472 return nil, err
473 end
474 end
475 end
476
477 local ok, err = zh:seek("set", cdr.offset)
478 if not ok then
479 return nil, err
480 end
481
482 local contents, err = read_file_in_zip(zh, cdr)
483 if not contents then
484 return nil, err
485 end
486 local pathname = dir.path(fs.current_dir(), file)
487 local wf, err = io.open(pathname, "wb")
488 if not wf then
489 zh:close()
490 return nil, err
491 end
492 wf:write(contents)
493 wf:close()
494
495 if cdr.external_attr > 0 then
496 fs.set_permissions(pathname, "exec", "all")
497 else
498 fs.set_permissions(pathname, "read", "all")
499 end
500 fs.set_time(pathname, cdr.last_mod_luatime)
501 end
502 end
503 zh:close()
504 return true
505end
506
507function zip.gzip(input_filename, output_filename)
508 assert(type(input_filename) == "string")
509 assert(output_filename == nil or type(output_filename) == "string")
510
511 if not output_filename then
512 output_filename = input_filename .. ".gz"
513 end
514
515 local fn = fun.partial(fun.flip(zlib_compress), "gzip")
516 return fs.filter_file(fn, input_filename, output_filename)
517end
518
519function zip.gunzip(input_filename, output_filename)
520 assert(type(input_filename) == "string")
521 assert(output_filename == nil or type(output_filename) == "string")
522
523 if not output_filename then
524 output_filename = input_filename:gsub("%.gz$", "")
525 end
526
527 local fn = fun.partial(fun.flip(zlib_uncompress), "gzip")
528 return fs.filter_file(fn, input_filename, output_filename)
529end
530
531return zip
diff --git a/src/luarocks/type_check.tl b/src/luarocks/type_check.tl
index 1252133d..f69f1441 100644
--- a/src/luarocks/type_check.tl
+++ b/src/luarocks/type_check.tl
@@ -1,6 +1,7 @@
1 1
2local record type_check 2local record type_check
3 MAGIC_PLATFORMS: integer 3 MAGIC_PLATFORMS: integer
4 type_check_table: function(version: string, tbl: {any: any}, typetbl: {string: string}, context: string): boolean, string --! tbl and typetbl types)
4end 5end
5 6
6local cfg = require("luarocks.core.cfg") 7local cfg = require("luarocks.core.cfg")