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