--- a/.luacheckrc Wed Jan 01 10:11:08 2020 +0100
+++ b/.luacheckrc Mon Dec 30 04:04:34 2019 +0100
@@ -1,6 +1,6 @@
cache = true
allow_defined_top = true
-unused_secondaries = false
+--unused_secondaries = false
max_line_length = 150
codes = true
ignore = { "411/err", "421/err", "411/ok", "421/ok", "211/_ENV" };
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_rest/README.markdown Mon Dec 30 04:04:34 2019 +0100
@@ -0,0 +1,54 @@
+---
+labels:
+- 'Stage-Alpha'
+summary: RESTful XMPP API
+---
+
+# Introduction
+
+This is yet another RESTful API for sending stanzas via Prosody.
+
+# Usage
+
+Note that there is currently **no authentication**, so be careful with
+exposing the API endpoint to the Internet.
+
+## Enabling
+
+``` {.lua}
+Component "rest.example.net" "rest"
+```
+
+## Sending stanzas
+
+The API endpoint becomes available at the path `/rest`, so the full URL
+will be something like `https://your-prosody.example:5281/rest`.
+
+To try it, simply `curl` an XML stanza payload:
+
+``` {.sh}
+curl https://prosody.example:5281/rest \
+ -H 'Content-Type: application/xmpp+xml' \
+ --data-binary '<message type="chat" to="user@example.org">
+ <body>Hello!</body>
+ </body>'
+```
+
+The `Content-Type` **MUST** be `application/xmpp+xml`.
+
+### Replies
+
+A POST containing an `<iq>` stanza automatically wait for the reply,
+long-polling style.
+
+``` {.sh}
+curl https://prosody.example:5281/rest \
+ -H 'Content-Type: application/xmpp+xml' \
+ --data-binary '<iq type="get" to="example.net">
+ <ping xmlns="urn:xmpp:ping"/>
+ </iq>'
+```
+
+# Compatibility
+
+Requires Prosody trunk / 0.12
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_rest/mod_rest.lua Mon Dec 30 04:04:34 2019 +0100
@@ -0,0 +1,80 @@
+-- RESTful API
+--
+-- Copyright (c) 2019 Kim Alvefur
+--
+-- This file is MIT/X11 licensed.
+
+local errors = require "util.error";
+local id = require "util.id";
+local jid = require "util.jid";
+local xml = require "util.xml";
+
+local allow_any_source = module:get_host_type() == "component";
+local validate_from_addresses = module:get_option_boolean("validate_from_addresses", true);
+
+local function handle_post(event)
+ local request, response = event.request, event.response;
+ if request.headers.content_type ~= "application/xmpp+xml" then
+ return errors.new({ code = 415, text = "'application/xmpp+xml' expected" });
+ end
+ local payload, err = xml.parse(request.body);
+ if not payload then
+ -- parse fail
+ return errors.new({ code = 400, text = err });
+ end
+ local to = jid.prep(payload.attr.to);
+ if not to then
+ return errors.new({ code = 400, text = "Invalid destination JID" });
+ end
+ local from = module.host;
+ if allow_any_source and payload.attr.from then
+ from = jid.prep(payload.attr.from);
+ if not from then
+ return errors.new({ code = 400, text = "Invalid source JID" });
+ end
+ if validate_from_addresses and not jid.compare(from, module.host) then
+ return errors.new({ code = 403, text = "Source JID must belong to current host" });
+ end
+ end
+ payload.attr = {
+ from = from,
+ to = to,
+ id = payload.attr.id or id.medium(),
+ type = payload.attr.type,
+ ["xml:lang"] = payload.attr["xml:lang"],
+ };
+ if payload.name == "iq" then
+ if payload.attr.type ~= "get" and payload.attr.type ~= "set" then
+ return errors.new({ code = 400, text = "'iq' stanza must be of type 'get' or 'set'" });
+ end
+ return module:send_iq(payload):next(
+ function (result)
+ response.headers.content_type = "application/xmpp+xml";
+ return tostring(result.stanza);
+ end,
+ function (error)
+ if error.context.stanza then
+ response.headers.content_type = "application/xmpp+xml";
+ return tostring(error.context.stanza);
+ else
+ return error;
+ end
+ end);
+ elseif payload.name == "message" or payload.name == "presence" then
+ if module:send(payload) then
+ return 202;
+ else
+ return 500;
+ end
+ else
+ return errors.new({ code = 400, text = "Invalid stanza, must be 'message', 'presence' or 'iq'." });
+ end
+end
+
+-- Handle stanzas submitted via HTTP
+module:depends("http");
+module:provides("http", {
+ route = {
+ POST = handle_post;
+ };
+ });