Software /
code /
prosody
Comparison
plugins/mod_http_file_share.lua @ 13776:977415e0122d 13.0
mod_http_file_share: Improve error reporting by using util.error more
This should pass back the error message as well as the status code to
the client.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Sun, 16 Mar 2025 15:20:45 +0100 |
parent | 13713:4a687745bb51 |
child | 13785:d7e54a2475cc |
comparison
equal
deleted
inserted
replaced
13774:6f7fdbd35502 | 13776:977415e0122d |
---|---|
60 | 60 |
61 local upload_errors = errors.init(module.name, namespace, { | 61 local upload_errors = errors.init(module.name, namespace, { |
62 access = { type = "auth"; condition = "forbidden" }; | 62 access = { type = "auth"; condition = "forbidden" }; |
63 filename = { type = "modify"; condition = "bad-request"; text = "Invalid filename" }; | 63 filename = { type = "modify"; condition = "bad-request"; text = "Invalid filename" }; |
64 filetype = { type = "modify"; condition = "not-acceptable"; text = "File type not allowed" }; | 64 filetype = { type = "modify"; condition = "not-acceptable"; text = "File type not allowed" }; |
65 filesize = { type = "modify"; condition = "not-acceptable"; text = "File too large"; | 65 filesize = { |
66 extra = {tag = st.stanza("file-too-large", {xmlns = namespace}):tag("max-file-size"):text(tostring(file_size_limit)) }; | 66 code = 413; |
67 type = "modify"; | |
68 condition = "not-acceptable"; | |
69 text = "File too large"; | |
70 extra = { | |
71 tag = st.stanza("file-too-large", { xmlns = namespace }):tag("max-file-size"):text(tostring(file_size_limit)); | |
72 }; | |
67 }; | 73 }; |
68 filesizefmt = { type = "modify"; condition = "bad-request"; text = "File size must be positive integer"; }; | 74 filesizefmt = { type = "modify"; condition = "bad-request"; text = "File size must be positive integer"; }; |
69 quota = { type = "wait"; condition = "resource-constraint"; text = "Daily quota reached"; }; | 75 quota = { type = "wait"; condition = "resource-constraint"; text = "Daily quota reached"; }; |
70 outofdisk = { type = "wait"; condition = "resource-constraint"; text = "Server global storage quota reached" }; | 76 outofdisk = { type = "wait"; condition = "resource-constraint"; text = "Server global storage quota reached" }; |
77 authzmalformed = { | |
78 code = 401; | |
79 type = "auth"; | |
80 condition = "not-authorized"; | |
81 text = "Missing or malformed Authorization header"; | |
82 }; | |
83 unauthz = { code = 403; type = "auth"; condition = "forbidden"; text = "Unauthorized or invalid token" }; | |
84 invalidslot = { | |
85 code = 400; | |
86 type = "modify"; | |
87 condition = "bad-request"; | |
88 text = "Invalid upload slot, must not contain '/'"; | |
89 }; | |
90 alreadycompleted = { code = 409; type = "cancel"; condition = "conflict"; text = "Upload already completed" }; | |
91 writefail = { code = 500; type = "wait"; condition = "internal-server-error" } | |
71 }); | 92 }); |
72 | 93 |
73 local upload_cache = cache.new(1024); | 94 local upload_cache = cache.new(1024); |
74 local quota_cache = cache.new(1024); | 95 local quota_cache = cache.new(1024); |
75 | 96 |
258 authz = authz:match("^Bearer (.*)") | 279 authz = authz:match("^Bearer (.*)") |
259 end | 280 end |
260 if not authz then | 281 if not authz then |
261 module:log("debug", "Missing or malformed Authorization header"); | 282 module:log("debug", "Missing or malformed Authorization header"); |
262 event.response.headers.www_authenticate = "Bearer"; | 283 event.response.headers.www_authenticate = "Bearer"; |
263 return 401; | 284 return upload_errors.new("authzmalformed", { request = request }); |
264 end | 285 end |
265 local authed, authed_upload_info = verify_jwt(authz); | 286 local authed, authed_upload_info = verify_jwt(authz); |
266 if not authed then | 287 if not authed then |
267 module:log("debug", "Unauthorized or invalid token: %s, %q", authz, authed_upload_info); | 288 module:log("debug", "Unauthorized or invalid token: %s, %q", authz, authed_upload_info); |
268 return 401; | 289 return upload_errors.new("unauthz", { request = request; wrapped_error = authed_upload_info }); |
269 end | 290 end |
270 if not path or authed_upload_info.slot ~= path:match("^[^/]+") then | 291 if not path or authed_upload_info.slot ~= path:match("^[^/]+") then |
271 module:log("debug", "Invalid upload slot: %q, path: %q", authed_upload_info.slot, path); | 292 module:log("debug", "Invalid upload slot: %q, path: %q", authed_upload_info.slot, path); |
272 return 400; | 293 return upload_errors.new("unauthz", { request = request }); |
273 end | 294 end |
274 if request.headers.content_length and tonumber(request.headers.content_length) ~= authed_upload_info.filesize then | 295 if request.headers.content_length and tonumber(request.headers.content_length) ~= authed_upload_info.filesize then |
275 return 413; | 296 return upload_errors.new("filesize", { request = request }); |
276 -- Note: We don't know the size if the upload is streamed in chunked encoding, | 297 -- Note: We don't know the size if the upload is streamed in chunked encoding, |
277 -- so we also check the final file size on completion. | 298 -- so we also check the final file size on completion. |
278 end | 299 end |
279 upload_info = authed_upload_info; | 300 upload_info = authed_upload_info; |
280 request.http_file_share_upload_info = upload_info; | 301 request.http_file_share_upload_info = upload_info; |
286 -- check if upload has been completed already | 307 -- check if upload has been completed already |
287 -- we want to allow retry of a failed upload attempt, but not after it's been completed | 308 -- we want to allow retry of a failed upload attempt, but not after it's been completed |
288 local f = io.open(filename, "r"); | 309 local f = io.open(filename, "r"); |
289 if f then | 310 if f then |
290 f:close(); | 311 f:close(); |
291 return 409; | 312 return upload_errors.new("alreadycompleted", { request = request }); |
292 end | 313 end |
293 end | 314 end |
294 | 315 |
295 if not request.body_sink then | 316 if not request.body_sink then |
296 module:log("debug", "Preparing to receive upload into %q, expecting %s", filename, B(upload_info.filesize)); | 317 module:log("debug", "Preparing to receive upload into %q, expecting %s", filename, B(upload_info.filesize)); |
297 local fh, err = io.open(filename.."~", "w"); | 318 local fh, err = io.open(filename.."~", "w"); |
298 if not fh then | 319 if not fh then |
299 module:log("error", "Could not open file for writing: %s", err); | 320 module:log("error", "Could not open file for writing: %s", err); |
300 return 500; | 321 return upload_errors.new("writefail", { request = request; wrapped_error = err }); |
301 end | 322 end |
302 function event.response:on_destroy() -- luacheck: ignore 212/self | 323 function event.response:on_destroy() -- luacheck: ignore 212/self |
303 -- Clean up incomplete upload | 324 -- Clean up incomplete upload |
304 if io.type(fh) == "file" then -- still open | 325 if io.type(fh) == "file" then -- still open |
305 fh:close(); | 326 fh:close(); |
328 if request.body_sink then | 349 if request.body_sink then |
329 local final_size = request.body_sink:seek(); | 350 local final_size = request.body_sink:seek(); |
330 local uploaded, err = errors.coerce(request.body_sink:close()); | 351 local uploaded, err = errors.coerce(request.body_sink:close()); |
331 if final_size ~= upload_info.filesize then | 352 if final_size ~= upload_info.filesize then |
332 -- Could be too short as well, but we say the same thing | 353 -- Could be too short as well, but we say the same thing |
333 uploaded, err = false, 413; | 354 uploaded, err = false, upload_errors.new("filesize", { request = request }); |
334 end | 355 end |
335 if uploaded then | 356 if uploaded then |
336 module:log("debug", "Upload of %q completed, %s", filename, B(final_size)); | 357 module:log("debug", "Upload of %q completed, %s", filename, B(final_size)); |
337 assert(os.rename(filename.."~", filename)); | 358 assert(os.rename(filename.."~", filename)); |
338 measure_uploads(final_size); | 359 measure_uploads(final_size); |