summaryrefslogtreecommitdiff
path: root/src/luarocks/upload/api.lua
diff options
context:
space:
mode:
authorMike Vink <mike@pionative.com>2025-02-03 21:29:42 +0100
committerMike Vink <mike@pionative.com>2025-02-03 21:29:42 +0100
commit5155816b7b925dec5d5feb1568b1d7ceb00938b9 (patch)
treedeca28ea15e79f6f804c3d90d2ba757881638af5 /src/luarocks/upload/api.lua
fetch tarballHEADmaster
Diffstat (limited to 'src/luarocks/upload/api.lua')
-rw-r--r--src/luarocks/upload/api.lua265
1 files changed, 265 insertions, 0 deletions
diff --git a/src/luarocks/upload/api.lua b/src/luarocks/upload/api.lua
new file mode 100644
index 0000000..e141370
--- /dev/null
+++ b/src/luarocks/upload/api.lua
@@ -0,0 +1,265 @@
+
+local api = {}
+
+local cfg = require("luarocks.core.cfg")
+local fs = require("luarocks.fs")
+local dir = require("luarocks.dir")
+local util = require("luarocks.util")
+local persist = require("luarocks.persist")
+local multipart = require("luarocks.upload.multipart")
+local json = require("luarocks.vendor.dkjson")
+local dir_sep = package.config:sub(1, 1)
+
+local Api = {}
+
+local function upload_config_file()
+ if not cfg.config_files.user.file then
+ return nil
+ end
+ return (cfg.config_files.user.file:gsub("[\\/][^\\/]+$", dir_sep .. "upload_config.lua"))
+end
+
+function Api:load_config()
+ local upload_conf = upload_config_file()
+ if not upload_conf then return nil end
+ local config, err = persist.load_into_table(upload_conf)
+ return config
+end
+
+function Api:save_config()
+ -- Test configuration before saving it.
+ local res, err = self:raw_method("status")
+ if not res then
+ return nil, err
+ end
+ if res.errors then
+ util.printerr("Server says: " .. tostring(res.errors[1]))
+ return
+ end
+ local upload_conf = upload_config_file()
+ if not upload_conf then return nil end
+ local ok, err = fs.make_dir(dir.dir_name(upload_conf))
+ if not ok then
+ return nil, err
+ end
+ persist.save_from_table(upload_conf, self.config)
+ fs.set_permissions(upload_conf, "read", "user")
+end
+
+function Api:check_version()
+ if not self._server_tool_version then
+ local tool_version = cfg.upload.tool_version
+ local res, err = self:request(tostring(self.config.server) .. "/api/tool_version", {
+ current = tool_version
+ })
+ if not res then
+ return nil, err
+ end
+ if not res.version then
+ return nil, "failed to fetch tool version"
+ end
+ self._server_tool_version = res.version
+ if res.force_update then
+ return nil, "Your upload client is too out of date to continue, please upgrade LuaRocks."
+ end
+ if res.version ~= tool_version then
+ util.warning("your LuaRocks is out of date, consider upgrading.")
+ end
+ end
+ return true
+end
+
+function Api:method(...)
+ local res, err = self:raw_method(...)
+ if not res then
+ return nil, err
+ end
+ if res.errors then
+ if res.errors[1] == "Invalid key" then
+ return nil, res.errors[1] .. " (use the --api-key flag to change)"
+ end
+ local msg = table.concat(res.errors, ", ")
+ return nil, "API Failed: " .. msg
+ end
+ return res
+end
+
+function Api:raw_method(path, ...)
+ self:check_version()
+ local url = tostring(self.config.server) .. "/api/" .. tostring(cfg.upload.api_version) .. "/" .. tostring(self.config.key) .. "/" .. tostring(path)
+ return self:request(url, ...)
+end
+
+local function encode_query_string(t, sep)
+ if sep == nil then
+ sep = "&"
+ end
+ local i = 0
+ local buf = { }
+ for k, v in pairs(t) do
+ if type(k) == "number" and type(v) == "table" then
+ k, v = v[1], v[2]
+ end
+ buf[i + 1] = multipart.url_escape(k)
+ buf[i + 2] = "="
+ buf[i + 3] = multipart.url_escape(v)
+ buf[i + 4] = sep
+ i = i + 4
+ end
+ buf[i] = nil
+ return table.concat(buf)
+end
+
+local function redact_api_url(url)
+ url = tostring(url)
+ return (url:gsub(".*/api/[^/]+/[^/]+", "")) or ""
+end
+
+local ltn12_ok, ltn12 = pcall(require, "ltn12")
+if not ltn12_ok then -- If not using LuaSocket and/or LuaSec...
+
+function Api:request(url, params, post_params)
+ local vars = cfg.variables
+
+ if fs.which_tool("downloader") == "wget" then
+ local curl_ok, err = fs.is_tool_available(vars.CURL, "curl")
+ if not curl_ok then
+ return nil, err
+ end
+ end
+
+ if not self.config.key then
+ return nil, "Must have API key before performing any actions."
+ end
+ if params and next(params) then
+ url = url .. ("?" .. encode_query_string(params))
+ end
+ local method = "GET"
+ local out
+ local tmpfile = fs.tmpname()
+ if post_params then
+ method = "POST"
+ local curl_cmd = vars.CURL.." "..vars.CURLNOCERTFLAG.." -f -L --silent --user-agent \""..cfg.user_agent.." via curl\" "
+ for k,v in pairs(post_params) do
+ local var = v
+ if type(v) == "table" then
+ var = "@"..v.fname
+ end
+ curl_cmd = curl_cmd .. "--form \""..k.."="..var.."\" "
+ end
+ if cfg.connection_timeout and cfg.connection_timeout > 0 then
+ curl_cmd = curl_cmd .. "--connect-timeout "..tonumber(cfg.connection_timeout).." "
+ end
+ local ok = fs.execute_string(curl_cmd..fs.Q(url).." -o "..fs.Q(tmpfile))
+ if not ok then
+ return nil, "API failure: " .. redact_api_url(url)
+ end
+ else
+ local ok, err = fs.download(url, tmpfile)
+ if not ok then
+ return nil, "API failure: " .. tostring(err) .. " - " .. redact_api_url(url)
+ end
+ end
+
+ local tmpfd = io.open(tmpfile)
+ if not tmpfd then
+ os.remove(tmpfile)
+ return nil, "API failure reading temporary file - " .. redact_api_url(url)
+ end
+ out = tmpfd:read("*a")
+ tmpfd:close()
+ os.remove(tmpfile)
+
+ if self.debug then
+ util.printout("[" .. tostring(method) .. " via curl] " .. redact_api_url(url) .. " ... ")
+ end
+
+ return json.decode(out)
+end
+
+else -- use LuaSocket and LuaSec
+
+local warned_luasec = false
+
+function Api:request(url, params, post_params)
+ local server = tostring(self.config.server)
+ local http_ok, http
+ local via = "luasocket"
+ if server:match("^https://") then
+ http_ok, http = pcall(require, "ssl.https")
+ if http_ok then
+ via = "luasec"
+ else
+ if not warned_luasec then
+ util.printerr("LuaSec is not available; using plain HTTP. Install 'luasec' to enable HTTPS.")
+ warned_luasec = true
+ end
+ http_ok, http = pcall(require, "socket.http")
+ url = url:gsub("^https", "http")
+ via = "luasocket"
+ end
+ else
+ http_ok, http = pcall(require, "socket.http")
+ end
+ if not http_ok then
+ return nil, "Failed loading socket library!"
+ end
+
+ if not self.config.key then
+ return nil, "Must have API key before performing any actions."
+ end
+ local body
+ local headers = {}
+ if params and next(params) then
+ url = url .. ("?" .. encode_query_string(params))
+ end
+ if post_params then
+ local boundary
+ body, boundary = multipart.encode(post_params)
+ headers["Content-length"] = #body
+ headers["Content-type"] = "multipart/form-data; boundary=" .. tostring(boundary)
+ end
+ local method = post_params and "POST" or "GET"
+ if self.debug then
+ util.printout("[" .. tostring(method) .. " via "..via.."] " .. redact_api_url(url) .. " ... ")
+ end
+ local out = {}
+ local _, status = http.request({
+ url = url,
+ headers = headers,
+ method = method,
+ sink = ltn12.sink.table(out),
+ source = body and ltn12.source.string(body)
+ })
+ if self.debug then
+ util.printout(tostring(status))
+ end
+ local pok, ret, err = pcall(json.decode, table.concat(out))
+ if pok and ret then
+ return ret
+ end
+ return nil, "API returned " .. tostring(status) .. " - " .. redact_api_url(url)
+end
+
+end
+
+function api.new(args)
+ local self = {}
+ setmetatable(self, { __index = Api })
+ self.config = self:load_config() or {}
+ self.config.server = args.server or self.config.server or cfg.upload.server
+ self.config.version = self.config.version or cfg.upload.version
+ self.config.key = args.temp_key or args.api_key or self.config.key
+ self.debug = args.debug
+ if not self.config.key then
+ return nil, "You need an API key to upload rocks.\n" ..
+ "Navigate to "..self.config.server.."/settings to get a key\n" ..
+ "and then pass it through the --api-key=<key> flag."
+ end
+ if args.api_key then
+ self:save_config()
+ end
+ return self
+end
+
+return api