changeset 2899:0273d7583373

mod_cache_c2s_caps: New module caching capabilities from local clients
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Wed, 07 Mar 2018 17:11:12 +0100
parents 9fd61234b6f0
children 0286ccacec7c
files mod_cache_c2s_caps/README.markdown mod_cache_c2s_caps/mod_cache_c2s_caps.lua
diffstat 2 files changed, 108 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_cache_c2s_caps/README.markdown	Wed Mar 07 17:11:12 2018 +0100
@@ -0,0 +1,12 @@
+---
+summary: Cache caps on user sessions
+---
+
+Description
+===========
+
+This module listens on presences containing caps (XEP-0115) and asks the client
+for the corresponding disco#info if it changed.
+
+It fires the c2s-capabilities-changed event once the disco#info result is
+received.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_cache_c2s_caps/mod_cache_c2s_caps.lua	Wed Mar 07 17:11:12 2018 +0100
@@ -0,0 +1,96 @@
+local st_iq = require "util.stanza".iq;
+local jid_split = require "util.jid".split;
+local uuid_gen = require "util.uuid".generate;
+local calculate_hash = require "util.caps".calculate_hash;
+
+-- Map of jid..node, to avoid querying the same client multiple times for the same value.
+local in_flight_iqs = {}
+
+-- Some clients (*ahem* poezio…) don’t include the @node in their result iq.
+local iq_node_map = {}
+
+local function iq_result_handler(event)
+	local origin, stanza = event.origin, event.stanza;
+	local from = stanza.attr.from;
+	local id = stanza.attr.id;
+
+	local query = stanza:get_child("query", "http://jabber.org/protocol/disco#info");
+
+	local node_string = query.attr.node;
+	local node_query = iq_node_map[from..id];
+	if node_string == nil then
+		node_string = node_query;
+		query.attr.node = node_query;
+	end
+	iq_node_map[from..id] = nil;
+	in_flight_iqs[from..node_string] = nil;
+
+	if node_string ~= node_query then
+		module:log("debug", "Wrong node for our disco#info query, expected %s, received %s", node_string, node_query);
+		return;
+	end
+
+	local node, ver = node_query:match("([^#]+)#([^#]+)");
+	local hash = calculate_hash(query)
+	if ver ~= hash then
+		module:log("debug", "Wrong hash for disco#info: %s ~= %s", ver, hash);
+	end
+
+	origin.caps_cache = query;
+	module:log("info", "Stored caps %s", ver);
+	module:fire_event("c2s-capabilities-changed", { origin = origin });
+	return true;
+end
+
+local function iq_error_handler(event)
+	local origin = event.origin;
+	origin.caps_cache = nil;
+	module:fire_event("c2s-capabilities-changed", { origin = origin });
+end
+
+local function presence_stanza_handler(event)
+	local origin, stanza = event.origin, event.stanza;
+
+	local from = stanza.attr.from;
+	if stanza.attr.to ~= nil then
+		return;
+	end
+
+	local caps = stanza:get_child("c", "http://jabber.org/protocol/caps");
+	if caps == nil then
+		module:log("debug", "Presence without caps received, skipping");
+		return;
+	end
+
+	local hash = caps.attr.hash;
+	local node = caps.attr.node;
+	local ver = caps.attr.ver;
+	if not hash or not node or not ver then
+		return;
+	end
+	if hash ~= "sha-1" then
+		module:log("warn", "Non-SHA-1 caps received: %s", hash);
+		return;
+	end
+
+	local node_query = node.."#"..ver;
+	if (origin.caps_cache and origin.caps_cache.attr.node == node_query) or in_flight_iqs[from..node_query] ~= nil then
+		module:log("debug", "Already requested these caps, skipping");
+		return;
+	end
+
+	module:log("debug", "Received presence with SHA-1 caps %s, querying disco#info", node_query);
+
+	local id = uuid_gen();
+	iq_node_map[from..id] = node_query
+	local iq = st_iq({ type = "get", from = module.host, to = from, id = id })
+		:tag("query", { xmlns = "http://jabber.org/protocol/disco#info", node = node_query });
+	module:hook("iq-result/host/"..id, iq_result_handler);
+	module:hook("iq-error/host/"..id, iq_error_handler);
+	module:send(iq);
+
+	in_flight_iqs[from..node_query] = true;
+end
+
+-- Handle only non-directed presences for now.
+module:hook("pre-presence/bare", presence_stanza_handler);