# HG changeset patch # User Kim Alvefur # Date 1577675074 -3600 # Node ID 4b258329e6e41f43e51512df9e3cb37600b92f6c # Parent 0d3926e49b55a17d752a32271342aaf39f1cc4a6 mod_rest: Initial commit of another RESTful API module diff -r 0d3926e49b55 -r 4b258329e6e4 .luacheckrc --- 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" }; diff -r 0d3926e49b55 -r 4b258329e6e4 mod_rest/README.markdown --- /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 ' + Hello! + ' +``` + +The `Content-Type` **MUST** be `application/xmpp+xml`. + +### Replies + +A POST containing an `` stanza automatically wait for the reply, +long-polling style. + +``` {.sh} +curl https://prosody.example:5281/rest \ + -H 'Content-Type: application/xmpp+xml' \ + --data-binary ' + + ' +``` + +# Compatibility + +Requires Prosody trunk / 0.12 diff -r 0d3926e49b55 -r 4b258329e6e4 mod_rest/mod_rest.lua --- /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; + }; + });