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