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