changeset 4834:7ed2467c9bb5

mod_bookmarks2: Add simple support for legacy PEP queries This implements the publish and items PubSub queries of XEP-0048 version 1.1, ignoring item id, publish-options, and any other query.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Thu, 23 Dec 2021 17:44:35 +0100
parents 15cf32e666da
children 60b2dbe032c0
files mod_bookmarks2/README.markdown mod_bookmarks2/mod_bookmarks2.lua
diffstat 2 files changed, 122 insertions(+), 38 deletions(-) [+]
line wrap: on
line diff
--- a/mod_bookmarks2/README.markdown	Wed Dec 22 15:05:31 2021 +0000
+++ b/mod_bookmarks2/README.markdown	Thu Dec 23 17:44:35 2021 +0100
@@ -1,29 +1,17 @@
 ---
 labels:
-- 'Stage-Alpha'
-summary: Synchronise bookmarks between Private XML and PEP
+- 'Stage-Beta'
+summary: Synchronise bookmarks between Private XML, legacy PEP, and PEP
 ...
 
-::: {.alert .alert-warning}
-**WARNING:** This module is incompatible with clients which only use
-[deprecated PEP bookmarks
-(XEP-0048)](https://xmpp.org/extensions/xep-0048.html), such as
-[Converse.js](https://conversejs.org).
-
-If you need to be compatible with these clients, use
-[mod\_bookmarks](mod_bookmarks.html) instead.
-:::
-
-
 Introduction
 ------------
 
-This module fetches users’ bookmarks from Private XML and pushes them
-to PEP on login, and then redirects any Private XML query to PEP.  This
-allows interop between older clients that use [XEP-0048: Bookmarks
-version 1.0](https://xmpp.org/extensions/attic/xep-0048-1.0.html) and
-recent clients which use
-[XEP-0402](https://xmpp.org/extensions/xep-0402.html).
+This module fetches users’ bookmarks from Private XML (or legacy PEP) and
+pushes them to PEP on login, and then redirects any Private XML query (or
+legacy PEP) to PEP.  This allows interoperability between older clients that
+use [XEP-0048](https://xmpp.org/extensions/xep-0048.html) and recent clients
+which use [XEP-0402](https://xmpp.org/extensions/xep-0402.html).
 
 Configuration
 -------------
--- a/mod_bookmarks2/mod_bookmarks2.lua	Wed Dec 22 15:05:31 2021 +0000
+++ b/mod_bookmarks2/mod_bookmarks2.lua	Thu Dec 23 17:44:35 2021 +0100
@@ -30,6 +30,72 @@
 	event.reply:tag("feature", { var = namespace.."#compat" }):up();
 end);
 
+local function generate_legacy_storage(items)
+	local storage = st.stanza("storage", { xmlns = "storage:bookmarks" });
+	for _, item_id in ipairs(items) do
+		local item = items[item_id];
+		local conference = st.stanza("conference");
+		conference.attr.jid = item.attr.id;
+		local bookmark = item:get_child("conference", namespace);
+		conference.attr.name = bookmark.attr.name;
+		conference.attr.autojoin = bookmark.attr.autojoin;
+		local nick = bookmark:get_child_text("nick");
+		if nick ~= nil then
+			conference:text_tag("nick", nick, { xmlns = "storage:bookmarks" }):up();
+		end
+		local password = bookmark:get_child_text("password");
+		if password ~= nil then
+			conference:text_tag("password", password):up();
+		end
+		storage:add_child(conference);
+	end
+
+	return storage;
+end
+
+local function on_retrieve_legacy_pep(event)
+	local stanza, session = event.stanza, event.origin;
+	local pubsub = stanza:get_child("pubsub", "http://jabber.org/protocol/pubsub");
+	if pubsub == nil then
+		return;
+	end
+
+	local items = pubsub:get_child("items");
+	if items == nil then
+		return;
+	end
+
+	local node = items.attr.node;
+	if node ~= "storage:bookmarks" then
+		return;
+	end
+
+	local username = session.username;
+	local jid = username.."@"..session.host;
+	local service = mod_pep.get_pep_service(username);
+	local ok, ret = service:get_items(namespace, session.full_jid);
+	if not ok then
+		if ret == "item-not-found" then
+			module:log("debug", "Got no PEP bookmarks item for %s, returning empty legacy PEP bookmarks", jid);
+			session.send(st.reply(stanza):add_child(query));
+		else
+			module:log("error", "Failed to retrieve PEP bookmarks of %s: %s", jid, ret);
+			session.send(st.error_reply(stanza, "cancel", ret, "Failed to retrive bookmarks from PEP"));
+		end
+		return true;
+	end
+
+	local storage = generate_legacy_storage(ret);
+
+	module:log("debug", "Sending back legacy PEP for %s: %s", jid, storage);
+	session.send(st.reply(stanza)
+		:tag("pubsub", {xmlns = "http://jabber.org/protocol/pubsub"})
+			:tag("items", {node = "storage:bookmarks"})
+				:tag("item", {id = "current"})
+					:add_child(storage));
+	return true;
+end
+
 local function on_retrieve_private_xml(event)
 	local stanza, session = event.stanza, event.origin;
 	local query = stanza:get_child("query", "jabber:iq:private");
@@ -59,24 +125,7 @@
 		return true;
 	end
 
-	local storage = st.stanza("storage", { xmlns = "storage:bookmarks" });
-	for _, item_id in ipairs(ret) do
-		local item = ret[item_id];
-		local conference = st.stanza("conference");
-		conference.attr.jid = item.attr.id;
-		local bookmark = item:get_child("conference", namespace);
-		conference.attr.name = bookmark.attr.name;
-		conference.attr.autojoin = bookmark.attr.autojoin;
-		local nick = bookmark:get_child_text("nick");
-		if nick ~= nil then
-			conference:text_tag("nick", nick, { xmlns = "storage:bookmarks" }):up();
-		end
-		local password = bookmark:get_child_text("password");
-		if password ~= nil then
-			conference:text_tag("password", password):up();
-		end
-		storage:add_child(conference);
-	end
+	local storage = generate_legacy_storage(ret);
 
 	module:log("debug", "Sending back private for %s: %s", jid, storage);
 	session.send(st.reply(stanza):query("jabber:iq:private"):add_child(storage));
@@ -190,6 +239,46 @@
 	return true;
 end
 
+-- Synchronise legacy PEP to PEP.
+local function on_publish_legacy_pep(event)
+	local stanza, session = event.stanza, event.origin;
+	local pubsub = stanza:get_child("pubsub", "http://jabber.org/protocol/pubsub");
+	if pubsub == nil then
+		return;
+	end
+
+	local publish = pubsub:get_child("publish");
+	if publish == nil or publish.attr.node ~= "storage:bookmarks" then
+		return;
+	end
+
+	local item = publish:get_child("item");
+	if item == nil then
+		return;
+	end
+
+	-- Here we ignore the item id, it’ll be generated as 'current' anyway.
+
+	local bookmarks = item:get_child("storage", "storage:bookmarks");
+	if bookmarks == nil then
+		return;
+	end
+
+	-- We also ignore the publish-options.
+
+	module:log("debug", "Legacy PEP bookmarks set by client, publishing to PEP.");
+
+	local ok, err = publish_to_pep(session.full_jid, bookmarks, true);
+	if not ok then
+		module:log("error", "Failed to publish to PEP bookmarks for %s@%s: %s", session.username, session.host, err);
+		session.send(st.error_reply(stanza, "cancel", "internal-server-error", "Failed to store bookmarks to PEP"));
+		return true;
+	end
+
+	session.send(st.reply(stanza));
+	return true;
+end
+
 -- Synchronise Private XML to PEP.
 local function on_publish_private_xml(event)
 	local stanza, session = event.stanza, event.origin;
@@ -203,7 +292,7 @@
 		return;
 	end
 
-	module:log("debug", "Private bookmarks set by client, publishing to pep.");
+	module:log("debug", "Private bookmarks set by client, publishing to PEP.");
 
 	local ok, err = publish_to_pep(session.full_jid, bookmarks, true);
 	if not ok then
@@ -300,6 +389,13 @@
 		return on_publish_private_xml(event);
 	end
 end, 1);
+module:hook("iq/bare/http://jabber.org/protocol/pubsub:pubsub", function (event)
+	if event.stanza.attr.type == "get" then
+		return on_retrieve_legacy_pep(event);
+	else
+		return on_publish_legacy_pep(event);
+	end
+end, 1);
 module:hook("resource-bind", migrate_legacy_bookmarks);
 module:handle_items("pep-service", function (event)
 	local service = event.item.service;