diff options
Diffstat (limited to 'plugins/mod_http_file_share.lua')
-rw-r--r-- | plugins/mod_http_file_share.lua | 108 |
1 files changed, 55 insertions, 53 deletions
diff --git a/plugins/mod_http_file_share.lua b/plugins/mod_http_file_share.lua index b6200628..cfc647d4 100644 --- a/plugins/mod_http_file_share.lua +++ b/plugins/mod_http_file_share.lua @@ -8,17 +8,16 @@ -- Again, from the top! local t_insert = table.insert; -local jid = require "util.jid"; -local st = require "util.stanza"; +local jid = require "prosody.util.jid"; +local st = require "prosody.util.stanza"; local url = require "socket.url"; -local dm = require "core.storagemanager".olddm; -local jwt = require "util.jwt"; -local errors = require "util.error"; -local dataform = require "util.dataforms".new; -local urlencode = require "util.http".urlencode; -local dt = require "util.datetime"; -local hi = require "util.human.units"; -local cache = require "util.cache"; +local dm = require "prosody.core.storagemanager".olddm; +local errors = require "prosody.util.error"; +local dataform = require "prosody.util.dataforms".new; +local urlencode = require "prosody.util.http".urlencode; +local dt = require "prosody.util.datetime"; +local hi = require "prosody.util.human.units"; +local cache = require "prosody.util.cache"; local lfs = require "lfs"; local unknown = math.abs(0/0); @@ -35,17 +34,21 @@ local uploads = module:open_store("uploads", "archive"); local persist_stats = module:open_store("upload_stats", "map"); -- id, <request>, time, owner -local secret = module:get_option_string(module.name.."_secret", require"util.id".long()); +local secret = module:get_option_string(module.name.."_secret", require"prosody.util.id".long()); local external_base_url = module:get_option_string(module.name .. "_base_url"); -local file_size_limit = module:get_option_number(module.name .. "_size_limit", 10 * 1024 * 1024); -- 10 MB +local file_size_limit = module:get_option_integer(module.name .. "_size_limit", 10 * 1024 * 1024, 0); -- 10 MB local file_types = module:get_option_set(module.name .. "_allowed_file_types", {}); local safe_types = module:get_option_set(module.name .. "_safe_file_types", {"image/*","video/*","audio/*","text/plain"}); -local expiry = module:get_option_number(module.name .. "_expires_after", 7 * 86400); -local daily_quota = module:get_option_number(module.name .. "_daily_quota", file_size_limit*10); -- 100 MB / day -local total_storage_limit = module:get_option_number(module.name.."_global_quota", unlimited); +local expiry = module:get_option_period(module.name .. "_expires_after", "1w"); +local daily_quota = module:get_option_integer(module.name .. "_daily_quota", file_size_limit*10, 0); -- 100 MB / day +local total_storage_limit = module:get_option_integer(module.name.."_global_quota", unlimited, 0); + +local create_jwt, verify_jwt = require"prosody.util.jwt".init("HS256", secret, secret, { default_ttl = 600 }); local access = module:get_option_set(module.name .. "_access", {}); +module:default_permission("prosody:registered", ":upload"); + if not external_base_url then module:depends("http"); end @@ -76,12 +79,12 @@ local measure_upload_cache_size = module:measure("upload_cache", "amount"); local measure_quota_cache_size = module:measure("quota_cache", "amount"); local measure_total_storage_usage = module:measure("total_storage", "amount", { unit = "bytes" }); -do +module:on_ready(function () local total, err = persist_stats:get(nil, "total"); if not err then total_storage_usage = tonumber(total) or 0; end -end +end) module:hook_global("stats-update", function () measure_upload_cache_size(upload_cache:count()); @@ -135,7 +138,7 @@ end function may_upload(uploader, filename, filesize, filetype) -- > boolean, error local uploader_host = jid.host(uploader); - if not ((access:empty() and prosody.hosts[uploader_host]) or access:contains(uploader) or access:contains(uploader_host)) then + if not (module:may(":upload", uploader) or access:contains(uploader) or access:contains(uploader_host)) then return false, upload_errors.new("access"); end @@ -169,16 +172,13 @@ function may_upload(uploader, filename, filesize, filetype) -- > boolean, error end function get_authz(slot, uploader, filename, filesize, filetype) -local now = os.time(); - return jwt.sign(secret, { + return create_jwt({ -- token properties sub = uploader; - iat = now; - exp = now+300; -- slot properties slot = slot; - expires = expiry >= 0 and (now+expiry) or nil; + expires = expiry < math.huge and (os.time()+expiry) or nil; -- file properties filename = filename; filesize = filesize; @@ -249,32 +249,34 @@ end function handle_upload(event, path) -- PUT /upload/:slot local request = event.request; - local authz = request.headers.authorization; - if authz then - authz = authz:match("^Bearer (.*)") - end - if not authz then - module:log("debug", "Missing or malformed Authorization header"); - event.response.headers.www_authenticate = "Bearer"; - return 401; - end - local authed, upload_info = jwt.verify(secret, authz); - if not (authed and type(upload_info) == "table" and type(upload_info.exp) == "number") then - module:log("debug", "Unauthorized or invalid token: %s, %q", authed, upload_info); - return 401; - end - if not request.body_sink and upload_info.exp < os.time() then - module:log("debug", "Authorization token expired on %s", dt.datetime(upload_info.exp)); - return 410; - end - if not path or upload_info.slot ~= path:match("^[^/]+") then - module:log("debug", "Invalid upload slot: %q, path: %q", upload_info.slot, path); - return 400; - end - if request.headers.content_length and tonumber(request.headers.content_length) ~= upload_info.filesize then - return 413; - -- Note: We don't know the size if the upload is streamed in chunked encoding, - -- so we also check the final file size on completion. + local upload_info = request.http_file_share_upload_info; + + if not upload_info then -- Initial handling of request + local authz = request.headers.authorization; + if authz then + authz = authz:match("^Bearer (.*)") + end + if not authz then + module:log("debug", "Missing or malformed Authorization header"); + event.response.headers.www_authenticate = "Bearer"; + return 401; + end + local authed, authed_upload_info = verify_jwt(authz); + if not authed then + module:log("debug", "Unauthorized or invalid token: %s, %q", authz, authed_upload_info); + return 401; + end + if not path or authed_upload_info.slot ~= path:match("^[^/]+") then + module:log("debug", "Invalid upload slot: %q, path: %q", authed_upload_info.slot, path); + return 400; + end + if request.headers.content_length and tonumber(request.headers.content_length) ~= authed_upload_info.filesize then + return 413; + -- Note: We don't know the size if the upload is streamed in chunked encoding, + -- so we also check the final file size on completion. + end + upload_info = authed_upload_info; + request.http_file_share_upload_info = upload_info; end local filename = get_filename(upload_info.slot, true); @@ -450,11 +452,11 @@ function handle_download(event, path) -- GET /uploads/:slot+filename return response:send_file(handle); end -if expiry >= 0 and not external_base_url then +if expiry < math.huge and not external_base_url then -- TODO HTTP DELETE to the external endpoint? - local array = require "util.array"; - local async = require "util.async"; - local ENOENT = require "util.pposix".ENOENT; + local array = require "prosody.util.array"; + local async = require "prosody.util.async"; + local ENOENT = require "prosody.util.pposix".ENOENT; local function sleep(t) local wait, done = async.waiter(); |