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