--- /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);