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 |
