Mercurial > prosody-modules
diff mod_http_upload/mod_http_upload.lua @ 1772:45f7e3c2557f
mod_http_upload: Implementation of Conversations HTTP upload file transfer mode
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Sat, 18 Jul 2015 23:05:09 +0200 |
parents | |
children | 25c28644fae8 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_http_upload/mod_http_upload.lua Sat Jul 18 23:05:09 2015 +0200 @@ -0,0 +1,110 @@ +-- mod_http_upload +-- +-- Copyright (C) 2015 Kim Alvefur +-- +-- This file is MIT/X11 licensed. +-- +-- Implementation of HTTP Upload file transfer mechanism used by Conversations +-- + +-- imports +local st = require"util.stanza"; +local lfs = require"lfs"; +local join_path = require"util.paths".join; +local uuid = require"util.uuid".generate; + +-- depends +module:depends("http"); + +-- namespace +local xmlns_http_upload = "eu:siacs:conversations:http:upload"; + +module:add_feature(xmlns_http_upload); + +-- state +local pending_slots = module:shared("upload_slots"); + +local storage_path = join_path(prosody.paths.data, module.name); +lfs.mkdir(storage_path); + +-- hooks +module:hook("iq/host/"..xmlns_http_upload..":request", function (event) + local stanza, origin = event.stanza, event.origin; + -- local clients only + if origin.type ~= "c2s" then + origin.send(st.error_reply(stanza, "cancel", "not-authorized")); + return true; + end + -- validate + local filename = stanza.tags[1]:get_child_text("filename"); + if not filename or filename:find("/") then + origin.send(st.error_reply(stanza, "modify", "bad-request")); + return true; + end + local reply = st.reply(stanza); + reply:tag("slot", { xmlns = xmlns_http_upload }); + local random = uuid(); + pending_slots[random.."/"..filename] = origin.full_jid; + local url = module:http_url() .. "/" .. random .. "/" .. filename; + reply:tag("get"):text(url):up(); + reply:tag("put"):text(url):up(); + origin.send(reply); + return true; +end); + +-- http service +local function upload_data(event, path) + if not pending_slots[path] then + return 401; + end + local random, filename = path:match("^([^/]+)/([^/]+)$"); + if not random then + return 400; + end + local dirname = join_path(storage_path, random); + if not lfs.mkdir(dirname) then + module:log("error", "Could not create directory %s for upload", dirname); + return 500; + end + local full_filename = join_path(dirname, filename); + local fh, ferr = io.open(full_filename, "w"); + if not fh then + module:log("error", "Could not open file %s for upload: %s", full_filename, ferr); + return 500; + end + local ok, err = fh:write(event.request.body); + if not ok then + module:log("error", "Could not write to file %s for upload: %s", full_filename, err); + os.remove(full_filename); + return 500; + end + ok, err = fh:close(); + if not ok then + module:log("error", "Could not write to file %s for upload: %s", full_filename, err); + os.remove(full_filename); + return 500; + end + module:log("info", "File uploaded by %s to slot %s", pending_slots[path], random); + pending_slots[path] = nil; + return 200; +end + +local serve_uploaded_files = module:depends("http_files").serve(storage_path); + +local function size_only(request, data) + request.headers.content_size = #data; + return 200; +end + +local function serve_head(event, path) + event.send = size_only; + return serve_uploaded_files(event, path); +end + +module:provides("http", { + route = { + ["GET /*"] = serve_uploaded_files; + ["HEAD /*"] = serve_head; + ["PUT /*"] = upload_data; + }; +});