1 local pubsub
= require
"util.pubsub";
2 local jid_bare
= require
"util.jid".bare
;
3 local jid_split
= require
"util.jid".split
;
4 local jid_join
= require
"util.jid".join
;
5 local set_new
= require
"util.set".new
;
6 local st
= require
"util.stanza";
7 local calculate_hash
= require
"util.caps".calculate_hash
;
8 local is_contact_subscribed
= require
"core.rostermanager".is_contact_subscribed
;
9 local cache
= require
"util.cache";
10 local set
= require
"util.set";
11 local new_id
= require
"util.id".medium
;
12 local storagemanager
= require
"core.storagemanager";
14 local xmlns_pubsub
= "http://jabber.org/protocol/pubsub";
15 local xmlns_pubsub_event
= "http://jabber.org/protocol/pubsub#event";
16 local xmlns_pubsub_owner
= "http://jabber.org/protocol/pubsub#owner";
18 local lib_pubsub
= module
:require
"pubsub";
20 local empty_set
= set_new();
22 -- username -> util.pubsub service object
25 -- username -> recipient -> set of nodes
26 local recipients
= {};
28 -- caps hash -> set of nodes
31 local host
= module
.host
;
33 local node_config
= module
:open_store("pep", "map");
34 local known_nodes
= module
:open_store("pep");
36 local max_max_items
= module
:get_option_number("pep_max_items", 256);
38 function module
.save()
41 recipients
= recipients
;
45 function module
.restore(data
)
46 services
= data
.services
;
47 recipients
= data
.recipients
;
48 for username
, service
in pairs(services
) do
49 local user_bare
= jid_join(username
, host
);
50 module
:add_item("pep-service", { service
= service
, jid
= user_bare
});
54 function is_item_stanza(item
)
55 return st
.is_stanza(item
) and item
.attr
.xmlns
== xmlns_pubsub
and item
.name
== "item";
58 function check_node_config(node
, actor
, new_config
) -- luacheck: ignore 212/node 212/actor
59 if (new_config
["max_items"] or 1) > max_max_items
then
62 if new_config
["access_model"] ~= "presence"
63 and new_config
["access_model"] ~= "whitelist"
64 and new_config
["access_model"] ~= "open" then
70 local function subscription_presence(username
, recipient
)
71 local user_bare
= jid_join(username
, host
);
72 local recipient_bare
= jid_bare(recipient
);
73 if (recipient_bare
== user_bare
) then return true; end
74 return is_contact_subscribed(username
, host
, recipient_bare
);
77 local function nodestore(username
)
78 -- luacheck: ignore 212/self
80 function store
:get(node
)
81 local data
, err
= node_config
:get(username
, node
)
83 -- COMPAT Previously stored only a boolean representing 'persist_items'
93 function store
:set(node
, data
)
95 -- Save the data without subscriptions
96 local subscribers
= {};
97 for jid
, sub
in pairs(data
.subscribers
) do
98 if type(sub
) ~= "table" or not sub
.presence
then
99 subscribers
[jid
] = sub
;
104 config
= data
.config
;
105 affiliations
= data
.affiliations
;
106 subscribers
= subscribers
;
109 return node_config
:set(username
, node
, data
);
111 function store
:users()
112 return pairs(known_nodes
:get(username
) or {});
117 local function simple_itemstore(username
)
118 local driver
= storagemanager
.get_driver(module
.host
, "pep_data");
119 return function (config
, node
)
120 if config
["persist_items"] then
121 module
:log("debug", "Creating new persistent item store for user %s, node %q", username
, node
);
122 local archive
= driver
:open("pep_"..node
, "archive");
123 return lib_pubsub
.archive_itemstore(archive
, config
, username
, node
, false);
125 module
:log("debug", "Creating new ephemeral item store for user %s, node %q", username
, node
);
126 return cache
.new(tonumber(config
["max_items"]));
131 local function get_broadcaster(username
)
132 local user_bare
= jid_join(username
, host
);
133 local function simple_broadcast(kind
, node
, jids
, item
, _
, node_obj
)
135 if node_obj
.config
["notify_"..kind
] == false then
139 if kind
== "retract" then
140 kind
= "items"; -- XEP-0060 signals retraction in an <items> container
143 item
= st
.clone(item
);
144 item
.attr
.xmlns
= nil; -- Clear the pubsub namespace
145 if kind
== "items" then
146 if node_obj
and node_obj
.config
.include_payload
== false then
147 item
:maptags(function () return nil; end);
153 local message
= st
.message({ from
= user_bare
, type = "headline", id
= id
})
154 :tag("event", { xmlns
= xmlns_pubsub_event
})
155 :tag(kind
, { node
= node
});
158 message
:add_child(item
);
161 for jid
in pairs(jids
) do
162 module
:log("debug", "Sending notification to %s from %s for node %s", jid
, user_bare
, node
);
163 message
.attr
.to
= jid
;
164 module
:send(message
);
167 return simple_broadcast
;
170 local function on_node_creation(event
)
171 local service
= event
.service
;
172 local node
= event
.node
;
173 local username
= service
.config
.pep_username
;
175 local service_recipients
= recipients
[username
];
176 if not service_recipients
then return; end
178 for recipient
, nodes
in pairs(service_recipients
) do
179 if nodes
:contains(node
) then
180 service
:add_subscription(node
, recipient
, recipient
, { presence
= true });
185 function get_pep_service(username
)
186 local user_bare
= jid_join(username
, host
);
187 local service
= services
[username
];
191 module
:log("debug", "Creating pubsub service for user %q", username
);
192 service
= pubsub
.new({
193 pep_username
= username
;
196 ["persist_items"] = true;
197 ["access_model"] = "presence";
200 autocreate_on_publish
= true;
201 autocreate_on_subscribe
= false;
203 nodestore
= nodestore(username
);
204 itemstore
= simple_itemstore(username
);
205 broadcaster
= get_broadcaster(username
);
206 itemcheck
= is_item_stanza
;
207 get_affiliation
= function (jid
)
208 if jid_bare(jid
) == user_bare
then
214 presence
= function (jid
)
215 if subscription_presence(username
, jid
) then
222 normalize_jid
= jid_bare
;
224 check_node_config
= check_node_config
;
226 local nodes
, err
= known_nodes
:get(username
);
228 module
:log("debug", "Restoring nodes for user %s", username
);
229 for node
in pairs(nodes
) do
230 module
:log("debug", "Restoring node %q", node
);
231 service
:create(node
, true);
234 module
:log("error", "Could not restore nodes for %s: %s", username
, err
);
236 module
:log("debug", "No known nodes");
238 services
[username
] = service
;
239 module
:add_item("pep-service", { service
= service
, jid
= user_bare
});
243 module
:hook("item-added/pep-service", function (event
)
244 local service
= event
.item
.service
;
245 module
:hook_object_event(service
.events
, "node-created", on_node_creation
);
248 function handle_pubsub_iq(event
)
249 local origin
, stanza
= event
.origin
, event
.stanza
;
250 local service_name
= origin
.username
;
251 if stanza
.attr
.to
~= nil then
252 service_name
= jid_split(stanza
.attr
.to
);
254 local service
= get_pep_service(service_name
);
256 return lib_pubsub
.handle_pubsub_iq(event
, service
)
259 module
:hook("iq/bare/"..xmlns_pubsub
..":pubsub", handle_pubsub_iq
);
260 module
:hook("iq/bare/"..xmlns_pubsub_owner
..":pubsub", handle_pubsub_iq
);
263 local function get_caps_hash_from_presence(stanza
, current
)
264 local t
= stanza
.attr
.type;
266 local child
= stanza
:get_child("c", "http://jabber.org/protocol/caps");
268 local attr
= child
.attr
;
269 if attr
.hash
then -- new caps
270 if attr
.hash
== 'sha-1' and attr
.node
and attr
.ver
then
271 return attr
.ver
, attr
.node
.."#"..attr
.ver
;
274 if attr
.node
and attr
.ver
then
275 return attr
.node
.."#"..attr
.ver
.."#"..(attr
.ext
or ""), attr
.node
.."#"..attr
.ver
;
279 return; -- no or bad caps
280 elseif t
== "unavailable" or t
== "error" then
283 return current
; -- no caps, could mean caps optimization, so return current
286 local function resend_last_item(jid
, node
, service
)
287 local ok
, id
, item
= service
:get_last_item(node
, jid
);
288 if not (ok
and id
) then return; end
289 service
.config
.broadcaster("items", node
, { [jid
] = true }, item
);
292 local function update_subscriptions(recipient
, service_name
, nodes
)
293 nodes
= nodes
or empty_set
;
295 local service_recipients
= recipients
[service_name
];
296 if not service_recipients
then
297 service_recipients
= {};
298 recipients
[service_name
] = service_recipients
;
301 local current
= service_recipients
[recipient
];
306 if (current
== empty_set
or current
:empty()) and (nodes
== empty_set
or nodes
:empty()) then
310 local service
= get_pep_service(service_name
);
311 for node
in current
- nodes
do
312 service
:remove_subscription(node
, recipient
, recipient
);
315 for node
in nodes
- current
do
316 if service
:add_subscription(node
, recipient
, recipient
, { presence
= true }) then
317 resend_last_item(recipient
, node
, service
);
321 if nodes
== empty_set
or nodes
:empty() then
325 service_recipients
[recipient
] = nodes
;
328 module
:hook("presence/bare", function(event
)
329 -- inbound presence to bare JID received
330 local origin
, stanza
= event
.origin
, event
.stanza
;
331 local t
= stanza
.attr
.type;
332 local is_self
= not stanza
.attr
.to
;
333 local username
= jid_split(stanza
.attr
.to
);
334 local user_bare
= jid_bare(stanza
.attr
.to
);
336 username
= origin
.username
;
337 user_bare
= jid_join(username
, host
);
340 if not t
then -- available presence
341 if is_self
or subscription_presence(username
, stanza
.attr
.from
) then
342 local recipient
= stanza
.attr
.from
;
343 local current
= recipients
[username
] and recipients
[username
][recipient
];
344 local hash
, query_node
= get_caps_hash_from_presence(stanza
, current
);
345 if current
== hash
or (current
and current
== hash_map
[hash
]) then return; end
347 update_subscriptions(recipient
, username
);
349 recipients
[username
] = recipients
[username
] or {};
350 if hash_map
[hash
] then
351 update_subscriptions(recipient
, username
, hash_map
[hash
]);
353 -- COMPAT from ~= stanza.attr.to because OneTeam can't deal with missing from attribute
355 st
.stanza("iq", {from
=user_bare
, to
=stanza
.attr
.from
, id
="disco", type="get"})
356 :tag("query", {xmlns
= "http://jabber.org/protocol/disco#info", node
= query_node
})
361 elseif t
== "unavailable" then
362 update_subscriptions(stanza
.attr
.from
, username
);
363 elseif not is_self
and t
== "unsubscribe" then
364 local from
= jid_bare(stanza
.attr
.from
);
365 local subscriptions
= recipients
[username
];
366 if subscriptions
then
367 for subscriber
in pairs(subscriptions
) do
368 if jid_bare(subscriber
) == from
then
369 update_subscriptions(subscriber
, username
);
376 module
:hook("iq-result/bare/disco", function(event
)
377 local origin
, stanza
= event
.origin
, event
.stanza
;
378 local disco
= stanza
:get_child("query", "http://jabber.org/protocol/disco#info");
383 -- Process disco response
384 local is_self
= stanza
.attr
.to
== nil;
385 local user_bare
= jid_bare(stanza
.attr
.to
);
386 local username
= jid_split(stanza
.attr
.to
);
388 username
= origin
.username
;
389 user_bare
= jid_join(username
, host
);
391 local contact
= stanza
.attr
.from
;
392 local ver
= calculate_hash(disco
.tags
); -- calculate hash
393 local notify
= set_new();
394 for _
, feature
in pairs(disco
.tags
) do
395 if feature
.name
== "feature" and feature
.attr
.var
then
396 local nfeature
= feature
.attr
.var
:match("^(.*)%+notify$");
397 if nfeature
then notify
:add(nfeature
); end
400 hash_map
[ver
] = notify
; -- update hash map
402 -- Optimization: Fiddle with other local users
403 for jid
, item
in pairs(origin
.roster
) do -- for all interested contacts
405 local contact_node
, contact_host
= jid_split(jid
);
406 if contact_host
== host
and (item
.subscription
== "both" or item
.subscription
== "from") then
407 update_subscriptions(user_bare
, contact_node
, notify
);
412 update_subscriptions(contact
, username
, notify
);
415 module
:hook("account-disco-info-node", function(event
)
416 local stanza
, origin
= event
.stanza
, event
.origin
;
417 local service_name
= origin
.username
;
418 if stanza
.attr
.to
~= nil then
419 service_name
= jid_split(stanza
.attr
.to
);
421 local service
= get_pep_service(service_name
);
422 return lib_pubsub
.handle_disco_info_node(event
, service
);
425 module
:hook("account-disco-info", function(event
)
426 local origin
, reply
= event
.origin
, event
.reply
;
428 reply
:tag('identity', {category
='pubsub', type='pep'}):up();
430 local username
= jid_split(reply
.attr
.from
) or origin
.username
;
431 local service
= get_pep_service(username
);
433 local supported_features
= lib_pubsub
.get_feature_set(service
) + set
.new
{
434 -- Features not covered by the above
436 "filtered-notifications",
438 "presence-notifications",
439 "presence-subscribe",
442 for feature
in supported_features
do
443 reply
:tag('feature', {var
=xmlns_pubsub
.."#"..feature
}):up();
447 module
:hook("account-disco-items-node", function(event
)
448 local stanza
, origin
= event
.stanza
, event
.origin
;
449 local is_self
= stanza
.attr
.to
== nil;
450 local username
= jid_split(stanza
.attr
.to
);
452 username
= origin
.username
;
454 local service
= get_pep_service(username
);
455 return lib_pubsub
.handle_disco_items_node(event
, service
);
458 module
:hook("account-disco-items", function(event
)
459 local reply
, stanza
, origin
= event
.reply
, event
.stanza
, event
.origin
;
461 local is_self
= stanza
.attr
.to
== nil;
462 local user_bare
= jid_bare(stanza
.attr
.to
);
463 local username
= jid_split(stanza
.attr
.to
);
465 username
= origin
.username
;
466 user_bare
= jid_join(username
, host
);
468 local service
= get_pep_service(username
);
470 local ok
, ret
= service
:get_nodes(jid_bare(stanza
.attr
.from
));
471 if not ok
then return; end
473 for node
, node_obj
in pairs(ret
) do
474 reply
:tag("item", { jid
= user_bare
, node
= node
, name
= node_obj
.config
.title
}):up();