comparison mod_http_upload_external/mod_http_upload_external.lua @ 2877:d6badf56ab5f

mod_http_upload_external: add support for XEP-0363 version 0.3
author Jonas Wielicki <jonas@wielicki.name>
date Sun, 11 Feb 2018 16:11:17 +0100
parents c2cf5b40b66d
children 280305c043b0
comparison
equal deleted inserted replaced
2876:ea6b5321db50 2877: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);