mod_http_upload_external/mod_http_upload_external.lua
changeset 2881 d6badf56ab5f
parent 2336 c2cf5b40b66d
child 2943 280305c043b0
equal deleted inserted replaced
2880:ea6b5321db50 2881:d6badf56ab5f
    19 
    19 
    20 -- depends
    20 -- depends
    21 module:depends("disco");
    21 module:depends("disco");
    22 
    22 
    23 -- namespace
    23 -- namespace
    24 local xmlns_http_upload = "urn:xmpp:http:upload";
    24 local legacy_namespace = "urn:xmpp:http:upload";
       
    25 local namespace = "urn:xmpp:http:upload:0";
    25 
    26 
    26 -- identity and feature advertising
    27 -- identity and feature advertising
    27 module:add_identity("store", "file", module:get_option_string("name", "HTTP File Upload"))
    28 module:add_identity("store", "file", module:get_option_string("name", "HTTP File Upload"))
    28 module:add_feature(xmlns_http_upload);
    29 module:add_feature(namespace);
       
    30 module:add_feature(legacy_namespace);
    29 
    31 
    30 module:add_extension(dataform {
    32 module:add_extension(dataform {
    31 	{ name = "FORM_TYPE", type = "hidden", value = xmlns_http_upload },
    33 	{ name = "FORM_TYPE", type = "hidden", value = namespace },
       
    34 	{ name = "max-file-size", type = "text-single" },
       
    35 }:form({ ["max-file-size"] = tostring(file_size_limit) }, "result"));
       
    36 
       
    37 module:add_extension(dataform {
       
    38 	{ name = "FORM_TYPE", type = "hidden", value = legacy_namespace },
    32 	{ name = "max-file-size", type = "text-single" },
    39 	{ name = "max-file-size", type = "text-single" },
    33 }:form({ ["max-file-size"] = tostring(file_size_limit) }, "result"));
    40 }:form({ ["max-file-size"] = tostring(file_size_limit) }, "result"));
    34 
    41 
    35 local function magic_crypto_dust(random, filename, filesize)
    42 local function magic_crypto_dust(random, filename, filesize)
    36 	local message = string.format("%s/%s %d", random, filename, filesize);
    43 	local message = string.format("%s/%s %d", random, filename, filesize);
    37 	local digest = HMAC(secret, message, true);
    44 	local digest = HMAC(secret, message, true);
    38 	random, filename = http.urlencode(random), http.urlencode(filename);
    45 	random, filename = http.urlencode(random), http.urlencode(filename);
    39 	return base_url .. random .. "/" .. filename, "?v=" .. digest;
    46 	return base_url .. random .. "/" .. filename, "?v=" .. digest;
    40 end
    47 end
    41 
    48 
    42 -- hooks
    49 local function handle_request(origin, stanza, xmlns, filename, filesize)
    43 module:hook("iq/host/"..xmlns_http_upload..":request", function (event)
       
    44 	local stanza, origin = event.stanza, event.origin;
       
    45 	local request = stanza.tags[1];
       
    46 	-- local clients only
    50 	-- local clients only
    47 	if origin.type ~= "c2s" then
    51 	if origin.type ~= "c2s" then
    48 		module:log("debug", "Request for upload slot from a %s", origin.type);
    52 		module:log("debug", "Request for upload slot from a %s", origin.type);
    49 		origin.send(st.error_reply(stanza, "cancel", "not-authorized"));
    53 		origin.send(st.error_reply(stanza, "cancel", "not-authorized"));
    50 		return true;
    54 		return nil, nil;
    51 	end
    55 	end
    52 	-- validate
    56 	-- validate
    53 	local filename = request:get_child_text("filename");
       
    54 	if not filename or filename:find("/") then
    57 	if not filename or filename:find("/") then
    55 		module:log("debug", "Filename %q not allowed", filename or "");
    58 		module:log("debug", "Filename %q not allowed", filename or "");
    56 		origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid filename"));
    59 		origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid filename"));
    57 		return true;
    60 		return nil, nil;
    58 	end
    61 	end
    59 	local filesize = tonumber(request:get_child_text("size"));
       
    60 	if not filesize then
    62 	if not filesize then
    61 		module:log("debug", "Missing file size");
    63 		module:log("debug", "Missing file size");
    62 		origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing or invalid file size"));
    64 		origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing or invalid file size"));
    63 		return true;
    65 		return nil, nil;
    64 	elseif filesize > file_size_limit then
    66 	elseif filesize > file_size_limit then
    65 		module:log("debug", "File too large (%d > %d)", filesize, file_size_limit);
    67 		module:log("debug", "File too large (%d > %d)", filesize, file_size_limit);
    66 		origin.send(st.error_reply(stanza, "modify", "not-acceptable", "File too large",
    68 		origin.send(st.error_reply(stanza, "modify", "not-acceptable", "File too large",
    67 			st.stanza("file-too-large", {xmlns=xmlns_http_upload})
    69 			st.stanza("file-too-large", {xmlns=xmlns})
    68 				:tag("max-size"):text(tostring(file_size_limit))));
    70 				:tag("max-size"):text(tostring(file_size_limit))));
       
    71 		return nil, nil;
       
    72 	end
       
    73 	local random = uuid();
       
    74 	local get_url, verify = magic_crypto_dust(random, filename, filesize);
       
    75 	local put_url = get_url .. verify;
       
    76 
       
    77 	module:log("info", "Handing out upload slot %s to %s@%s", get_url, origin.username, origin.host);
       
    78 
       
    79 	return get_url, put_url;
       
    80 end
       
    81 
       
    82 -- hooks
       
    83 module:hook("iq/host/"..legacy_namespace..":request", function (event)
       
    84 	local stanza, origin = event.stanza, event.origin;
       
    85 	local request = stanza.tags[1];
       
    86 	local filename = request:get_child_text("filename");
       
    87 	local filesize = tonumber(request:get_child_text("size"));
       
    88 
       
    89 	local get_url, put_url = handle_request(
       
    90 		origin, stanza, legacy_namespace, filename, filesize);
       
    91 
       
    92 	if not get_url then
       
    93 		-- error was already sent
    69 		return true;
    94 		return true;
    70 	end
    95 	end
    71 	local reply = st.reply(stanza);
    96 
    72 	reply:tag("slot", { xmlns = xmlns_http_upload });
    97 	local reply = st.reply(stanza)
    73 	local random = uuid();
    98 		:tag("slot", { xmlns = legacy_namespace })
    74 	local get_url, verify = magic_crypto_dust(random, filename, filesize);
    99 			:tag("get"):text(get_url):up()
    75 	reply:tag("get"):text(get_url):up();
   100 			:tag("put"):text(put_url):up()
    76 	reply:tag("put"):text(get_url .. verify):up();
   101 		:up();
    77 	module:log("info", "Handed out upload slot %s to %s@%s", get_url, origin.username, origin.host);
       
    78 	origin.send(reply);
   102 	origin.send(reply);
    79 	return true;
   103 	return true;
    80 end);
   104 end);
       
   105 
       
   106 module:hook("iq/host/"..namespace..":request", function (event)
       
   107 	local stanza, origin = event.stanza, event.origin;
       
   108 	local request = stanza.tags[1];
       
   109 	local filename = request.attr.filename;
       
   110 	local filesize = tonumber(request.attr.size);
       
   111 	local get_url, put_url = handle_request(
       
   112 		origin, stanza, legacy_namespace, filename, filesize);
       
   113 
       
   114 	if not get_url then
       
   115 		-- error was already sent
       
   116 		return true;
       
   117 	end
       
   118 
       
   119 	local reply = st.reply(stanza)
       
   120 		:tag("slot", { xmlns = namespace})
       
   121 			:tag("get", { url = get_url }):up()
       
   122 			:tag("put", { url = put_url }):up()
       
   123 		:up();
       
   124 	origin.send(reply);
       
   125 	return true;
       
   126 end);