diff options
author | V1K1NGbg <victor@ilchev.com> | 2024-08-22 17:48:56 -0300 |
---|---|---|
committer | Hisham Muhammad <hisham@gobolinux.org> | 2024-10-21 13:30:51 -0300 |
commit | c083f3418dbaa8b354febf936fe08037a9514d40 (patch) | |
tree | ef3d7964c5219c8125abe240503b20e1143944a1 /src | |
parent | 0bbe8cab30ddade580f9773f5cb6303f25fe3913 (diff) | |
download | luarocks-c083f3418dbaa8b354febf936fe08037a9514d40.tar.gz luarocks-c083f3418dbaa8b354febf936fe08037a9514d40.tar.bz2 luarocks-c083f3418dbaa8b354febf936fe08037a9514d40.zip |
Teal: convert luarocks.upload.api
Diffstat (limited to 'src')
-rw-r--r-- | src/luarocks/upload/api.tl | 313 |
1 files changed, 174 insertions, 139 deletions
diff --git a/src/luarocks/upload/api.tl b/src/luarocks/upload/api.tl index e1413702..dc0b568c 100644 --- a/src/luarocks/upload/api.tl +++ b/src/luarocks/upload/api.tl | |||
@@ -1,5 +1,22 @@ | |||
1 | local record api | ||
2 | record Configuration | ||
3 | key: string | ||
4 | server: string | ||
5 | version: string | ||
6 | end | ||
1 | 7 | ||
2 | local api = {} | 8 | record Api |
9 | load_config: function(Api): Configuration | ||
10 | save_config: function(Api): boolean, string | ||
11 | check_version: function(Api): boolean, string | ||
12 | method: function(Api, string, ...:Parameters): {string : any}, string | ||
13 | raw_method: function(Api, string, ...:Parameters): {string : any}, string | ||
14 | request: function(Api, string, ?Parameters, ?Parameters): {string : any}, string | ||
15 | config: Configuration | ||
16 | debug: boolean | ||
17 | _server_tool_version: string | ||
18 | end | ||
19 | end | ||
3 | 20 | ||
4 | local cfg = require("luarocks.core.cfg") | 21 | local cfg = require("luarocks.core.cfg") |
5 | local fs = require("luarocks.fs") | 22 | local fs = require("luarocks.fs") |
@@ -10,43 +27,50 @@ local multipart = require("luarocks.upload.multipart") | |||
10 | local json = require("luarocks.vendor.dkjson") | 27 | local json = require("luarocks.vendor.dkjson") |
11 | local dir_sep = package.config:sub(1, 1) | 28 | local dir_sep = package.config:sub(1, 1) |
12 | 29 | ||
13 | local Api = {} | 30 | local type Parameters = multipart.Parameters |
31 | local type Api = api.Api | ||
32 | local type Args = require("luarocks.core.types.args").Args | ||
33 | local type Configuration = api.Configuration | ||
34 | local type File = multipart.File | ||
35 | |||
36 | local type PersistableTable = require("luarocks.core.types.persist").PersistableTable | ||
14 | 37 | ||
15 | local function upload_config_file() | 38 | local function upload_config_file(): string |
16 | if not cfg.config_files.user.file then | 39 | if not cfg.config_files.user.file then |
17 | return nil | 40 | return nil |
18 | end | 41 | end |
19 | return (cfg.config_files.user.file:gsub("[\\/][^\\/]+$", dir_sep .. "upload_config.lua")) | 42 | return (cfg.config_files.user.file:gsub("[\\/][^\\/]+$", dir_sep .. "upload_config.lua")) |
20 | end | 43 | end |
21 | 44 | ||
22 | function Api:load_config() | 45 | function api.Api:load_config(): Configuration |
23 | local upload_conf = upload_config_file() | 46 | local upload_conf = upload_config_file() |
24 | if not upload_conf then return nil end | 47 | if not upload_conf then return nil end |
25 | local config, err = persist.load_into_table(upload_conf) | 48 | local config = persist.load_into_table(upload_conf) as Configuration |
26 | return config | 49 | return config |
27 | end | 50 | end |
28 | 51 | ||
29 | function Api:save_config() | 52 | function api.Api:save_config(): boolean, string |
30 | -- Test configuration before saving it. | 53 | -- Test configuration before saving it. |
31 | local res, err = self:raw_method("status") | 54 | local res, errraw = self:raw_method("status") |
32 | if not res then | 55 | if not res then |
33 | return nil, err | 56 | return nil, errraw |
34 | end | 57 | end |
35 | if res.errors then | 58 | local reserrors = res.errors |
36 | util.printerr("Server says: " .. tostring(res.errors[1])) | 59 | if reserrors is {string} then |
37 | return | 60 | return nil, ("Server error: " .. tostring(reserrors[1])) |
38 | end | 61 | end |
39 | local upload_conf = upload_config_file() | 62 | local upload_conf = upload_config_file() |
40 | if not upload_conf then return nil end | 63 | if not upload_conf then return nil end |
41 | local ok, err = fs.make_dir(dir.dir_name(upload_conf)) | 64 | local ok, errmake = fs.make_dir(dir.dir_name(upload_conf)) |
42 | if not ok then | 65 | if not ok then |
43 | return nil, err | 66 | return nil, errmake |
44 | end | 67 | end |
45 | persist.save_from_table(upload_conf, self.config) | 68 | persist.save_from_table(upload_conf, self.config as PersistableTable) |
46 | fs.set_permissions(upload_conf, "read", "user") | 69 | fs.set_permissions(upload_conf, "read", "user") |
70 | return true | ||
47 | end | 71 | end |
48 | 72 | ||
49 | function Api:check_version() | 73 | function api.Api:check_version(): boolean, string |
50 | if not self._server_tool_version then | 74 | if not self._server_tool_version then |
51 | local tool_version = cfg.upload.tool_version | 75 | local tool_version = cfg.upload.tool_version |
52 | local res, err = self:request(tostring(self.config.server) .. "/api/tool_version", { | 76 | local res, err = self:request(tostring(self.config.server) .. "/api/tool_version", { |
@@ -58,7 +82,7 @@ function Api:check_version() | |||
58 | if not res.version then | 82 | if not res.version then |
59 | return nil, "failed to fetch tool version" | 83 | return nil, "failed to fetch tool version" |
60 | end | 84 | end |
61 | self._server_tool_version = res.version | 85 | self._server_tool_version = tostring(res.version) |
62 | if res.force_update then | 86 | if res.force_update then |
63 | return nil, "Your upload client is too out of date to continue, please upgrade LuaRocks." | 87 | return nil, "Your upload client is too out of date to continue, please upgrade LuaRocks." |
64 | end | 88 | end |
@@ -69,40 +93,45 @@ function Api:check_version() | |||
69 | return true | 93 | return true |
70 | end | 94 | end |
71 | 95 | ||
72 | function Api:method(...) | 96 | function api.Api:method(path: string, ...: Parameters): {string : any}, string |
73 | local res, err = self:raw_method(...) | 97 | local res, err = self:raw_method(path, ...) |
74 | if not res then | 98 | if not res then |
75 | return nil, err | 99 | return nil, err |
76 | end | 100 | end |
77 | if res.errors then | 101 | local reserrors = res.errors |
78 | if res.errors[1] == "Invalid key" then | 102 | if reserrors is {string} then --! not checking the contents |
79 | return nil, res.errors[1] .. " (use the --api-key flag to change)" | 103 | if reserrors[1] == "Invalid key" then |
104 | return nil, reserrors[1] .. " (use the --api-key flag to change)" | ||
80 | end | 105 | end |
81 | local msg = table.concat(res.errors, ", ") | 106 | local msg = table.concat(reserrors, ", ") |
82 | return nil, "API Failed: " .. msg | 107 | return nil, "API Failed: " .. msg |
83 | end | 108 | end |
84 | return res | 109 | return res |
85 | end | 110 | end |
86 | 111 | ||
87 | function Api:raw_method(path, ...) | 112 | function api.Api:raw_method(path: string, ...: Parameters): {string : any}, string |
88 | self:check_version() | 113 | self:check_version() |
89 | local url = tostring(self.config.server) .. "/api/" .. tostring(cfg.upload.api_version) .. "/" .. tostring(self.config.key) .. "/" .. tostring(path) | 114 | local url = tostring(self.config.server) .. "/api/" .. tostring(cfg.upload.api_version) .. "/" .. tostring(self.config.key) .. "/" .. path |
90 | return self:request(url, ...) | 115 | return self:request(url, ...) |
91 | end | 116 | end |
92 | 117 | ||
93 | local function encode_query_string(t, sep) | 118 | local function encode_query_string(t: Parameters, sep?: string): string |
94 | if sep == nil then | 119 | if sep == nil then |
95 | sep = "&" | 120 | sep = "&" |
96 | end | 121 | end |
97 | local i = 0 | 122 | local i = 0 |
98 | local buf = { } | 123 | local buf: {string} = { } |
99 | for k, v in pairs(t) do | 124 | for k, v in pairs(t) do |
100 | if type(k) == "number" and type(v) == "table" then | 125 | local ks, vs: string, string |
101 | k, v = v[1], v[2] | 126 | local vf = v |
127 | if vf is File then | ||
128 | ks, vs = k, vf:content() | ||
129 | else | ||
130 | ks, vs = k, vf | ||
102 | end | 131 | end |
103 | buf[i + 1] = multipart.url_escape(k) | 132 | buf[i + 1] = multipart.url_escape(ks) |
104 | buf[i + 2] = "=" | 133 | buf[i + 2] = "=" |
105 | buf[i + 3] = multipart.url_escape(v) | 134 | buf[i + 3] = multipart.url_escape(vs) |
106 | buf[i + 4] = sep | 135 | buf[i + 4] = sep |
107 | i = i + 4 | 136 | i = i + 4 |
108 | end | 137 | end |
@@ -110,141 +139,144 @@ local function encode_query_string(t, sep) | |||
110 | return table.concat(buf) | 139 | return table.concat(buf) |
111 | end | 140 | end |
112 | 141 | ||
113 | local function redact_api_url(url) | 142 | local function redact_api_url(url: any): string |
114 | url = tostring(url) | 143 | local urls = tostring(url) |
115 | return (url:gsub(".*/api/[^/]+/[^/]+", "")) or "" | 144 | return (urls:gsub(".*/api/[^/]+/[^/]+", "")) or "" |
116 | end | 145 | end |
117 | 146 | ||
118 | local ltn12_ok, ltn12 = pcall(require, "ltn12") | 147 | local ltn12_ok, ltn12 = pcall(require, "ltn12") |
119 | if not ltn12_ok then -- If not using LuaSocket and/or LuaSec... | 148 | if not ltn12_ok then -- If not using LuaSocket and/or LuaSec... |
120 | 149 | ||
121 | function Api:request(url, params, post_params) | 150 | api.Api.request = function(self: Api, url: string, params?: Parameters, post_params?: Parameters): {string : any}, string |
122 | local vars = cfg.variables | 151 | local vars = cfg.variables |
123 | |||
124 | if fs.which_tool("downloader") == "wget" then | ||
125 | local curl_ok, err = fs.is_tool_available(vars.CURL, "curl") | ||
126 | if not curl_ok then | ||
127 | return nil, err | ||
128 | end | ||
129 | end | ||
130 | 152 | ||
131 | if not self.config.key then | 153 | if fs.which_tool("downloader") == "wget" then |
132 | return nil, "Must have API key before performing any actions." | 154 | local curl_ok, err = fs.is_tool_available(vars.CURL, "curl") |
133 | end | 155 | if not curl_ok then |
134 | if params and next(params) then | 156 | return nil, err |
135 | url = url .. ("?" .. encode_query_string(params)) | ||
136 | end | ||
137 | local method = "GET" | ||
138 | local out | ||
139 | local tmpfile = fs.tmpname() | ||
140 | if post_params then | ||
141 | method = "POST" | ||
142 | local curl_cmd = vars.CURL.." "..vars.CURLNOCERTFLAG.." -f -L --silent --user-agent \""..cfg.user_agent.." via curl\" " | ||
143 | for k,v in pairs(post_params) do | ||
144 | local var = v | ||
145 | if type(v) == "table" then | ||
146 | var = "@"..v.fname | ||
147 | end | 157 | end |
148 | curl_cmd = curl_cmd .. "--form \""..k.."="..var.."\" " | ||
149 | end | 158 | end |
150 | if cfg.connection_timeout and cfg.connection_timeout > 0 then | 159 | |
151 | curl_cmd = curl_cmd .. "--connect-timeout "..tonumber(cfg.connection_timeout).." " | 160 | if not self.config.key then |
161 | return nil, "Must have API key before performing any actions." | ||
152 | end | 162 | end |
153 | local ok = fs.execute_string(curl_cmd..fs.Q(url).." -o "..fs.Q(tmpfile)) | 163 | if params and next(params) then |
154 | if not ok then | 164 | url = url .. ("?" .. encode_query_string(params)) |
155 | return nil, "API failure: " .. redact_api_url(url) | ||
156 | end | 165 | end |
157 | else | 166 | local method = "GET" |
158 | local ok, err = fs.download(url, tmpfile) | 167 | local out: string |
159 | if not ok then | 168 | local tmpfile = fs.tmpname() |
160 | return nil, "API failure: " .. tostring(err) .. " - " .. redact_api_url(url) | 169 | if post_params then |
170 | method = "POST" | ||
171 | local curl_cmd = vars.CURL.." "..vars.CURLNOCERTFLAG.." -f -L --silent --user-agent \""..cfg.user_agent.." via curl\" " | ||
172 | for k,v in pairs(post_params) do | ||
173 | local var: string | ||
174 | if v is File then | ||
175 | var = "@"..v.fname | ||
176 | else | ||
177 | var = v | ||
178 | end | ||
179 | curl_cmd = curl_cmd .. "--form \""..k.."="..var.."\" " | ||
180 | end | ||
181 | if cfg.connection_timeout and cfg.connection_timeout > 0 then | ||
182 | curl_cmd = curl_cmd .. "--connect-timeout "..tonumber(cfg.connection_timeout).." " | ||
183 | end | ||
184 | local ok = fs.execute_string(curl_cmd..fs.Q(url).." -o "..fs.Q(tmpfile)) | ||
185 | if not ok then | ||
186 | return nil, "API failure: " .. redact_api_url(url) | ||
187 | end | ||
188 | else | ||
189 | local name, err = fs.download(url, tmpfile) | ||
190 | if not name then | ||
191 | return nil, "API failure: " .. tostring(err) .. " - " .. redact_api_url(url) | ||
192 | end | ||
161 | end | 193 | end |
162 | end | ||
163 | 194 | ||
164 | local tmpfd = io.open(tmpfile) | 195 | local tmpfd = io.open(tmpfile) |
165 | if not tmpfd then | 196 | if not tmpfd then |
197 | os.remove(tmpfile) | ||
198 | return nil, "API failure reading temporary file - " .. redact_api_url(url) | ||
199 | end | ||
200 | out = tmpfd:read("*a") | ||
201 | tmpfd:close() | ||
166 | os.remove(tmpfile) | 202 | os.remove(tmpfile) |
167 | return nil, "API failure reading temporary file - " .. redact_api_url(url) | ||
168 | end | ||
169 | out = tmpfd:read("*a") | ||
170 | tmpfd:close() | ||
171 | os.remove(tmpfile) | ||
172 | 203 | ||
173 | if self.debug then | 204 | if self.debug then |
174 | util.printout("[" .. tostring(method) .. " via curl] " .. redact_api_url(url) .. " ... ") | 205 | util.printout("[" .. tostring(method) .. " via curl] " .. redact_api_url(url) .. " ... ") |
175 | end | 206 | end |
176 | 207 | ||
177 | return json.decode(out) | 208 | return json.decode(out) |
178 | end | 209 | end |
179 | 210 | ||
180 | else -- use LuaSocket and LuaSec | 211 | else -- use LuaSocket and LuaSec |
181 | 212 | ||
182 | local warned_luasec = false | 213 | local warned_luasec = false |
183 | 214 | ||
184 | function Api:request(url, params, post_params) | 215 | api.Api.request = function(self: Api, url: string, params?: Parameters, post_params?: Parameters): {string : any}, string |
185 | local server = tostring(self.config.server) | 216 | local server = tostring(self.config.server) |
186 | local http_ok, http | 217 | local type Http = require("socket.http") |
187 | local via = "luasocket" | 218 | local http_ok, http: boolean, Http |
188 | if server:match("^https://") then | 219 | local via = "luasocket" |
189 | http_ok, http = pcall(require, "ssl.https") | 220 | if server:match("^https://") then |
190 | if http_ok then | 221 | http_ok, http = pcall(require, "ssl.https") as (boolean, Http) |
191 | via = "luasec" | 222 | if http_ok then |
192 | else | 223 | via = "luasec" |
193 | if not warned_luasec then | 224 | else |
194 | util.printerr("LuaSec is not available; using plain HTTP. Install 'luasec' to enable HTTPS.") | 225 | if not warned_luasec then |
195 | warned_luasec = true | 226 | util.printerr("LuaSec is not available; using plain HTTP. Install 'luasec' to enable HTTPS.") |
227 | warned_luasec = true | ||
228 | end | ||
229 | http_ok, http = pcall(require, "socket.http") | ||
230 | url = url:gsub("^https", "http") | ||
231 | via = "luasocket" | ||
196 | end | 232 | end |
233 | else | ||
197 | http_ok, http = pcall(require, "socket.http") | 234 | http_ok, http = pcall(require, "socket.http") |
198 | url = url:gsub("^https", "http") | ||
199 | via = "luasocket" | ||
200 | end | 235 | end |
201 | else | 236 | if not http_ok then |
202 | http_ok, http = pcall(require, "socket.http") | 237 | return nil, "Failed loading socket library!" |
203 | end | 238 | end |
204 | if not http_ok then | ||
205 | return nil, "Failed loading socket library!" | ||
206 | end | ||
207 | 239 | ||
208 | if not self.config.key then | 240 | if not self.config.key then |
209 | return nil, "Must have API key before performing any actions." | 241 | return nil, "Must have API key before performing any actions." |
210 | end | 242 | end |
211 | local body | 243 | local body: string |
212 | local headers = {} | 244 | local headers: {string: string} = {} |
213 | if params and next(params) then | 245 | if params and next(params) then |
214 | url = url .. ("?" .. encode_query_string(params)) | 246 | url = url .. ("?" .. encode_query_string(params)) |
215 | end | 247 | end |
216 | if post_params then | 248 | if post_params then |
217 | local boundary | 249 | local boundary: string |
218 | body, boundary = multipart.encode(post_params) | 250 | body, boundary = multipart.encode(post_params) |
219 | headers["Content-length"] = #body | 251 | headers["Content-length"] = tostring(#body) |
220 | headers["Content-type"] = "multipart/form-data; boundary=" .. tostring(boundary) | 252 | headers["Content-type"] = "multipart/form-data; boundary=" .. tostring(boundary) |
221 | end | 253 | end |
222 | local method = post_params and "POST" or "GET" | 254 | local method = post_params and "POST" or "GET" |
223 | if self.debug then | 255 | if self.debug then |
224 | util.printout("[" .. tostring(method) .. " via "..via.."] " .. redact_api_url(url) .. " ... ") | 256 | util.printout("[" .. tostring(method) .. " via "..via.."] " .. redact_api_url(url) .. " ... ") |
225 | end | 257 | end |
226 | local out = {} | 258 | local out = {} |
227 | local _, status = http.request({ | 259 | local _, status = http.request({ |
228 | url = url, | 260 | url = url, |
229 | headers = headers, | 261 | headers = headers, |
230 | method = method, | 262 | method = method, |
231 | sink = ltn12.sink.table(out), | 263 | sink = ltn12.sink.table(out), |
232 | source = body and ltn12.source.string(body) | 264 | source = body and ltn12.source.string(body) |
233 | }) | 265 | }) |
234 | if self.debug then | 266 | if self.debug then |
235 | util.printout(tostring(status)) | 267 | util.printout(tostring(status)) |
236 | end | 268 | end |
237 | local pok, ret, err = pcall(json.decode, table.concat(out)) | 269 | local pok, ret = pcall(json.decode, table.concat(out)) |
238 | if pok and ret then | 270 | if pok and ret then |
239 | return ret | 271 | return ret |
272 | end | ||
273 | return nil, "API returned " .. tostring(status) .. " - " .. redact_api_url(url) | ||
240 | end | 274 | end |
241 | return nil, "API returned " .. tostring(status) .. " - " .. redact_api_url(url) | ||
242 | end | ||
243 | 275 | ||
244 | end | 276 | end |
245 | 277 | ||
246 | function api.new(args) | 278 | function api.new(args: Args): Api, string |
247 | local self = {} | 279 | local self: Api = {} |
248 | setmetatable(self, { __index = Api }) | 280 | setmetatable(self, { __index = Api }) |
249 | self.config = self:load_config() or {} | 281 | self.config = self:load_config() or {} |
250 | self.config.server = args.server or self.config.server or cfg.upload.server | 282 | self.config.server = args.server or self.config.server or cfg.upload.server |
@@ -257,7 +289,10 @@ function api.new(args) | |||
257 | "and then pass it through the --api-key=<key> flag." | 289 | "and then pass it through the --api-key=<key> flag." |
258 | end | 290 | end |
259 | if args.api_key then | 291 | if args.api_key then |
260 | self:save_config() | 292 | local ok, err = self:save_config() |
293 | if not ok then | ||
294 | return nil, err | ||
295 | end | ||
261 | end | 296 | end |
262 | return self | 297 | return self |
263 | end | 298 | end |