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