# 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);
+ 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;
+ };
+});