aboutsummaryrefslogtreecommitdiffstats
path: root/prosody-module/mod_http_upload_external/mod_http_upload_external.lua
blob: 768a95698d9f8c57d8b5c307103f33f4b28e938c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
-- mod_http_upload_external
--
-- Copyright (C) 2016 Sebastian Luksch
--
-- This file is MIT/X11 licensed.
-- 
-- Implementation of HTTP Upload file transfer mechanism used by Conversations
-- 
-- Query external HTTP server to retrieve URLs
--

-- configuration
local external_url = module:get_option("http_upload_external_url");
local xmpp_server_key = module:get_option("http_upload_external_server_key");

-- imports
local st = require"util.stanza";
local http = require"socket.http";
local json = require"util.json";

-- depends
module:depends("disco");

-- namespace
local xmlns_http_upload = "urn:xmpp:http:upload";

module:add_feature(xmlns_http_upload);

-- hooks
module:hook("iq/host/"..xmlns_http_upload..":request", function (event)
   local stanza, origin = event.stanza, event.origin;
   local orig_from = stanza.attr.from;
   local request = stanza.tags[1];
   -- local clients only
   if origin.type ~= "c2s" then
      origin.send(st.error_reply(stanza, "cancel", "not-authorized"));
      return true;
   end
   -- check configuration
   if not external_url or not xmpp_server_key then
      module:log("debug", "missing configuration options: http_upload_external_url and/or http_upload_external_server_key");
      origin.send(st.error_reply(stanza, "cancel", "internal-server-error"));
      return true;
   end
   -- validate
   local filename = request:get_child_text("filename");
   if not filename then
      origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid filename"));
      return true;
   end
   local filesize = tonumber(request:get_child_text("size"));
   if not filesize then
      origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing or invalid file size"));
      return true;
   end

   local content_type = request:get_child_text("content-type");
   
   -- build the body
   local reqbody = "xmpp_server_key=" .. xmpp_server_key .. "&size=" .. filesize .. "&filename=" .. filename .. "&user_jid=" .. orig_from;
   if content_type then
      reqbody = reqbody .. "&content_type=" .. content_type;
   end

   -- the request
   local respbody, statuscode = http.request(external_url, reqbody);
   respbody = string.gsub(respbody, "\\/", "/")

   local get_url = nil;
   local put_url = nil;

   -- check the response
   if statuscode == 500 then
      origin.send(st.error_reply(stanza, "cancel", "service-unavailable", respbody));
   elseif statuscode == 406 or statuscode == 400 or statuscode == 403 then
      local errobj, pos, err = json.decode(respbody);
      if err then
         origin.send(st.error_reply(stanza, "wait", "internal-server-error", err));
         return true;
      else
         if errobj["err_code"] ~= nil and errobj["msg"] ~= nil then
            if errobj.err_code == 1 then
               origin.send(st.error_reply(stanza, "modify", "not-acceptable", errobj.msg));
               return true;
            elseif errobj.err_code == 2 then
               origin.send(st.error_reply(stanza, "modify", "not-acceptable", errobj.msg,
                  st.stanza("file-too-large", {xmlns=xmlns_http_upload})
                     :tag("max-size"):text(errobj.parameters.max_file_size)));
               return true;
            elseif errobj.err_code == 3 then
               origin.send(st.error_reply(stanza, "modify", "not-acceptable", errobj.msg,
                  st.stanza("invalid-character", {xmlns=xmlns_http_upload})
                     :text(errobj.parameters.invalid_character)));
               return true;
            elseif errobj.err_code == 4 then
               origin.send(st.error_reply(stanza, "cancel", "internal-server-error", errobj.msg,
                  st.stanza("missing-parameter", {xmlns=xmlns_http_upload})
                     :text(errobj.parameters.missing_parameter)));
               return true;
            else
               origin.send(st.error_reply(stanza, "cancel", "undefined-condition", "unknown err_code"));
               return true;
            end
         elseif statuscode == 403 and errobj["msg"] ~= nil then
            origin.send(st.error_reply(stanza, "cancel", "internal-server-error", errobj.msg));
         else
            origin.send(st.error_reply(stanza, "cancel", "undefined-condition", "msg or err_code not found"));
            return true;
         end
      end
   elseif statuscode == 200 then
      local respobj, pos, err = json.decode(respbody);
      if err then
         origin.send(st.error_reply(stanza, "wait", "internal-server-error", err));
         return true;
      else
         if respobj["get"] ~= nil and respobj["put"] ~= nil then
            get_url = respobj.get;
            put_url = respobj.put;
         else
            origin.send(st.error_reply(stanza, "cancel", "undefined-condition", "get or put not found"));
            return true;
         end
      end
   else
      origin.send(st.error_reply(stanza, "cancel", "undefined-condition", "status code: " .. statuscode .. " response: " ..respbody));
      return true;
   end

   local reply = st.reply(stanza);
   reply:tag("slot", { xmlns = xmlns_http_upload });
   reply:tag("get"):text(get_url):up();
   reply:tag("put"):text(put_url):up();
   origin.send(reply);
   return true;
end);