# HG changeset patch # User Emmanuel Gil Peyrot # Date 1603645082 -3600 # Node ID 3943032533a7ba65634330a719f6e6e24e32a6ea # Parent 3eb595cf847ff7642b5445340a0b5631e531dede mod_http_prebind: New module diff -r 3eb595cf847f -r 3943032533a7 mod_http_prebind/README.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_http_prebind/README.markdown Sun Oct 25 17:58:02 2020 +0100 @@ -0,0 +1,14 @@ +--- +labels: +- 'Stage-Alpha' +summary: Implements BOSH pre-bind +... + +For why this can be useful, see +https://metajack.im/2009/12/14/fastest-xmpp-sessions-with-http-prebinding/ + +# To enable this module + +Add `"http_prebind"` to `modules_enabled` on an anonymous virtual host. + +This only works on anonymous ones for now. diff -r 3eb595cf847f -r 3943032533a7 mod_http_prebind/mod_http_prebind.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_http_prebind/mod_http_prebind.lua Sun Oct 25 17:58:02 2020 +0100 @@ -0,0 +1,141 @@ +module:depends("http"); + +local http = require "net.http"; +local format = require "util.format".format; +local json_encode = require "util.json".encode; +local promise = require "util.promise"; +local xml = require "util.xml"; +local t_insert = table.insert; + +local function new_options(host) + return { + headers = { + ["Content-Type"] = "text/xml; charset=utf-8", + ["Host"] = host, + }, + method = "POST", + }; +end + +local function connect_to_bosh(url, hostname) + local rid = math.random(100000, 100000000) + local options = new_options(hostname); + options.body = format([[]], rid, hostname); + local rid = rid + 1; + return promise.new(function (on_fulfilled, on_error) + assert(http.request(url, options, function (body, code) + if code ~= 200 then + on_error("Failed to fetch, HTTP error code "..code); + return; + end + local body = xml.parse(body); + local sid = body.attr.sid; + local mechanisms = {}; + for mechanism in body:get_child("features", "http://etherx.jabber.org/streams") + :get_child("mechanisms", "urn:ietf:params:xml:ns:xmpp-sasl") + :childtags("mechanism", "urn:ietf:params:xml:ns:xmpp-sasl") do + mechanisms[mechanism:get_text()] = true; + end + on_fulfilled({ url = url, sid = sid, rid = rid, mechanisms = mechanisms }); + end)); + end); +end + +local function authenticate(data) + local options = new_options(); + options.body = format([[ + + ]], data.sid, data.rid); + data.rid = data.rid + 1; + return promise.new(function (on_fulfilled, on_error) + if data.mechanisms["ANONYMOUS"] == nil then + on_error("No SASL ANONYMOUS mechanism supported on this host."); + return; + end + assert(http.request(data.url, options, function (body, code) + if code ~= 200 then + on_error("Failed to fetch, HTTP error code "..code); + return; + end + local body = xml.parse(body); + local success = body:get_child("success", "urn:ietf:params:xml:ns:xmpp-sasl"); + if success then + data.mechanisms = nil; + on_fulfilled(data); + else + on_error("Authentication failed."); + end + end)); + end); +end; + +local function restart_stream(data) + local options = new_options(); + options.body = format([[ + ]], data.sid, data.rid); + data.rid = data.rid + 1; + return promise.new(function (on_fulfilled, on_error) + assert(http.request(data.url, options, function (body, code) + if code ~= 200 then + on_error("Failed to fetch, HTTP error code "..code); + return; + end + local body = xml.parse(body); + on_fulfilled(data); + end)); + end); +end; + +local function bind(data) + local options = new_options(); + options.body = format([[ + + + + + ]], data.sid, data.rid); + data.rid = data.rid + 1; + return promise.new(function (on_fulfilled, on_error) + assert(http.request(data.url, options, function (body, code) + if code ~= 200 then + on_error("Failed to fetch, HTTP error code "..code); + return; + end + local body = xml.parse(body); + local jid = body:get_child("iq", "jabber:client") + :get_child("bind", "urn:ietf:params:xml:ns:xmpp-bind") + :get_child_text("jid", "urn:ietf:params:xml:ns:xmpp-bind"); + on_fulfilled(json_encode({rid = data.rid, sid = data.sid, jid = jid})); + end)); + end); +end; + +module:provides("http", { + route = { + ["GET"] = function (event) + return connect_to_bosh("http://[::1]:5280/http-bind", "anon.localhost") + :next(authenticate) + :next(restart_stream) + :next(bind); + end; + }; +});