comparison mod_rest/mod_rest.lua @ 3795:f51308fcba83

mod_rest: Allow specifying a webhook/callback to handle incoming stanzas
author Kim Alvefur <zash@zash.se>
date Mon, 30 Dec 2019 04:07:25 +0100
parents 4b258329e6e4
children d1ad10b76b00
comparison
equal deleted inserted replaced
3794:4b258329e6e4 3795:f51308fcba83
3 -- Copyright (c) 2019 Kim Alvefur 3 -- Copyright (c) 2019 Kim Alvefur
4 -- 4 --
5 -- This file is MIT/X11 licensed. 5 -- This file is MIT/X11 licensed.
6 6
7 local errors = require "util.error"; 7 local errors = require "util.error";
8 local http = require "net.http";
8 local id = require "util.id"; 9 local id = require "util.id";
9 local jid = require "util.jid"; 10 local jid = require "util.jid";
11 local st = require "util.stanza";
10 local xml = require "util.xml"; 12 local xml = require "util.xml";
11 13
12 local allow_any_source = module:get_host_type() == "component"; 14 local allow_any_source = module:get_host_type() == "component";
13 local validate_from_addresses = module:get_option_boolean("validate_from_addresses", true); 15 local validate_from_addresses = module:get_option_boolean("validate_from_addresses", true);
14 16
76 module:provides("http", { 78 module:provides("http", {
77 route = { 79 route = {
78 POST = handle_post; 80 POST = handle_post;
79 }; 81 };
80 }); 82 });
83
84 -- Forward stanzas from XMPP to HTTP and return any reply
85 local rest_url = module:get_option_string("rest_callback_url", nil);
86 if rest_url then
87
88 local function handle_stanza(event)
89 local stanza, origin = event.stanza, event.origin;
90 local reply_needed = stanza.name == "iq";
91
92 http.request(rest_url, {
93 body = tostring(stanza),
94 headers = {
95 ["Content-Type"] = "application/xmpp+xml",
96 ["Content-Language"] = stanza.attr["xml:lang"],
97 Accept = "application/xmpp+xml, text/plain",
98 },
99 }, function (body, code, response)
100 if (code == 202 or code == 204) and not reply_needed then
101 -- Delivered, no reply
102 return;
103 end
104 local reply, reply_text;
105
106 if response.headers["content-type"] == "application/xmpp+xml" then
107 local parsed, err = xml.parse(body);
108 if not parsed then
109 module:log("warn", "REST callback responded with invalid XML: %s, %q", err, body);
110 elseif parsed.name ~= stanza.name then
111 module:log("warn", "REST callback responded with the wrong stanza type, got %s but expected %s", parsed.name, stanza.name);
112 else
113 parsed.attr.to, parsed.attr.from = stanza.attr.from, stanza.attr.to;
114 if parsed.name == "iq" then
115 parsed.attr.id = stanza.attr.id;
116 end
117 reply = parsed;
118 end
119 elseif response.headers["content-type"] == "text/plain" then
120 reply = st.reply(stanza);
121 if body ~= "" then
122 reply_text = body;
123 end
124 elseif body ~= "" then -- ignore empty body
125 module:log("debug", "Callback returned response of unhandled type %q", response.headers["content-type"]);
126 end
127
128 if not reply then
129 local code_hundreds = code - (code % 100);
130 if code_hundreds == 200 then
131 reply = st.reply(stanza);
132 if stanza.name ~= "iq" then
133 reply.attr.id = id.medium();
134 end
135 if reply_text and reply.name == "message" then
136 reply:body(reply_text, { ["xml:lang"] = response.headers["content-language"] });
137 end
138 -- TODO presence/status=body ?
139 elseif code_hundreds == 400 then
140 reply = st.error_reply(stanza, "modify", "bad-request", reply_text);
141 elseif code_hundreds == 500 then
142 reply = st.error_reply(stanza, "cancel", "internal-server-error", reply_text);
143 else
144 reply = st.error_reply(stanza, "cancel", "undefined-condition", reply_text);
145 end
146 end
147
148 origin.send(reply);
149 end);
150
151 return true;
152 end
153
154 if module:get_host_type() == "component" then
155 module:hook("iq/bare", handle_stanza, -1);
156 module:hook("message/bare", handle_stanza, -1);
157 module:hook("presence/bare", handle_stanza, -1);
158 module:hook("iq/full", handle_stanza, -1);
159 module:hook("message/full", handle_stanza, -1);
160 module:hook("presence/full", handle_stanza, -1);
161 module:hook("iq/host", handle_stanza, -1);
162 module:hook("message/host", handle_stanza, -1);
163 module:hook("presence/host", handle_stanza, -1);
164 else
165 -- Don't override everything on normal VirtualHosts
166 module:hook("iq/host", handle_stanza, -1);
167 module:hook("message/host", handle_stanza, -1);
168 module:hook("presence/host", handle_stanza, -1);
169 end
170 end