diff options
author | V1K1NGbg <victor@ilchev.com> | 2024-08-03 12:31:21 +0300 |
---|---|---|
committer | V1K1NGbg <victor@ilchev.com> | 2024-08-05 20:51:31 +0300 |
commit | 3c2ab76e26c3cd4c863b7902cfd3f4424784d6b6 (patch) | |
tree | c4b67611ac3888133b08f34591644023775fca15 | |
parent | 9a5c9fb314c8ec4d8876a424d12c17879d58db5f (diff) | |
download | luarocks-3c2ab76e26c3cd4c863b7902cfd3f4424784d6b6.tar.gz luarocks-3c2ab76e26c3cd4c863b7902cfd3f4424784d6b6.tar.bz2 luarocks-3c2ab76e26c3cd4c863b7902cfd3f4424784d6b6.zip |
patch
-rw-r--r-- | src/luarocks/core/cfg.d.tl | 2 | ||||
-rw-r--r-- | src/luarocks/tools/patch.tl | 80 | ||||
-rw-r--r-- | src/luarocks/tools/tar.tl | 191 | ||||
-rw-r--r-- | src/luarocks/tools/zip.tl | 531 | ||||
-rw-r--r-- | src/luarocks/type_check.tl | 1 |
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 |
90 | end | 92 | end |
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 | ||
11 | local record patch | 11 | local 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 | ||
13 | end | 25 | end |
14 | 26 | ||
15 | local fs = require("luarocks.fs") | 27 | local fs = require("luarocks.fs") |
16 | local fun = require("luarocks.fun") | 28 | local fun = require("luarocks.fun") |
17 | 29 | ||
18 | local io = io | 30 | local type Lineends = patch.Lineends |
19 | local os = os | 31 | local type Hunks = patch.Hunks |
20 | local string = string | 32 | |
21 | local table = table | 33 | -- local io = io |
22 | local 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 |
25 | local debugmode = false | 40 | local debugmode = false |
@@ -38,7 +53,7 @@ local function endswith(s: string, s2: string): boolean | |||
38 | end | 53 | end |
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. |
41 | local function endlstrip(s: string): string | 56 | local function endlstrip(s: string): string, integer |
42 | return s:gsub('[\r\n]+$', '') | 57 | return s:gsub('[\r\n]+$', '') |
43 | end | 58 | end |
44 | 59 | ||
@@ -57,13 +72,13 @@ local function exists(filename: string): boolean | |||
57 | end | 72 | end |
58 | local function isfile(): boolean return true end --FIX? --! | 73 | local function isfile(): boolean return true end --FIX? --! |
59 | 74 | ||
60 | local function string_as_file(s: string) --! | 75 | local 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 |
79 | end | 94 | end |
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 |
121 | end | 137 | end |
122 | 138 | ||
123 | local function match_linerange(line: string) | 139 | local 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 |
129 | end | 145 | end |
130 | 146 | ||
131 | local function match_epoch(str) | 147 | local 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]") |
133 | end | 149 | end |
134 | 150 | ||
135 | function patch.read_patch(filename, data) | 151 | function 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) | ||
3 | local tar = {} | ||
4 | |||
5 | local fs = require("luarocks.fs") | ||
6 | local dir = require("luarocks.dir") | ||
7 | local fun = require("luarocks.fun") | ||
8 | |||
9 | local blocksize = 512 | ||
10 | |||
11 | local 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" | ||
26 | end | ||
27 | |||
28 | local 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 | ||
41 | end | ||
42 | |||
43 | local 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 | ||
60 | end | ||
61 | |||
62 | local function nullterm(s) | ||
63 | return s:match("^[^%z]*") | ||
64 | end | ||
65 | |||
66 | local 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 | ||
97 | end | ||
98 | |||
99 | function 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 | ||
189 | end | ||
190 | |||
191 | return 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. | ||
4 | local zip = {} | ||
5 | |||
6 | local zlib = require("zlib") | ||
7 | local fs = require("luarocks.fs") | ||
8 | local fun = require("luarocks.fun") | ||
9 | local dir = require("luarocks.dir") | ||
10 | |||
11 | local pack = table.pack or function(...) return { n = select("#", ...), ... } end | ||
12 | |||
13 | local function shr(n, m) | ||
14 | return math.floor(n / 2^m) | ||
15 | end | ||
16 | |||
17 | local function shl(n, m) | ||
18 | return n * 2^m | ||
19 | end | ||
20 | local function lowbits(n, m) | ||
21 | return n % 2^m | ||
22 | end | ||
23 | |||
24 | local 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 | ||
32 | end | ||
33 | |||
34 | -- zlib module can be provided by both lzlib and lua-lzib packages. | ||
35 | -- Create a compatibility layer. | ||
36 | local zlib_compress, zlib_uncompress, zlib_crc32 | ||
37 | if 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 | ||
49 | elseif 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 | ||
61 | else | ||
62 | error("unknown zlib library", 0) | ||
63 | end | ||
64 | |||
65 | local 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) | ||
73 | end | ||
74 | |||
75 | local 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) | ||
82 | end | ||
83 | |||
84 | local LOCAL_FILE_HEADER_SIGNATURE = number_to_lestring(0x04034b50, 4) | ||
85 | local DATA_DESCRIPTOR_SIGNATURE = number_to_lestring(0x08074b50, 4) | ||
86 | local CENTRAL_DIRECTORY_SIGNATURE = number_to_lestring(0x02014b50, 4) | ||
87 | local 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. | ||
93 | local 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 | ||
108 | end | ||
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. | ||
114 | local 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 | ||
125 | end | ||
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. | ||
130 | local 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 | ||
166 | end | ||
167 | |||
168 | -- @return boolean or (boolean, string): true on success, | ||
169 | -- false and an error message on failure. | ||
170 | local 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 | ||
204 | end | ||
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. | ||
209 | local 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 | ||
250 | end | ||
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. | ||
255 | function 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 | ||
273 | end | ||
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. | ||
281 | function 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 | ||
307 | end | ||
308 | |||
309 | |||
310 | local 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 | ||
324 | end | ||
325 | |||
326 | local 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 | ||
359 | end | ||
360 | |||
361 | local 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 | ||
393 | end | ||
394 | |||
395 | local 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 | ||
430 | end | ||
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. | ||
436 | function 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 | ||
505 | end | ||
506 | |||
507 | function 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) | ||
517 | end | ||
518 | |||
519 | function 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) | ||
529 | end | ||
530 | |||
531 | return 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 | ||
2 | local record type_check | 2 | local 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) | ||
4 | end | 5 | end |
5 | 6 | ||
6 | local cfg = require("luarocks.core.cfg") | 7 | local cfg = require("luarocks.core.cfg") |