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 |
216 |
[{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 |
164 |
#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 |
164 |
#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 |
164 |
#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 |
30 |
Payloads = mod_pubsub_db_backend:get_user_payloads(LUser, LServer), |
360 |
30 |
Nodes = mod_pubsub_db_backend:get_user_nodes(LUser, LServer), |
361 |
30 |
Subscriptions = mod_pubsub_db_backend:get_user_subscriptions(LUser, LServer), |
362 |
|
|
363 |
30 |
[{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 |
104 |
Features = [?NS_PUBSUB | [feature(F) || F <- features(LServer, <<>>)]], |
668 |
104 |
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 |
474 |
Type = mongoose_acc:stanza_type(Acc), |
797 |
474 |
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 |
469 |
{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 |
317 |
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 |
51 |
LUser = jid:nodeprep(User), |
919 |
51 |
LServer = jid:nameprep(Server), |
920 |
51 |
Host = host(LServer), |
921 |
51 |
lists:foreach(fun(PType) -> |
922 |
134 |
remove_user_per_plugin_safe(LUser, LServer, plugin(PType)) |
923 |
|
end, plugins(Host)), |
924 |
51 |
Acc. |
925 |
|
|
926 |
|
remove_user_per_plugin_safe(LUser, LServer, Plugin) -> |
927 |
134 |
try |
928 |
134 |
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 |
99 |
Attrs = case get_option(Options, title) of |
1195 |
|
false -> |
1196 |
99 |
[{<<"jid">>, Host} |
1197 |
|
| node_attr(SubNode)]; |
1198 |
|
[Title] -> |
1199 |
:-( |
[{<<"jid">>, Host}, |
1200 |
|
{<<"name">>, Title} |
1201 |
|
| node_attr(SubNode)] |
1202 |
|
end, |
1203 |
99 |
#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 |
218 |
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 |
218 |
Parent = get_parent(Host, Type, Node), |
2027 |
218 |
case node_call(Host, Type, create_node_permission, |
2028 |
|
[Host, ServerHost, Node, Parent, Owner, Access]) of |
2029 |
|
{result, true} -> |
2030 |
218 |
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 |
218 |
case node_call(Host, Type, node_to_path, [Node]) of |
2037 |
|
{result, [Node]} -> |
2038 |
204 |
<<>>; |
2039 |
|
{result, Path} -> |
2040 |
14 |
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 |
218 |
Parents = case Parent of |
2046 |
204 |
<<>> -> []; |
2047 |
14 |
_ -> [Parent] |
2048 |
|
end, |
2049 |
218 |
case tree_call(Host, create_node, [Host, Node, Type, Owner, NodeOptions, Parents]) of |
2050 |
|
{ok, Nidx} -> |
2051 |
217 |
SubsByDepth = get_node_subs_by_depth(Host, Node, Owner), |
2052 |
211 |
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 |
147 |
case node_call(Host, Type, get_affiliation, [Nidx, Owner]) of |
2138 |
|
{result, owner} -> |
2139 |
147 |
SubsByDepth = get_node_subs_by_depth(Host, Node, service_jid(Host)), |
2140 |
146 |
Removed = tree_call(Host, delete_node, [Host, Node]), |
2141 |
146 |
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 |
739 |
case Host of |
3319 |
51 |
{U, S, _} -> {jid, U, S, <<>>, U, S, <<>>}; |
3320 |
688 |
_ -> {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 |
479 |
ParentTree = tree_call(Host, get_parentnodes_tree, [Host, Node, From]), |
3616 |
479 |
[{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 |
536 |
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 |
529 |
[{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 |
363 |
JID = jid:make(User, Server, <<>>), |
3761 |
363 |
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 |
2484 |
get_option(Options, Var) -> get_option(Options, Var, false). |
3825 |
|
|
3826 |
|
get_option(Options, Var, Def) -> |
3827 |
2980 |
case lists:keysearch(Var, 1, Options) of |
3828 |
2533 |
{value, {_Val, Ret}} -> Ret; |
3829 |
447 |
_ -> 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 |
379 |
config(ServerHost, host, <<"pubsub.", ServerHost/binary>>). |
4185 |
|
|
4186 |
|
serverhost({_U, Server, _R})-> |
4187 |
53 |
Server; |
4188 |
|
serverhost(Host) -> |
4189 |
3445 |
case config(Host, host, undefined) of |
4190 |
|
undefined -> |
4191 |
3211 |
[_, ServerHost] = binary:split(Host, <<".">>), |
4192 |
3211 |
ServerHost; |
4193 |
|
_ -> |
4194 |
234 |
Host |
4195 |
|
end. |
4196 |
|
|
4197 |
|
tree(Host) -> |
4198 |
1902 |
case config(serverhost(Host), nodetree) of |
4199 |
4 |
undefined -> tree(Host, ?STDTREE); |
4200 |
1898 |
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 |
3798 |
plugin(Name). |
4210 |
|
|
4211 |
|
plugin(Name) -> |
4212 |
3951 |
binary_to_atom(<<"node_", Name/binary>>, utf8). |
4213 |
|
|
4214 |
|
plugins(Host) -> |
4215 |
750 |
case config(serverhost(Host), plugins) of |
4216 |
2 |
undefined -> [?STDNODE]; |
4217 |
:-( |
[] -> [?STDNODE]; |
4218 |
748 |
Plugins -> Plugins |
4219 |
|
end. |
4220 |
|
|
4221 |
|
config(ServerHost, Key) -> |
4222 |
3133 |
config(ServerHost, Key, undefined). |
4223 |
|
config(ServerHost, Key, Default) -> |
4224 |
7260 |
case catch ets:lookup(gen_mod:get_module_proc(ServerHost, config), Key) of |
4225 |
4041 |
[{Key, Value}] -> Value; |
4226 |
3219 |
_ -> 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 |
4149 |
feature(Feature) -> <<(?NS_PUBSUB)/binary, "#", Feature/binary>>. |
4253 |
|
|
4254 |
|
features() -> |
4255 |
108 |
[% 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 |
1120 |
Module = plugin(Host, Type), |
4279 |
1120 |
case catch gen_pubsub_node:features(Module) of |
4280 |
:-( |
{'EXIT', {undef, _}} -> []; |
4281 |
1120 |
Result -> Result |
4282 |
|
end. |
4283 |
|
|
4284 |
|
features(Host, <<>>) -> |
4285 |
108 |
lists:usort(lists:foldl(fun (Plugin, Acc) -> |
4286 |
276 |
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 |
1471 |
?LOG_DEBUG(#{what => pubsub_tree_call, sub_host => Host, |
4303 |
1471 |
action_function => Function, args => Args}), |
4304 |
1471 |
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 |
2384 |
?LOG_DEBUG(#{what => pubsub_node_call, node_type => Type, |
4321 |
2384 |
action_function => Function, args => Args}), |
4322 |
2384 |
PluginModule = plugin(Host, Type), |
4323 |
2384 |
plugin_call(PluginModule, Function, Args). |
4324 |
|
|
4325 |
|
plugin_call(PluginModule, Function, Args) -> |
4326 |
2537 |
CallModule = maybe_default_node(PluginModule, Function, Args), |
4327 |
2537 |
case apply(CallModule, Function, Args) of |
4328 |
|
{result, Result} -> |
4329 |
2057 |
{result, Result}; |
4330 |
|
{error, Error} -> |
4331 |
23 |
{error, Error}; |
4332 |
|
{'EXIT', Reason} -> |
4333 |
:-( |
{error, Reason}; |
4334 |
|
Result -> |
4335 |
442 |
{result, Result} %% any other return value is forced as result |
4336 |
|
end. |
4337 |
|
|
4338 |
|
maybe_default_node(PluginModule, Function, Args) -> |
4339 |
4736 |
case erlang:function_exported(PluginModule, Function, length(Args)) of |
4340 |
|
true -> |
4341 |
2537 |
PluginModule; |
4342 |
|
_ -> |
4343 |
2199 |
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 |
2199 |
maybe_default_node(BaseModule, Function, Args) |
4350 |
|
end |
4351 |
|
end. |
4352 |
|
|
4353 |
|
node_action(Host, Type, Function, Args) -> |
4354 |
443 |
?LOG_DEBUG(#{what => pubsub_node_action, sub_host => Host, |
4355 |
443 |
node_type => Type, action_function => Function, args => Args}), |
4356 |
443 |
ErrorDebug = #{ |
4357 |
|
action => {node_action, Function}, |
4358 |
|
pubsub_host => Host, |
4359 |
|
node_type => Type, |
4360 |
|
args => Args |
4361 |
|
}, |
4362 |
443 |
mod_pubsub_db_backend:dirty(fun() -> |
4363 |
443 |
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 |
586 |
case tree_call(Host, get_node, [Host, Node]) of |
4384 |
|
#pubsub_node{} = N -> |
4385 |
568 |
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 |
899 |
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 |
335 |
{User, Server, Resource} = jid:to_lower(JID), |
4497 |
335 |
case user_resources(User, Server) of |
4498 |
328 |
[] -> purge_offline({User, Server, Resource}); |
4499 |
7 |
_ -> true |
4500 |
|
end, |
4501 |
335 |
Acc. |
4502 |
|
|
4503 |
|
purge_offline({_, LServer, _} = LJID) -> |
4504 |
328 |
Host = host(LServer), |
4505 |
328 |
Plugins = plugins(Host), |
4506 |
328 |
Affs = lists:foldl( |
4507 |
|
fun (PluginType, Acc) -> |
4508 |
477 |
check_plugin_features_and_acc_affs(Host, PluginType, LJID, Acc) |
4509 |
|
end, [], Plugins), |
4510 |
328 |
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 |
477 |
Features = plugin_features(Host, PluginType), |
4525 |
477 |
case lists:member(<<"retract-items">>, Features) |
4526 |
379 |
andalso lists:member(<<"persistent-items">>, Features) |
4527 |
379 |
andalso lists:member(<<"retrieve-affiliations">>, Features) of |
4528 |
|
true -> |
4529 |
379 |
{result, Affs} = node_action(Host, PluginType, get_entity_affiliations, [Host, LJID]), |
4530 |
379 |
[Affs | AffsAcc]; |
4531 |
|
false -> |
4532 |
98 |
?LOG_DEBUG(#{what => pubsub_plugin_features_check_error, |
4533 |
|
text => <<"Cannot purge items on offline">>, |
4534 |
98 |
plugin => PluginType, user => jid:to_binary(LJID)}), |
4535 |
98 |
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). |