Software /
code /
prosody-modules
Diff
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 |
parent | 3794:4b258329e6e4 |
child | 3796:d1ad10b76b00 |
line wrap: on
line diff
--- a/mod_rest/mod_rest.lua Mon Dec 30 04:04:34 2019 +0100 +++ b/mod_rest/mod_rest.lua Mon Dec 30 04:07:25 2019 +0100 @@ -5,8 +5,10 @@ -- This file is MIT/X11 licensed. local errors = require "util.error"; +local http = require "net.http"; local id = require "util.id"; local jid = require "util.jid"; +local st = require "util.stanza"; local xml = require "util.xml"; local allow_any_source = module:get_host_type() == "component"; @@ -78,3 +80,91 @@ POST = handle_post; }; }); + +-- Forward stanzas from XMPP to HTTP and return any reply +local rest_url = module:get_option_string("rest_callback_url", nil); +if rest_url then + + local function handle_stanza(event) + local stanza, origin = event.stanza, event.origin; + local reply_needed = stanza.name == "iq"; + + http.request(rest_url, { + body = tostring(stanza), + headers = { + ["Content-Type"] = "application/xmpp+xml", + ["Content-Language"] = stanza.attr["xml:lang"], + Accept = "application/xmpp+xml, text/plain", + }, + }, function (body, code, response) + if (code == 202 or code == 204) and not reply_needed then + -- Delivered, no reply + return; + end + local reply, reply_text; + + if response.headers["content-type"] == "application/xmpp+xml" then + local parsed, err = xml.parse(body); + if not parsed then + module:log("warn", "REST callback responded with invalid XML: %s, %q", err, body); + elseif parsed.name ~= stanza.name then + module:log("warn", "REST callback responded with the wrong stanza type, got %s but expected %s", parsed.name, stanza.name); + else + parsed.attr.to, parsed.attr.from = stanza.attr.from, stanza.attr.to; + if parsed.name == "iq" then + parsed.attr.id = stanza.attr.id; + end + reply = parsed; + end + elseif response.headers["content-type"] == "text/plain" then + reply = st.reply(stanza); + if body ~= "" then + reply_text = body; + end + elseif body ~= "" then -- ignore empty body + module:log("debug", "Callback returned response of unhandled type %q", response.headers["content-type"]); + end + + if not reply then + local code_hundreds = code - (code % 100); + if code_hundreds == 200 then + reply = st.reply(stanza); + if stanza.name ~= "iq" then + reply.attr.id = id.medium(); + end + if reply_text and reply.name == "message" then + reply:body(reply_text, { ["xml:lang"] = response.headers["content-language"] }); + end + -- TODO presence/status=body ? + elseif code_hundreds == 400 then + reply = st.error_reply(stanza, "modify", "bad-request", reply_text); + elseif code_hundreds == 500 then + reply = st.error_reply(stanza, "cancel", "internal-server-error", reply_text); + else + reply = st.error_reply(stanza, "cancel", "undefined-condition", reply_text); + end + end + + origin.send(reply); + end); + + return true; + end + + if module:get_host_type() == "component" then + module:hook("iq/bare", handle_stanza, -1); + module:hook("message/bare", handle_stanza, -1); + module:hook("presence/bare", handle_stanza, -1); + module:hook("iq/full", handle_stanza, -1); + module:hook("message/full", handle_stanza, -1); + module:hook("presence/full", handle_stanza, -1); + module:hook("iq/host", handle_stanza, -1); + module:hook("message/host", handle_stanza, -1); + module:hook("presence/host", handle_stanza, -1); + else + -- Don't override everything on normal VirtualHosts + module:hook("iq/host", handle_stanza, -1); + module:hook("message/host", handle_stanza, -1); + module:hook("presence/host", handle_stanza, -1); + end +end