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