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);