./ct_report/coverage/mod_pubsub.COVER.html

1 %%% ====================================================================
2 %%% ``The contents of this file are subject to the Erlang Public License,
3 %%% Version 1.1, (the "License"); you may not use this file except in
4 %%% compliance with the License. You should have received a copy of the
5 %%% Erlang Public License along with this software. If not, it can be
6 %%% retrieved via the world wide web at http://www.erlang.org/.
7 %%%
8 %%%
9 %%% Software distributed under the License is distributed on an "AS IS"
10 %%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
11 %%% the License for the specific language governing rights and limitations
12 %%% under the License.
13 %%%
14 %%%
15 %%% The Initial Developer of the Original Code is ProcessOne.
16 %%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
17 %%% All Rights Reserved.''
18 %%% This software is copyright 2006-2015, ProcessOne.
19 %%%
20 %%% @copyright 2006-2015 ProcessOne
21 %%% @author Christophe Romain <christophe.romain@process-one.net>
22 %%% [http://www.process-one.net/]
23 %%% @end
24 %%% ====================================================================
25
26 %%% @doc The module <strong>{@module}</strong> is the core of the PubSub
27 %%% extension. It relies on PubSub plugins for a large part of its functions.
28 %%%
29 %%% @headerfile "pubsub.hrl"
30 %%%
31 %%% @reference See <a href="http://www.xmpp.org/extensions/xep-0060.html">XEP-0060: Pubsub</a> for
32 %%% the latest version of the PubSub specification.
33 %%% This module uses version 1.12 of the specification as a base.
34 %%% Most of the specification is implemented.
35 %%% Functions concerning configuration should be rewritten.
36 %%%
37 %%% Support for subscription-options and multi-subscribe features was
38 %%% added by Brian Cully (bjc AT kublai.com). For information on
39 %%% subscription-options and multi-subscribe see XEP-0060 sections 6.1.6,
40 %%% 6.2.3.1, 6.2.3.5, and 6.3. For information on subscription leases see
41 %%% XEP-0060 section 12.18.
42
43 -module(mod_pubsub).
44 -behaviour(gen_mod).
45 -behaviour(gen_server).
46 -behaviour(mongoose_packet_handler).
47 -behaviour(mongoose_module_metrics).
48 -author('christophe.romain@process-one.net').
49
50 -xep([{xep, 60}, {version, "1.25.0"}]).
51 -xep([{xep, 163}, {version, "1.2.2"}]).
52 -xep([{xep, 248}, {version, "0.3.0"}]).
53 -xep([{xep, 277}, {version, "0.6.5"}]).
54
55 %% https://xmpp.org/extensions/xep-0384.html#server-side
56 -xep([{xep, 384}, {version, "0.8.3"}]).
57
58 -include("mongoose.hrl").
59 -include("adhoc.hrl").
60 -include("jlib.hrl").
61 -include("pubsub.hrl").
62 -include("mongoose_config_spec.hrl").
63 -include("session.hrl").
64
65 -define(STDTREE, nodetree_tree).
66 -define(STDNODE, <<"flat">>).
67 -define(STDNODE_MODULE, node_flat).
68 -define(PEPNODE, <<"pep">>).
69 -define(PUSHNODE, <<"push">>).
70
71 %% exports for hooks
72 -export([presence_probe/3,
73 caps_recognised/3,
74 in_subscription/3,
75 out_subscription/3,
76 on_user_offline/3,
77 remove_user/3,
78 disco_local_features/3,
79 disco_sm_identity/3,
80 disco_sm_features/3,
81 disco_sm_items/3,
82 handle_pep_authorization_response/3,
83 foreign_event/3]).
84
85 %% exported iq handlers
86 -export([iq_sm/4]).
87
88 %% exports for console debug manual use
89 -export([create_node/5, create_node/7, delete_node/3,
90 subscribe_node/5, unsubscribe_node/5, publish_item/6,
91 delete_item/4, send_items/7, get_items/2, get_item/3,
92 get_cached_item/2, tree_action/3, node_action/4]).
93
94 %% general helpers for plugins
95 -export([subscription_to_string/1, affiliation_to_string/1,
96 string_to_subscription/1, string_to_affiliation/1,
97 extended_error/2, extended_error/3, service_jid/1,
98 tree/1, plugin/1, plugin_call/3, host/2, serverhost/1,
99 host_to_host_type/1]).
100
101 %% API and gen_server callbacks
102 -export([start_link/2, start/2, stop/1, deps/2, init/1,
103 handle_call/3, handle_cast/2, handle_info/2,
104 terminate/2, code_change/3]).
105
106 %% Config callbacks
107 -export([config_spec/0, process_pep_mapping/1]).
108
109 -export([default_host/0]).
110
111 -export([get_personal_data/3]).
112
113 %% packet handler export
114 -export([process_packet/5]).
115
116 -export([config_metrics/1]).
117
118 %% Private export for wpool worker callbacks
119 -export([handle_msg/1]).
120
121 -define(MOD_PUBSUB_DB_BACKEND, mod_pubsub_db_backend).
122 -ignore_xref([
123 {?MOD_PUBSUB_DB_BACKEND, transaction, 2},
124 {?MOD_PUBSUB_DB_BACKEND, get_user_nodes, 2},
125 {?MOD_PUBSUB_DB_BACKEND, get_user_payloads, 2},
126 {?MOD_PUBSUB_DB_BACKEND, get_user_subscriptions, 2},
127 {?MOD_PUBSUB_DB_BACKEND, start, 0},
128 {?MOD_PUBSUB_DB_BACKEND, set_subscription_opts, 4},
129 {?MOD_PUBSUB_DB_BACKEND, stop, 0},
130 affiliation_to_string/1,
131 create_node/7,
132 default_host/0,
133 delete_item/4,
134 delete_node/3,
135 extended_error/3,
136 get_cached_item/2,
137 get_item/3,
138 get_items/2,
139 host/2,
140 iq_sm/4,
141 node_action/4,
142 node_call/4,
143 plugin/2,
144 plugin/1,
145 publish_item/6,
146 send_items/7,
147 serverhost/1,
148 start_link/2,
149 string_to_affiliation/1,
150 string_to_subscription/1,
151 subscribe_node/5,
152 subscription_to_string/1,
153 tree_action/3,
154 unsubscribe_node/5,
155 handle_msg/1
156 ]).
157
158 -type plugin_name() :: binary().
159
160 -export_type([
161 host/0,
162 hostPubsub/0,
163 hostPEP/0,
164 %%
165 nodeIdx/0,
166 nodeId/0,
167 itemId/0,
168 subId/0,
169 payload/0,
170 %%
171 nodeOption/0,
172 nodeOptions/0,
173 subOption/0,
174 subOptions/0,
175 %%
176 affiliation/0,
177 subscription/0,
178 accessModel/0,
179 publishModel/0
180 ]).
181
182 %% -type payload() defined here because the -type exml:element() is not accessible
183 %% from pubsub.hrl
184 -type(payload() :: [] | [exml:element(), ...]).
185 -type(publishOptions() :: undefined | exml:element()).
186
187 -export_type([
188 pubsubNode/0,
189 pubsubState/0,
190 pubsubItem/0,
191 pubsubLastItem/0,
192 publishOptions/0
193 ]).
194
195 -type(pubsubNode() ::
196 #pubsub_node{
197 nodeid :: {Host::mod_pubsub:host(), Node::mod_pubsub:nodeId()},
198 id :: Nidx::mod_pubsub:nodeIdx(),
199 parents :: [Node::mod_pubsub:nodeId()],
200 type :: Type::binary(),
201 owners :: [Owner::jid:ljid(), ...],
202 options :: Opts::mod_pubsub:nodeOptions()
203 }
204 ).
205
206 -type(pubsubState() ::
207 #pubsub_state{
208 stateid :: {Entity::jid:ljid(), Nidx::mod_pubsub:nodeIdx()},
209 items :: [ItemId::mod_pubsub:itemId()],
210 affiliation :: Affs::mod_pubsub:affiliation(),
211 subscriptions :: [{Sub::mod_pubsub:subscription(), SubId::mod_pubsub:subId()}]
212 }
213 ).
214
215 -type(pubsubItem() ::
216 #pubsub_item{
217 itemid :: {ItemId::mod_pubsub:itemId(), Nidx::mod_pubsub:nodeIdx()},
218 creation :: {integer(), jid:ljid()},
219 modification :: {integer(), jid:ljid()},
220 payload :: mod_pubsub:payload()
221 }
222 ).
223
224 -type(pubsubLastItem() ::
225 #pubsub_last_item{
226 nodeid :: mod_pubsub:nodeIdx(),
227 itemid :: mod_pubsub:itemId(),
228 creation :: {integer(), jid:ljid()},
229 payload :: mod_pubsub:payload()
230 }
231 ).
232
233 -record(state,
234 {
235 server_host,
236 host,
237 access,
238 pep_mapping = #{},
239 ignore_pep_from_offline = true,
240 last_item_cache = false,
241 max_items_node = ?MAXITEMS,
242 max_subscriptions_node = undefined,
243 default_node_config = [],
244 nodetree = ?STDTREE,
245 plugins = [?STDNODE] :: [plugin_name()]
246 }).
247
248 -type(state() ::
249 #state{
250 server_host :: binary(),
251 host :: mod_pubsub:hostPubsub(),
252 access :: atom(),
253 pep_mapping :: map(),
254 ignore_pep_from_offline :: boolean(),
255 last_item_cache :: mnesia | rdbms | false,
256 max_items_node :: non_neg_integer(),
257 max_subscriptions_node :: non_neg_integer()|undefined,
258 default_node_config :: [{atom(), binary()|boolean()|integer()|atom()}],
259 nodetree :: module(),
260 plugins :: [binary(), ...]
261 }
262
263 ).
264
265 %%====================================================================
266 %% API
267 %%====================================================================
268
269 %% @doc: Starts the server.
270 -spec start_link(mongooseim:domain_name(), gen_mod:module_opts()) -> {ok, pid()} | ignore | {error, any()}.
271 start_link(Host, Opts) ->
272 39 Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
273 39 gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
274
275 deps(_Host, _Opts) ->
276 239 [{mod_caps, #{cache_size => 1000, cache_life_time => timer:hours(24) div 1000}, optional}].
277
278 start(Host, Opts) ->
279 39 Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
280 39 ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
281 transient, 1000, worker, [?MODULE]},
282 39 ensure_metrics(Host),
283 39 start_pool(Host, Opts),
284 39 ejabberd_sup:start_child(ChildSpec).
285
286 start_pool(HostType, #{wpool := WpoolOpts}) ->
287 39 {ok, _} = mongoose_wpool:start(generic, HostType, pubsub_notify, maps:to_list(WpoolOpts)).
288
289 stop(Host) ->
290 39 Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
291 39 gen_server:call(Proc, stop),
292 39 mongoose_wpool:stop(generic, Host, pubsub_notify),
293 39 ejabberd_sup:stop_child(Proc).
294
295 -spec config_spec() -> mongoose_config_spec:config_section().
296 config_spec() ->
297 106 #section{
298 items = #{<<"iqdisc">> => mongoose_config_spec:iqdisc(),
299 <<"host">> => #option{type = string,
300 validate = subdomain_template,
301 process = fun mongoose_subdomain_utils:make_subdomain_pattern/1},
302 <<"backend">> => #option{type = atom,
303 validate = {module, mod_pubsub_db}},
304 <<"access_createnode">> => #option{type = atom,
305 validate = access_rule},
306 <<"max_items_node">> => #option{type = integer,
307 validate = non_negative},
308 <<"max_subscriptions_node">> => #option{type = integer,
309 validate = non_negative},
310 <<"nodetree">> => #option{type = binary,
311 validate = {module, nodetree},
312 process = fun tree_mod/1},
313 <<"ignore_pep_from_offline">> => #option{type = boolean},
314 <<"last_item_cache">> => #option{type = atom,
315 validate = {enum, [mnesia, rdbms, false]}},
316 <<"plugins">> => #list{items = #option{type = binary,
317 validate = {module, node}}},
318 <<"pep_mapping">> => #list{items = pep_mapping_config_spec(),
319 format_items = map},
320 <<"default_node_config">> => default_node_config_spec(),
321 <<"item_publisher">> => #option{type = boolean},
322 <<"sync_broadcast">> => #option{type = boolean},
323 <<"wpool">> => wpool_spec()
324 },
325 defaults = #{<<"iqdisc">> => one_queue,
326 <<"host">> => default_host(),
327 <<"backend">> => mnesia,
328 <<"access_createnode">> => all,
329 <<"max_items_node">> => ?MAXITEMS,
330 <<"nodetree">> => ?STDTREE,
331 <<"ignore_pep_from_offline">> => true,
332 <<"last_item_cache">> => false,
333 <<"plugins">> => [?STDNODE],
334 <<"pep_mapping">> => #{},
335 <<"default_node_config">> => [],
336 <<"item_publisher">> => false,
337 <<"sync_broadcast">> => false}
338 }.
339
340 wpool_spec() ->
341 106 Wpool = mongoose_config_spec:wpool(#{}),
342 106 Wpool#section{include = always}.
343
344 pep_mapping_config_spec() ->
345 106 #section{
346 items = #{<<"namespace">> => #option{type = binary,
347 validate = non_empty},
348 <<"node">> => #option{type = binary,
349 validate = non_empty}},
350 required = all,
351 process = fun ?MODULE:process_pep_mapping/1
352 }.
353
354 default_node_config_spec() ->
355 106 #section{
356 items = #{<<"access_model">> => #option{type = atom,
357 validate = non_empty},
358 <<"deliver_notifications">> => #option{type = boolean},
359 <<"deliver_payloads">> => #option{type = boolean},
360 <<"max_items">> => #option{type = integer,
361 validate = non_negative},
362 <<"max_payload_size">> => #option{type = integer,
363 validate = non_negative},
364 <<"node_type">> => #option{type = atom,
365 validate = non_empty},
366 <<"notification_type">> => #option{type = atom,
367 validate = non_empty},
368 <<"notify_config">> => #option{type = boolean},
369 <<"notify_delete">> => #option{type = boolean},
370 <<"notify_retract">> => #option{type = boolean},
371 <<"persist_items">> => #option{type = boolean},
372 <<"presence_based_delivery">> => #option{type = boolean},
373 <<"publish_model">> => #option{type = atom,
374 validate = non_empty},
375 <<"purge_offline">> => #option{type = boolean},
376 <<"roster_groups_allowed">> => #list{items = #option{type = binary,
377 validate = non_empty}},
378 <<"send_last_published_item">> => #option{type = atom,
379 validate = non_empty},
380 <<"subscribe">> => #option{type = boolean}
381 },
382 format_items = list
383 }.
384
385 process_pep_mapping(#{namespace := NameSpace, node := Node}) ->
386
:-(
{NameSpace, Node}.
387
388 -spec default_host() -> mongoose_subdomain_utils:subdomain_pattern().
389 default_host() ->
390 106 mongoose_subdomain_utils:make_subdomain_pattern(<<"pubsub.@HOST@">>).
391
392 %% State is an extra data, required for processing
393 -spec process_packet(Acc :: mongoose_acc:t(), From ::jid:jid(), To ::jid:jid(), El :: exml:element(),
394 #{state := #state{}}) -> mongoose_acc:t().
395 process_packet(Acc, From, To, El, #{state := State}) ->
396 834 #state{server_host = ServerHost, access = Access, plugins = Plugins} = State,
397 834 do_route(Acc, ServerHost, Access, Plugins, To#jid.lserver, From, To, El).
398
399 %%====================================================================
400 %% GDPR callback
401 %%====================================================================
402
403 -spec get_personal_data(Acc, Params, Extra) -> {ok, Acc} when
404 Acc :: gdpr:personal_data(),
405 Params :: #{jid := jid:jid()},
406 Extra :: gen_hook:extra().
407 get_personal_data(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, _) ->
408 21 Payloads = mod_pubsub_db_backend:get_user_payloads(LUser, LServer),
409 21 Nodes = mod_pubsub_db_backend:get_user_nodes(LUser, LServer),
410 21 Subscriptions = mod_pubsub_db_backend:get_user_subscriptions(LUser, LServer),
411
412 21 NewAcc = [{pubsub_payloads, ["node_name", "item_id", "payload"], Payloads},
413 {pubsub_nodes, ["node_name", "type"], Nodes},
414 {pubsub_subscriptions, ["node_name"], Subscriptions} | Acc],
415 21 {ok, NewAcc}.
416
417 %%====================================================================
418 %% gen_server callbacks
419 %%====================================================================
420
421 -spec init([binary() | gen_mod:module_opts(), ...]) -> {'ok', state()}.
422 init([ServerHost, Opts = #{host := SubdomainPattern}]) ->
423 39 ?LOG_DEBUG(#{what => pubsub_init, server => ServerHost, opts => Opts}),
424 39 Host = mongoose_subdomain_utils:get_fqdn(SubdomainPattern, ServerHost),
425
426 39 init_backend(ServerHost, Opts),
427 39 Plugins = init_plugins(Host, ServerHost, Opts),
428
429 39 gen_hook:add_handlers(hooks(ServerHost)),
430 39 case lists:member(?PEPNODE, Plugins) of
431 true ->
432 4 gen_hook:add_handlers(pep_hooks(ServerHost)),
433 4 add_pep_iq_handlers(ServerHost, Opts);
434 false ->
435 35 ok
436 end,
437 39 State = init_state(ServerHost, Opts, Plugins),
438
439 %% Pass State as extra into ?MODULE:process_packet/5 function
440 39 PacketHandler = mongoose_packet_handler:new(?MODULE, #{state => State}),
441 %% TODO: Conversion of this module is not done, it doesn't support dynamic
442 %% domains yet. Only subdomain registration is done properly.
443 39 mongoose_domain_api:register_subdomain(ServerHost, SubdomainPattern, PacketHandler),
444 39 {ok, State}.
445
446 init_backend(ServerHost, Opts) ->
447 39 mod_pubsub_db_backend:init(ServerHost, Opts),
448 39 maybe_start_cache_module(ServerHost, Opts).
449
450 hooks(ServerHost) ->
451 78 [{disco_local_features, ServerHost, fun ?MODULE:disco_local_features/3, #{}, 75},
452 {sm_remove_connection_hook, ServerHost, fun ?MODULE:on_user_offline/3, #{}, 75},
453 {presence_probe_hook, ServerHost, fun ?MODULE:presence_probe/3, #{}, 80},
454 {roster_in_subscription, ServerHost, fun ?MODULE:in_subscription/3, #{}, 50},
455 {roster_out_subscription, ServerHost, fun ?MODULE:out_subscription/3, #{}, 50},
456 {remove_user, ServerHost, fun ?MODULE:remove_user/3, #{}, 50},
457 {anonymous_purge_hook, ServerHost, fun ?MODULE:remove_user/3, #{}, 50},
458 {get_personal_data, ServerHost, fun ?MODULE:get_personal_data/3, #{}, 50}].
459
460 pep_hooks(ServerHost) ->
461 8 [
462 {caps_recognised, ServerHost, fun ?MODULE:caps_recognised/3, #{}, 80},
463 {disco_sm_identity, ServerHost, fun ?MODULE:disco_sm_identity/3, #{}, 75},
464 {disco_sm_features, ServerHost, fun ?MODULE:disco_sm_features/3, #{}, 75},
465 {disco_sm_items, ServerHost, fun ?MODULE:disco_sm_items/3, #{}, 75},
466 {filter_local_packet, ServerHost, fun ?MODULE:handle_pep_authorization_response/3, #{}, 1},
467 {foreign_event, ServerHost, fun ?MODULE:foreign_event/3, #{}, 100}
468 ].
469
470 add_pep_iq_handlers(ServerHost, #{iqdisc := IQDisc}) ->
471 4 gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB, ?MODULE, iq_sm, IQDisc),
472 4 gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB_OWNER,
473 ?MODULE, iq_sm, IQDisc).
474
475 delete_pep_iq_handlers(ServerHost) ->
476 4 gen_iq_handler:remove_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB),
477 4 gen_iq_handler:remove_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB_OWNER).
478
479 %% Plugins is a subset of configured plugins which are able to start
480 %% TODO Evaluate if we should just use plugins from the config instead
481 init_state(ServerHost, #{last_item_cache := LastItemCache, max_items_node := MaxItemsNode,
482 pep_mapping := PepMapping, ignore_pep_from_offline := PepOffline,
483 access_createnode := Access}, Plugins) ->
484 39 HostType = host_to_host_type(ServerHost),
485 39 NodeTree = tree(HostType),
486 39 Host = host(HostType, ServerHost),
487 39 #state{host = Host, server_host = ServerHost,
488 access = Access, pep_mapping = PepMapping,
489 ignore_pep_from_offline = PepOffline,
490 last_item_cache = LastItemCache,
491 max_items_node = MaxItemsNode, nodetree = NodeTree,
492 plugins = Plugins }.
493
494 %% @doc Call the init/1 function for each plugin declared in the config file.
495 %% The default plugin module is implicit.
496 %% <p>The Erlang code for the plugin is located in a module called
497 %% <em>node_plugin</em>. The 'node_' prefix is mandatory.</p>
498 %% <p>The modules are initialized in alphabetical order and the list is checked
499 %% and sorted to ensure that each module is initialized only once.</p>
500 %% <p>See {@link node_hometree:init/1} for an example implementation.</p>
501 init_plugins(Host, ServerHost, Opts = #{nodetree := TreePlugin, plugins := Plugins}) ->
502 39 {ok, HostType} = mongoose_domain_api:get_host_type(ServerHost),
503 39 ?LOG_DEBUG(#{what => pubsub_tree_plugin, tree_plugin => TreePlugin}),
504 39 gen_pubsub_nodetree:init(TreePlugin, HostType, Opts),
505 39 PluginsOK = lists:foldl(pa:bind(fun init_plugin/5, Host, ServerHost, Opts), [], Plugins),
506 39 lists:reverse(PluginsOK).
507
508 init_plugin(Host, ServerHost, Opts, Name, Acc) ->
509 58 Plugin = plugin(Name),
510 58 case catch apply(Plugin, init, [Host, ServerHost, Opts]) of
511 {'EXIT', Error} ->
512
:-(
?LOG_ERROR(#{what => pubsub_plugin_init_failed, plugin => Plugin,
513
:-(
server => ServerHost, sub_host => Host, opts => Opts, error => Error}),
514
:-(
Acc;
515 _ ->
516 58 ?LOG_DEBUG(#{what => pubsub_init_plugin, plugin_name => Name}),
517 58 [Name | Acc]
518 end.
519
520 terminate_plugins(Host, ServerHost, Plugins, TreePlugin) ->
521 39 lists:foreach(
522 fun (Name) ->
523 58 ?LOG_DEBUG(#{what => pubsub_terminate_plugin, plugin_name => Name}),
524 58 Plugin = plugin(Name),
525 58 gen_pubsub_node:terminate(Plugin, Host, ServerHost)
526 end,
527 Plugins),
528 39 gen_pubsub_nodetree:terminate(TreePlugin, Host, ServerHost),
529 39 ok.
530
531 notify_worker(HostType, HashKey, Request) ->
532 404 mongoose_wpool:cast(generic, HostType, pubsub_notify, HashKey,
533 {?MODULE, handle_msg, [Request]}).
534
535 handle_msg({send_last_pubsub_items, Host, Recipient, Plugins}) ->
536 377 send_last_pubsub_items(Host, Recipient, Plugins);
537 handle_msg({send_last_pep_items, Host, IgnorePepFromOffline, RecipientJID, RecipientPid, Features}) ->
538 9 send_last_pep_items(Host, IgnorePepFromOffline, RecipientJID, RecipientPid, Features);
539 handle_msg({send_last_items_from_owner, Host, NodeOwner, RecipientInfo}) ->
540 18 send_last_items_from_owner(Host, NodeOwner, RecipientInfo).
541
542 send_last_pubsub_items(Host, Recipient, Plugins) ->
543 377 F = fun(PluginType) -> send_last_pubsub_items_for_plugin(Host, PluginType, Recipient) end,
544 377 lists:foreach(F, Plugins).
545
546 send_last_pubsub_items_for_plugin(Host, PluginType, Recipient) ->
547 552 JIDs = [Recipient, jid:to_lower(Recipient), jid:to_bare(Recipient)],
548 552 Subs = get_subscriptions_for_send_last(Host, PluginType, JIDs),
549 552 lists:foreach(
550 fun({#pubsub_node{nodeid={_, Node}, id=Nidx, options=Options}, _, _, SubJID}) ->
551
:-(
send_items(Host, Node, Nidx, PluginType, Options, SubJID, last)
552 end,
553 lists:usort(Subs)).
554
555 send_last_pep_items(Host, IgnorePepFromOffline, RecipientJID, RecipientPid, Features) ->
556 9 RecipientLJID = jid:to_lower(RecipientJID),
557 9 [send_last_item_to_jid(NodeOwnerJID, NodeRec, RecipientLJID) ||
558 9 NodeOwnerJID <- get_contacts_for_sending_last_item(RecipientPid, IgnorePepFromOffline),
559 10 NodeRec = #pubsub_node{nodeid = {_, Node}} <- get_nodes_for_sending_last_item(Host, NodeOwnerJID),
560 2 lists:member(<<Node/binary, "+notify">>, Features)],
561 9 ok.
562
563 get_contacts_for_sending_last_item(RecipientPid, IgnorePepFromOffline) ->
564 9 case catch mod_presence:get_subscribed(RecipientPid) of
565 Contacts when is_list(Contacts) ->
566 8 [Contact || Contact = #jid{luser = U, lserver = S} <- Contacts,
567 10 user_resources(U, S) /= [] orelse not IgnorePepFromOffline];
568 _ ->
569 1 []
570 end.
571
572 send_last_items_from_owner(Host, NodeOwner, _Recipient = {U, S, Resources}) ->
573 18 [send_last_item_to_jid(NodeOwner, NodeRec, {U, S, R}) ||
574 18 NodeRec = #pubsub_node{nodeid = {_, Node}} <- get_nodes_for_sending_last_item(Host, NodeOwner),
575 7 R <- Resources,
576 7 not should_drop_pep_message(Node, jid:make(U, S, R))
577 ],
578 18 ok.
579
580 should_drop_pep_message(Node, RecipientJid) ->
581 7 case ejabberd_sm:get_session_pid(RecipientJid) of
582
:-(
none -> true;
583 Pid ->
584 7 Event = {filter_pep_recipient, RecipientJid, <<Node/binary, "+notify">>},
585 7 try mongoose_c2s:call(Pid, ?MODULE, Event)
586
:-(
catch exit:timeout -> true
587 end
588 end.
589
590 get_nodes_for_sending_last_item(Host, NodeOwnerJID) ->
591 28 lists:filter(fun(#pubsub_node{options = Options}) ->
592 9 match_option(Options, send_last_published_item, on_sub_and_presence)
593 end,
594 get_nodes_owned_by(Host, NodeOwnerJID)).
595
596 get_nodes_owned_by(Host, OwnerJID) ->
597 28 OwnerBLJID = jid:to_bare(jid:to_lower(OwnerJID)),
598 28 tree_action(Host, get_nodes, [OwnerBLJID, OwnerJID]).
599
600 send_last_item_to_jid(NodeOwner, #pubsub_node{nodeid = {_, Node}, type = NodeType,
601 id = Nidx, options = NodeOptions}, RecipientJID) ->
602 6 NodeOwnerBLJID = jid:to_bare(jid:to_lower(NodeOwner)),
603 6 case is_subscribed(RecipientJID, NodeOwnerBLJID, NodeOptions) of
604 6 true -> send_items(NodeOwnerBLJID, Node, Nidx, NodeType, NodeOptions, RecipientJID, last);
605
:-(
false -> ok
606 end.
607
608 is_subscribed(Recipient, NodeOwner, NodeOptions) ->
609 6 case get_option(NodeOptions, access_model) of
610
:-(
open -> true;
611 6 presence -> true;
612
:-(
whitelist -> false; % subscribers are added manually
613
:-(
authorize -> false; % likewise
614 roster ->
615
:-(
Grps = get_option(NodeOptions, roster_groups_allowed, []),
616
:-(
{OU, OS, _} = NodeOwner,
617
:-(
element(2, get_roster_info(OU, OS, Recipient, Grps))
618 end.
619
620 %% -------
621 %% disco hooks handling functions
622 %%
623
624 -spec identities(jid:lserver(), ejabberd:lang()) -> [mongoose_disco:identity()].
625 identities(Host, Lang) ->
626 4 pubsub_identity(Lang) ++ node_identity(Host, ?PEPNODE) ++ node_identity(Host, ?PUSHNODE).
627
628 -spec pubsub_identity(ejabberd:lang()) -> [mongoose_disco:identity()].
629 pubsub_identity(Lang) ->
630 4 [#{category => <<"pubsub">>,
631 type => <<"service">>,
632 name => translate:translate(Lang, <<"Publish-Subscribe">>)}].
633
634 -spec node_identity(jid:lserver(), binary()) -> [mongoose_disco:identity()].
635 node_identity(Host, Type) ->
636 8 case lists:member(Type, plugins(Host)) of
637 2 true -> [#{category => <<"pubsub">>, type => Type}];
638 6 false -> []
639 end.
640
641
642 -spec disco_local_features(mongoose_disco:feature_acc(),
643 map(),
644 map()) -> {ok, mongoose_disco:feature_acc()}.
645 disco_local_features(Acc = #{to_jid := #jid{lserver = LServer}, node := <<>>}, _, _) ->
646 113 Features = [?NS_PUBSUB | [feature(F) || F <- features(LServer, <<>>)]],
647 113 {ok, mongoose_disco:add_features(Features, Acc)};
648 disco_local_features(Acc, _, _) ->
649
:-(
{ok, Acc}.
650
651 -spec disco_sm_identity(Acc, Params, Extra) -> {ok, Acc} when
652 Acc :: mongoose_disco:identity_acc(),
653 Params :: map(),
654 Extra :: gen_hook:extra().
655 disco_sm_identity(Acc = #{from_jid := From, to_jid := To, node := Node}, _, _) ->
656 1 Identities = disco_identity(jid:to_lower(jid:to_bare(To)), Node, From),
657 1 {ok, mongoose_disco:add_identities(Identities, Acc)}.
658
659 disco_identity(error, _Node, _From) ->
660
:-(
[];
661 disco_identity(_Host, <<>>, _From) ->
662 1 [pep_identity()];
663 disco_identity(Host, Node, From) ->
664
:-(
Action = fun (#pubsub_node{id = Nidx, type = Type, options = Options, owners = Owners}) ->
665
:-(
case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
666 {result, _} ->
667
:-(
{result, [pep_identity(), pep_identity(Options)]};
668 _ ->
669
:-(
{result, []}
670 end
671 end,
672
:-(
case dirty(Host, Node, Action, ?FUNCTION_NAME) of
673
:-(
{result, {_, Result}} -> Result;
674
:-(
_ -> []
675 end.
676
677 pep_identity(Options) ->
678
:-(
Identity = pep_identity(),
679
:-(
case get_option(Options, title) of
680
:-(
false -> Identity;
681
:-(
[Title] -> Identity#{name => Title}
682 end.
683
684 pep_identity() ->
685 1 #{category => <<"pubsub">>, type => <<"pep">>}.
686
687 -spec disco_sm_features(Acc, Params, Extra) -> {ok, Acc} when
688 Acc :: mongoose_disco:feature_acc(),
689 Params :: map(),
690 Extra :: gen_hook:extra().
691 disco_sm_features(Acc = #{from_jid := From, to_jid := To, node := Node}, _, _) ->
692 1 Features = disco_features(jid:to_lower(jid:to_bare(To)), Node, From),
693 1 {ok, mongoose_disco:add_features(Features, Acc)}.
694
695 -spec disco_features(error | jid:simple_jid(), binary(), jid:jid()) -> [mongoose_disco:feature()].
696 disco_features(error, _Node, _From) ->
697
:-(
[];
698 disco_features(_Host, <<>>, _From) ->
699 1 [?NS_PUBSUB | [feature(F) || F <- plugin_features(<<"pep">>)]];
700 disco_features(Host, Node, From) ->
701
:-(
Action = fun (#pubsub_node{id = Nidx, type = Type, options = Options, owners = Owners}) ->
702
:-(
case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
703 {result, _} ->
704
:-(
{result, [?NS_PUBSUB | [feature(F) || F <- plugin_features(<<"pep">>)]]};
705 _ ->
706
:-(
{result, []}
707 end
708 end,
709
:-(
case dirty(Host, Node, Action, ?FUNCTION_NAME) of
710
:-(
{result, {_, Result}} -> Result;
711
:-(
_ -> []
712 end.
713
714 -spec disco_sm_items(Acc, Params, Extra) -> {ok, Acc} when
715 Acc :: mongoose_disco:item_acc(),
716 Params :: map(),
717 Extra :: gen_hook:extra().
718 disco_sm_items(Acc = #{from_jid := From, to_jid := To, node := Node}, _, _) ->
719 2 Items = disco_items(jid:to_lower(jid:to_bare(To)), Node, From),
720 2 {ok, mongoose_disco:add_items(Items, Acc)}.
721
722 -spec disco_items(mod_pubsub:host(), mod_pubsub:nodeId(), jid:jid()) -> [mongoose_disco:item()].
723 disco_items(Host, <<>>, From) ->
724 2 Action = fun (#pubsub_node{nodeid = {_, Node},
725 options = Options, type = Type, id = Nidx, owners = Owners},
726 Acc) ->
727 1 case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
728 {result, _} ->
729 1 [disco_item(Node, Host, Options) | Acc];
730 _ ->
731
:-(
Acc
732 end
733 end,
734 2 NodeBloc = fun() ->
735 2 {result,
736 lists:foldl(Action, [], tree_call(Host, get_nodes, [Host, From]))}
737 end,
738 2 ErrorDebug = #{
739 action => disco_items,
740 pubsub_host => Host,
741 from => From
742 },
743 2 case mod_pubsub_db_backend:dirty(NodeBloc, ErrorDebug) of
744 2 {result, Items} -> Items;
745
:-(
_ -> []
746 end;
747 disco_items(Host, Node, From) ->
748
:-(
Action = fun (#pubsub_node{id = Nidx, type = Type, options = Options, owners = Owners}) ->
749
:-(
case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
750 {result, Items} ->
751
:-(
{result, [disco_item(Host, ItemId) ||
752
:-(
#pubsub_item{itemid = {ItemId, _}} <- Items]};
753 _ ->
754
:-(
{result, []}
755 end
756 end,
757
:-(
case dirty(Host, Node, Action, ?FUNCTION_NAME) of
758
:-(
{result, {_, Result}} -> Result;
759
:-(
_ -> []
760 end.
761
762 disco_item(Node, Host, Options) ->
763 1 Item = #{node => Node,
764 jid => jid:to_binary(Host)},
765 1 case get_option(Options, title) of
766 1 false -> Item;
767
:-(
[Title] -> Item#{name => Title}
768 end.
769
770 disco_item(Host, ItemId) ->
771
:-(
#{jid => jid:to_binary(Host),
772 name => ItemId}.
773
774 %% -------
775 %% callback that prevents routing subscribe authorizations back to the sender
776 %%
777
778 -spec handle_pep_authorization_response(Acc, Params, Extra) -> {ok, Acc} when
779 Acc :: mongoose_hooks:filter_packet_acc(),
780 Params :: map(),
781 Extra :: gen_hook:extra().
782 handle_pep_authorization_response({From, To, Acc, #xmlel{ name = Name } = Packet}, _, _) ->
783 626 Type = mongoose_acc:stanza_type(Acc),
784 626 {ok, handle_pep_authorization_response(Name, Type, From, To, Acc, Packet)}.
785
786 handle_pep_authorization_response(_, <<"error">>, From, To, Acc, Packet) ->
787 13 {From, To, Acc, Packet};
788 handle_pep_authorization_response(<<"message">>, _, From, To, Acc, Packet)
789 when From#jid.luser == To#jid.luser, From#jid.lserver == To#jid.lserver ->
790 2 case find_authorization_response(Packet) of
791 none ->
792
:-(
{From, To, Acc, Packet};
793 invalid ->
794 1 {From, To, Acc, Packet};
795 KVs ->
796 1 handle_authorization_response(Acc, jid:to_lower(To), From, To, Packet, KVs),
797 1 drop
798 end;
799 handle_pep_authorization_response(_, _, From, To, Acc, Packet) ->
800 611 {From, To, Acc, Packet}.
801
802 %% -------
803 %% callback for foreign_event calls, to distribute pep messages from the node owner c2s process
804 %%
805 -spec foreign_event(mongoose_acc:t(), mongoose_c2s_hooks:params(), gen_hook:extra()) ->
806 mongoose_c2s_hooks:result().
807 foreign_event(Acc, #{c2s_data := StateData,
808 event_type := {call, From},
809 event_tag := ?MODULE,
810 event_content := {get_pep_recipients, Feature}}, _Extra) ->
811 18 Reply = mongoose_hooks:get_pep_recipients(StateData, Feature),
812 18 Acc1 = mongoose_c2s_acc:to_acc(Acc, actions, [{reply, From, Reply}]),
813 18 {stop, Acc1};
814 foreign_event(Acc, #{c2s_data := StateData,
815 event_type := {call, From},
816 event_tag := ?MODULE,
817 event_content := {filter_pep_recipient, To, Feature}}, _Extra) ->
818 7 Reply = mongoose_hooks:filter_pep_recipient(StateData, Feature, To),
819 7 Acc1 = mongoose_c2s_acc:to_acc(Acc, actions, [{reply, From, Reply}]),
820 7 {stop, Acc1};
821 foreign_event(Acc, _Params, _Extra) ->
822
:-(
{ok, Acc}.
823
824 %% -------
825 %% presence hooks handling functions
826 %%
827
828 -spec caps_recognised(Acc, Params, Extra) -> {ok, Acc} when
829 Acc :: mongoose_acc:t(),
830 Params :: #{from := jid:jid(), pid := pid(), features := [binary()]},
831 Extra :: gen_hook:extra().
832 caps_recognised(Acc, #{from := #jid{ luser = U, lserver = S } = JID, pid := Pid, features := Features}, _) ->
833 9 Host = host(S, S),
834 9 IgnorePepFromOffline = gen_mod:get_module_opt(S, ?MODULE, ignore_pep_from_offline),
835 9 notify_worker(S, U, {send_last_pep_items, Host, IgnorePepFromOffline, JID, Pid, Features}),
836 9 {ok, Acc}.
837
838 -spec presence_probe(Acc, Params, Extra) -> {ok, Acc} when
839 Acc :: mongoose_acc:t(),
840 Params :: #{from := jid:jid(), to := jid:jid()},
841 Extra :: gen_hook:extra().
842 presence_probe(Acc, #{from := #jid{luser = U, lserver = S, lresource = _R} = JID, to := JID}, _) ->
843 %% Get subdomain
844 377 Host = host(S, S),
845 377 Plugins = plugins(S),
846 377 notify_worker(S, U, {send_last_pubsub_items, Host, _Recipient = JID, Plugins}),
847 377 {ok, Acc};
848 presence_probe(Acc, _, _) ->
849 7 {ok, Acc}.
850
851 %% -------
852 %% subscription hooks handling functions
853 %%
854
855 -spec out_subscription(Acc, Params, Extra) -> {ok, Acc} when
856 Acc :: mongoose_acc:t(),
857 Params :: #{to := jid:jid(),
858 from := jid:jid(),
859 type := mod_roster:sub_presence()},
860 Extra :: gen_hook:extra().
861 out_subscription(Acc,
862 #{to := #jid{luser = PUser, lserver = PServer, lresource = PResource},
863 from := #jid{luser = LUser, lserver = LServer} = FromJID,
864 type := subscribed},
865 _) ->
866 18 PResources = case PResource of
867 18 <<>> -> user_resources(PUser, PServer);
868
:-(
_ -> [PResource]
869 end,
870 18 Host = host(LServer, LServer),
871 18 notify_worker(LServer, LUser, {send_last_items_from_owner, Host, FromJID,
872 {PUser, PServer, PResources}}),
873 18 {ok, Acc};
874 out_subscription(Acc, _, _) ->
875 20 {ok, Acc}.
876
877 -spec in_subscription(Acc, Params, Extra) -> {ok, Acc} when
878 Acc :: mongoose_acc:t(),
879 Params :: #{to := jid:jid(),
880 from := jid:jid(),
881 type := mod_roster:sub_presence()},
882 Extra :: gen_hook:extra().
883 in_subscription(Acc, #{to := ToJID, from := OwnerJID, type := unsubscribed}, _) ->
884 2 unsubscribe_user(ToJID, OwnerJID),
885 2 {ok, Acc};
886 in_subscription(Acc, _, _) ->
887 36 {ok, Acc}.
888
889 unsubscribe_user(Entity, Owner) ->
890 2 ServerHosts = lists:usort(lists:foldl(
891 fun(UserHost, Acc) ->
892 4 case gen_mod:is_loaded(UserHost, mod_pubsub) of
893 4 true -> [UserHost|Acc];
894
:-(
false -> Acc
895 end
896 end, [], [Entity#jid.lserver, Owner#jid.lserver])),
897 2 [unsubscribe_user(ServerHost, Entity, Owner) || ServerHost <- ServerHosts],
898 2 ok.
899
900 unsubscribe_user(Host, Entity, Owner) ->
901 2 BJID = jid:to_lower(jid:to_bare(Owner)),
902 2 lists:foreach(fun (PType) ->
903 4 unsubscribe_user_per_plugin(Host, Entity, BJID, PType)
904 end, plugins(Host)).
905
906 unsubscribe_user_per_plugin(Host, Entity, BJID, PType) ->
907 4 {result, Subs} = node_action(Host, PType, get_entity_subscriptions, [Host, Entity]),
908 4 lists:foreach(fun({#pubsub_node{options = Options, owners = Owners, id = Nidx},
909 subscribed, _, JID}) ->
910 2 Unsubscribe = match_option(Options, access_model, presence)
911 2 andalso lists:member(BJID, Owners),
912 2 case Unsubscribe of
913 true ->
914 2 node_action(Host, PType,
915 unsubscribe_node, [Nidx, Entity, JID, all]);
916 false ->
917
:-(
ok
918 end;
919 (_) ->
920
:-(
ok
921 end, Subs).
922
923 %% -------
924 %% user remove hook handling function
925 %%
926 -spec remove_user(Acc, Params, Extra) -> {ok, Acc} when
927 Acc :: mongoose_acc:t(),
928 Params :: #{jid := jid:jid()},
929 Extra :: #{host_type := mongooseim:host_type()}.
930 remove_user(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, _) ->
931 26 lists:foreach(fun(PType) ->
932 78 remove_user_per_plugin_safe(LUser, LServer, plugin(PType))
933 end, plugins(LServer)),
934 26 {ok, Acc}.
935
936 remove_user_per_plugin_safe(LUser, LServer, Plugin) ->
937 78 try
938 78 plugin_call(Plugin, remove_user, [LUser, LServer])
939 catch
940 Class:Reason:StackTrace ->
941
:-(
?LOG_WARNING(#{what => pubsub_delete_user_failed, user => LUser,
942 server => LServer, class => Class, reason => Reason,
943
:-(
stacktrace => StackTrace})
944 end.
945
946 handle_call(server_host, _From, State) ->
947
:-(
{reply, State#state.server_host, State};
948 handle_call(plugins, _From, State) ->
949 1203 {reply, State#state.plugins, State};
950 handle_call(pep_mapping, _From, State) ->
951
:-(
{reply, State#state.pep_mapping, State};
952 handle_call(nodetree, _From, State) ->
953
:-(
{reply, State#state.nodetree, State};
954 handle_call(stop, _From, State) ->
955 39 {stop, normal, ok, State}.
956
957 %%--------------------------------------------------------------------
958 %% Function: handle_cast(Msg, State) -> {noreply, State} |
959 %% {noreply, State, Timeout} |
960 %% {stop, Reason, State}
961 %% Description: Handling cast messages
962 %%--------------------------------------------------------------------
963 %% @private
964
:-(
handle_cast(_Msg, State) -> {noreply, State}.
965
966 %%--------------------------------------------------------------------
967 %% Function: handle_info(Info, State) -> {noreply, State} |
968 %% {noreply, State, Timeout} |
969 %% {stop, Reason, State}
970 %% Description: Handling all non call/cast messages
971 %%--------------------------------------------------------------------
972 %% @private
973 handle_info(_Info, State) ->
974
:-(
{noreply, State}.
975
976 %%--------------------------------------------------------------------
977 %% Function: terminate(Reason, State) -> void()
978 %% Description: This function is called by a gen_server when it is about to
979 %% terminate. It should be the opposite of Module:init/1 and do any necessary
980 %% cleaning up. When it returns, the gen_server terminates with Reason.
981 %% The return value is ignored.
982 %%--------------------------------------------------------------------
983 %% @private
984 terminate(_Reason, #state{host = Host, server_host = ServerHost,
985 nodetree = TreePlugin, plugins = Plugins}) ->
986 39 SubdomainPattern = gen_mod:get_module_opt(ServerHost, ?MODULE, host),
987 39 mongoose_domain_api:unregister_subdomain(ServerHost, SubdomainPattern),
988 39 case lists:member(?PEPNODE, Plugins) of
989 true ->
990 4 gen_hook:delete_handlers(pep_hooks(ServerHost)),
991 4 delete_pep_iq_handlers(ServerHost);
992 35 false -> ok
993 end,
994 39 gen_hook:delete_handlers(hooks(ServerHost)),
995 39 case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of
996 undefined ->
997 39 ?LOG_ERROR(#{what => pubsub_process_is_dead,
998 text => <<"process is dead, pubsub was broken">>,
999
:-(
process => ?LOOPNAME});
1000 Pid ->
1001
:-(
Pid ! stop
1002 end,
1003 39 terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
1004 39 mod_pubsub_db_backend:stop().
1005
1006 %%--------------------------------------------------------------------
1007 %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
1008 %% Description: Convert process state when code is changed
1009 %%--------------------------------------------------------------------
1010 %% @private
1011
:-(
code_change(_OldVsn, State, _Extra) -> {ok, State}.
1012
1013 -spec do_route(
1014 Acc :: mongoose_acc:t(),
1015 ServerHost :: binary(),
1016 Access :: atom(),
1017 Plugins :: [binary(), ...],
1018 Host :: mod_pubsub:hostPubsub(),
1019 From ::jid:jid(),
1020 To ::jid:jid(),
1021 Packet :: exml:element())
1022 -> mongoose_acc:t().
1023
1024 %%--------------------------------------------------------------------
1025 %%% Internal functions
1026 %%--------------------------------------------------------------------
1027 do_route(Acc, ServerHost, Access, Plugins, Host, From,
1028 #jid{luser = <<>>, lresource = <<>>} = To,
1029 #xmlel{ name = <<"iq">> } = Packet) ->
1030 829 case jlib:iq_query_info(Packet) of
1031 #iq{type = get, xmlns = ?NS_DISCO_INFO, sub_el = SubEl, lang = Lang} = IQ ->
1032 4 #xmlel{attrs = QAttrs} = SubEl,
1033 4 Node = xml:get_attr_s(<<"node">>, QAttrs),
1034 4 InfoXML = mongoose_disco:get_info(ServerHost, ?MODULE, <<>>, <<>>),
1035 4 Res = case iq_disco_info(ServerHost, Host, Node, From, Lang) of
1036 {result, IQRes} ->
1037 4 jlib:iq_to_xml(IQ#iq{type = result,
1038 sub_el =
1039 [#xmlel{name = <<"query">>,
1040 attrs = QAttrs,
1041 children = IQRes ++ InfoXML}]});
1042 {error, Error} ->
1043
:-(
make_error_reply(Packet, Error)
1044 end,
1045 4 ejabberd_router:route(To, From, Acc, Res);
1046 #iq{type = get, xmlns = ?NS_DISCO_ITEMS, sub_el = SubEl} = IQ ->
1047 15 #xmlel{attrs = QAttrs} = SubEl,
1048 15 Node = xml:get_attr_s(<<"node">>, QAttrs),
1049 15 Res = case iq_disco_items(Host, Node, From, jlib:rsm_decode(IQ)) of
1050 {result, IQRes} ->
1051 12 jlib:iq_to_xml(IQ#iq{type = result,
1052 sub_el =
1053 [#xmlel{name = <<"query">>,
1054 attrs = QAttrs,
1055 children = IQRes}]});
1056 {error, Error} ->
1057 3 make_error_reply(Packet, Error)
1058 end,
1059 15 ejabberd_router:route(To, From, Acc, Res);
1060 #iq{type = IQType, xmlns = ?NS_PUBSUB, lang = Lang, sub_el = SubEl} = IQ ->
1061 549 Res = case iq_pubsub(Host, ServerHost, From, IQType,
1062 SubEl, Lang, Access, Plugins)
1063 of
1064 {result, IQRes} ->
1065 517 jlib:iq_to_xml(IQ#iq{type = result, sub_el = IQRes});
1066 {error, Error} ->
1067 32 make_error_reply(Packet, Error)
1068 end,
1069 549 ejabberd_router:route(To, From, Acc, Res);
1070 #iq{type = IQType, xmlns = ?NS_PUBSUB_OWNER, lang = Lang, sub_el = SubEl} = IQ ->
1071 255 Res = case iq_pubsub_owner(Host, ServerHost, From,
1072 IQType, SubEl, Lang)
1073 of
1074 {result, IQRes} ->
1075 233 jlib:iq_to_xml(IQ#iq{type = result, sub_el = IQRes});
1076 {error, {Error, NewPayload}} ->
1077 2 make_error_reply(Packet#xmlel{ children = NewPayload }, Error);
1078 {error, Error} ->
1079 20 make_error_reply(Packet, Error)
1080 end,
1081 255 ejabberd_router:route(To, From, Acc, Res);
1082 #iq{type = get, xmlns = (?NS_VCARD) = XMLNS, lang = Lang, sub_el = _SubEl} = IQ ->
1083
:-(
Res = IQ#iq{type = result,
1084 sub_el =
1085 [#xmlel{name = <<"vCard">>,
1086 attrs = [{<<"xmlns">>, XMLNS}],
1087 children = iq_get_vcard(Lang)}]},
1088
:-(
ejabberd_router:route(To, From, Acc, jlib:iq_to_xml(Res));
1089 #iq{type = set, xmlns = ?NS_COMMANDS} = IQ ->
1090 6 Res = case iq_command(Host, ServerHost, From, IQ, Access, Plugins) of
1091 {error, Error} ->
1092 2 make_error_reply(Packet, Error);
1093 {result, IQRes} ->
1094 4 jlib:iq_to_xml(IQ#iq{type = result, sub_el = IQRes})
1095 end,
1096 6 ejabberd_router:route(To, From, Acc, Res);
1097 #iq{} ->
1098
:-(
Err = make_error_reply(Packet, mongoose_xmpp_errors:feature_not_implemented()),
1099
:-(
ejabberd_router:route(To, From, Acc, Err);
1100 _ ->
1101
:-(
Acc
1102 end;
1103 do_route(Acc, _ServerHost, _Access, _Plugins, Host, From,
1104 #jid{luser = <<>>, lresource = <<>>} = To,
1105 #xmlel{ name = <<"message">> } = Packet) ->
1106 5 case exml_query:attr(Packet, <<"type">>) of
1107 <<"error">> ->
1108 1 ok;
1109 _ ->
1110 4 case find_authorization_response(Packet) of
1111 none ->
1112
:-(
Acc;
1113 invalid ->
1114
:-(
Err = make_error_reply(Packet, mongoose_xmpp_errors:bad_request()),
1115
:-(
ejabberd_router:route(To, From, Acc, Err);
1116 XFields ->
1117 4 handle_authorization_response(Acc, Host, From, To, Packet, XFields)
1118 end
1119 end;
1120 do_route(Acc, _ServerHost, _Access, _Plugins, _Host, _From,
1121 #jid{luser = <<>>, lresource = <<>>} = _To, _Packet) ->
1122
:-(
Acc;
1123 do_route(Acc, _ServerHost, _Access, _Plugins, _Host, From, To, Packet) ->
1124
:-(
case exml_query:attr(Packet, <<"type">>) of
1125 <<"error">> ->
1126
:-(
Acc;
1127 <<"result">> ->
1128
:-(
Acc;
1129 _ ->
1130
:-(
Err = make_error_reply(Packet, mongoose_xmpp_errors:item_not_found()),
1131
:-(
ejabberd_router:route(To, From, Acc, Err)
1132 end.
1133
1134 command_disco_info(_Host, ?NS_COMMANDS, _From) ->
1135
:-(
IdentityEl = #xmlel{name = <<"identity">>,
1136 attrs = [{<<"category">>, <<"automation">>},
1137 {<<"type">>, <<"command-list">>}]},
1138
:-(
{result, [IdentityEl]};
1139 command_disco_info(_Host, ?NS_PUBSUB_GET_PENDING, _From) ->
1140
:-(
IdentityEl = #xmlel{name = <<"identity">>,
1141 attrs = [{<<"category">>, <<"automation">>},
1142 {<<"type">>, <<"command-node">>}]},
1143
:-(
FeaturesEl = #xmlel{name = <<"feature">>,
1144 attrs = [{<<"var">>, ?NS_COMMANDS}]},
1145
:-(
{result, [IdentityEl, FeaturesEl]}.
1146
1147 node_disco_info(Host, Node, From) ->
1148
:-(
node_disco_info(Host, Node, From, true, true).
1149
1150 node_disco_info(Host, Node, _From, _Identity, _Features) ->
1151
:-(
Action = fun (#pubsub_node{type = Type, options = Options}) ->
1152
:-(
NodeType = case get_option(Options, node_type) of
1153
:-(
collection -> <<"collection">>;
1154
:-(
_ -> <<"leaf">>
1155 end,
1156
:-(
I = #xmlel{name = <<"identity">>,
1157 attrs = [{<<"category">>, <<"pubsub">>},
1158 {<<"type">>, NodeType}]},
1159
:-(
F = [#xmlel{name = <<"feature">>,
1160 attrs = [{<<"var">>, ?NS_PUBSUB}]}
1161
:-(
| [#xmlel{name = <<"feature">>,
1162 attrs = [{<<"var">>, feature(F)}]}
1163
:-(
|| F <- plugin_features(Type)]],
1164
:-(
{result, [I | F]}
1165 end,
1166
:-(
case dirty(Host, Node, Action, ?FUNCTION_NAME) of
1167
:-(
{result, {_, Result}} -> {result, Result};
1168
:-(
Other -> Other
1169 end.
1170
1171 iq_disco_info(ServerHost, Host, SNode, From, Lang) ->
1172 4 [Node | _] = case SNode of
1173 4 <<>> -> [<<>>];
1174
:-(
_ -> mongoose_bin:tokens(SNode, <<"!">>)
1175 end,
1176 4 case Node of
1177 <<>> ->
1178 4 Identities = identities(ServerHost, Lang),
1179 4 Features = [?NS_DISCO_INFO,
1180 ?NS_DISCO_ITEMS,
1181 ?NS_PUBSUB,
1182 ?NS_COMMANDS,
1183 151 ?NS_VCARD] ++ [feature(F) || F <- features(ServerHost, Node)],
1184 4 {result, mongoose_disco:identities_to_xml(Identities) ++
1185 mongoose_disco:features_to_xml(Features)};
1186 ?NS_COMMANDS ->
1187
:-(
command_disco_info(Host, Node, From);
1188 ?NS_PUBSUB_GET_PENDING ->
1189
:-(
command_disco_info(Host, Node, From);
1190 _ ->
1191
:-(
node_disco_info(Host, Node, From)
1192 end.
1193
1194 -spec iq_disco_items(
1195 Host :: mod_pubsub:host(),
1196 Node :: <<>> | mod_pubsub:nodeId(),
1197 From ::jid:jid(),
1198 Rsm :: none | jlib:rsm_in())
1199 -> {result, [exml:element()]} | {error, term()}.
1200 iq_disco_items(Host, <<>>, From, _RSM) ->
1201 8 {result,
1202 lists:map(fun (#pubsub_node{nodeid = {_, SubNode}, options = Options}) ->
1203 98 Attrs = case get_option(Options, title) of
1204 false ->
1205 98 [{<<"jid">>, Host}
1206 | node_attr(SubNode)];
1207 [Title] ->
1208
:-(
[{<<"jid">>, Host},
1209 {<<"name">>, Title}
1210 | node_attr(SubNode)]
1211 end,
1212 98 #xmlel{name = <<"item">>, attrs = Attrs}
1213 end,
1214 tree_action(Host, get_subnodes, [Host, <<>>, From]))};
1215 iq_disco_items(Host, ?NS_COMMANDS, _From, _RSM) ->
1216
:-(
{result, [#xmlel{name = <<"item">>,
1217 attrs = [{<<"jid">>, Host},
1218 {<<"node">>, ?NS_PUBSUB_GET_PENDING},
1219 {<<"name">>, <<"Get Pending">>}]}]};
1220 iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From, _RSM) ->
1221
:-(
{result, []};
1222 iq_disco_items(Host, Item, From, RSM) ->
1223 7 case mongoose_bin:tokens(Item, <<"!">>) of
1224 [_Node, _ItemId] ->
1225
:-(
{result, []};
1226 [Node] ->
1227 7 Action = fun (PubSubNode) ->
1228 4 iq_disco_items_transaction(Host, From, Node, RSM, PubSubNode)
1229 end,
1230 7 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
1231 4 {result, {_, Result}} -> {result, Result};
1232 3 Other -> Other
1233 end
1234 end.
1235
1236 iq_disco_items_transaction(Host, From, Node, RSM,
1237 #pubsub_node{id = Nidx, type = Type, options = Options, owners = Owners}) ->
1238 4 {NodeItems, RsmOut} = case get_allowed_items_call(Host, Nidx,
1239 From, Type, Options, Owners, RSM)
1240 of
1241 4 {result, R} -> R;
1242
:-(
_ -> {[], none}
1243 end,
1244 4 Nodes = lists:map(fun (#pubsub_node{nodeid = {_, SubNode}, options = SubOptions}) ->
1245 3 Attrs = case get_option(SubOptions, title) of
1246 false ->
1247 3 [{<<"jid">>, Host}
1248 | node_attr(SubNode)];
1249 [Title] ->
1250
:-(
[{<<"jid">>, Host},
1251 {<<"name">>, Title}
1252 | node_attr(SubNode)]
1253 end,
1254 3 #xmlel{name = <<"item">>, attrs = Attrs}
1255 end,
1256 tree_call(Host, get_subnodes, [Host, Node, From])),
1257 4 Items = lists:map(fun (#pubsub_item{itemid = {RN, _}}) ->
1258
:-(
{result, Name} = node_call(Type, get_item_name,
1259 [Host, Node, RN]),
1260
:-(
#xmlel{name = <<"item">>,
1261 attrs = [{<<"jid">>, Host}, {<<"name">>, Name}]}
1262 end,
1263 NodeItems),
1264 4 {result, Nodes ++ Items ++ jlib:rsm_encode(RsmOut)}.
1265
1266 -spec iq_sm(From ::jid:jid(),
1267 To ::jid:jid(),
1268 Acc :: mongoose_acc:t(),
1269 IQ :: jlib:iq())
1270 -> {mongoose_acc:t(), jlib:iq()}.
1271 iq_sm(From, To, Acc, #iq{type = Type, sub_el = SubEl, xmlns = XMLNS, lang = Lang} = IQ) ->
1272 42 ServerHost = To#jid.lserver,
1273 42 LOwner = jid:to_lower(jid:to_bare(To)),
1274 42 Res = case XMLNS of
1275 ?NS_PUBSUB ->
1276 37 iq_pubsub(LOwner, ServerHost, From, Type, SubEl, Lang);
1277 ?NS_PUBSUB_OWNER ->
1278 5 iq_pubsub_owner(LOwner, ServerHost, From, Type, SubEl, Lang)
1279 end,
1280 42 case Res of
1281 37 {result, IQRes} -> {Acc, IQ#iq{type = result, sub_el = IQRes}};
1282 5 {error, Error} -> {Acc, make_error_reply(IQ, Error)}
1283 end.
1284
1285 iq_get_vcard(Lang) ->
1286
:-(
Desc = <<(translate:translate(Lang, <<"ejabberd Publish-Subscribe module">>))/binary,
1287 "\nCopyright (c) 2004-2015 ProcessOne">>,
1288
:-(
[#xmlel{name = <<"FN">>, attrs = [],
1289 children = [#xmlcdata{content = <<"ejabberd/mod_pubsub">>}]},
1290 #xmlel{name = <<"URL">>, attrs = [],
1291 children = [#xmlcdata{content = ?MONGOOSE_URI}]},
1292 #xmlel{name = <<"DESC">>, attrs = [],
1293 children = [#xmlcdata{content = Desc}]}].
1294
1295 -spec iq_pubsub(Host :: mod_pubsub:host(),
1296 ServerHost :: binary(),
1297 From ::jid:jid(),
1298 IQType :: get | set,
1299 QueryEl :: exml:element(),
1300 Lang :: binary()) -> {result, [exml:element()]} | {error, exml:element()}.
1301 iq_pubsub(Host, ServerHost, From, IQType, QueryEl, Lang) ->
1302 37 iq_pubsub(Host, ServerHost, From, IQType, QueryEl, Lang, all, plugins(ServerHost)).
1303
1304 -spec iq_pubsub(Host :: mod_pubsub:host(),
1305 ServerHost :: binary(),
1306 From ::jid:jid(),
1307 IQType :: 'get' | 'set',
1308 QueryEl :: exml:element(),
1309 Lang :: binary(),
1310 Access :: atom(),
1311 Plugins :: [binary(), ...]) -> {result, [exml:element()]} | {error, exml:element()}.
1312 iq_pubsub(Host, ServerHost, From, IQType, #xmlel{children = SubEls} = QueryEl,
1313 Lang, Access, Plugins) ->
1314 586 case xml:remove_cdata(SubEls) of
1315 [#xmlel{name = Name} = ActionEl | _] ->
1316 586 report_iq_action_metrics_before_result(ServerHost, IQType, Name),
1317 586 Node = exml_query:attr(ActionEl, <<"node">>, <<>>),
1318 586 {Time, Result} = timer:tc(fun iq_pubsub_action/6,
1319 [IQType, Name, Host, Node, From,
1320 #{server_host => ServerHost,
1321 plugins => Plugins,
1322 access => Access,
1323 action_el => ActionEl,
1324 query_el => QueryEl,
1325 lang => Lang}]),
1326 586 report_iq_action_metrics_after_return(ServerHost, Result, Time, IQType, Name),
1327 586 Result;
1328 Other ->
1329
:-(
?LOG_INFO(#{what => pubsub_bad_request, exml_packet => Other}),
1330
:-(
{error, mongoose_xmpp_errors:bad_request()}
1331 end.
1332
1333 iq_pubsub_action(IQType, Name, Host, Node, From, ExtraArgs) ->
1334 586 case {IQType, Name} of
1335 {set, <<"create">>} ->
1336 216 iq_pubsub_set_create(Host, Node, From, ExtraArgs);
1337 {set, <<"publish">>} ->
1338 204 iq_pubsub_set_publish(Host, Node, From, ExtraArgs);
1339 {set, <<"retract">>} ->
1340 8 iq_pubsub_set_retract(Host, Node, From, ExtraArgs);
1341 {set, <<"subscribe">>} ->
1342 92 iq_pubsub_set_subscribe(Host, Node, From, ExtraArgs);
1343 {set, <<"unsubscribe">>} ->
1344 5 iq_pubsub_set_unsubscribe(Host, Node, From, ExtraArgs);
1345 {get, <<"items">>} ->
1346 33 iq_pubsub_get_items(Host, Node, From, ExtraArgs);
1347 {get, <<"subscriptions">>} ->
1348 10 get_subscriptions(Host, Node, From, ExtraArgs);
1349 {get, <<"affiliations">>} ->
1350
:-(
get_affiliations(Host, Node, From, ExtraArgs);
1351 {get, <<"options">>} ->
1352 14 iq_pubsub_get_options(Host, Node, From, ExtraArgs);
1353 {set, <<"options">>} ->
1354 4 iq_pubsub_set_options(Host, Node, ExtraArgs);
1355 _ ->
1356
:-(
{error, mongoose_xmpp_errors:feature_not_implemented()}
1357 end.
1358
1359 ensure_metrics(Host) ->
1360 39 [mongoose_metrics:ensure_metric(Host, metric_name(IQType, Name, MetricSuffix), Type) ||
1361 39 {IQType, Name} <- all_metrics(),
1362 663 {MetricSuffix, Type} <- [{count, spiral},
1363 {errors, spiral},
1364 {time, histogram}]].
1365
1366 all_metrics() ->
1367 39 [{set, create},
1368 {set, publish},
1369 {set, retract},
1370 {set, subscribe},
1371 {set, unsubscribe},
1372 {get, items},
1373 {get, options},
1374 {set, options},
1375 {get, configure},
1376 {set, configure},
1377 {get, default},
1378 {set, delete},
1379 {set, purge},
1380 {get, subscriptions},
1381 {set, subscriptions},
1382 {get, affiliations},
1383 {set, affiliations}].
1384
1385 432 iq_action_to_metric_name(<<"create">>) -> create;
1386 408 iq_action_to_metric_name(<<"publish">>) -> publish;
1387 16 iq_action_to_metric_name(<<"retract">>) -> retract;
1388 184 iq_action_to_metric_name(<<"subscribe">>) -> subscribe;
1389 10 iq_action_to_metric_name(<<"unsubscribe">>) -> unsubscribe;
1390 66 iq_action_to_metric_name(<<"items">>) -> items;
1391 36 iq_action_to_metric_name(<<"options">>) -> options;
1392 42 iq_action_to_metric_name(<<"configure">>) -> configure;
1393 4 iq_action_to_metric_name(<<"default">>) -> default;
1394 302 iq_action_to_metric_name(<<"delete">>) -> delete;
1395 16 iq_action_to_metric_name(<<"purge">>) -> purge;
1396 52 iq_action_to_metric_name(<<"subscriptions">>) -> subscriptions;
1397 120 iq_action_to_metric_name(<<"affiliations">>) -> affiliations.
1398
1399
1400 metric_name(IQType, Name, MetricSuffix) when is_binary(Name) ->
1401 1688 NameAtom = iq_action_to_metric_name(Name),
1402 1688 metric_name(IQType, NameAtom, MetricSuffix);
1403 metric_name(IQType, Name, MetricSuffix) when is_atom(Name) ->
1404 3677 [pubsub, IQType, Name, MetricSuffix].
1405
1406 report_iq_action_metrics_before_result(Host, IQType, Name) ->
1407 844 mongoose_metrics:update(Host, metric_name(IQType, Name, count), 1).
1408
1409 report_iq_action_metrics_after_return(Host, Result, Time, IQType, Name) ->
1410 844 case Result of
1411 {error, _} ->
1412 57 mongoose_metrics:update(Host, metric_name(IQType, Name, erros), 1);
1413 _ ->
1414 787 mongoose_metrics:update(Host, metric_name(IQType, Name, time), Time)
1415 end.
1416
1417 iq_pubsub_set_create(Host, Node, From,
1418 #{server_host := ServerHost, access := Access, plugins := Plugins,
1419 action_el := CreateEl, query_el := QueryEl}) ->
1420 216 Config = exml_query:subelement(QueryEl, <<"configure">>),
1421 216 Type = exml_query:attr(CreateEl, <<"type">>, hd(Plugins)),
1422 216 case lists:member(Type, Plugins) of
1423 false ->
1424
:-(
{error,
1425 extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"create-nodes">>)};
1426 true ->
1427 216 create_node(Host, ServerHost, Node, From, Type, Access, Config)
1428 end.
1429
1430 iq_pubsub_set_publish(_Host, <<>>, _From, _ExtraArgs) ->
1431 2 {error, extended_error(mongoose_xmpp_errors:bad_request(), <<"nodeid-required">>)};
1432 iq_pubsub_set_publish(Host, Node, From, #{server_host := ServerHost, access := Access,
1433 action_el := ActionEl, query_el := QueryEl}) ->
1434 202 case xml:remove_cdata(ActionEl#xmlel.children) of
1435 [#xmlel{name = <<"item">>, attrs = ItemAttrs, children = Payload}] ->
1436 202 ItemId = xml:get_attr_s(<<"id">>, ItemAttrs),
1437 202 PublishOptions = exml_query:subelement(QueryEl, <<"publish-options">>),
1438 202 publish_item(Host, ServerHost, Node, From, ItemId,
1439 Payload, Access, PublishOptions);
1440 [] ->
1441
:-(
{error, extended_error(mongoose_xmpp_errors:bad_request(), <<"item-required">>)};
1442 _ ->
1443
:-(
{error, extended_error(mongoose_xmpp_errors:bad_request(), <<"invalid-payload">>)}
1444 end.
1445
1446 iq_pubsub_set_retract(Host, Node, From,
1447 #{action_el := #xmlel{attrs = RetractAttrs, children = RetractSubEls}}) ->
1448 8 ForceNotify = case xml:get_attr_s(<<"notify">>, RetractAttrs) of
1449
:-(
<<"1">> -> true;
1450
:-(
<<"true">> -> true;
1451 8 _ -> false
1452 end,
1453 8 case xml:remove_cdata(RetractSubEls) of
1454 [#xmlel{name = <<"item">>, attrs = ItemAttrs}] ->
1455 8 ItemId = xml:get_attr_s(<<"id">>, ItemAttrs),
1456 8 delete_item(Host, Node, From, ItemId, ForceNotify);
1457 _ ->
1458
:-(
{error,
1459 extended_error(mongoose_xmpp_errors:bad_request(), <<"item-required">>)}
1460 end.
1461
1462 iq_pubsub_set_subscribe(Host, Node, From, #{query_el := QueryEl,
1463 action_el := #xmlel{attrs = SubscribeAttrs}}) ->
1464 92 ConfigXForm = case exml_query:subelement(QueryEl, <<"options">>) of
1465 82 undefined -> undefined;
1466 10 Options -> mongoose_data_forms:find_form(Options)
1467 end,
1468 92 JID = xml:get_attr_s(<<"jid">>, SubscribeAttrs),
1469 92 subscribe_node(Host, Node, From, JID, ConfigXForm).
1470
1471 iq_pubsub_set_unsubscribe(Host, Node, From, #{action_el := #xmlel{attrs = UnsubscribeAttrs}}) ->
1472 5 JID = xml:get_attr_s(<<"jid">>, UnsubscribeAttrs),
1473 5 SubId = xml:get_attr_s(<<"subid">>, UnsubscribeAttrs),
1474 5 unsubscribe_node(Host, Node, From, JID, SubId).
1475
1476 iq_pubsub_get_items(Host, Node, From,
1477 #{query_el := QueryEl,
1478 action_el := #xmlel{attrs = GetItemsAttrs, children = GetItemsSubEls}}) ->
1479 33 MaxItems = xml:get_attr_s(<<"max_items">>, GetItemsAttrs),
1480 33 SubId = xml:get_attr_s(<<"subid">>, GetItemsAttrs),
1481 33 ItemIds = extract_item_ids(GetItemsSubEls),
1482 33 get_items(Host, Node, From, SubId, MaxItems, ItemIds, jlib:rsm_decode(QueryEl)).
1483
1484 extract_item_ids(GetItemsSubEls) ->
1485 33 case lists:foldl(fun extract_item_id/2, [], GetItemsSubEls) of
1486 [] ->
1487 25 undefined;
1488 List ->
1489 8 List
1490 end.
1491
1492 extract_item_id(#xmlel{name = <<"item">>} = Item, Acc) ->
1493 8 case exml_query:attr(Item, <<"id">>) of
1494
:-(
undefined -> Acc;
1495 8 ItemId -> [ItemId | Acc]
1496 end;
1497
:-(
extract_item_id(_, Acc) -> Acc.
1498
1499
1500 iq_pubsub_get_options(Host, Node, Lang, #{action_el := #xmlel{attrs = GetOptionsAttrs}}) ->
1501 14 SubId = xml:get_attr_s(<<"subid">>, GetOptionsAttrs),
1502 14 JID = xml:get_attr_s(<<"jid">>, GetOptionsAttrs),
1503 14 get_options(Host, Node, JID, SubId, Lang).
1504
1505 iq_pubsub_set_options(Host, Node, #{action_el := #xmlel{attrs = SetOptionsAttrs} = ActionEl}) ->
1506 4 XForm = mongoose_data_forms:find_form(ActionEl),
1507 4 SubId = xml:get_attr_s(<<"subid">>, SetOptionsAttrs),
1508 4 JID = xml:get_attr_s(<<"jid">>, SetOptionsAttrs),
1509 4 set_options(Host, Node, JID, SubId, XForm).
1510
1511 -spec iq_pubsub_owner(
1512 Host :: mod_pubsub:host(),
1513 ServerHost :: binary(),
1514 From ::jid:jid(),
1515 IQType :: 'get' | 'set',
1516 SubEl :: exml:element(),
1517 Lang :: binary())
1518 -> {result, [exml:element()]}
1519 | {error, exml:element() | [exml:element()] | {exml:element(), [exml:element()]}}.
1520 iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) ->
1521 260 #xmlel{children = SubEls} = SubEl,
1522 260 Action = xml:remove_cdata(SubEls),
1523 260 case Action of
1524 [#xmlel{name = Name} = ActionEl] ->
1525 258 report_iq_action_metrics_before_result(ServerHost, IQType, Name),
1526 258 Node = exml_query:attr(ActionEl, <<"node">>, <<>>),
1527 258 {Time, Result} = timer:tc(fun iq_pubsub_owner_action/6,
1528 [IQType, Name, Host, From, Node,
1529 #{server_host => ServerHost,
1530 action_el => ActionEl,
1531 lang => Lang}]),
1532 258 report_iq_action_metrics_after_return(ServerHost, Result, Time, IQType, Name),
1533 258 Result;
1534 _ ->
1535 2 ?LOG_INFO(#{what => pubsub_too_many_actions, exml_packet => Action}),
1536 2 {error, mongoose_xmpp_errors:bad_request()}
1537 end.
1538
1539 iq_pubsub_owner_action(IQType, Name, Host, From, Node, ExtraParams) ->
1540 258 case {IQType, Name} of
1541 {get, <<"configure">>} ->
1542 7 get_configure(Host, Node, From, ExtraParams);
1543 {set, <<"configure">>} ->
1544 14 set_configure(Host, Node, From, ExtraParams);
1545 {get, <<"default">>} ->
1546 2 get_default(Host, Node, From, ExtraParams);
1547 {set, <<"delete">>} ->
1548 151 delete_node(Host, Node, From);
1549 {set, <<"purge">>} ->
1550 8 purge_node(Host, Node, From);
1551 {get, <<"subscriptions">>} ->
1552 10 get_subscriptions(Host, Node, From);
1553 {set, <<"subscriptions">>} ->
1554 6 set_subscriptions(Host, Node, From, ExtraParams);
1555 {get, <<"affiliations">>} ->
1556 8 get_affiliations(Host, Node, From);
1557 {set, <<"affiliations">>} ->
1558 52 set_affiliations(Host, Node, From, ExtraParams);
1559 _ ->
1560
:-(
{error, mongoose_xmpp_errors:feature_not_implemented()}
1561 end.
1562
1563 iq_command(Host, ServerHost, From, IQ, Access, Plugins) ->
1564 6 case adhoc:parse_request(IQ) of
1565 Req when is_record(Req, adhoc_request) ->
1566 6 case adhoc_request(Host, ServerHost, From, Req, Access, Plugins) of
1567 Resp when is_record(Resp, xmlel) ->
1568 4 {result, [Resp]};
1569 Error ->
1570 2 Error
1571 end;
1572
:-(
Err -> Err
1573 end.
1574
1575 %% @doc <p>Processes an Ad Hoc Command.</p>
1576 adhoc_request(Host, _ServerHost, Owner,
1577 Request = #adhoc_request{node = ?NS_PUBSUB_GET_PENDING,
1578 action = <<"execute">>,
1579 xdata = false},
1580 _Access, Plugins) ->
1581 2 send_pending_node_form(Request, Host, Owner, Plugins);
1582 adhoc_request(Host, _ServerHost, Owner,
1583 Request = #adhoc_request{node = ?NS_PUBSUB_GET_PENDING,
1584 action = <<"execute">>, xdata = XData},
1585 _Access, _Plugins) ->
1586 4 ParseOptions = adhoc_get_pending_parse_options(Host, XData),
1587 4 case ParseOptions of
1588 {result, XForm} ->
1589 2 case lists:keysearch(node, 1, XForm) of
1590 2 {value, {_, Node}} -> send_pending_auth_events(Request, Host, Node, Owner);
1591
:-(
false -> {error, extended_error(mongoose_xmpp_errors:bad_request(), <<"bad-payload">>)}
1592 end;
1593 2 Error -> Error
1594 end;
1595 adhoc_request(_Host, _ServerHost, _Owner,
1596 #adhoc_request{action = <<"cancel">>} = Request, _Access,
1597 _Plugins) ->
1598
:-(
adhoc:produce_response(Request, canceled);
1599 adhoc_request(Host, ServerHost, Owner,
1600 #adhoc_request{action = <<>>} = R, Access, Plugins) ->
1601
:-(
adhoc_request(Host, ServerHost, Owner,
1602 R#adhoc_request{action = <<"execute">>}, Access,
1603 Plugins);
1604 adhoc_request(_Host, _ServerHost, _Owner, Other, _Access, _Plugins) ->
1605
:-(
?LOG_DEBUG(#{what => pubsub_adhoc_request_error,
1606
:-(
text => <<"Couldn't process ad hoc command">>, command => Other}),
1607
:-(
{error, mongoose_xmpp_errors:item_not_found()}.
1608
1609 %% @doc <p>Sends the process pending subscriptions XForm for Host to Owner.</p>
1610 send_pending_node_form(Request, Host, Owner, Plugins) ->
1611 2 Filter = fun (Type) ->
1612 2 lists:member(<<"get-pending">>, plugin_features(Type))
1613 end,
1614 2 case lists:filter(Filter, Plugins) of
1615 [] ->
1616
:-(
{error, mongoose_xmpp_errors:feature_not_implemented()};
1617 Ps ->
1618 2 Options = get_pending_nodes(Host, Owner, Ps),
1619 2 Field = #{type => <<"list-single">>, var => <<"pubsub#node">>,
1620 options => lists:usort(Options)},
1621 2 Form = mongoose_data_forms:form(#{fields => [Field]}),
1622 2 adhoc:produce_response(Request, executing, <<"execute">>, [Form])
1623 end.
1624
1625 get_pending_nodes(Host, Owner, Plugins) ->
1626 2 Tr = fun (Type) ->
1627 2 case node_call(Type, get_pending_nodes, [Host, Owner]) of
1628 2 {result, Nodes} -> Nodes;
1629
:-(
_ -> []
1630 end
1631 end,
1632 2 Action = fun() -> {result, lists:flatmap(Tr, Plugins)} end,
1633 2 ErrorDebug = #{
1634 action => get_pending_nodes,
1635 pubsub_host => Host,
1636 owner => Owner,
1637 plugins => Plugins
1638 },
1639 2 case mod_pubsub_db_backend:dirty(Action, ErrorDebug) of
1640 2 {result, Res} -> Res;
1641
:-(
Err -> Err
1642 end.
1643
1644 adhoc_get_pending_parse_options(Host, XEl) ->
1645 4 case mongoose_data_forms:parse_form_fields(XEl) of
1646 #{type := <<"submit">>, kvs := KVs} ->
1647 2 case set_xoption(Host, maps:to_list(KVs), []) of
1648 2 NewOpts when is_list(NewOpts) -> {result, NewOpts};
1649
:-(
Err -> Err
1650 end;
1651 #{} ->
1652 2 {error, mongoose_xmpp_errors:bad_request(<<"en">>, <<"Invalid form type">>)}
1653 end.
1654
1655 %% @doc <p>Send a subscription approval form to Owner for all pending
1656 %% subscriptions on Host and Node.</p>
1657 send_pending_auth_events(Request, Host, Node, Owner) ->
1658 2 ?LOG_DEBUG(#{what => pubsub_sending_pending_auth_events,
1659 2 owner => jid:to_binary(Owner), sub_host => Host, pubsub_node => Node}),
1660 2 Action = fun(PubSubNode) ->
1661 2 get_node_subscriptions_transaction(Owner, PubSubNode)
1662 end,
1663 2 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
1664 {result, {N, Subs}} ->
1665 2 lists:foreach(fun
1666 4 ({J, pending, _SubId, _}) -> send_authorization_request(N, jid:make(J));
1667
:-(
(_) -> ok
1668 end,
1669 Subs),
1670 2 adhoc:produce_response(Request, undefined);
1671 Err ->
1672
:-(
Err
1673 end.
1674
1675 get_node_subscriptions_transaction(Owner, #pubsub_node{id = Nidx, type = Type}) ->
1676 2 case lists:member(<<"get-pending">>, plugin_features(Type)) of
1677 true ->
1678 2 case node_call(Type, get_affiliation, [Nidx, Owner]) of
1679 2 {result, owner} -> node_call(Type, get_node_subscriptions, [Nidx]);
1680
:-(
_ -> {error, mongoose_xmpp_errors:forbidden()}
1681 end;
1682 false ->
1683
:-(
{error, mongoose_xmpp_errors:feature_not_implemented()}
1684 end.
1685
1686 %%% authorization handling
1687
1688 send_authorization_request(#pubsub_node{nodeid = {Host, Node}, owners = Owners},
1689 Subscriber) ->
1690 13 Lang = <<"en">>,
1691 13 Title = translate:translate(Lang, <<"PubSub subscriber request">>),
1692 13 Instructions = translate:translate(Lang, <<"Choose whether to approve this entity's "
1693 "subscription.">>),
1694 13 Fields = [#{var => <<"pubsub#node">>,
1695 type => <<"text-single">>,
1696 label => translate:translate(Lang, <<"Node ID">>),
1697 values => [Node]},
1698 #{var => <<"pubsub#subscriber_jid">>,
1699 type => <<"jid-single">>,
1700 label => translate:translate(Lang, <<"Subscriber Address">>),
1701 values => [jid:to_binary(Subscriber)]},
1702 #{var => <<"pubsub#allow">>,
1703 type => <<"boolean">>,
1704 label => translate:translate(Lang, <<"Allow this Jabber ID to subscribe to "
1705 "this pubsub node?">>),
1706 values => [<<"false">>]}],
1707 13 Form = mongoose_data_forms:form(#{title => Title, instructions => Instructions,
1708 ns => ?NS_PUBSUB_SUB_AUTH, fields => Fields}),
1709 13 Stanza = #xmlel{name = <<"message">>,
1710 attrs = [{<<"id">>, mongoose_bin:gen_from_crypto()}],
1711 children = [Form]},
1712 13 lists:foreach(fun(Owner) ->
1713 13 ejabberd_router:route(service_jid(Host), jid:make(Owner), Stanza)
1714 end, Owners).
1715
1716 find_authorization_response(El) ->
1717 6 case mongoose_data_forms:find_form(El) of
1718 undefined ->
1719
:-(
none;
1720 Form ->
1721 6 case mongoose_data_forms:parse_form_fields(Form) of
1722 #{type := <<"submit">>, ns := ?NS_PUBSUB_SUB_AUTH, kvs := KVs} ->
1723 5 KVs;
1724 _ ->
1725 1 invalid
1726 end
1727 end.
1728
1729 %% @doc Send a message to JID with the supplied Subscription
1730 send_authorization_approval(Host, JID, SNode, Subscription) ->
1731 5 SubAttrs = case Subscription of
1732 %{S, SID} ->
1733 % [{<<"subscription">>, subscription_to_string(S)},
1734 % {<<"subid">>, SID}];
1735 S ->
1736 5 [{<<"subscription">>, subscription_to_string(S)}]
1737 end,
1738 5 Stanza = event_stanza(<<"subscription">>,
1739 [{<<"jid">>, jid:to_binary(JID)}
1740 | node_attr(SNode)]
1741 ++ SubAttrs),
1742 5 ejabberd_router:route(service_jid(Host), JID, Stanza).
1743
1744 handle_authorization_response(Acc, Host, From, To, Packet, XFields) ->
1745 5 case XFields of
1746 #{<<"pubsub#node">> := [Node],
1747 <<"pubsub#subscriber_jid">> := [SSubscriber],
1748 <<"pubsub#allow">> := [SAllow]} ->
1749 5 FromLJID = jid:to_lower(jid:to_bare(From)),
1750 5 Subscriber = jid:from_binary(SSubscriber),
1751 5 Allow = string_allow_to_boolean(SAllow),
1752 5 Action = fun (PubSubNode) ->
1753 5 handle_authorization_response_transaction(Host, FromLJID, Subscriber,
1754 Allow, Node, PubSubNode)
1755 end,
1756 5 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
1757 {error, Error} ->
1758
:-(
Err = make_error_reply(Packet, Error),
1759
:-(
ejabberd_router:route(To, From, Acc, Err);
1760 {result, {_, _NewSubscription}} ->
1761 %% XXX: notify about subscription state change, section 12.11
1762 5 Acc;
1763 _ ->
1764
:-(
Err = make_error_reply(Packet, mongoose_xmpp_errors:internal_server_error()),
1765
:-(
ejabberd_router:route(To, From, Acc, Err)
1766 end;
1767 _ ->
1768
:-(
Err = make_error_reply(Packet, mongoose_xmpp_errors:not_acceptable()),
1769
:-(
ejabberd_router:route(To, From, Acc, Err)
1770 end.
1771
1772
:-(
string_allow_to_boolean(<<"1">>) -> true;
1773 3 string_allow_to_boolean(<<"true">>) -> true;
1774 2 string_allow_to_boolean(_) -> false.
1775
1776 handle_authorization_response_transaction(Host, FromLJID, Subscriber, Allow, Node,
1777 #pubsub_node{type = Type, id = Nidx, owners = Owners}) ->
1778 5 case lists:member(FromLJID, Owners) of
1779 true ->
1780 5 {result, Subs} = node_call(Type, get_subscriptions, [Nidx, Subscriber]),
1781 5 update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs);
1782 false ->
1783
:-(
{error, mongoose_xmpp_errors:forbidden()}
1784 end.
1785
1786 update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) ->
1787 5 Sub = lists:filter(fun
1788 5 ({pending, _, _}) -> true;
1789
:-(
(_) -> false
1790 end,
1791 Subs),
1792 5 case Sub of
1793 [{pending, SubId, _}] ->
1794 5 NewSub = case Allow of
1795 3 true -> subscribed;
1796 2 false -> none
1797 end,
1798 5 node_call(Type, set_subscriptions, [Nidx, Subscriber, NewSub, SubId]),
1799 5 send_authorization_approval(Host, Subscriber, Node, NewSub),
1800 5 {result, ok};
1801 _ ->
1802
:-(
{error, mongoose_xmpp_errors:unexpected_request()}
1803 end.
1804
1805 -define(XFIELD(Type, Label, Var, Val),
1806 #{type => Type,
1807 label => translate:translate(Lang, Label),
1808 var => Var,
1809 values => [Val]}).
1810
1811 -define(BOOLXFIELD(Label, Var, Val),
1812 ?XFIELD(<<"boolean">>, Label, Var,
1813 case Val of
1814 true -> <<"1">>;
1815 _ -> <<"0">>
1816 end)).
1817
1818 -define(STRINGXFIELD(Label, Var, Val),
1819 ?XFIELD(<<"text-single">>, Label, Var, Val)).
1820
1821 -define(STRINGMXFIELD(Label, Var, Vals),
1822 #{type => <<"text-multi">>,
1823 label => translate:translate(Lang, Label),
1824 var => Var,
1825 values => Vals}).
1826
1827 -define(XFIELDOPT(Type, Label, Var, Val, Opts),
1828 #{type => Type,
1829 label => translate:translate(Lang, Label),
1830 var => Var,
1831 options => Opts,
1832 values => [Val]}).
1833
1834 -define(LISTXFIELD(Label, Var, Val, Opts),
1835 ?XFIELDOPT(<<"list-single">>, Label, Var, Val, Opts)).
1836
1837 -define(LISTMXFIELD(Label, Var, Vals, Opts),
1838 #{type => <<"list-multi">>,
1839 label => translate:translate(Lang, Label),
1840 var => Var,
1841 options => Opts,
1842 values => Vals}).
1843
1844 %% @doc <p>Create new pubsub nodes</p>
1845 %%<p>In addition to method-specific error conditions, there are several general reasons
1846 %% why the node creation request might fail:</p>
1847 %%<ul>
1848 %%<li>The service does not support node creation.</li>
1849 %%<li>Only entities that are registered with the service are allowed to create nodes
1850 %% but the requesting entity is not registered.</li>
1851 %%<li>The requesting entity does not have sufficient privileges to create nodes.</li>
1852 %%<li>The requested Node already exists.</li>
1853 %%<li>The request did not include a Node and "instant nodes" are not supported.</li>
1854 %%</ul>
1855 %%<p>Note: node creation is a particular case, error return code is evaluated at many places:</p>
1856 %%<ul>
1857 %%<li>iq_pubsub checks if service supports node creation (type exists)</li>
1858 %%<li>create_node checks if instant nodes are supported</li>
1859 %%<li>create_node asks node plugin if entity have sufficient privilege</li>
1860 %%<li>nodetree create_node checks if nodeid already exists</li>
1861 %%<li>node plugin create_node just sets default affiliation/subscription</li>
1862 %%</ul>
1863 %% @end
1864
1865 create_node(Host, ServerHost, Node, Owner, Type) ->
1866 2 create_node(Host, ServerHost, Node, Owner, Type, all, undefined).
1867
1868 -spec create_node(Host, ServerHost, Node, Owner, Type, Access, Configuration) -> R when
1869 Host :: mod_pubsub:host(),
1870 ServerHost :: binary(),
1871 Node :: <<>> | mod_pubsub:nodeId(),
1872 Owner :: jid:jid(),
1873 Type :: binary(),
1874 Access :: atom(),
1875 Configuration :: exml:element() | undefined,
1876 R :: {result, [exml:element(), ...]}
1877 | {error, exml:element()}.
1878 create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) ->
1879
:-(
case lists:member(<<"instant-nodes">>, plugin_features(Type)) of
1880 true ->
1881
:-(
Node = mongoose_bin:gen_from_crypto(),
1882
:-(
case create_node(Host, ServerHost, Node, Owner, Type, Access, Configuration) of
1883 {result, _} ->
1884
:-(
{result, [#xmlel{name = <<"pubsub">>,
1885 attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
1886 children = [#xmlel{name = <<"create">>,
1887 attrs = node_attr(Node)}]}]};
1888 Error ->
1889
:-(
Error
1890 end;
1891 false ->
1892
:-(
{error, extended_error(mongoose_xmpp_errors:not_acceptable(), <<"nodeid-required">>)}
1893 end;
1894 create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
1895 234 Type = select_type(ServerHost, Host, Node, GivenType),
1896 234 case parse_create_node_options(Host, Type, Configuration) of
1897 {result, NodeOptions} ->
1898 230 CreateNode = fun () ->
1899 230 create_node_transaction(Host, ServerHost, Node, Owner,
1900 Type, Access, NodeOptions)
1901 end,
1902 230 ErrorDebug = #{
1903 action => create_node,
1904 pubsub_host => Host,
1905 owner => Owner,
1906 node_name => Node },
1907 230 case mod_pubsub_db_backend:transaction(CreateNode, ErrorDebug) of
1908 {result, {Nidx, SubsByDepth, {Result, broadcast}}} ->
1909 229 broadcast_created_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth),
1910 229 create_node_reply(Node, Result);
1911 {result, {_Nidx, _SubsByDepth, Result}} ->
1912
:-(
create_node_reply(Node, Result);
1913 Error ->
1914 %% in case we change transaction to sync_dirty...
1915 %% node_call(Type, delete_node, [Host, Node]),
1916 %% tree_call(Host, delete_node, [Host, Node]),
1917 1 Error
1918 end;
1919 Error ->
1920 4 ?LOG_INFO(#{what => pubsub_bad_node_configuration,
1921 4 pubsub_node => Node, configuration => Configuration}),
1922 4 Error
1923 end.
1924
1925 parse_create_node_options(Host, Type, undefined) ->
1926 114 {result, node_options(Host, Type)};
1927 parse_create_node_options(Host, Type, Configuration) ->
1928 120 case mongoose_data_forms:find_and_parse_form(Configuration) of
1929 #{type := <<"submit">>, kvs := KVs} ->
1930 116 case set_xoption(Host, maps:to_list(KVs), node_options(Host, Type)) of
1931 114 NewOpts when is_list(NewOpts) -> {result, NewOpts};
1932 2 Err -> Err
1933 end;
1934 #{} ->
1935 2 {error, mongoose_xmpp_errors:bad_request(<<"en">>, <<"Invalid form type">>)};
1936 {error, _} ->
1937 2 {result, node_options(Host, Type)}
1938 end.
1939
1940 create_node_transaction(Host, ServerHost, Node, Owner, Type, Access, NodeOptions) ->
1941 230 Parent = get_parent(Type, Node),
1942 230 case node_call(Type, create_node_permission,
1943 [Host, ServerHost, Node, Parent, Owner, Access]) of
1944 {result, true} ->
1945 230 create_node_authorized_transaction(Host, Node, Parent, Owner, Type, NodeOptions);
1946 _ ->
1947
:-(
{error, mongoose_xmpp_errors:forbidden()}
1948 end.
1949
1950 get_parent(Type, Node) ->
1951 230 case node_call(Type, node_to_path, [Node]) of
1952 {result, [Node]} ->
1953 218 <<>>;
1954 {result, Path} ->
1955 12 element(2, node_call(Type, path_to_node, [lists:sublist(Path, length(Path)-1)]))
1956 end.
1957
1958 create_node_authorized_transaction(Host, Node, Parent, Owner, Type, NodeOptions) ->
1959 230 Parents = case Parent of
1960 219 <<>> -> [];
1961 11 _ -> [Parent]
1962 end,
1963 230 case tree_call(Host, create_node, [Host, Node, Type, Owner, NodeOptions, Parents]) of
1964 {ok, Nidx} ->
1965 229 SubsByDepth = get_node_subs_by_depth(Host, Node, Owner),
1966 229 case node_call(Type, create_node, [Nidx, Owner]) of
1967 229 {result, Result} -> {result, {Nidx, SubsByDepth, Result}};
1968
:-(
Error -> Error
1969 end;
1970 {error, {virtual, Nidx}} ->
1971
:-(
case node_call(Type, create_node, [Nidx, Owner]) of
1972
:-(
{result, Result} -> {result, {Nidx, [], Result}};
1973
:-(
Error -> Error
1974 end;
1975 Error ->
1976 1 Error
1977 end.
1978
1979 create_node_reply(Node, default) ->
1980 229 {result, create_node_make_reply(Node)};
1981 create_node_reply(_Node, Result) ->
1982
:-(
{result, Result}.
1983
1984 create_node_make_reply(Node) ->
1985 229 [#xmlel{name = <<"pubsub">>,
1986 attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
1987 children = [#xmlel{name = <<"create">>,
1988 attrs = node_attr(Node)}]}].
1989
1990 %% @doc <p>Delete specified node and all childs.</p>
1991 %%<p>There are several reasons why the node deletion request might fail:</p>
1992 %%<ul>
1993 %%<li>The requesting entity does not have sufficient privileges to delete the node.</li>
1994 %%<li>The node is the root collection node, which cannot be deleted.</li>
1995 %%<li>The specified node does not exist.</li>
1996 %%</ul>
1997 -spec delete_node(
1998 Host :: mod_pubsub:host(),
1999 Node :: mod_pubsub:nodeId(),
2000 Owner :: jid:jid())
2001 -> {result, [exml:element(), ...]} | {error, exml:element()}.
2002 delete_node(_Host, <<>>, _Owner) ->
2003
:-(
{error, mongoose_xmpp_errors:not_allowed()};
2004 delete_node(Host, Node, Owner) ->
2005 151 Action = fun (PubSubNode) -> delete_node_transaction(Host, Owner, Node, PubSubNode) end,
2006 151 case transaction(Host, Node, Action, ?FUNCTION_NAME) of
2007 {result, {_, {SubsByDepth, {Result, broadcast, Removed}}}} ->
2008 147 lists:foreach(fun ({RNode, _RSubs}) ->
2009 148 {RH, RN} = RNode#pubsub_node.nodeid,
2010 148 RNidx = RNode#pubsub_node.id,
2011 148 RType = RNode#pubsub_node.type,
2012 148 ROptions = RNode#pubsub_node.options,
2013 148 broadcast_removed_node(RH, RN, RNidx,
2014 RType, ROptions, SubsByDepth)
2015 end,
2016 Removed),
2017 147 case Result of
2018 147 default -> {result, []};
2019
:-(
_ -> {result, Result}
2020 end;
2021 {result, {_, {_, {Result, _}}}} ->
2022 4 case Result of
2023
:-(
default -> {result, []};
2024 4 _ -> {result, Result}
2025 end;
2026 Error ->
2027
:-(
Error
2028 end.
2029
2030 delete_node_transaction(Host, Owner, Node, #pubsub_node{type = Type, id = Nidx}) ->
2031 151 case node_call(Type, get_affiliation, [Nidx, Owner]) of
2032 {result, owner} ->
2033 151 SubsByDepth = get_node_subs_by_depth(Host, Node, service_jid(Host)),
2034 151 Removed = tree_call(Host, delete_node, [Host, Node]),
2035 151 case node_call(Type, delete_node, [Removed]) of
2036 151 {result, Res} -> {result, {SubsByDepth, Res}};
2037
:-(
Error -> Error
2038 end;
2039 _ ->
2040
:-(
{error, mongoose_xmpp_errors:forbidden()}
2041 end.
2042
2043 %% @see node_hometree:subscribe_node/5
2044 %% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
2045 %%<p>There are several reasons why the subscription request might fail:</p>
2046 %%<ul>
2047 %%<li>The bare JID portions of the JIDs do not match.</li>
2048 %%<li>The node has an access model of "presence" and the requesting entity
2049 %% is not subscribed to the owner's presence.</li>
2050 %%<li>The node has an access model of "roster" and the requesting entity
2051 %% is not in one of the authorized roster groups.</li>
2052 %%<li>The node has an access model of "whitelist"
2053 %% and the requesting entity is not on the whitelist.</li>
2054 %%<li>The service requires payment for subscriptions to the node.</li>
2055 %%<li>The requesting entity is anonymous and the service
2056 %% does not allow anonymous entities to subscribe.</li>
2057 %%<li>The requesting entity has a pending subscription.</li>
2058 %%<li>The requesting entity is blocked from subscribing
2059 %% (e.g., because having an affiliation of outcast).</li>
2060 %%<li>The node does not support subscriptions.</li>
2061 %%<li>The node does not exist.</li>
2062 %%</ul>
2063 -spec subscribe_node(
2064 Host :: mod_pubsub:host(),
2065 Node :: mod_pubsub:nodeId(),
2066 From ::jid:jid(),
2067 JID :: binary(),
2068 ConfigurationXForm :: exml:element() | undefined)
2069 -> {result, [exml:element(), ...]} | {error, exml:element()}.
2070 subscribe_node(Host, Node, From, JID, ConfigurationXForm) ->
2071 92 SubOpts = case pubsub_form_utils:parse_sub_xform(ConfigurationXForm) of
2072 92 {ok, GoodSubOpts} -> GoodSubOpts;
2073
:-(
_ -> invalid
2074 end,
2075 92 Subscriber = string_to_ljid(JID),
2076 92 Action = fun (PubSubNode) ->
2077 92 subscribe_node_transaction(Host, SubOpts, From, Subscriber, PubSubNode)
2078 end,
2079 92 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
2080 {result, {TNode, {Result, subscribed, SubId, send_last}}} ->
2081 8 Nidx = TNode#pubsub_node.id,
2082 8 Type = TNode#pubsub_node.type,
2083 8 Options = TNode#pubsub_node.options,
2084 8 send_items(Host, Node, Nidx, Type, Options, Subscriber, last),
2085 8 case Result of
2086 8 default -> {result, subscribe_node_reply(Subscriber, Node, {subscribed, SubId})};
2087
:-(
_ -> {result, Result}
2088 end;
2089 {result, {_TNode, {default, subscribed, SubId}}} ->
2090 71 {result, subscribe_node_reply(Subscriber, Node, {subscribed, SubId})};
2091 {result, {_TNode, {Result, subscribed, _SubId}}} ->
2092
:-(
{result, Result};
2093 {result, {TNode, {default, pending, _SubId}}} ->
2094 9 send_authorization_request(TNode, Subscriber),
2095 9 {result, subscribe_node_reply(Subscriber, Node, pending)};
2096 {result, {TNode, {Result, pending}}} ->
2097
:-(
send_authorization_request(TNode, Subscriber),
2098
:-(
{result, Result};
2099 {result, {_, Result}} ->
2100
:-(
{result, Result};
2101 4 Error -> Error
2102 end.
2103
2104 subscribe_node_transaction(Host, SubOpts, From, Subscriber, PubSubNode) ->
2105 92 Features = plugin_features(PubSubNode#pubsub_node.type),
2106 92 subscribe_node_transaction_step1(Host, SubOpts, From, Subscriber, PubSubNode, Features).
2107
2108 subscribe_node_transaction_step1(Host, SubOpts, From, Subscriber, PubSubNode, Features) ->
2109 92 case lists:member(<<"subscribe">>, Features) of
2110 false ->
2111
:-(
{error, extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"subscribe">>)};
2112 true ->
2113 92 subscribe_node_transaction_step2(Host, SubOpts, From, Subscriber, PubSubNode, Features)
2114 end.
2115
2116 subscribe_node_transaction_step2(Host, SubOpts, From, Subscriber, PubSubNode, Features) ->
2117 92 case get_option(PubSubNode#pubsub_node.options, subscribe) of
2118 false ->
2119
:-(
{error, extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"subscribe">>)};
2120 true ->
2121 92 subscribe_node_transaction_step3(Host, SubOpts, From, Subscriber, PubSubNode, Features)
2122 end.
2123
2124 subscribe_node_transaction_step3(Host, SubOpts, From, Subscriber, PubSubNode, Features) ->
2125 92 case {SubOpts /= [], lists:member(<<"subscription-options">>, Features)} of
2126 {true, false} ->
2127
:-(
{error,
2128 extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"subscription-options">>)};
2129 _ ->
2130 92 subscribe_node_transaction_step4(Host, SubOpts, From, Subscriber, PubSubNode)
2131 end.
2132
2133 subscribe_node_transaction_step4(_Host, invalid, _From, _Subscriber, _PubSubNode) ->
2134
:-(
{error, extended_error(mongoose_xmpp_errors:bad_request(), <<"invalid-options">>)};
2135 subscribe_node_transaction_step4(Host, SubOpts, From, Subscriber,
2136 #pubsub_node{options = Options, type = Type,
2137 id = Nidx, owners = Owners}) ->
2138 92 case check_subs_limit(Host, Type, Nidx) of
2139 true ->
2140 2 {error, extended_error(mongoose_xmpp_errors:not_allowed(), <<"closed-node">>)};
2141 false ->
2142 90 AccessModel = get_option(Options, access_model),
2143 90 SendLast = get_option(Options, send_last_published_item),
2144 90 AllowedGroups = get_option(Options, roster_groups_allowed, []),
2145
2146 90 {PS, RG} = get_presence_and_roster_permissions(Host, Subscriber,
2147 Owners, AccessModel, AllowedGroups),
2148 90 node_call(Type, subscribe_node,
2149 [Nidx, From, Subscriber, AccessModel,
2150 SendLast, PS, RG, SubOpts])
2151 end.
2152
2153 check_subs_limit(Host, Type, Nidx) ->
2154 92 case get_max_subscriptions_node(Host) of
2155 Max when is_integer(Max) ->
2156 4 case node_call(Type, get_node_subscriptions, [Nidx]) of
2157 4 {result, NodeSubs} -> count_subscribed(NodeSubs) >= Max;
2158
:-(
_ -> false
2159 end;
2160 _ ->
2161 88 false
2162 end.
2163
2164 count_subscribed(NodeSubs) ->
2165 4 lists:foldl(
2166 2 fun ({_JID, subscribed, _SubId, _Opts}, Acc) -> Acc+1;
2167
:-(
(_, Acc) -> Acc
2168 end, 0, NodeSubs).
2169
2170 subscribe_node_reply(Subscriber, Node, {subscribed, SubId}) ->
2171 79 SubAttrs = [{<<"subscription">>, subscription_to_string(subscribed)},
2172 {<<"subid">>, SubId}, {<<"node">>, Node}],
2173 79 subscribe_node_reply(Subscriber, SubAttrs);
2174 subscribe_node_reply(Subscriber, Node, Subscription) ->
2175 9 SubAttrs = [{<<"subscription">>, subscription_to_string(Subscription)},
2176 {<<"node">>, Node}],
2177 9 subscribe_node_reply(Subscriber, SubAttrs).
2178
2179 subscribe_node_reply(Subscriber, SubAttrs) ->
2180 88 [#xmlel{name = <<"pubsub">>,
2181 attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
2182 children = [#xmlel{name = <<"subscription">>,
2183 attrs = [{<<"jid">>, jid:to_binary(Subscriber)}
2184 | SubAttrs]}]}].
2185
2186 %% @doc <p>Unsubscribe <tt>JID</tt> from the <tt>Node</tt>.</p>
2187 %%<p>There are several reasons why the unsubscribe request might fail:</p>
2188 %%<ul>
2189 %%<li>The requesting entity has multiple subscriptions to the node
2190 %% but does not specify a subscription ID.</li>
2191 %%<li>The request does not specify an existing subscriber.</li>
2192 %%<li>The requesting entity does not have sufficient privileges
2193 %% to unsubscribe the specified JID.</li>
2194 %%<li>The node does not exist.</li>
2195 %%<li>The request specifies a subscription ID that is not valid or current.</li>
2196 %%</ul>
2197 -spec unsubscribe_node(
2198 Host :: mod_pubsub:host(),
2199 Node :: mod_pubsub:nodeId(),
2200 From ::jid:jid(),
2201 JID :: binary() | jid:ljid(),
2202 SubId :: mod_pubsub:subId())
2203 -> {result, []} | {error, exml:element()}.
2204 unsubscribe_node(Host, Node, From, JID, SubId) when is_binary(JID) ->
2205 5 unsubscribe_node(Host, Node, From, string_to_ljid(JID), SubId);
2206 unsubscribe_node(Host, Node, From, Subscriber, SubId) ->
2207 5 Action = fun (#pubsub_node{type = Type, id = Nidx}) ->
2208 5 node_call(Type, unsubscribe_node, [Nidx, From, Subscriber, SubId])
2209 end,
2210 5 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
2211 5 {result, {_, default}} -> {result, []};
2212 % {result, {_, Result}} -> {result, Result};
2213
:-(
Error -> Error
2214 end.
2215
2216 %% @doc <p>Publish item to a PubSub node.</p>
2217 %% <p>The permission to publish an item must be verified by the plugin implementation.</p>
2218 %%<p>There are several reasons why the publish request might fail:</p>
2219 %%<ul>
2220 %%<li>The requesting entity does not have sufficient privileges to publish.</li>
2221 %%<li>The node does not support item publication.</li>
2222 %%<li>The node does not exist.</li>
2223 %%<li>The payload size exceeds a service-defined limit.</li>
2224 %%<li>The item contains more than one payload element or the namespace of the root payload element
2225 %% does not match the configured namespace for the node.</li>
2226 %%<li>The request does not match the node configuration.</li>
2227 %%</ul>
2228 -spec publish_item(
2229 Host :: mod_pubsub:host(),
2230 ServerHost :: binary(),
2231 Node :: mod_pubsub:nodeId(),
2232 Publisher ::jid:jid(),
2233 ItemId :: <<>> | mod_pubsub:itemId(),
2234 Payload :: mod_pubsub:payload())
2235 -> {result, [exml:element(), ...]} | {error, exml:element()}.
2236 publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
2237 16 publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, all).
2238 publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access) ->
2239 16 publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access, undefined).
2240 publish_item(Host, ServerHost, Node, Publisher, <<>>, Payload, Access, PublishOptions) ->
2241 61 publish_item(Host, ServerHost, Node, Publisher, uniqid(), Payload, Access, PublishOptions);
2242 publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access, PublishOptions) ->
2243 218 ItemPublisher = config(serverhost(Host), item_publisher),
2244 218 Action =
2245 fun (#pubsub_node{options = Options, type = Type, id = Nidx}) ->
2246 202 Features = plugin_features(Type),
2247 202 PublishFeature = lists:member(<<"publish">>, Features),
2248 202 PubOptsFeature = lists:member(<<"publish-options">>, Features),
2249 202 PublishModel = get_option(Options, publish_model),
2250 202 DeliverPayloads = get_option(Options, deliver_payloads),
2251 202 PersistItems = get_option(Options, persist_items),
2252 202 MaxItems = max_items(Host, Options),
2253 202 PayloadCount = payload_xmlelements(Payload),
2254 202 PayloadSize = byte_size(term_to_binary(Payload)) - 2,
2255 202 PayloadMaxSize = get_option(Options, max_payload_size),
2256
2257 202 Errors = [ %% [{Condition :: boolean(), Reason :: term()}]
2258 {not PublishFeature,
2259 extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"publish">>)},
2260 202 {not PubOptsFeature andalso PublishOptions /= undefined,
2261 extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported,
2262 <<"publish-options">>)},
2263 {PayloadSize > PayloadMaxSize,
2264 extended_error(mongoose_xmpp_errors:not_acceptable(), <<"payload-too-big">>)},
2265 {(PayloadCount == 0) and (Payload == []),
2266 extended_error(mongoose_xmpp_errors:bad_request(), <<"payload-required">>)},
2267 {(PayloadCount > 1) or (PayloadCount == 0),
2268 extended_error(mongoose_xmpp_errors:bad_request(), <<"invalid-payload">>)},
2269 {(DeliverPayloads == false) and (PersistItems == false) and (PayloadSize > 0),
2270 extended_error(mongoose_xmpp_errors:bad_request(), <<"item-forbidden">>)},
2271 {((DeliverPayloads == true) or (PersistItems == true)) and (PayloadSize == 0),
2272 extended_error(mongoose_xmpp_errors:bad_request(), <<"item-required">>)},
2273 202 {PubOptsFeature andalso check_publish_options(Type, PublishOptions, Options),
2274 extended_error(mongoose_xmpp_errors:conflict(), <<"precondition-not-met">>)}
2275 ],
2276
2277 202 case lists:keyfind(true, 1, Errors) of
2278 {true, Reason} ->
2279 6 {error, Reason};
2280 false ->
2281 196 node_call(Type, publish_item,
2282 [ServerHost, Nidx, Publisher, PublishModel, MaxItems, ItemId,
2283 ItemPublisher, Payload, PublishOptions])
2284 end
2285 end,
2286 218 Reply = [#xmlel{name = <<"pubsub">>,
2287 attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
2288 children = [#xmlel{name = <<"publish">>, attrs = node_attr(Node),
2289 children = [#xmlel{name = <<"item">>,
2290 attrs = item_attr(ItemId)}]}]}],
2291 218 ErrorItemNotFound = mongoose_xmpp_errors:item_not_found(),
2292 218 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
2293 {result, {TNode, {Result, Broadcast, Removed}}} ->
2294 122 Nidx = TNode#pubsub_node.id,
2295 122 Type = TNode#pubsub_node.type,
2296 122 Options = TNode#pubsub_node.options,
2297 122 BrPayload = case Broadcast of
2298 122 broadcast -> Payload;
2299
:-(
PluginPayload -> PluginPayload
2300 end,
2301 122 set_cached_item(Host, Nidx, ItemId, Publisher, BrPayload),
2302 122 case get_option(Options, deliver_notifications) of
2303 true ->
2304 119 broadcast_publish_item(Host, Node, Nidx, Type, Options, ItemId,
2305 Publisher, BrPayload, Removed, ItemPublisher);
2306 false ->
2307 3 ok
2308 end,
2309 122 case Result of
2310 122 default -> {result, Reply};
2311
:-(
_ -> {result, Result}
2312 end;
2313 {result, {TNode, {default, Removed}}} ->
2314
:-(
Nidx = TNode#pubsub_node.id,
2315
:-(
Type = TNode#pubsub_node.type,
2316
:-(
Options = TNode#pubsub_node.options,
2317
:-(
broadcast_retract_items(Host, Node, Nidx, Type, Options, Removed),
2318
:-(
set_cached_item(Host, Nidx, ItemId, Publisher, Payload),
2319
:-(
{result, Reply};
2320 {result, {TNode, {Result, Removed}}} ->
2321
:-(
Nidx = TNode#pubsub_node.id,
2322
:-(
Type = TNode#pubsub_node.type,
2323
:-(
Options = TNode#pubsub_node.options,
2324
:-(
broadcast_retract_items(Host, Node, Nidx, Type, Options, Removed),
2325
:-(
set_cached_item(Host, Nidx, ItemId, Publisher, Payload),
2326
:-(
{result, Result};
2327 {result, {_, default}} ->
2328 62 {result, Reply};
2329 {result, {_, Result}} ->
2330
:-(
{result, Result};
2331 {error, ErrorItemNotFound} ->
2332 16 Type = select_type(ServerHost, Host, Node),
2333 16 autocreate_if_supported_and_publish(Host, ServerHost, Node, Publisher,
2334 Type, Access, ItemId, Payload, PublishOptions);
2335 Error ->
2336 18 Error
2337 end.
2338
2339 autocreate_if_supported_and_publish(Host, ServerHost, Node, Publisher,
2340 Type, Access, ItemId, Payload, PublishOptions) ->
2341 16 ErrorItemNotFound = mongoose_xmpp_errors:item_not_found(),
2342 16 case lists:member(<<"auto-create">>, plugin_features(Type)) of
2343 true ->
2344 16 case create_node(Host, ServerHost, Node, Publisher, Type, Access, PublishOptions) of
2345 {result,
2346 [#xmlel{name = <<"pubsub">>,
2347 attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
2348 children = [#xmlel{name = <<"create">>,
2349 attrs = [{<<"node">>, NewNode}]}]}]} ->
2350 16 publish_item(Host, ServerHost, NewNode, Publisher, ItemId, Payload);
2351 _ ->
2352
:-(
{error, ErrorItemNotFound}
2353 end;
2354 false ->
2355
:-(
{error, ErrorItemNotFound}
2356 end.
2357
2358 %% @doc <p>Delete item from a PubSub node.</p>
2359 %% <p>The permission to delete an item must be verified by the plugin implementation.</p>
2360 %%<p>There are several reasons why the item retraction request might fail:</p>
2361 %%<ul>
2362 %%<li>The publisher does not have sufficient privileges to delete the requested item.</li>
2363 %%<li>The node or item does not exist.</li>
2364 %%<li>The request does not specify a node.</li>
2365 %%<li>The request does not include an <item/> element
2366 %% or the <item/> element does not specify an ItemId.</li>
2367 %%<li>The node does not support persistent items.</li>
2368 %%<li>The service does not support the deletion of items.</li>
2369 %%</ul>
2370 -spec delete_item(
2371 Host :: mod_pubsub:host(),
2372 Node :: mod_pubsub:nodeId(),
2373 Publisher ::jid:jid(),
2374 ItemId :: mod_pubsub:itemId())
2375 -> {result, []} | {error, exml:element()}.
2376 delete_item(Host, Node, Publisher, ItemId) ->
2377
:-(
delete_item(Host, Node, Publisher, ItemId, false).
2378 delete_item(_, <<>>, _, _, _) ->
2379
:-(
{error, extended_error(mongoose_xmpp_errors:bad_request(), <<"node-required">>)};
2380 delete_item(Host, Node, Publisher, ItemId, ForceNotify) ->
2381 8 Action = fun(PubSubNode) -> delete_item_transaction(Publisher, ItemId, PubSubNode) end,
2382 8 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
2383 {result, {TNode, {Result, broadcast}}} ->
2384 6 Nidx = TNode#pubsub_node.id,
2385 6 Type = TNode#pubsub_node.type,
2386 6 Options = TNode#pubsub_node.options,
2387 6 broadcast_retract_items(Host, Node, Nidx, Type, Options, [ItemId], ForceNotify),
2388 6 case get_cached_item(Host, Nidx) of
2389
:-(
#pubsub_item{itemid = {ItemId, Nidx}} -> unset_cached_item(Host, Nidx);
2390 6 _ -> ok
2391 end,
2392 6 case Result of
2393 6 default -> {result, []};
2394
:-(
_ -> {result, Result}
2395 end;
2396 {result, {_, default}} ->
2397
:-(
{result, []};
2398 {result, {_, Result}} ->
2399
:-(
{result, Result};
2400 Error ->
2401 2 Error
2402 end.
2403
2404 delete_item_transaction(Publisher, ItemId,
2405 #pubsub_node{options = Options, type = Type, id = Nidx}) ->
2406 8 Features = plugin_features(Type),
2407 8 case lists:member(<<"persistent-items">>, Features) of
2408 true ->
2409 8 case lists:member(<<"delete-items">>, Features) of
2410 true ->
2411 8 PublishModel = get_option(Options, publish_model),
2412 8 node_call(Type, delete_item, [Nidx, Publisher, PublishModel, ItemId]);
2413 false ->
2414
:-(
{error,
2415 extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"delete-items">>)}
2416 end;
2417 false ->
2418
:-(
{error,
2419 extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"persistent-items">>)}
2420 end.
2421
2422 %% @doc <p>Delete all items of specified node owned by JID.</p>
2423 %%<p>There are several reasons why the node purge request might fail:</p>
2424 %%<ul>
2425 %%<li>The node or service does not support node purging.</li>
2426 %%<li>The requesting entity does not have sufficient privileges to purge the node.</li>
2427 %%<li>The node is not configured to persist items.</li>
2428 %%<li>The specified node does not exist.</li>
2429 %%</ul>
2430 -spec purge_node(
2431 Host :: mod_pubsub:host(),
2432 Node :: mod_pubsub:nodeId(),
2433 Owner :: jid:jid())
2434 -> {result, []} | {error, exml:element()}.
2435 purge_node(Host, Node, Owner) ->
2436 8 Action = fun (PubSubNode) -> purge_node_transaction(Owner, PubSubNode) end,
2437 8 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
2438 {result, {TNode, {Result, broadcast}}} ->
2439 4 Nidx = TNode#pubsub_node.id,
2440 4 Type = TNode#pubsub_node.type,
2441 4 Options = TNode#pubsub_node.options,
2442 4 broadcast_purge_node(Host, Node, Nidx, Type, Options),
2443 4 unset_cached_item(Host, Nidx),
2444 4 case Result of
2445 4 default -> {result, []};
2446
:-(
_ -> {result, Result}
2447 end;
2448 {result, {_, default}} ->
2449
:-(
{result, []};
2450 {result, {_, Result}} ->
2451
:-(
{result, Result};
2452 Error ->
2453 4 Error
2454 end.
2455
2456 purge_node_transaction(Owner, #pubsub_node{options = Options, type = Type, id = Nidx}) ->
2457 8 Features = plugin_features(Type),
2458 8 case {lists:member(<<"purge-nodes">>, Features),
2459 lists:member(<<"persistent-items">>, Features),
2460 get_option(Options, persist_items)} of
2461 {false, _, _} ->
2462
:-(
{error,
2463 extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"purge-nodes">>)};
2464 {_, false, _} ->
2465
:-(
{error,
2466 extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"persistent-items">>)};
2467 {_, _, false} ->
2468
:-(
{error,
2469 extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"persistent-items">>)};
2470 8 _ -> node_call(Type, purge_node, [Nidx, Owner])
2471 end.
2472
2473 %% @doc <p>Return the items of a given node.</p>
2474 %% <p>The number of items to return is limited by MaxItems.</p>
2475 %% <p>The permission are not checked in this function.</p>
2476 %% @todo We probably need to check that the user doing the query has the right
2477 %% to read the items.
2478 -spec get_items(Host :: mod_pubsub:host(),
2479 Node :: mod_pubsub:nodeId(),
2480 From ::jid:jid(),
2481 SubId :: mod_pubsub:subId(),
2482 SMaxItems :: binary(),
2483 ItemIds :: [mod_pubsub:itemId()],
2484 Rsm :: none | jlib:rsm_in()) -> {result, [exml:element(), ...]} | {error, exml:element()}.
2485 get_items(Host, Node, From, SubId, <<>>, ItemIds, RSM) ->
2486 33 MaxItems = case get_max_items_node(Host) of
2487
:-(
undefined -> ?MAXITEMS;
2488 33 Max -> Max
2489 end,
2490 33 get_items_with_limit(Host, Node, From, SubId, ItemIds, RSM, MaxItems);
2491 get_items(Host, Node, From, SubId, SMaxItems, ItemIds, RSM) ->
2492
:-(
MaxItems = case catch binary_to_integer(SMaxItems) of
2493
:-(
{'EXIT', _} -> {error, mongoose_xmpp_errors:bad_request()};
2494
:-(
Val -> Val
2495 end,
2496
:-(
get_items_with_limit(Host, Node, From, SubId, ItemIds, RSM, MaxItems).
2497
2498 get_items_with_limit(_Host, _Node, _From, _SubId, _ItemIds, _RSM, {error, _} = Err) ->
2499
:-(
Err;
2500 get_items_with_limit(Host, Node, From, SubId, ItemIds, RSM, MaxItems) ->
2501 33 Action = fun (PubSubNode) ->
2502 33 get_items_transaction(Host, From, RSM, SubId, PubSubNode, MaxItems, ItemIds)
2503 end,
2504 33 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
2505 {result, {_, {Items, RsmOut}}} ->
2506 33 {result,
2507 [#xmlel{name = <<"pubsub">>,
2508 attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
2509 children =
2510 [#xmlel{name = <<"items">>, attrs = node_attr(Node),
2511 children = items_els(Items)}
2512 | jlib:rsm_encode(RsmOut)]}]};
2513 Error ->
2514
:-(
Error
2515 end.
2516
2517 get_items_transaction(Host, From, RSM, SubId,
2518 #pubsub_node{options = Options, type = Type, id = Nidx, owners = Owners},
2519 MaxItems, ItemIds) ->
2520 33 Features = plugin_features(Type),
2521 33 case {lists:member(<<"retrieve-items">>, Features),
2522 lists:member(<<"persistent-items">>, Features)} of
2523 {false, _} ->
2524
:-(
{error,
2525 extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"retrieve-items">>)};
2526 {_, false} ->
2527
:-(
{error,
2528 extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"persistent-items">>)};
2529 _ ->
2530 33 AccessModel = get_option(Options, access_model),
2531 33 AllowedGroups = get_option(Options, roster_groups_allowed, []),
2532 33 {PS, RG} = get_presence_and_roster_permissions(Host, From, Owners,
2533 AccessModel, AllowedGroups),
2534 33 Opts = #{access_model => AccessModel,
2535 presence_permission => PS,
2536 roster_permission => RG,
2537 rsm => RSM,
2538 max_items => MaxItems,
2539 item_ids => ItemIds,
2540 subscription_id => SubId},
2541
2542 33 node_call(Type, get_items_if_authorised, [Nidx, From, Opts])
2543 end.
2544
2545 get_items(Host, Node) ->
2546 4 Action = fun (#pubsub_node{type = Type, id = Nidx}) ->
2547 2 node_call(Type, get_items, [Nidx, service_jid(Host), #{}])
2548 end,
2549 4 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
2550 2 {result, {_, {Items, _}}} -> Items;
2551 2 Error -> Error
2552 end.
2553
2554 get_item(Host, Node, ItemId) ->
2555 4 Action = fun (#pubsub_node{type = Type, id = Nidx}) ->
2556 4 node_call(Type, get_item, [Nidx, ItemId])
2557 end,
2558 4 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
2559 2 {result, {_, Items}} -> Items;
2560 2 Error -> Error
2561 end.
2562
2563 get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) ->
2564 1 case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, none) of
2565 1 {result, {Items, _RSM}} -> {result, Items};
2566
:-(
Error -> Error
2567 end.
2568 get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, RSM) ->
2569 5 AccessModel = get_option(Options, access_model),
2570 5 AllowedGroups = get_option(Options, roster_groups_allowed, []),
2571 5 {PS, RG} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups),
2572 5 Opts = #{access_model => AccessModel,
2573 presence_permission => PS,
2574 roster_permission => RG,
2575 rsm => RSM},
2576 5 node_call(Type, get_items_if_authorised, [Nidx, From, Opts]).
2577
2578 get_last_item(Host, Type, Nidx, LJID) ->
2579 14 case get_cached_item(Host, Nidx) of
2580 false ->
2581 9 case node_action(Host, Type, get_items, [Nidx, LJID, #{}]) of
2582 5 {result, {[LastItem|_], _}} -> LastItem;
2583 4 _ -> undefined
2584 end;
2585 LastItem ->
2586 5 LastItem
2587 end.
2588
2589 get_last_items(Host, Type, Nidx, LJID, Number) ->
2590
:-(
case node_action(Host, Type, get_items, [Nidx, LJID, #{}]) of
2591
:-(
{result, {Items, _}} -> lists:sublist(Items, Number);
2592
:-(
_ -> []
2593 end.
2594
2595 %% @doc <p>Resend the items of a node to the user.</p>
2596 %% @todo use cache-last-item feature
2597 send_items(Host, Node, Nidx, Type, Options, LJID, last) ->
2598 14 case get_last_item(Host, Type, Nidx, LJID) of
2599 undefined ->
2600 4 ok;
2601 LastItem ->
2602 10 Stanza = items_event_stanza(Node, [LastItem]),
2603 10 dispatch_items(Host, LJID, Options, Stanza)
2604 end;
2605 send_items(Host, Node, Nidx, Type, Options, LJID, Number) when Number > 0 ->
2606
:-(
Stanza = items_event_stanza(Node, get_last_items(Host, Type, Nidx, Number, LJID)),
2607
:-(
dispatch_items(Host, LJID, Options, Stanza);
2608 send_items(Host, Node, _Nidx, _Type, Options, LJID, _) ->
2609
:-(
Stanza = items_event_stanza(Node, []),
2610
:-(
dispatch_items(Host, LJID, Options, Stanza).
2611
2612 dispatch_items({_, FromS, _} = From, To, Options, Stanza) ->
2613 6 NotificationType = get_option(Options, notification_type, headline),
2614 6 Message = add_message_type(Stanza, NotificationType),
2615 6 {ok, HostType} = mongoose_domain_api:get_domain_host_type(FromS),
2616 6 FromJid = jid:make(From),
2617 6 ToJid = jid:make(To),
2618 6 AccParams = #{host_type => HostType, lserver => FromS, location => ?LOCATION,
2619 element => Message, from_jid => FromJid, to_jid => ToJid},
2620 6 Acc = mongoose_acc:new(AccParams),
2621 6 ejabberd_router:route(FromJid, ToJid, Acc);
2622 dispatch_items(From, To, Options, Stanza) ->
2623 4 NotificationType = get_option(Options, notification_type, headline),
2624 4 Message = add_message_type(Stanza, NotificationType),
2625 4 ejabberd_router:route(service_jid(From), jid:make(To), Message).
2626
2627 %% @doc <p>Return the list of affiliations as an XMPP response.</p>
2628 -spec get_affiliations(
2629 Host :: mod_pubsub:host(),
2630 Node :: mod_pubsub:nodeId(),
2631 JID :: jid:jid(),
2632 Plugins :: #{plugins := [binary()]})
2633 -> {result, [exml:element()]} | {error, exml:element()}.
2634 get_affiliations(Host, Node, JID, #{plugins := Plugins}) when is_list(Plugins) ->
2635
:-(
Result = lists:foldl(
2636 fun(Type, {Status, Acc}) ->
2637
:-(
Features = plugin_features(Type),
2638
:-(
case lists:member(<<"retrieve-affiliations">>, Features) of
2639 true ->
2640
:-(
{result, Affs} = node_action(Host, Type, get_entity_affiliations,
2641 [Host, JID]),
2642
:-(
{Status, [Affs | Acc]};
2643 false ->
2644
:-(
{{error,
2645 extended_error(mongoose_xmpp_errors:feature_not_implemented(),
2646 unsupported, <<"retrieve-affiliations">>)},
2647 Acc}
2648 end
2649 end, {ok, []}, Plugins),
2650
:-(
case Result of
2651 {ok, Affs} ->
2652
:-(
Entities = lists:flatmap(
2653 fun ({_, none}) ->
2654
:-(
[];
2655 ({#pubsub_node{nodeid = {_, NodeId}}, Aff}) when
2656 Node == <<>> orelse Node == NodeId ->
2657
:-(
[#xmlel{name = <<"affiliation">>,
2658 attrs = [{<<"affiliation">>, affiliation_to_string(Aff)}
2659 | node_attr(NodeId)]}];
2660 (_) ->
2661
:-(
[]
2662 end,
2663 lists:usort(lists:flatten(Affs))),
2664
:-(
{result,
2665 [#xmlel{name = <<"pubsub">>,
2666 attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
2667 children = [#xmlel{name = <<"affiliations">>, attrs = [],
2668 children = Entities}]}]};
2669 {Error, _} ->
2670
:-(
Error
2671 end.
2672
2673 -spec get_affiliations(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(), JID :: jid:jid()) ->
2674 {result, [exml:element(), ...]} | {error, exml:element()}.
2675 get_affiliations(Host, Node, JID) ->
2676 8 Action = fun (PubSubNode) -> get_affiliations_transaction(JID, PubSubNode) end,
2677 8 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
2678 {result, {_, []}} ->
2679
:-(
{error, mongoose_xmpp_errors:item_not_found()};
2680 {result, {_, Affs}} ->
2681 6 Entities =
2682 lists:flatmap(fun({_, none}) ->
2683
:-(
[];
2684 ({AJID, Aff}) ->
2685 6 [#xmlel{
2686 name = <<"affiliation">>,
2687 attrs = [{<<"jid">>, jid:to_binary(AJID)},
2688 {<<"affiliation">>, affiliation_to_string(Aff)}]}]
2689 end, Affs),
2690 6 {result,
2691 [#xmlel{name = <<"pubsub">>,
2692 attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}],
2693 children = [#xmlel{name = <<"affiliations">>,
2694 attrs = node_attr(Node), children = Entities}]}]};
2695 Error ->
2696 2 Error
2697 end.
2698
2699 get_affiliations_transaction(JID, #pubsub_node{type = Type, id = Nidx}) ->
2700 8 Features = plugin_features(Type),
2701 8 case lists:member(<<"modify-affiliations">>, Features) of
2702 true ->
2703 8 case node_call(Type, get_affiliation, [Nidx, JID]) of
2704 {result, owner} ->
2705 6 node_call(Type, get_node_affiliations, [Nidx]);
2706 _ ->
2707 2 {error, mongoose_xmpp_errors:forbidden()}
2708 end;
2709 false ->
2710
:-(
{error,
2711 extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"modify-affiliations">>)}
2712 end.
2713
2714 -spec set_affiliations(
2715 Host :: mod_pubsub:host(),
2716 Node :: mod_pubsub:nodeId(),
2717 From ::jid:jid(),
2718 EntitiesEls :: #{action_el := exml:element()})
2719 -> {result, []} | {error, exml:element() | {exml:element(), [exml:element()]}} | {error, exml:element()}.
2720 set_affiliations(Host, Node, From, #{action_el := ActionEl} ) ->
2721 52 EntitiesEls = xml:remove_cdata(ActionEl#xmlel.children),
2722 52 Owner = jid:to_lower(jid:to_bare(From)),
2723 52 Entities = lists:foldl(fun
2724 (_, error) ->
2725
:-(
error;
2726 (#xmlel{name = <<"affiliation">>, attrs = Attrs}, Acc) ->
2727 56 JID = jid:from_binary(xml:get_attr_s(<<"jid">>, Attrs)),
2728 56 Affiliation = string_to_affiliation(
2729 xml:get_attr_s(<<"affiliation">>, Attrs)),
2730 56 case (JID == error) or (Affiliation == false) of
2731
:-(
true -> error;
2732 56 false -> [{jid:to_lower(JID), Affiliation} | Acc]
2733 end;
2734 (_, _) ->
2735
:-(
error
2736 end,
2737 [], EntitiesEls),
2738 52 case Entities of
2739 error ->
2740
:-(
{error, mongoose_xmpp_errors:bad_request()};
2741 _ ->
2742 52 Action = fun (PubSubNode) ->
2743 52 set_affiliations_transaction(Host, Owner, PubSubNode, Entities)
2744 end,
2745 52 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
2746 50 {result, {_, Result}} -> {result, Result};
2747 2 Other -> Other
2748 end
2749 end.
2750
2751 set_affiliations_transaction(Host, Owner,
2752 #pubsub_node{owners = Owners, nodeid = {_, NodeId}} = N, Entities) ->
2753 52 case lists:member(Owner, Owners) of
2754 true ->
2755 % It is a very simple check, as XEP doesn't state any
2756 % other invalid affiliation transitions
2757 52 OwnersDryRun = lists:foldl(
2758 fun({JID, owner}, Acc) ->
2759 2 sets:add_element(jid:to_bare(JID), Acc);
2760 ({JID, _}, Acc) ->
2761 54 sets:del_element(jid:to_bare(JID), Acc)
2762 end, sets:from_list(Owners), Entities),
2763 52 case sets:size(OwnersDryRun) of
2764 0 ->
2765 2 OwnersPayload = [ #xmlel{ name = <<"affiliation">>,
2766 attrs = [{<<"jid">>, jid:to_binary(Unchanged)},
2767 {<<"affiliation">>, <<"owner">>}] }
2768 2 || Unchanged <- Owners ],
2769 2 AffiliationsPayload = #xmlel{ name = <<"affiliations">>,
2770 attrs = [{<<"node">>, NodeId}],
2771 children = OwnersPayload },
2772 2 NewPubSubPayload = #xmlel{ name = <<"pubsub">>,
2773 attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}],
2774 children = [AffiliationsPayload] },
2775 2 {error, {mongoose_xmpp_errors:not_acceptable(), [NewPubSubPayload]}};
2776 _ ->
2777 50 set_validated_affiliations_transaction(Host, N, Owners, Entities),
2778 50 {result, []}
2779 end;
2780 _ ->
2781
:-(
{error, mongoose_xmpp_errors:forbidden()}
2782 end.
2783
2784 set_validated_affiliations_transaction(Host, #pubsub_node{ type = Type, id = Nidx } = N,
2785 Owners, Entities) ->
2786 50 lists:foreach(fun ({JID, owner}) ->
2787 2 node_call(Type, set_affiliation, [Nidx, JID, owner]),
2788 2 NewOwner = jid:to_bare(JID),
2789 2 NewOwners = [NewOwner | Owners],
2790 2 tree_call(Host,
2791 set_node,
2792 [N#pubsub_node{owners = NewOwners}]);
2793 ({JID, none}) ->
2794 2 node_call(Type, set_affiliation, [Nidx, JID, none]),
2795 2 OldOwner = jid:to_bare(JID),
2796 2 case lists:member(OldOwner, Owners) of
2797 true ->
2798 2 NewOwners = Owners -- [OldOwner],
2799 2 tree_call(Host,
2800 set_node,
2801 [N#pubsub_node{owners = NewOwners}]);
2802 _ ->
2803
:-(
ok
2804 end;
2805 ({JID, Affiliation}) ->
2806 50 node_call(Type, set_affiliation, [Nidx, JID, Affiliation])
2807 end,
2808 Entities).
2809
2810 get_options(Host, Node, JID, SubId, Lang) ->
2811 14 Action = fun(PubSubNode) ->
2812 14 get_options_transaction(Node, JID, SubId, Lang, PubSubNode)
2813 end,
2814 14 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
2815 8 {result, {_Node, XForm}} -> {result, [XForm]};
2816 6 Error -> Error
2817 end.
2818
2819 get_options_transaction(Node, JID, SubId, Lang, #pubsub_node{type = Type, id = Nidx}) ->
2820 14 case lists:member(<<"subscription-options">>, plugin_features(Type)) of
2821 true ->
2822 14 get_sub_options_xml(JID, Lang, Node, Nidx, SubId, Type);
2823 false ->
2824
:-(
{error,
2825 extended_error(mongoose_xmpp_errors:feature_not_implemented(),
2826 unsupported, <<"subscription-options">>)}
2827 end.
2828
2829 % TODO: Support Lang at some point again
2830 get_sub_options_xml(JID, _Lang, Node, Nidx, RequestedSubId, Type) ->
2831 14 Subscriber = string_to_ljid(JID),
2832 14 {result, Subs} = node_call(Type, get_subscriptions, [Nidx, Subscriber]),
2833 14 SubscribedSubs = [{Id, Opts} || {Sub, Id, Opts} <- Subs, Sub == subscribed],
2834
2835 14 case {RequestedSubId, SubscribedSubs} of
2836 {_, []} ->
2837 6 {error, extended_error(mongoose_xmpp_errors:not_acceptable(), <<"not-subscribed">>)};
2838 {<<>>, [{TheOnlySID, Opts}]} ->
2839 8 make_and_wrap_sub_xform(Opts, Node, Subscriber, TheOnlySID);
2840 {<<>>, _} ->
2841
:-(
{error, extended_error(mongoose_xmpp_errors:not_acceptable(), <<"subid-required">>)};
2842 {_, _} ->
2843
:-(
case lists:keyfind(RequestedSubId, 1, SubscribedSubs) of
2844 {_, Opts} ->
2845
:-(
make_and_wrap_sub_xform(Opts, Node, Subscriber, RequestedSubId);
2846 _ ->
2847
:-(
{error, extended_error(mongoose_xmpp_errors:not_acceptable(),
2848 <<"invalid-subid">>)}
2849 end
2850 end.
2851
2852 make_and_wrap_sub_xform(Options, Node, Subscriber, SubId) ->
2853 8 {ok, XForm} = pubsub_form_utils:make_sub_xform(Options),
2854 8 OptionsEl = #xmlel{name = <<"options">>,
2855 attrs = [{<<"jid">>, jid:to_binary(Subscriber)},
2856 {<<"subid">>, SubId}
2857 | node_attr(Node)],
2858 children = [XForm]},
2859 8 PubSubEl = #xmlel{name = <<"pubsub">>,
2860 attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
2861 children = [OptionsEl]},
2862 8 {result, PubSubEl}.
2863
2864 set_options(Host, Node, JID, SubId, ConfigXForm) ->
2865 4 Action = fun(PubSubNode) ->
2866 4 ok = set_options_transaction(JID, SubId, ConfigXForm, PubSubNode),
2867 4 {result, []}
2868 end,
2869 4 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
2870 4 {result, {_Node, Result}} -> {result, Result};
2871
:-(
Error -> Error
2872 end.
2873
2874 set_options_transaction(JID, SubId, ConfigXForm, #pubsub_node{type = Type, id = Nidx}) ->
2875 4 case lists:member(<<"subscription-options">>, plugin_features(Type)) of
2876 true ->
2877 4 validate_and_set_options_helper(ConfigXForm, JID, Nidx, SubId, Type);
2878 false ->
2879
:-(
{error,
2880 extended_error(mongoose_xmpp_errors:feature_not_implemented(),
2881 unsupported, <<"subscription-options">>)}
2882 end.
2883
2884 validate_and_set_options_helper(ConfigXForm, JID, Nidx, SubId, Type) ->
2885 4 SubOpts = pubsub_form_utils:parse_sub_xform(ConfigXForm),
2886 4 set_options_helper(SubOpts, JID, Nidx, SubId, Type).
2887
2888 set_options_helper({error, Reason}, JID, Nidx, RequestedSubId, _Type) ->
2889 % TODO: Make smarter logging (better details formatting)
2890
:-(
?LOG_DEBUG(#{what => pubsub_invalid_subscription_options, jid => JID,
2891
:-(
nidx => Nidx, sub_id => RequestedSubId, reason => Reason}),
2892
:-(
{error, extended_error(mongoose_xmpp_errors:bad_request(), <<"invalid-options">>)};
2893 set_options_helper({ok, []}, _JID, _Nidx, _RequestedSubId, _Type) ->
2894
:-(
{result, []};
2895 set_options_helper({ok, SubOpts}, JID, Nidx, RequestedSubId, Type) ->
2896 4 Subscriber = string_to_ljid(JID),
2897 4 {result, Subs} = node_call(Type, get_subscriptions, [Nidx, Subscriber]),
2898 4 SubIds = [Id || {Sub, Id, _Opts} <- Subs, Sub == subscribed],
2899 4 case {RequestedSubId, SubIds} of
2900 {_, []} ->
2901
:-(
{error, extended_error(mongoose_xmpp_errors:not_acceptable(), <<"not-subscribed">>)};
2902 {<<>>, [TheOnlySID]} ->
2903 4 mod_pubsub_db_backend:set_subscription_opts(Nidx, Subscriber, TheOnlySID, SubOpts);
2904 {<<>>, _} ->
2905
:-(
{error, extended_error(mongoose_xmpp_errors:not_acceptable(), <<"subid-required">>)};
2906 {_, _} ->
2907
:-(
case lists:member(RequestedSubId, SubIds) of
2908 true ->
2909
:-(
mod_pubsub_db_backend:set_subscription_opts(Nidx, Subscriber, RequestedSubId,
2910 SubOpts);
2911 false ->
2912
:-(
{error, extended_error(mongoose_xmpp_errors:not_acceptable(),
2913 <<"invalid-subid">>)}
2914 end
2915 end.
2916
2917 %% @doc <p>Return the list of subscriptions as an XMPP response.</p>
2918 -spec get_subscriptions(Host, Node, JID, Plugins) -> {error, Reason} | {result, Response} when
2919 Host :: host(),
2920 Node :: pubsubNode(),
2921 JID :: jid:jid(),
2922 Plugins :: map(),
2923 Reason :: any(),
2924 Response :: [exml:element()].
2925 get_subscriptions(Host, Node, JID, #{plugins := Plugins}) when is_list(Plugins) ->
2926 10 Result = lists:foldl(fun (Type, {Status, Acc}) ->
2927 11 Features = plugin_features(Type),
2928 11 case lists:member(<<"retrieve-subscriptions">>, Features) of
2929 true ->
2930 11 Subscriber = jid:to_bare(JID),
2931 11 {result, Subs} = node_action(Host, Type,
2932 get_entity_subscriptions,
2933 [Host, Subscriber]),
2934 11 {Status, [Subs | Acc]};
2935 false ->
2936
:-(
{{error,
2937 extended_error(mongoose_xmpp_errors:feature_not_implemented(),
2938 unsupported,
2939 <<"retrieve-subscriptions">>)},
2940 Acc}
2941 end
2942 end,
2943 {ok, []}, Plugins),
2944 10 case Result of
2945 {ok, Subs} ->
2946 10 Entities = lists:flatmap(fun(Sub) -> subscription_to_xmlel(Sub, Node) end,
2947 lists:usort(lists:flatten(Subs))),
2948 10 {result,
2949 [#xmlel{name = <<"pubsub">>,
2950 attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
2951 children = [#xmlel{name = <<"subscriptions">>, attrs = [],
2952 children = Entities}]}]};
2953 {Error, _} ->
2954
:-(
Error
2955 end.
2956
2957 %% 2-element tuples are not used by any node type probably
2958 subscription_to_xmlel({_, none}, _Node) ->
2959
:-(
[];
2960 subscription_to_xmlel({#pubsub_node{nodeid = {_, SubsNode}}, Sub}, <<>>) ->
2961
:-(
[#xmlel{name = <<"subscription">>,
2962 attrs =
2963 [{<<"subscription">>, subscription_to_string(Sub)}
2964 | node_attr(SubsNode)]}];
2965 subscription_to_xmlel({#pubsub_node{nodeid = {_, SubsNode}}, Sub}, SubsNode) ->
2966
:-(
[#xmlel{name = <<"subscription">>,
2967 attrs =
2968 [{<<"subscription">>, subscription_to_string(Sub)}]}];
2969 subscription_to_xmlel({#pubsub_node{nodeid = {_, _}}, _}, _) ->
2970
:-(
[];
2971 %% no idea how to trigger this one
2972 subscription_to_xmlel({_, none, _}, _Node) ->
2973
:-(
[];
2974 %% sometimes used by node_pep
2975 subscription_to_xmlel({#pubsub_node{nodeid = {_, SubsNode}}, Sub, SubId, SubJID}, <<>>) ->
2976 9 [#xmlel{name = <<"subscription">>,
2977 attrs =
2978 [{<<"jid">>, jid:to_binary(SubJID)},
2979 {<<"subid">>, SubId},
2980 {<<"subscription">>, subscription_to_string(Sub)}
2981 | node_attr(SubsNode)]}];
2982 subscription_to_xmlel({#pubsub_node{nodeid = {_, SubsNode}}, Sub, SubId, SubJID}, SubsNode) ->
2983
:-(
[#xmlel{name = <<"subscription">>,
2984 attrs =
2985 [{<<"jid">>, jid:to_binary(SubJID)},
2986 {<<"subid">>, SubId},
2987 {<<"subscription">>, subscription_to_string(Sub)}]}];
2988 subscription_to_xmlel({#pubsub_node{nodeid = {_, _}}, _, _, _}, _Node) ->
2989
:-(
[];
2990 %% used by node_flat (therefore by dag, hometree and push as well)
2991 subscription_to_xmlel({#pubsub_node{nodeid = {_, SubsNode}}, Sub, SubJID}, <<>>) ->
2992 1 [#xmlel{name = <<"subscription">>,
2993 attrs =
2994 [{<<"jid">>, jid:to_binary(SubJID)},
2995 {<<"subscription">>, subscription_to_string(Sub)}
2996 | node_attr(SubsNode)]}];
2997 subscription_to_xmlel({#pubsub_node{nodeid = {_, SubsNode}}, Sub, SubJID}, SubsNode) ->
2998
:-(
[#xmlel{name = <<"subscription">>,
2999 attrs =
3000 [{<<"jid">>, jid:to_binary(SubJID)},
3001 {<<"subscription">>, subscription_to_string(Sub)}]}];
3002 subscription_to_xmlel({#pubsub_node{nodeid = {_, _}}, _, _}, _Node) ->
3003
:-(
[].
3004
3005 get_subscriptions(Host, Node, JID) ->
3006 10 Action = fun (PubSubNode) -> get_subscriptions_transaction(JID, PubSubNode) end,
3007 10 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
3008 {result, {_, Subs}} ->
3009 8 Entities =
3010 lists:flatmap(fun({_, pending, _, _}) ->
3011
:-(
[];
3012 ({AJID, Sub, SubId, _}) ->
3013 12 [#xmlel{name = <<"subscription">>,
3014 attrs =
3015 [{<<"jid">>, jid:to_binary(AJID)},
3016 {<<"subscription">>, subscription_to_string(Sub)},
3017 {<<"subid">>, SubId}]}]
3018 end, Subs),
3019 8 {result,
3020 [#xmlel{name = <<"pubsub">>,
3021 attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}],
3022 children = [#xmlel{name = <<"subscriptions">>,
3023 attrs = node_attr(Node),
3024 children = Entities}]}]};
3025 Error ->
3026 2 Error
3027 end.
3028
3029 get_subscriptions_transaction(JID, #pubsub_node{type = Type, id = Nidx}) ->
3030 10 Features = plugin_features(Type),
3031 10 case lists:member(<<"manage-subscriptions">>, Features) of
3032 true ->
3033 10 case node_call(Type, get_affiliation, [Nidx, JID]) of
3034 {result, owner} ->
3035 8 node_call(Type, get_node_subscriptions, [Nidx]);
3036 _ ->
3037 2 {error, mongoose_xmpp_errors:forbidden()}
3038 end;
3039 false ->
3040
:-(
{error,
3041 extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"manage-subscriptions">>)}
3042 end.
3043
3044 get_subscriptions_for_send_last(Host, PType, [JID, LJID, BJID]) ->
3045 552 {result, Subs} = node_action(Host, PType,
3046 get_entity_subscriptions,
3047 [Host, JID]),
3048 552 [{Node, Sub, SubId, SubJID}
3049 552 || {Node, Sub, SubId, SubJID} <- Subs,
3050
:-(
Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID),
3051
:-(
match_option(Node, send_last_published_item, on_sub_and_presence)];
3052 get_subscriptions_for_send_last(_Host, _PType, _JIDs) ->
3053
:-(
[].
3054
3055 set_subscriptions(Host, Node, From, #{action_el := ActionEl} ) ->
3056 6 EntitiesEls = xml:remove_cdata(ActionEl#xmlel.children),
3057 6 Owner = jid:to_lower(jid:to_bare(From)),
3058 6 Entities = lists:foldl(fun(_, error) ->
3059
:-(
error;
3060 (#xmlel{name = <<"subscription">>, attrs = Attrs}, Acc) ->
3061 10 JID = jid:from_binary(xml:get_attr_s(<<"jid">>, Attrs)),
3062 10 Sub = string_to_subscription(xml:get_attr_s(<<"subscription">>,
3063 Attrs)),
3064 10 SubId = xml:get_attr_s(<<"subid">>, Attrs),
3065 10 case (JID == error) or (Sub == false) of
3066
:-(
true -> error;
3067 10 false -> [{jid:to_lower(JID), Sub, SubId} | Acc]
3068 end;
3069 (_, _) ->
3070
:-(
error
3071 end, [], EntitiesEls),
3072 6 case Entities of
3073 error ->
3074
:-(
{error, mongoose_xmpp_errors:bad_request()};
3075 _ ->
3076 6 Action = fun (PubSubNode) ->
3077 6 set_subscriptions_transaction(Host, Owner, Node, PubSubNode, Entities)
3078 end,
3079 6 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
3080 4 {result, {_, Result}} -> {result, Result};
3081 2 Other -> Other
3082 end
3083 end.
3084
3085 set_subscriptions_transaction(Host, Owner, Node,
3086 #pubsub_node{type = Type, id = Nidx, owners = Owners}, Entities) ->
3087 6 case lists:member(Owner, Owners) of
3088 true ->
3089 4 Result =
3090 lists:foldl(fun(Entity, Acc) ->
3091 8 set_subscription_transaction(Host, Node, Nidx, Type, Entity, Acc)
3092 end,
3093 [], Entities),
3094 4 case Result of
3095 4 [] -> {result, []};
3096
:-(
_ -> {error, mongoose_xmpp_errors:not_acceptable()}
3097 end;
3098 _ ->
3099 2 {error, mongoose_xmpp_errors:forbidden()}
3100 end.
3101
3102 set_subscription_transaction(Host, Node, Nidx, Type, {JID, Sub, SubId}, Acc) ->
3103 8 case node_call(Type, set_subscriptions, [Nidx, JID, Sub, SubId]) of
3104
:-(
{error, Err} -> [{error, Err} | Acc];
3105 8 _ -> notify_subscription_change(Host, Node, JID, Sub), Acc
3106 end.
3107
3108 notify_subscription_change(Host, Node, JID, Sub) ->
3109 8 SubscriptionEl = #xmlel{name = <<"subscription">>,
3110 attrs = [{<<"jid">>, jid:to_binary(JID)},
3111 {<<"subscription">>, subscription_to_string(Sub)}
3112 | node_attr(Node)]},
3113 8 PubSubEl = #xmlel{name = <<"pubsub">>,
3114 attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
3115 children = [SubscriptionEl]},
3116 8 Stanza = #xmlel{name = <<"message">>, attrs = [], children = [PubSubEl]},
3117 8 ejabberd_router:route(service_jid(Host), jid:make(JID), Stanza).
3118
3119 -spec get_presence_and_roster_permissions(Host :: mod_pubsub:host(),
3120 From :: jid:jid() | jid:ljid(),
3121 Owners :: [jid:ljid(), ...],
3122 AccessModel :: mod_pubsub:accessModel(),
3123 AllowedGroups :: [binary()]) ->
3124 {PresenceSubscription :: boolean(), RosterGroup :: boolean()}.
3125 get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups)
3126 when (AccessModel == presence) orelse (AccessModel == roster) ->
3127 3 case Host of
3128 {User, Server, _} ->
3129 3 get_roster_info(User, Server, From, AllowedGroups);
3130 _ ->
3131
:-(
[{OUser, OServer, _} | _] = Owners,
3132
:-(
get_roster_info(OUser, OServer, From, AllowedGroups)
3133 end;
3134 get_presence_and_roster_permissions(_Host, _From, _Owners, _AccessModel, _AllowedGroups) ->
3135 125 {true, true}.
3136
3137 get_roster_info(_, _, {<<>>, <<>>, _}, _) ->
3138
:-(
{false, false};
3139 get_roster_info(OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, _}, AllowedGroups) ->
3140 3 LJID = {SubscriberUser, SubscriberServer, <<>>},
3141 3 {Subscription, Groups} = mongoose_hooks:roster_get_jid_info(
3142 OwnerServer,
3143 jid:make_bare(OwnerUser, OwnerServer), LJID),
3144 3 PresenceSubscription = Subscription == both orelse
3145 1 Subscription == from orelse
3146 1 {OwnerUser, OwnerServer} == {SubscriberUser, SubscriberServer},
3147 3 RosterGroup = lists:any(fun (Group) ->
3148
:-(
lists:member(Group, AllowedGroups)
3149 end,
3150 Groups),
3151 3 {PresenceSubscription, RosterGroup};
3152 get_roster_info(OwnerUser, OwnerServer, JID, AllowedGroups) ->
3153 1 get_roster_info(OwnerUser, OwnerServer, jid:to_lower(JID), AllowedGroups).
3154
3155 2 string_to_affiliation(<<"owner">>) -> owner;
3156 2 string_to_affiliation(<<"publisher">>) -> publisher;
3157 46 string_to_affiliation(<<"publish-only">>) -> publish_only;
3158 4 string_to_affiliation(<<"member">>) -> member;
3159
:-(
string_to_affiliation(<<"outcast">>) -> outcast;
3160 2 string_to_affiliation(<<"none">>) -> none;
3161
:-(
string_to_affiliation(_) -> false.
3162
3163 8 string_to_subscription(<<"subscribed">>) -> subscribed;
3164
:-(
string_to_subscription(<<"pending">>) -> pending;
3165 2 string_to_subscription(<<"none">>) -> none;
3166
:-(
string_to_subscription(_) -> false.
3167
3168 6 affiliation_to_string(owner) -> <<"owner">>;
3169
:-(
affiliation_to_string(publisher) -> <<"publisher">>;
3170
:-(
affiliation_to_string(publish_only) -> <<"publish-only">>;
3171
:-(
affiliation_to_string(member) -> <<"member">>;
3172
:-(
affiliation_to_string(outcast) -> <<"outcast">>;
3173
:-(
affiliation_to_string(_) -> <<"none">>.
3174
3175 108 subscription_to_string(subscribed) -> <<"subscribed">>;
3176 11 subscription_to_string(pending) -> <<"pending">>;
3177 4 subscription_to_string(_) -> <<"none">>.
3178
3179 -spec service_jid(mod_pubsub:host()) -> jid:jid().
3180 service_jid({U, S, _}) ->
3181 62 jid:make_bare(U, S);
3182 service_jid(Host) ->
3183 602 jid:make_bare(<<>>, Host).
3184
3185 %% @doc <p>Check if a notification must be delivered or not based on
3186 %% node and subscription options.</p>
3187 -spec is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) -> boolean() when
3188 LJID :: jid:ljid(),
3189 NotifyType :: items | nodes,
3190 Depth :: integer(),
3191 NodeOptions :: [{atom(), term()}],
3192 SubOptions :: [{atom(), term()}].
3193 is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) ->
3194 50 sub_to_deliver(LJID, NotifyType, Depth, SubOptions)
3195 48 andalso node_to_deliver(LJID, NodeOptions).
3196
3197 sub_to_deliver(_LJID, NotifyType, Depth, SubOptions) ->
3198 50 lists:all(fun (Option) ->
3199 4 sub_option_can_deliver(NotifyType, Depth, Option)
3200 end,
3201 SubOptions).
3202
3203 node_to_deliver(LJID, NodeOptions) ->
3204 48 presence_can_deliver(LJID, get_option(NodeOptions, presence_based_delivery)).
3205
3206
:-(
sub_option_can_deliver(items, _, {subscription_type, nodes}) -> false;
3207
:-(
sub_option_can_deliver(nodes, _, {subscription_type, items}) -> false;
3208
:-(
sub_option_can_deliver(_, _, {subscription_depth, all}) -> true;
3209
:-(
sub_option_can_deliver(_, Depth, {subscription_depth, D}) -> Depth =< D;
3210 2 sub_option_can_deliver(_, _, {deliver, false}) -> false;
3211
:-(
sub_option_can_deliver(_, _, {expire, When}) -> timestamp() < When;
3212 2 sub_option_can_deliver(_, _, _) -> true.
3213
3214 -spec presence_can_deliver(Entity :: jid:ljid(), PresenceBasedDelivery :: boolean()) -> boolean().
3215 presence_can_deliver(_, false) ->
3216 45 true;
3217 presence_can_deliver({User, Server, <<>>}, true) ->
3218 2 ejabberd_sm:get_user_present_resources(jid:make_noprep(User, Server, <<>>)) =/= [];
3219 presence_can_deliver({User, Server, Resource}, true) ->
3220 1 JID = jid:make_noprep(User, Server, Resource),
3221 1 case ejabberd_sm:get_session(JID) of
3222 1 #session{priority = SPriority} when SPriority /= undefined -> true;
3223
:-(
_ -> false
3224 end.
3225
3226 -spec state_can_deliver(
3227 Entity::jid:ljid(),
3228 SubOptions :: mod_pubsub:subOptions() | [])
3229 -> [jid:ljid()].
3230 45 state_can_deliver({U, S, R}, []) -> [{U, S, R}];
3231 state_can_deliver({U, S, R}, SubOptions) ->
3232 2 case lists:keysearch(show_values, 1, SubOptions) of
3233 %% If not in suboptions, item can be delivered, case doesn't apply
3234 2 false -> [{U, S, R}];
3235 %% If in a suboptions ...
3236 {_, {_, ShowValues}} ->
3237
:-(
Resources = case R of
3238 %% If the subscriber JID is a bare one, get all its resources
3239
:-(
<<>> -> user_resources(U, S);
3240 %% If the subscriber JID is a full one, use its resource
3241
:-(
R -> [R]
3242 end,
3243
:-(
lists:foldl(fun (Resource, Acc) ->
3244
:-(
get_resource_state({U, S, Resource}, ShowValues, Acc)
3245 end,
3246 [], Resources)
3247 end.
3248
3249 -spec get_resource_state(
3250 Entity :: jid:ljid(),
3251 ShowValues :: [binary()],
3252 JIDs :: [jid:ljid()])
3253 -> [jid:ljid()].
3254 get_resource_state({U, S, R}, ShowValues, JIDs) ->
3255
:-(
case ejabberd_sm:get_session_pid(jid:make_noprep(U, S, R)) of
3256 none ->
3257 %% If no PID, item can be delivered
3258
:-(
lists:append([{U, S, R}], JIDs);
3259 Pid ->
3260
:-(
Show = case mod_presence:get_presence(Pid) of
3261
:-(
{_, _, <<"available">>, _} -> <<"online">>;
3262
:-(
{_, _, State, _} -> State
3263 end,
3264
:-(
case lists:member(Show, ShowValues) of
3265 %% If yes, item can be delivered
3266
:-(
true -> lists:append([{U, S, R}], JIDs);
3267 %% If no, item can't be delivered
3268
:-(
false -> JIDs
3269 end
3270 end.
3271
3272 -spec payload_xmlelements(
3273 Payload :: mod_pubsub:payload())
3274 -> Count :: non_neg_integer().
3275 payload_xmlelements(Payload) ->
3276 202 payload_xmlelements(Payload, 0).
3277
3278 202 payload_xmlelements([], Count) -> Count;
3279 payload_xmlelements([#xmlel{} | Tail], Count) ->
3280 202 payload_xmlelements(Tail, Count + 1);
3281 payload_xmlelements([_ | Tail], Count) ->
3282
:-(
payload_xmlelements(Tail, Count).
3283
3284 items_event_stanza(Node, Items) ->
3285 10 MoreEls =
3286 case Items of
3287 [LastItem] ->
3288 10 {ModifNow, ModifUSR} = LastItem#pubsub_item.modification,
3289 10 Sec = erlang:convert_time_unit(ModifNow, microsecond, second),
3290 10 TString = calendar:system_time_to_rfc3339(Sec, [{offset, "Z"}]),
3291 10 [#xmlel{name = <<"delay">>,
3292 attrs = [{<<"xmlns">>, ?NS_DELAY},
3293 {<<"from">>, jid:to_binary(ModifUSR)},
3294 {<<"stamp">>, list_to_binary(TString)}],
3295 children = [{xmlcdata, <<>>}]}];
3296 _ ->
3297
:-(
[]
3298 end,
3299 10 event_stanza_with_els([#xmlel{name = <<"items">>,
3300 attrs = [{<<"type">>, <<"headline">>} | node_attr(Node)],
3301 children = items_els(Items)}],
3302 MoreEls).
3303
3304 event_stanza(Els) ->
3305 355 event_stanza_with_els(Els, []).
3306 event_stanza_with_els(Els, MoreEls) ->
3307 370 #xmlel{name = <<"message">>, attrs = [],
3308 children = [#xmlel{name = <<"event">>,
3309 attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}],
3310 children = Els}
3311 | MoreEls]}.
3312
3313 event_stanza(Event, EvAttr) ->
3314 5 event_stanza_with_els([#xmlel{name = Event, attrs = EvAttr}], []).
3315
3316 %%%%%% broadcast functions
3317
3318 broadcast_publish_item(Host, Node, Nidx, Type, NodeOptions,
3319 ItemId, From, Payload, Removed, ItemPublisher) ->
3320 119 case get_collection_subscriptions(Host, Node) of
3321 SubsByDepth when is_list(SubsByDepth) ->
3322 119 Content = case get_option(NodeOptions, deliver_payloads) of
3323 116 true -> Payload;
3324 3 false -> []
3325 end,
3326 119 ItemAttr = case ItemPublisher of
3327 4 true -> item_attr(ItemId, From);
3328 115 false -> item_attr(ItemId)
3329 end,
3330 119 Stanza = event_stanza(
3331 [#xmlel{name = <<"items">>, attrs = node_attr(Node),
3332 children = [#xmlel{name = <<"item">>, attrs = ItemAttr,
3333 children = Content}]}]),
3334 119 broadcast_step(Host, fun() ->
3335 119 broadcast_stanza(Host, From, Node, Nidx, Type,
3336 NodeOptions, SubsByDepth, items, Stanza, true),
3337 119 broadcast_auto_retract_notification(Host, Node, Nidx, Type,
3338 NodeOptions, SubsByDepth, Removed)
3339 end),
3340 119 {result, true};
3341 _ ->
3342
:-(
{result, false}
3343 end.
3344
3345 broadcast_auto_retract_notification(_Host, _Node, _Nidx, _Type, _NodeOptions, _SubsByDepth, []) ->
3346 113 ok;
3347 broadcast_auto_retract_notification(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, Removed) ->
3348 6 case get_option(NodeOptions, notify_retract) of
3349 true ->
3350 2 RetractEls = [#xmlel{name = <<"retract">>, attrs = item_attr(RId)} || RId <- Removed],
3351 2 RetractStanza = event_stanza([#xmlel{name = <<"items">>, attrs = node_attr(Node),
3352 children = RetractEls}]),
3353 2 broadcast_stanza(Host, Node, Nidx, Type,
3354 NodeOptions, SubsByDepth,
3355 items, RetractStanza, true);
3356 _ ->
3357 4 ok
3358 end.
3359
3360 broadcast_retract_items(Host, Node, Nidx, Type, NodeOptions, ItemIds) ->
3361
:-(
broadcast_retract_items(Host, Node, Nidx, Type, NodeOptions, ItemIds, false).
3362 broadcast_retract_items(_Host, _Node, _Nidx, _Type, _NodeOptions, [], _ForceNotify) ->
3363
:-(
{result, false};
3364 broadcast_retract_items(Host, Node, Nidx, Type, NodeOptions, ItemIds, ForceNotify) ->
3365 8 case (get_option(NodeOptions, notify_retract) or ForceNotify) of
3366 true ->
3367 2 case get_collection_subscriptions(Host, Node) of
3368 SubsByDepth when is_list(SubsByDepth) ->
3369 2 Stanza = event_stanza(
3370 [#xmlel{name = <<"items">>, attrs = node_attr(Node),
3371 2 children = [#xmlel{name = <<"retract">>,
3372 attrs = item_attr(ItemId)}
3373 2 || ItemId <- ItemIds]}]),
3374 2 broadcast_step(Host, fun() ->
3375 2 broadcast_stanza(Host, Node, Nidx, Type,
3376 NodeOptions, SubsByDepth, items, Stanza, true)
3377 end),
3378 2 {result, true};
3379 _ ->
3380
:-(
{result, false}
3381 end;
3382 _ ->
3383 6 {result, false}
3384 end.
3385
3386 broadcast_purge_node(Host, Node, Nidx, Type, NodeOptions) ->
3387 4 case get_option(NodeOptions, notify_retract) of
3388 true ->
3389
:-(
case get_collection_subscriptions(Host, Node) of
3390 SubsByDepth when is_list(SubsByDepth) ->
3391
:-(
Stanza = event_stanza(
3392 [#xmlel{name = <<"purge">>, attrs = node_attr(Node)}]),
3393
:-(
broadcast_step(Host, fun() ->
3394
:-(
broadcast_stanza(Host, Node, Nidx, Type,
3395 NodeOptions, SubsByDepth, nodes, Stanza, false)
3396 end),
3397
:-(
{result, true};
3398 _ ->
3399
:-(
{result, false}
3400 end;
3401 _ ->
3402 4 {result, false}
3403 end.
3404
3405 broadcast_removed_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth) ->
3406 148 case get_option(NodeOptions, notify_delete) of
3407 true ->
3408 1 case SubsByDepth of
3409 [] ->
3410
:-(
{result, false};
3411 _ ->
3412 1 Stanza = event_stanza(
3413 [#xmlel{name = <<"delete">>, attrs = node_attr(Node)}]),
3414 1 broadcast_step(Host, fun() ->
3415 1 broadcast_stanza(Host, Node, Nidx, Type,
3416 NodeOptions, SubsByDepth, nodes, Stanza, false)
3417 end),
3418 1 {result, true}
3419 end;
3420 _ ->
3421 147 {result, false}
3422 end.
3423
3424 broadcast_created_node(_, _, _, _, _, []) ->
3425
:-(
{result, false};
3426 broadcast_created_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth) ->
3427 229 Stanza = event_stanza([#xmlel{name = <<"create">>, attrs = node_attr(Node)}]),
3428 229 broadcast_step(Host, fun() ->
3429 229 broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, nodes, Stanza, true)
3430 end),
3431 229 {result, true}.
3432
3433 broadcast_config_notification(Host, Node, Nidx, Type, NodeOptions, Lang) ->
3434 6 case get_option(NodeOptions, notify_config) of
3435 true ->
3436 2 case get_collection_subscriptions(Host, Node) of
3437 SubsByDepth when is_list(SubsByDepth) ->
3438 2 Content = payload_by_option(Type, NodeOptions, Lang),
3439 2 Stanza = event_stanza([#xmlel{name = <<"configuration">>,
3440 attrs = node_attr(Node), children = Content}]),
3441 2 broadcast_step(Host, fun() ->
3442 2 broadcast_stanza(Host, Node, Nidx, Type,
3443 NodeOptions, SubsByDepth, nodes, Stanza, false)
3444 end),
3445 2 {result, true};
3446 _ ->
3447
:-(
{result, false}
3448 end;
3449 _ ->
3450 4 {result, false}
3451 end.
3452
3453 payload_by_option(_Type, NodeOptions, Lang) ->
3454 2 case get_option(NodeOptions, deliver_payloads) of
3455 true ->
3456 2 [configure_form(<<"result">>, NodeOptions, Lang, [])];
3457 false ->
3458
:-(
[]
3459 end.
3460
3461 get_collection_subscriptions(Host, Node) ->
3462 123 Action = fun() ->
3463 123 {result, get_node_subs_by_depth(Host, Node, service_jid(Host))}
3464 end,
3465 123 ErrorDebug = #{
3466 pubsub_host => Host,
3467 action => get_collection_subscriptions,
3468 node_name => Node
3469 },
3470 123 case mod_pubsub_db_backend:dirty(Action, ErrorDebug) of
3471 123 {result, CollSubs} -> CollSubs;
3472
:-(
_ -> []
3473 end.
3474
3475 get_node_subs_by_depth(Host, Node, From) ->
3476 503 ParentTree = tree_call(Host, get_parentnodes_tree, [Host, Node, From]),
3477 503 [{Depth, [{N, get_node_subs(N)} || N <- Nodes]} || {Depth, Nodes} <- ParentTree].
3478
3479 get_node_subs(#pubsub_node{type = Type, id = Nidx}) ->
3480 560 case node_call(Type, get_node_subscriptions, [Nidx]) of
3481 {result, Subs} ->
3482 % TODO: Replace with proper DB/plugin call with sub type filter
3483 560 [{JID, SubID, Opts} || {JID, SubType, SubID, Opts} <- Subs, SubType == subscribed];
3484 Other ->
3485
:-(
Other
3486 end.
3487
3488 %% Execute broadcasting step in a new process
3489 %% F contains one or more broadcast_stanza calls, executed sequentially
3490 broadcast_step(Host, F) ->
3491 353 case gen_mod:get_module_opt(serverhost(Host), ?MODULE, sync_broadcast) of
3492 true ->
3493
:-(
F();
3494 false ->
3495 353 proc_lib:spawn(F)
3496 end.
3497
3498 broadcast_stanza(Host, Node, _Nidx, _Type, NodeOptions,
3499 SubsByDepth, NotifyType, BaseStanza, SHIM) ->
3500 355 NotificationType = get_option(NodeOptions, notification_type, headline),
3501 %% Option below is not standard, but useful
3502 355 BroadcastAll = get_option(NodeOptions, broadcast_all_resources),
3503 355 From = service_jid(Host),
3504 355 Stanza = add_message_type(BaseStanza, NotificationType),
3505 %% Handles explicit subscriptions
3506 355 SubIDsByJID = subscribed_nodes_by_jid(NotifyType, SubsByDepth),
3507 355 lists:foreach(fun ({LJID, SubNodeName, SubIDs}) ->
3508 46 LJIDs = case BroadcastAll of
3509 true ->
3510
:-(
{U, S, _} = LJID,
3511
:-(
[{U, S, R} || R <- user_resources(U, S)];
3512 false ->
3513 46 [LJID]
3514 end,
3515 46 StanzaToSend = maybe_add_shim_headers(Stanza, SHIM, SubIDs,
3516 Node, SubNodeName),
3517
3518 46 lists:foreach(fun(To) ->
3519 46 ejabberd_router:route(From, jid:make(To),
3520 StanzaToSend)
3521 end, LJIDs)
3522 end, SubIDsByJID).
3523
3524 broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeOptions,
3525 SubsByDepth, NotifyType, BaseStanza, SHIM) ->
3526 18 broadcast_stanza({LUser, LServer, LResource}, Node, Nidx, Type, NodeOptions,
3527 SubsByDepth, NotifyType, BaseStanza, SHIM),
3528 %% Handles implicit presence subscriptions
3529 18 SenderResource = user_resource(LUser, LServer, LResource),
3530 18 case ejabberd_sm:get_session_pid(jid:make(LUser, LServer, SenderResource)) of
3531 C2SPid when is_pid(C2SPid) ->
3532 18 NotificationType = get_option(NodeOptions, notification_type, headline),
3533 18 Stanza = add_message_type(BaseStanza, NotificationType),
3534 %% set the from address on the notification to the bare JID of the account owner
3535 %% Also, add "replyto" if entity has presence subscription to the account owner
3536 %% See XEP-0163 1.1 section 4.3.1
3537 18 ReplyTo = extended_headers([jid:to_binary(Publisher)]),
3538 18 Feature = <<((Node))/binary, "+notify">>,
3539 18 Recipients = mongoose_c2s:call(C2SPid, ?MODULE, {get_pep_recipients, Feature}),
3540 18 Packet = add_extended_headers(Stanza, ReplyTo),
3541 18 From = jid:make_bare(LUser, LServer),
3542 18 lists:foreach(fun(USR) -> ejabberd_router:route(From, jid:make(USR), Packet) end,
3543 lists:usort(Recipients));
3544 _ ->
3545
:-(
?LOG_DEBUG(#{what => pubsub_no_session,
3546 text => <<"User has no session; cannot deliver stanza to contacts">>,
3547
:-(
user => LUser, server => LServer, exml_packet => BaseStanza})
3548 end;
3549 broadcast_stanza(Host, _Publisher, Node, Nidx, Type, NodeOptions,
3550 SubsByDepth, NotifyType, BaseStanza, SHIM) ->
3551 101 broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth,
3552 NotifyType, BaseStanza, SHIM).
3553
3554 subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
3555 355 DepthsToDeliver =
3556 fun({Depth, SubsByNode}, Acc1) ->
3557 389 lists:foldl(fun({Node, Subs}, Acc2) ->
3558 391 nodes_to_deliver(NotifyType, Depth, Node, Subs, Acc2)
3559 end, Acc1, SubsByNode)
3560 end,
3561 355 {_, JIDSubs} = lists:foldl(DepthsToDeliver, {[], []}, SubsByDepth),
3562 355 JIDSubs.
3563
3564 nodes_to_deliver(NotifyType, Depth, Node, Subs, Acc) ->
3565 391 NodeName = case Node#pubsub_node.nodeid of
3566 391 {_, N} -> N;
3567
:-(
Other -> Other
3568 end,
3569 391 NodeOptions = Node#pubsub_node.options,
3570 391 lists:foldl(fun({LJID, SubID, SubOptions}, InnerAcc) ->
3571 50 case is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) of
3572 true ->
3573 47 JIDsToDeliver = state_can_deliver(LJID, SubOptions),
3574 47 process_jids_to_deliver(NodeName, SubID, JIDsToDeliver, InnerAcc);
3575 false ->
3576 3 InnerAcc
3577 end
3578 end, Acc, Subs).
3579
3580 process_jids_to_deliver(NodeName, SubID, JIDsToDeliver, {JIDs, Recipients}) ->
3581 47 lists:foldl(
3582 fun(JIDToDeliver, {JIDsAcc, RecipientsAcc}) ->
3583 47 process_jid_to_deliver(JIDs, SubID, NodeName,
3584 JIDToDeliver, {JIDsAcc, RecipientsAcc})
3585 end, {JIDs, Recipients}, JIDsToDeliver).
3586
3587 process_jid_to_deliver(JIDs, SubID, NodeName, JIDToDeliver, {JIDsAcc, RecipientsAcc}) ->
3588 47 case lists:member(JIDToDeliver, JIDs) of
3589 %% check if the JIDs co-accumulator contains the Subscription Jid,
3590 false ->
3591 %% - if not,
3592 %% - add the Jid to JIDs list co-accumulator ;
3593 %% - create a tuple of the Jid, Nidx, and SubID (as list),
3594 %% and add the tuple to the Recipients list co-accumulator
3595 46 {[JIDToDeliver | JIDsAcc],
3596 [{JIDToDeliver, NodeName, [SubID]}
3597 | RecipientsAcc]};
3598 true ->
3599 %% - if the JIDs co-accumulator contains the Jid
3600 %% get the tuple containing the Jid from the Recipient list co-accumulator
3601 1 {_, {JIDToDeliver, NodeName1, SubIDs}} =
3602 lists:keysearch(JIDToDeliver, 1, RecipientsAcc),
3603 %% delete the tuple from the Recipients list
3604 % v1 : Recipients1 = lists:keydelete(LJID, 1, Recipients),
3605 % v2 : Recipients1 = lists:keyreplace(LJID, 1, Recipients,
3606 % {LJID, Nidx1, [SubID | SubIDs]}),
3607 %% add the SubID to the SubIDs list in the tuple,
3608 %% and add the tuple back to the Recipients list co-accumulator
3609 % v1.1 : {JIDs, lists:append(Recipients1,
3610 % [{LJID, Nidx1, lists:append(SubIDs, [SubID])}])}
3611 % v1.2 : {JIDs, [{LJID, Nidx1, [SubID | SubIDs]} | Recipients1]}
3612 % v2: {JIDs, Recipients1}
3613 1 {JIDsAcc,
3614 lists:keyreplace(JIDToDeliver, 1,
3615 RecipientsAcc,
3616 {JIDToDeliver, NodeName1,
3617 [SubID | SubIDs]})}
3618 end.
3619
3620 user_resources(User, Server) ->
3621 419 JID = jid:make_bare(User, Server),
3622 419 ejabberd_sm:get_user_resources(JID).
3623
3624 user_resource(User, Server, <<>>) ->
3625 18 case user_resources(User, Server) of
3626 18 [R | _] -> R;
3627
:-(
_ -> <<>>
3628 end;
3629 user_resource(_, _, Resource) ->
3630
:-(
Resource.
3631
3632 %%%%%%% Configuration handling
3633
3634 get_configure(Host, Node, From, #{server_host := ServerHost, lang := Lang}) ->
3635 7 Action = fun(PubSubNode) ->
3636 7 get_configure_transaction(ServerHost, Node, From, Lang, PubSubNode)
3637 end,
3638 7 case dirty(Host, Node, Action, ?FUNCTION_NAME) of
3639 7 {result, {_, Result}} -> {result, Result};
3640
:-(
Other -> Other
3641 end.
3642
3643 get_configure_transaction(ServerHost, Node, From, Lang,
3644 #pubsub_node{options = Options, type = Type, id = Nidx}) ->
3645 7 case node_call(Type, get_affiliation, [Nidx, From]) of
3646 {result, owner} ->
3647 7 Groups = mongoose_hooks:roster_groups(ServerHost),
3648 7 XEl = configure_form(<<"form">>, Options, Lang, Groups),
3649 7 ConfigureEl = #xmlel{name = <<"configure">>,
3650 attrs = node_attr(Node),
3651 children = [XEl]},
3652 7 {result,
3653 [#xmlel{name = <<"pubsub">>,
3654 attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}],
3655 children = [ConfigureEl]}]};
3656 _ ->
3657
:-(
{error, mongoose_xmpp_errors:forbidden()}
3658 end.
3659
3660 get_default(Host, Node, _From, #{lang := Lang}) ->
3661 2 Type = select_type(Host, Node),
3662 2 Options = node_options(Host, Type),
3663 2 XEl = configure_form(<<"form">>, Options, Lang, []),
3664 2 DefaultEl = #xmlel{name = <<"default">>, attrs = [], children = [XEl]},
3665 2 {result,
3666 [#xmlel{name = <<"pubsub">>,
3667 attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}],
3668 children = [DefaultEl]}]}.
3669
3670 match_option(Node, Var, Val) when is_record(Node, pubsub_node) ->
3671
:-(
match_option(Node#pubsub_node.options, Var, Val);
3672 match_option(Options, Var, Val) when is_list(Options) ->
3673 11 get_option(Options, Var) == Val;
3674 match_option(_, _, _) ->
3675
:-(
false.
3676
3677 95 get_option([], _) -> false;
3678 2768 get_option(Options, Var) -> get_option(Options, Var, false).
3679
3680 get_option(Options, Var, Def) ->
3681 3312 case lists:keysearch(Var, 1, Options) of
3682 2829 {value, {_Val, Ret}} -> Ret;
3683 483 _ -> Def
3684 end.
3685
3686 -spec check_publish_options(binary(), undefined | exml:element(), mod_pubsub:nodeOptions()) ->
3687 boolean().
3688 check_publish_options(Type, PublishOptions, Options) ->
3689 92 ParsedPublishOptions = parse_publish_options(PublishOptions),
3690 92 ConvertedOptions = convert_options(Options),
3691 92 case node_call(Type, check_publish_options, [ParsedPublishOptions, ConvertedOptions]) of
3692 {error, _} ->
3693
:-(
true;
3694 {result, Result} ->
3695 92 Result
3696 end.
3697
3698 -spec parse_publish_options(undefined | exml:element()) -> invalid_form | #{binary() => [binary()]}.
3699 parse_publish_options(undefined) ->
3700 18 #{};
3701 parse_publish_options(PublishOptions) ->
3702 74 case mongoose_data_forms:find_and_parse_form(PublishOptions) of
3703 #{type := <<"submit">>, kvs := KVs, ns := ?NS_PUBSUB_PUB_OPTIONS} ->
3704 73 KVs;
3705 _ ->
3706 1 invalid_form
3707 end.
3708
3709 -spec convert_options(mod_pubsub:nodeOptions()) -> #{binary() => [binary()]}.
3710 convert_options(Options) ->
3711 92 ConvertedOptions = lists:map(fun({Key, Value}) ->
3712 1472 {atom_to_binary(Key), convert_option_value(Value)}
3713 end, Options),
3714 92 maps:from_list(ConvertedOptions).
3715
3716 -spec convert_option_value(binary() | [binary()] | atom() | non_neg_integer()) -> [binary()].
3717 convert_option_value(true) ->
3718 391 [<<"1">>];
3719 convert_option_value(false) ->
3720 437 [<<"0">>];
3721 convert_option_value(Element) when is_atom(Element) ->
3722 368 [atom_to_binary(Element)];
3723 convert_option_value(Element) when is_integer(Element) ->
3724 184 [integer_to_binary(Element)];
3725 convert_option_value(List) when is_list(List) ->
3726 92 List.
3727
3728 node_options(Host, Type) ->
3729 234 ConfiguredOpts = lists:keysort(1, config(serverhost(Host), default_node_config)),
3730 234 DefaultOpts = lists:keysort(1, node_plugin_options(Type)),
3731 234 lists:keymerge(1, ConfiguredOpts, DefaultOpts).
3732
3733 node_plugin_options(Type) ->
3734 234 Module = plugin(Type),
3735 234 case catch gen_pubsub_node:options(Module) of
3736 {'EXIT', {undef, _}} ->
3737
:-(
DefaultModule = plugin(?STDNODE),
3738
:-(
gen_pubsub_node:options(DefaultModule);
3739 Result ->
3740 234 Result
3741 end.
3742
3743 %% @doc <p>Return the maximum number of items for a given node.</p>
3744 %% <p>Unlimited means that there is no limit in the number of items that can
3745 %% be stored.</p>
3746 %% @todo In practice, the current data structure means that we cannot manage
3747 %% millions of items on a given node. This should be addressed in a new
3748 %% version.
3749 -spec max_items(Host, Options) -> MaxItems when
3750 Host :: host(),
3751 Options :: [Option],
3752 Option :: {Key :: atom(), Value :: term()},
3753 MaxItems :: integer() | unlimited.
3754 max_items(Host, Options) ->
3755 202 case get_option(Options, persist_items) of
3756 true ->
3757 130 case get_option(Options, max_items) of
3758
:-(
I when is_integer(I), I < 0 -> 0;
3759 130 I when is_integer(I) -> I;
3760
:-(
_ -> ?MAXITEMS
3761 end;
3762 false ->
3763 %% Don't publish if it means sending the item without a way to retrieve it
3764 72 case get_option(Options, send_last_published_item) == never
3765 69 orelse is_last_item_cache_enabled(Host) of
3766 3 true -> 0;
3767 69 false -> 1
3768 end
3769 end.
3770
3771 -define(BOOL_CONFIG_FIELD(Label, Var),
3772 ?BOOLXFIELD(Label,
3773 <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
3774 (get_option(Options, Var)))).
3775
3776 -define(STRING_CONFIG_FIELD(Label, Var),
3777 ?STRINGXFIELD(Label,
3778 <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
3779 (get_option(Options, Var, <<>>)))).
3780
3781 -define(INTEGER_CONFIG_FIELD(Label, Var),
3782 ?STRINGXFIELD(Label,
3783 <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
3784 (integer_to_binary(get_option(Options, Var))))).
3785
3786 -define(JLIST_CONFIG_FIELD(Label, Var, Opts),
3787 ?LISTXFIELD(Label,
3788 <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
3789 (jid:to_binary(get_option(Options, Var))),
3790 [jid:to_binary(O) || O <- Opts])).
3791
3792 -define(ALIST_CONFIG_FIELD(Label, Var, Opts),
3793 ?LISTXFIELD(Label,
3794 <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
3795 (atom_to_binary(get_option(Options, Var), latin1)),
3796 [atom_to_binary(O, latin1) || O <- Opts])).
3797
3798 -define(LISTM_CONFIG_FIELD(Label, Var, Opts),
3799 ?LISTMXFIELD(Label,
3800 <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
3801 (get_option(Options, Var)), Opts)).
3802
3803 -define(NLIST_CONFIG_FIELD(Label, Var),
3804 ?STRINGMXFIELD(Label,
3805 <<"pubsub#", (atom_to_binary(Var, latin1))/binary>>,
3806 get_option(Options, Var, []))).
3807
3808 configure_form(Type, Options, Lang, Groups) ->
3809 11 Fields = get_configure_xfields(Options, Lang, Groups),
3810 11 mongoose_data_forms:form(#{type => Type, ns => ?NS_PUBSUB_NODE_CONFIG, fields => Fields}).
3811
3812 get_configure_xfields(Options, Lang, Groups) ->
3813 11 [?BOOL_CONFIG_FIELD(<<"Deliver payloads with event notifications">>,
3814 11 deliver_payloads),
3815 ?BOOL_CONFIG_FIELD(<<"Deliver event notifications">>,
3816 11 deliver_notifications),
3817 ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node configuration changes">>,
3818 11 notify_config),
3819 ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node is deleted">>,
3820 11 notify_delete),
3821 ?BOOL_CONFIG_FIELD(<<"Notify subscribers when items are removed from the node">>,
3822 11 notify_retract),
3823 ?BOOL_CONFIG_FIELD(<<"Persist items to storage">>,
3824 11 persist_items),
3825 ?STRING_CONFIG_FIELD(<<"A friendly name for the node">>,
3826 title),
3827 ?INTEGER_CONFIG_FIELD(<<"Max # of items to persist">>,
3828 max_items),
3829 ?BOOL_CONFIG_FIELD(<<"Whether to allow subscriptions">>,
3830 11 subscribe),
3831 ?ALIST_CONFIG_FIELD(<<"Specify the access model">>,
3832 55 access_model, [open, authorize, presence, roster, whitelist]),
3833 ?LISTM_CONFIG_FIELD(<<"Roster groups allowed to subscribe">>,
3834 roster_groups_allowed, Groups),
3835 ?ALIST_CONFIG_FIELD(<<"Specify the publisher model">>,
3836 33 publish_model, [publishers, subscribers, open]),
3837 ?BOOL_CONFIG_FIELD(<<"Purge all items when the relevant publisher goes offline">>,
3838 11 purge_offline),
3839 ?ALIST_CONFIG_FIELD(<<"Specify the event message type">>,
3840 22 notification_type, [headline, normal]),
3841 ?INTEGER_CONFIG_FIELD(<<"Max payload size in bytes">>,
3842 max_payload_size),
3843 ?ALIST_CONFIG_FIELD(<<"When to send the last published item">>,
3844 33 send_last_published_item, [never, on_sub, on_sub_and_presence]),
3845 ?BOOL_CONFIG_FIELD(<<"Only deliver notifications to available users">>,
3846 11 presence_based_delivery),
3847 ?STRING_CONFIG_FIELD(<<"Specify the type of payload data to be provided at this node">>,
3848 type),
3849 ?NLIST_CONFIG_FIELD(<<"The collections with which a node is affiliated">>,
3850 collection)].
3851
3852 %%<p>There are several reasons why the node configuration request might fail:</p>
3853 %%<ul>
3854 %%<li>The service does not support node configuration.</li>
3855 %%<li>The requesting entity does not have sufficient privileges to configure the node.</li>
3856 %%<li>The request did not specify a node.</li>
3857 %%<li>The node has no configuration options.</li>
3858 %%<li>The specified node does not exist.</li>
3859 %%</ul>
3860 set_configure(Host, Node, From, #{action_el := ActionEl, lang := Lang}) ->
3861 14 case mongoose_data_forms:find_and_parse_form(ActionEl) of
3862 #{type := <<"cancel">>} ->
3863
:-(
{result, []};
3864 #{type := <<"submit">>, kvs := KVs} ->
3865 8 set_configure_submit(Host, Node, From, KVs, Lang);
3866 {error, Msg} ->
3867 4 {error, mongoose_xmpp_errors:bad_request(Lang, Msg)};
3868 _ ->
3869 2 {error, mongoose_xmpp_errors:bad_request(Lang, <<"Invalid form type">>)}
3870 end.
3871
3872 set_configure_submit(Host, Node, User, KVs, Lang) ->
3873 8 Action = fun(NodeRec) ->
3874 8 set_configure_transaction(Host, User, KVs, NodeRec)
3875 end,
3876 8 case transaction(Host, Node, Action, ?FUNCTION_NAME) of
3877 {result, {_OldNode, TNode}} ->
3878 6 Nidx = TNode#pubsub_node.id,
3879 6 Type = TNode#pubsub_node.type,
3880 6 Options = TNode#pubsub_node.options,
3881 6 broadcast_config_notification(Host, Node, Nidx, Type, Options, Lang),
3882 6 {result, []};
3883 Other ->
3884 2 Other
3885 end.
3886
3887 set_configure_transaction(Host, User, KVs, #pubsub_node{ type = Type, id = Nidx } = NodeRec) ->
3888 8 case node_call(Type, get_affiliation, [Nidx, User]) of
3889 {result, owner} ->
3890 8 set_configure_valid_transaction(Host, NodeRec, KVs);
3891 _ ->
3892
:-(
{error, mongoose_xmpp_errors:forbidden()}
3893 end.
3894
3895 set_configure_valid_transaction(Host, #pubsub_node{ type = Type, options = Options } = NodeRec,
3896 KVs) ->
3897 8 OldOpts = case Options of
3898
:-(
[] -> node_options(Host, Type);
3899 8 _ -> Options
3900 end,
3901 8 case set_xoption(Host, maps:to_list(KVs), OldOpts) of
3902 NewOpts when is_list(NewOpts) ->
3903 6 NewNode = NodeRec#pubsub_node{options = NewOpts},
3904 6 case tree_call(Host, set_node, [NewNode]) of
3905 6 {ok, _} -> {result, NewNode};
3906
:-(
Err -> Err
3907 end;
3908 Error ->
3909 2 Error
3910 end.
3911
3912 add_opt(Key, Value, Opts) ->
3913 205 [{Key, Value} | lists:keydelete(Key, 1, Opts)].
3914
3915 -define(SET_BOOL_XOPT(Opt, Val),
3916 BoolVal = case Val of
3917 <<"0">> -> false;
3918 <<"1">> -> true;
3919 <<"false">> -> false;
3920 <<"true">> -> true;
3921 _ -> error
3922 end,
3923 case BoolVal of
3924 error -> {error, mongoose_xmpp_errors:not_acceptable()};
3925 _ -> set_xoption(Host, Opts, add_opt(Opt, BoolVal, NewOpts))
3926 end).
3927
3928 -define(SET_STRING_XOPT(Opt, Val),
3929 set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))).
3930
3931 -define(SET_INTEGER_XOPT(Opt, Val, Min, Max),
3932 case catch binary_to_integer(Val) of
3933 IVal when is_integer(IVal), IVal >= Min ->
3934 if (Max =:= undefined) orelse (IVal =< Max) ->
3935 set_xoption(Host, Opts, add_opt(Opt, IVal, NewOpts));
3936 true ->
3937 {error, mongoose_xmpp_errors:not_acceptable()}
3938 end;
3939 _ ->
3940 {error, mongoose_xmpp_errors:not_acceptable()}
3941 end).
3942
3943 -define(SET_ALIST_XOPT(Opt, Val, Vals),
3944 case lists:member(Val, [atom_to_binary(V, latin1) || V <- Vals]) of
3945 true ->
3946 set_xoption(Host, Opts, add_opt(Opt, binary_to_atom(Val, utf8), NewOpts));
3947 false ->
3948 {error, mongoose_xmpp_errors:not_acceptable()}
3949 end).
3950
3951 -define(SET_LIST_XOPT(Opt, Val),
3952 set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))).
3953
3954 122 set_xoption(_Host, [], NewOpts) -> NewOpts;
3955 set_xoption(Host, [{<<"pubsub#roster_groups_allowed">>, Value} | Opts], NewOpts) ->
3956 2 ?SET_LIST_XOPT(roster_groups_allowed, Value);
3957 set_xoption(Host, [{<<"pubsub#deliver_payloads">>, [Val]} | Opts], NewOpts) ->
3958 5 ?SET_BOOL_XOPT(deliver_payloads, Val);
3959 set_xoption(Host, [{<<"pubsub#deliver_notifications">>, [Val]} | Opts], NewOpts) ->
3960 5 ?SET_BOOL_XOPT(deliver_notifications, Val);
3961 set_xoption(Host, [{<<"pubsub#notify_config">>, [Val]} | Opts], NewOpts) ->
3962 8 ?SET_BOOL_XOPT(notify_config, Val);
3963 set_xoption(Host, [{<<"pubsub#notify_delete">>, [Val]} | Opts], NewOpts) ->
3964 2 ?SET_BOOL_XOPT(notify_delete, Val);
3965 set_xoption(Host, [{<<"pubsub#notify_retract">>, [Val]} | Opts], NewOpts) ->
3966 6 ?SET_BOOL_XOPT(notify_retract, Val);
3967 set_xoption(Host, [{<<"pubsub#persist_items">>, [Val]} | Opts], NewOpts) ->
3968 5 ?SET_BOOL_XOPT(persist_items, Val);
3969 set_xoption(Host, [{<<"pubsub#max_items">>, [Val]} | Opts], NewOpts) ->
3970 4 MaxItems = get_max_items_node(Host),
3971 4 ?SET_INTEGER_XOPT(max_items, Val, 0, MaxItems);
3972 set_xoption(Host, [{<<"pubsub#subscribe">>, [Val]} | Opts], NewOpts) ->
3973 2 ?SET_BOOL_XOPT(subscribe, Val);
3974 set_xoption(Host, [{<<"pubsub#access_model">>, [Val]} | Opts], NewOpts) ->
3975 54 ?SET_ALIST_XOPT(access_model, Val, [open, authorize, presence, roster, whitelist]);
3976 set_xoption(Host, [{<<"pubsub#publish_model">>, [Val]} | Opts], NewOpts) ->
3977 47 ?SET_ALIST_XOPT(publish_model, Val, [publishers, subscribers, open]);
3978 set_xoption(Host, [{<<"pubsub#notification_type">>, [Val]} | Opts], NewOpts) ->
3979 2 ?SET_ALIST_XOPT(notification_type, Val, [headline, normal]);
3980 set_xoption(Host, [{<<"pubsub#node_type">>, [Val]} | Opts], NewOpts) ->
3981 19 ?SET_ALIST_XOPT(node_type, Val, [leaf, collection]);
3982 set_xoption(Host, [{<<"pubsub#max_payload_size">>, [Val]} | Opts], NewOpts) ->
3983 2 ?SET_INTEGER_XOPT(max_payload_size, Val, 0, (?MAX_PAYLOAD_SIZE));
3984 set_xoption(Host, [{<<"pubsub#send_last_published_item">>, [Val]} | Opts], NewOpts) ->
3985 8 ?SET_ALIST_XOPT(send_last_published_item, Val, [never, on_sub, on_sub_and_presence]);
3986 set_xoption(Host, [{<<"pubsub#presence_based_delivery">>, [Val]} | Opts], NewOpts) ->
3987 6 ?SET_BOOL_XOPT(presence_based_delivery, Val);
3988 set_xoption(Host, [{<<"pubsub#purge_offline">>, [Val]} | Opts], NewOpts) ->
3989 4 ?SET_BOOL_XOPT(purge_offline, Val);
3990 set_xoption(Host, [{<<"pubsub#title">>, Value} | Opts], NewOpts) ->
3991 4 ?SET_STRING_XOPT(title, Value);
3992 set_xoption(Host, [{<<"pubsub#type">>, Value} | Opts], NewOpts) ->
3993 2 ?SET_STRING_XOPT(type, Value);
3994 set_xoption(Host, [{<<"pubsub#body_xslt">>, Value} | Opts], NewOpts) ->
3995
:-(
?SET_STRING_XOPT(body_xslt, Value);
3996 set_xoption(Host, [{<<"pubsub#collection">>, Value} | Opts], NewOpts) ->
3997 20 ?SET_LIST_XOPT(collection, Value);
3998 set_xoption(Host, [{<<"pubsub#node">>, [Value]} | Opts], NewOpts) ->
3999 2 ?SET_LIST_XOPT(node, Value);
4000 set_xoption(Host, [_ | Opts], NewOpts) ->
4001
:-(
set_xoption(Host, Opts, NewOpts).
4002
4003 get_max_items_node({_, ServerHost, _}) ->
4004
:-(
get_max_items_node(ServerHost);
4005 get_max_items_node(Host) ->
4006 37 config(serverhost(Host), max_items_node).
4007
4008 get_max_subscriptions_node({_, ServerHost, _}) ->
4009 3 get_max_subscriptions_node(ServerHost);
4010 get_max_subscriptions_node(Host) ->
4011 92 config(serverhost(Host), max_subscriptions_node).
4012
4013 %%%% last item cache handling
4014 maybe_start_cache_module(ServerHost, #{last_item_cache := Cache} = Opts) ->
4015 39 case Cache of
4016 36 false -> ok;
4017 3 _Backend -> mod_pubsub_cache_backend:start(ServerHost, Opts)
4018 end.
4019
4020 is_last_item_cache_enabled(Host) ->
4021 217 cache_backend(Host) =/= false.
4022
4023 cache_backend(Host) ->
4024 217 gen_mod:get_module_opt(serverhost(Host), mod_pubsub, last_item_cache).
4025
4026 set_cached_item({_, ServerHost, _}, Nidx, ItemId, Publisher, Payload) ->
4027 18 set_cached_item(ServerHost, Nidx, ItemId, Publisher, Payload);
4028 set_cached_item(Host, Nidx, ItemId, Publisher, Payload) ->
4029 122 is_last_item_cache_enabled(Host) andalso
4030 14 mod_pubsub_cache_backend:upsert_last_item(serverhost(Host), Nidx, ItemId, Publisher, Payload).
4031
4032 unset_cached_item({_, ServerHost, _}, Nidx) ->
4033
:-(
unset_cached_item(ServerHost, Nidx);
4034 unset_cached_item(Host, Nidx) ->
4035 4 is_last_item_cache_enabled(Host) andalso
4036 2 mod_pubsub_cache_backend:delete_last_item(serverhost(Host), Nidx).
4037
4038 -spec get_cached_item(ServerHost :: mod_pubsub:host(),
4039 Nidx :: mod_pubsub:nodeIdx()) -> false | mod_pubsub:pubsubItem().
4040 get_cached_item({_, ServerHost, _}, Nidx) ->
4041 8 get_cached_item(ServerHost, Nidx);
4042 get_cached_item(Host, Nidx) ->
4043 22 is_last_item_cache_enabled(Host) andalso
4044 8 case mod_pubsub_cache_backend:get_last_item(serverhost(Host), Nidx) of
4045 {ok, #pubsub_last_item{itemid = ItemId, creation = Creation, payload = Payload}} ->
4046 5 #pubsub_item{itemid = {ItemId, Nidx},
4047 payload = Payload, creation = Creation,
4048 modification = Creation};
4049 _ ->
4050 3 false
4051 end.
4052
4053 %%%% plugin handling
4054
4055 -spec host(HostType :: mongooseim:host_type(), ServerHost :: mongooseim:domain_name()) -> host().
4056 host(HostType, ServerHost) ->
4057 809 SubdomainPattern = config(HostType, host),
4058 809 mongoose_subdomain_utils:get_fqdn(SubdomainPattern, ServerHost).
4059
4060 -spec serverhost(host()) -> host().
4061 serverhost({_U, Server, _R})->
4062 203 Server;
4063 serverhost(Host) ->
4064 3585 case config(Host, host) of
4065 undefined ->
4066 3503 [_, ServerHost] = binary:split(Host, <<".">>),
4067 3503 ServerHost;
4068 _ ->
4069 82 Host
4070 end.
4071
4072 -spec host_to_host_type(mod_pubsub:host()) -> mongooseim:host_type().
4073 host_to_host_type(Host) ->
4074 2611 SH = serverhost(Host),
4075 2611 {ok, HT} = mongoose_domain_api:get_host_type(SH),
4076 2611 HT.
4077
4078 -spec tree(HostType :: mongooseim:host_type() | host()) -> module() | nodetree_virtual.
4079 tree(HostType) ->
4080 2611 try gen_mod:get_module_opt(HostType, ?MODULE, nodetree)
4081 catch error:{badkey, _} ->
4082 %todo remove when pubsub supports dynamic domains
4083 1582 HT = host_to_host_type(HostType),
4084 1582 gen_mod:get_module_opt(HT, ?MODULE, nodetree)
4085 end.
4086
4087 tree_mod(<<"virtual">>) ->
4088
:-(
nodetree_virtual; % special case, virtual does not use any backend
4089 tree_mod(Name) ->
4090
:-(
binary_to_atom(<<"nodetree_", Name/binary>>, utf8).
4091
4092 -spec plugin(Name :: plugin_name()) -> module().
4093 plugin(Name) ->
4094 4819 binary_to_atom(<<"node_", Name/binary>>, utf8).
4095
4096 -spec plugins(ServerHost :: mongooseim:domain_name()) -> [plugin_name()].
4097 plugins(ServerHost) ->
4098 1203 Proc = gen_mod:get_module_proc(ServerHost, ?PROCNAME),
4099 %% TODO This call could be replaced with persistent terms
4100 1203 gen_server:call(Proc, plugins).
4101
4102 config(ServerHost, Key) ->
4103 5005 config(ServerHost, Key, undefined).
4104 config(ServerHost, Key, Default) ->
4105 5005 gen_mod:get_module_opt(ServerHost, ?MODULE, Key, Default).
4106
4107 select_type(Host, Node) ->
4108 2 select_type(serverhost(Host), Host, Node).
4109
4110 select_type(ServerHost, Host, Node) ->
4111 18 select_type(ServerHost, Host, Node, hd(plugins(ServerHost))).
4112
4113 select_type(ServerHost, Host, Node, Type) ->
4114 252 SelectedType = case Host of
4115 {_User, _Server, _Resource} ->
4116 30 case config(ServerHost, pep_mapping) of
4117
:-(
undefined -> ?PEPNODE;
4118 30 Mapping -> maps:get(Node, Mapping, ?PEPNODE)
4119 end;
4120 _ ->
4121 222 Type
4122 end,
4123 252 ConfiguredTypes = plugins(ServerHost),
4124 252 case lists:member(SelectedType, ConfiguredTypes) of
4125 252 true -> SelectedType;
4126
:-(
false -> hd(ConfiguredTypes)
4127 end.
4128
4129 4633 feature(Feature) -> <<(?NS_PUBSUB)/binary, "#", Feature/binary>>.
4130
4131 features() ->
4132 117 [% see plugin "access-authorize", % OPTIONAL
4133 <<"access-open">>, % OPTIONAL this relates to access_model option in node_hometree
4134 <<"access-presence">>, % OPTIONAL this relates to access_model option in node_pep
4135 <<"access-whitelist">>, % OPTIONAL
4136 <<"collections">>, % RECOMMENDED
4137 <<"config-node">>, % RECOMMENDED
4138 <<"create-and-configure">>, % RECOMMENDED
4139 <<"item-ids">>, % RECOMMENDED
4140 <<"last-published">>, % RECOMMENDED
4141 <<"member-affiliation">>, % RECOMMENDED
4142 <<"presence-notifications">>, % OPTIONAL
4143 <<"presence-subscribe">>, % RECOMMENDED
4144 <<"publisher-affiliation">>, % RECOMMENDED
4145 <<"publish-only-affiliation">>, % OPTIONAL
4146 <<"retrieve-default">>,
4147 <<"rsm">>, % RECOMMENDED
4148 <<"shim">>]. % RECOMMENDED
4149 % see plugin "retrieve-items", % RECOMMENDED
4150 % see plugin "retrieve-subscriptions", % RECOMMENDED
4151 % see plugin "subscribe", % REQUIRED
4152 % see plugin "subscription-options", % OPTIONAL
4153 % see plugin "subscription-notifications" % OPTIONAL
4154
4155 plugin_features(Type) ->
4156 1223 Module = plugin(Type),
4157 1223 case catch gen_pubsub_node:features(Module) of
4158
:-(
{'EXIT', {undef, _}} -> [];
4159 1223 Result -> Result
4160 end.
4161
4162 features(Host, <<>>) ->
4163 117 lists:usort(lists:foldl(fun (Plugin, Acc) ->
4164 276 Acc ++ plugin_features(Plugin)
4165 end,
4166 features(), plugins(Host)));
4167 features(Host, Node) when is_binary(Node) ->
4168
:-(
Action = fun (#pubsub_node{type = Type}) ->
4169
:-(
{result, plugin_features(Type)}
4170 end,
4171
:-(
case dirty(Host, Node, Action, ?FUNCTION_NAME) of
4172
:-(
{result, Features} -> lists:usort(features() ++ Features);
4173
:-(
_ -> features()
4174 end.
4175
4176 %% @doc <p>node tree plugin call.</p>
4177 tree_call(HostType, Function, Args) ->
4178 1582 ?LOG_DEBUG(#{what => pubsub_tree_call, action_function => Function,
4179 1582 args => Args, host_type => HostType}),
4180 1582 apply(tree(HostType), Function, Args).
4181
4182 tree_action(HostType, Function, Args) ->
4183 36 ?LOG_DEBUG(#{what => pubsub_tree_action,
4184 36 action_function => Function, args => Args}),
4185 36 Fun = fun () -> tree_call(HostType, Function, Args) end,
4186 36 ErrorDebug = #{
4187 action => tree_action,
4188 host_type => HostType,
4189 function => Function,
4190 args => Args
4191 },
4192 36 catch mod_pubsub_db_backend:dirty(Fun, ErrorDebug).
4193
4194 %% @doc <p>node plugin call.</p>
4195 node_call(Type, Function, Args) ->
4196 3156 ?LOG_DEBUG(#{what => pubsub_node_call, node_type => Type,
4197 3156 action_function => Function, args => Args}),
4198 3156 PluginModule = plugin(Type),
4199 3156 plugin_call(PluginModule, Function, Args).
4200
4201 -spec plugin_call(module(), atom(), [term()]) -> {result, any()} | {error, any()}.
4202 plugin_call(PluginModule, Function, Args) ->
4203 3246 CallModule = maybe_default_node(PluginModule, Function, Args),
4204 3246 case apply(CallModule, Function, Args) of
4205 {result, Result} ->
4206 2733 {result, Result};
4207 {error, Error} ->
4208 22 {error, Error};
4209 {'EXIT', Reason} ->
4210
:-(
{error, Reason};
4211 Result ->
4212 491 {result, Result} %% any other return value is forced as result
4213 end.
4214
4215 maybe_default_node(PluginModule, Function, Args) ->
4216 5476 case erlang:function_exported(PluginModule, Function, length(Args)) of
4217 true ->
4218 3246 PluginModule;
4219 _ ->
4220 2230 case gen_pubsub_node:based_on(PluginModule) of
4221 none ->
4222
:-(
?LOG_ERROR(#{what => pubsub_undefined_function,
4223
:-(
node_plugin => PluginModule, action_function => Function}),
4224
:-(
exit(udefined_node_plugin_function);
4225 BaseModule ->
4226 2230 maybe_default_node(BaseModule, Function, Args)
4227 end
4228 end.
4229
4230 node_action(Host, Type, Function, Args) ->
4231 1003 ?LOG_DEBUG(#{what => pubsub_node_action, sub_host => Host,
4232 1003 node_type => Type, action_function => Function, args => Args}),
4233 1003 ErrorDebug = #{
4234 action => {node_action, Function},
4235 pubsub_host => Host,
4236 node_type => Type,
4237 args => Args
4238 },
4239 1003 mod_pubsub_db_backend:dirty(fun() ->
4240 1003 node_call(Type, Function, Args)
4241 end, ErrorDebug).
4242
4243 dirty(Host, Node, Action, ActionName) ->
4244 487 ErrorDebug = #{
4245 pubsub_host => Host,
4246 node_name => Node,
4247 action => ActionName },
4248 487 mod_pubsub_db_backend:dirty(db_call_fun(Host, Node, Action), ErrorDebug).
4249
4250 transaction(Host, Node, Action, ActionName) ->
4251 159 ErrorDebug = #{
4252 pubsub_host => Host,
4253 node_name => Node,
4254 action => ActionName
4255 },
4256 159 mod_pubsub_db_backend:transaction(db_call_fun(Host, Node, Action), ErrorDebug).
4257
4258 db_call_fun(Host, Node, Action) ->
4259 646 fun () ->
4260 646 case tree_call(Host, get_node, [Host, Node]) of
4261 #pubsub_node{} = N ->
4262 625 case Action(N) of
4263 579 {result, Result} -> {result, {N, Result}};
4264
:-(
{atomic, {result, Result}} -> {result, {N, Result}};
4265 46 Other -> Other
4266 end;
4267 21 Error -> Error
4268 end
4269 end.
4270
4271 %%%% helpers
4272
4273 %% Add pubsub-specific error element
4274 extended_error(Error, Ext) ->
4275 1228 extended_error(Error, Ext, [{<<"xmlns">>, ?NS_PUBSUB_ERRORS}]).
4276
4277 extended_error(Error, unsupported, Feature) ->
4278 %% Give a uniq identifier
4279 404 extended_error(Error, <<"unsupported">>,
4280 [{<<"xmlns">>, ?NS_PUBSUB_ERRORS},
4281 {<<"feature">>, Feature}]);
4282 extended_error(#xmlel{name = Error, attrs = Attrs, children = SubEls}, Ext, ExtAttrs) ->
4283 1632 #xmlel{name = Error, attrs = Attrs,
4284 children = lists:reverse([#xmlel{name = Ext, attrs = ExtAttrs} | SubEls])}.
4285
4286 string_to_ljid(JID) ->
4287 115 case jid:from_binary(JID) of
4288 error ->
4289
:-(
{<<>>, <<>>, <<>>};
4290 J ->
4291 115 case jid:to_lower(J) of
4292
:-(
error -> {<<>>, <<>>, <<>>};
4293 115 J1 -> J1
4294 end
4295 end.
4296
4297 -spec uniqid() -> mod_pubsub:itemId().
4298 uniqid() ->
4299 61 uuid:uuid_to_string(uuid:get_v4(), binary_standard).
4300
4301 998 node_attr(Node) -> [{<<"node">>, Node}].
4302
4303
:-(
item_attr([]) -> [];
4304 377 item_attr(ItemId) -> [{<<"id">>, ItemId}].
4305
4306 40 item_attr(ItemId, undefined) -> item_attr(ItemId);
4307
:-(
item_attr([], Publisher) -> [{<<"publisher">>,
4308 jid:to_binary(jid:to_lower(Publisher))}];
4309 6 item_attr(ItemId, Publisher) -> [{<<"id">>, ItemId},
4310 {<<"publisher">>,
4311 jid:to_binary(jid:to_lower(Publisher))}].
4312
4313 items_els(Items) ->
4314 43 [#xmlel{name = <<"item">>, attrs = item_attr(ItemId, Publisher), children = Payload}
4315 43 || #pubsub_item{itemid = {ItemId, _}, publisher = Publisher, payload = Payload } <- Items].
4316
4317 -spec add_message_type(Message :: exml:element(), Type :: atom()) -> exml:element().
4318 1 add_message_type(Message, normal) -> Message;
4319 add_message_type(#xmlel{name = <<"message">>, attrs = Attrs, children = Els}, Type) ->
4320 382 #xmlel{name = <<"message">>,
4321 attrs = [{<<"type">>, atom_to_binary(Type, utf8)} | Attrs],
4322 children = Els};
4323 add_message_type(XmlEl, _Type) ->
4324
:-(
XmlEl.
4325
4326 maybe_add_shim_headers(Stanza, false, _SubIDs, _OriginNode, _SubNode) ->
4327 2 Stanza;
4328 maybe_add_shim_headers(Stanza, true, SubIDs, OriginNode, SubNode) ->
4329 44 Headers1 = case SubIDs of
4330 [_OnlyOneSubID] ->
4331 43 [];
4332 _ ->
4333 1 subid_shim(SubIDs)
4334 end,
4335 44 Headers2 = case SubNode of
4336 OriginNode ->
4337 30 Headers1;
4338 _ ->
4339 14 [collection_shim(SubNode) | Headers1]
4340 end,
4341 44 add_headers(Stanza, <<"headers">>, ?NS_SHIM, Headers2).
4342
4343 add_extended_headers(Stanza, HeaderEls) ->
4344 18 add_headers(Stanza, <<"addresses">>, ?NS_ADDRESS, HeaderEls).
4345
4346 add_headers(#xmlel{name = Name, attrs = Attrs, children = Els}, HeaderName, HeaderNS, HeaderEls) ->
4347 62 HeaderEl = #xmlel{name = HeaderName,
4348 attrs = [{<<"xmlns">>, HeaderNS}],
4349 children = HeaderEls},
4350 62 #xmlel{name = Name, attrs = Attrs,
4351 children = lists:append(Els, [HeaderEl])}.
4352
4353 subid_shim(SubIds) ->
4354 1 [#xmlel{ name = <<"header">>,
4355 attrs = [{<<"name">>, <<"SubId">>}],
4356 children = [#xmlcdata{ content = SubId }]}
4357 1 || SubId <- SubIds].
4358
4359 collection_shim(CollectionNode) ->
4360 14 #xmlel{ name = <<"header">>,
4361 attrs = [{<<"name">>, <<"Collection">>}],
4362 children = [#xmlcdata{ content = CollectionNode }] }.
4363
4364 %% The argument is a list of Jids because this function could be used
4365 %% with the 'pubsub#replyto' (type=jid-multi) node configuration.
4366 extended_headers(Jids) ->
4367 18 [#xmlel{name = <<"address">>,
4368 attrs = [{<<"type">>, <<"replyto">>}, {<<"jid">>, Jid}]}
4369 18 || Jid <- Jids].
4370
4371 -spec on_user_offline(Acc, Params, Extra) -> {ok, Acc} when
4372 Acc :: mongoose_acc:t(),
4373 Params :: #{jid := jid:jid()},
4374 Extra :: gen_hook:extra().
4375 on_user_offline(Acc,
4376 #{jid := #jid{luser = User, lserver = Server, lresource = Resource}},
4377 #{host_type := HostType}) ->
4378 373 case user_resources(User, Server) of
4379 366 [] -> purge_offline(HostType, {User, Server, Resource});
4380 7 _ -> true
4381 end,
4382 373 {ok, Acc}.
4383
4384 purge_offline(HT, {_, LServer, _} = LJID) ->
4385 366 Host = host(HT, LServer),
4386 366 Plugins = plugins(LServer),
4387 366 Affs = lists:foldl(
4388 fun (PluginType, Acc) ->
4389 536 check_plugin_features_and_acc_affs(Host, PluginType, LJID, Acc)
4390 end, [], Plugins),
4391 366 lists:foreach(
4392 fun ({Node, Affiliation}) ->
4393 103 Options = Node#pubsub_node.options,
4394 103 IsPublisherOrOwner = lists:member(Affiliation, [owner, publisher, publish_only]),
4395 103 OpenNode = get_option(Options, publish_model) == open,
4396 103 ShouldPurge = get_option(Options, purge_offline)
4397 2 andalso get_option(Options, persist_items),
4398 103 case (IsPublisherOrOwner or OpenNode) and ShouldPurge of
4399 2 true -> purge_offline(Host, LJID, Node);
4400 101 false -> ok
4401 end
4402 end, lists:usort(lists:flatten(Affs))).
4403
4404 check_plugin_features_and_acc_affs(Host, PluginType, LJID, AffsAcc) ->
4405 536 Features = plugin_features(PluginType),
4406 536 case lists:member(<<"retract-items">>, Features)
4407 421 andalso lists:member(<<"persistent-items">>, Features)
4408 421 andalso lists:member(<<"retrieve-affiliations">>, Features) of
4409 true ->
4410 421 {result, Affs} = node_action(Host, PluginType, get_entity_affiliations, [Host, LJID]),
4411 421 [Affs | AffsAcc];
4412 false ->
4413 115 ?LOG_DEBUG(#{what => pubsub_plugin_features_check_error,
4414 text => <<"Cannot purge items on offline">>,
4415 115 plugin => PluginType, user => jid:to_binary(LJID)}),
4416 115 AffsAcc
4417 end.
4418
4419 purge_offline(Host, {User, Server, _} = _LJID, #pubsub_node{ id = Nidx, type = Type } = Node) ->
4420 2 case node_action(Host, Type, get_items, [Nidx, service_jid(Host), #{}]) of
4421 {result, {[], _}} ->
4422
:-(
ok;
4423 {result, {Items, _}} ->
4424 2 lists:foreach(fun(#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, _}}})
4425 when (U == User) and (S == Server) ->
4426 2 purge_item_of_offline_user(Host, Node, ItemId, U, S);
4427 (_) ->
4428 2 true
4429 end, Items);
4430 Error ->
4431
:-(
Error
4432 end.
4433
4434 purge_item_of_offline_user(Host, #pubsub_node{ id = Nidx, nodeid = {_, NodeId},
4435 options = Options, type = Type }, ItemId, U, S) ->
4436 2 PublishModel = get_option(Options, publish_model),
4437 2 ForceNotify = get_option(Options, notify_retract),
4438 2 case node_action(Host, Type, delete_item, [Nidx, {U, S, <<>>}, PublishModel, ItemId]) of
4439 {result, {_, broadcast}} ->
4440 2 broadcast_retract_items(Host, NodeId, Nidx, Type, Options, [ItemId], ForceNotify),
4441 2 case get_cached_item(Host, Nidx) of
4442
:-(
#pubsub_item{itemid = {ItemId, Nidx}} -> unset_cached_item(Host, Nidx);
4443 2 _ -> ok
4444 end;
4445 {result, _} ->
4446
:-(
ok;
4447 Error ->
4448
:-(
Error
4449 end.
4450
4451 timestamp() ->
4452
:-(
os:system_time(microsecond).
4453
4454 make_error_reply(#iq{ sub_el = SubEl } = IQ, #xmlel{} = ErrorEl) ->
4455 5 IQ#iq{type = error, sub_el = [ErrorEl, SubEl]};
4456 make_error_reply(#iq{ sub_el = SubEl } = IQ, Error) ->
4457
:-(
?LOG_ERROR(#{what => pubsub_crash, reason => Error}),
4458
:-(
IQ#iq{type = error, sub_el = [mongoose_xmpp_errors:internal_server_error(), SubEl]};
4459 make_error_reply(Packet, #xmlel{} = ErrorEl) ->
4460 59 jlib:make_error_reply(Packet, ErrorEl);
4461 make_error_reply(Packet, Error) ->
4462
:-(
?LOG_ERROR(#{what => pubsub_crash, reason => Error}),
4463
:-(
jlib:make_error_reply(Packet, mongoose_xmpp_errors:internal_server_error()).
4464
4465 config_metrics(Host) ->
4466 24 mongoose_module_metrics:opts_for_module(Host, ?MODULE, [backend]).
Line Hits Source