./ct_report/coverage/mod_roster.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_roster.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : Roster management
5 %%% Created : 11 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2013 ProcessOne
9 %%%
10 %%% This program is free software; you can redistribute it and/or
11 %%% modify it under the terms of the GNU General Public License as
12 %%% published by the Free Software Foundation; either version 2 of the
13 %%% License, or (at your option) any later version.
14 %%%
15 %%% This program is distributed in the hope that it will be useful,
16 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 %%% General Public License for more details.
19 %%%
20 %%% You should have received a copy of the GNU General Public License
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 %%%
24 %%%----------------------------------------------------------------------
25
26 %%% @doc Roster management.
27 %%%
28 %%% Includes support for XEP-0237: Roster Versioning.
29 %%% The roster versioning follows an all-or-nothing strategy:
30 %%% - If the version supplied by the client is the latest, return an empty response.
31 %%% - If not, return the entire new roster (with updated version string).
32 %%% Roster version is a hash digest of the entire roster.
33 %%% No additional data is stored in DB.
34
35 -module(mod_roster).
36 -author('alexey@process-one.net').
37 -xep([{xep, 237}, {version, "1.3"}]).
38 -xep([{xep, 83}, {version, "1.0"}]).
39 -xep([{xep, 93}, {version, "1.2"}]).
40 -behaviour(gen_mod).
41 -behaviour(mongoose_module_metrics).
42
43 -export([start/2,
44 stop/1,
45 hooks/1,
46 config_spec/0,
47 supported_features/0,
48 instrumentation/1,
49 process_iq/5,
50 get_roster_entry/4,
51 item_to_map/1,
52 set_roster_entry/5,
53 remove_from_roster/3,
54 item_to_xml/1
55 ]).
56
57 % Hook handlers
58 -export([
59 get_user_roster/3,
60 in_subscription/3,
61 out_subscription/3,
62 get_subscription_lists/3,
63 get_jid_info/3,
64 remove_user/3,
65 remove_domain/3,
66 get_versioning_feature/3,
67 get_personal_data/3
68 ]).
69
70 -export([set_items/3,
71 transaction/2,
72 process_subscription_t/6,
73 get_user_rosters_length/2]). % for testing
74
75 -export([config_metrics/1]).
76
77 -ignore_xref([get_user_rosters_length/2,
78 item_to_xml/1,
79 process_subscription_t/6,
80 set_items/3,
81 transaction/2
82 ]).
83
84 -include("mongoose.hrl").
85 -include("jlib.hrl").
86 -include("mod_roster.hrl").
87 -include("mongoose_config_spec.hrl").
88
89 -type roster() :: #roster{}.
90
91 -type sub_presence() :: subscribe | subscribed | unsubscribe | unsubscribed.
92
93 -type subscription_state() :: none | from | to | both | remove.
94
95 -type get_user_roster_strategy() :: db_versioning | hash_versioning | no_versioning.
96
97 %% Types used in the backend API
98
99 -type contact() :: jid:ljid().
100 -type transaction_state() :: in_transaction | no_transaction.
101 -type entry_format() :: full | short.
102 -type version() :: binary().
103
104 -export_type([roster/0, sub_presence/0, subscription_state/0]).
105 -export_type([contact/0, transaction_state/0, entry_format/0, version/0]).
106
107 %%--------------------------------------------------------------------
108 %% gdpr callback
109 %%--------------------------------------------------------------------
110
111 -spec get_personal_data(Acc, Params, Extra) -> {ok, Acc} when
112 Acc :: gdpr:personal_data(),
113 Params :: #{jid := jid:jid()},
114 Extra :: gen_hook:extra().
115 get_personal_data(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, #{host_type := HostType}) ->
116 87 Schema = ["jid", "name", "subscription", "ask", "groups", "askmessage", "xs"],
117 87 Records = get_roster(HostType, LUser, LServer),
118 87 SerializedRecords = lists:map(fun roster_record_to_gdpr_entry/1, Records),
119 87 {ok, [{roster, Schema, SerializedRecords} | Acc]}.
120
121 -spec roster_record_to_gdpr_entry(roster()) -> gdpr:entry().
122 roster_record_to_gdpr_entry(#roster{ jid = JID, name = Name,
123 subscription = Subscription, ask = Ask, groups = Groups,
124 askmessage = AskMessage, xs = XS }) ->
125 2 [
126 jid:to_binary(JID),
127 Name,
128 atom_to_binary(Subscription, utf8),
129 atom_to_binary(Ask, utf8),
130
:-(
string:join([ unicode:characters_to_list(G) || G <- Groups ], ", "),
131 AskMessage,
132
:-(
<< (exml:to_binary(X)) || X <- XS >>
133 ].
134
135 %%--------------------------------------------------------------------
136 %% mod_roster's callbacks
137 %%--------------------------------------------------------------------
138
139 -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> any().
140 start(HostType, Opts = #{iqdisc := IQDisc}) ->
141 450 mod_roster_backend:init(HostType, Opts),
142 450 gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_ROSTER, ejabberd_sm,
143 fun ?MODULE:process_iq/5, #{}, IQDisc).
144
145 -spec stop(mongooseim:host_type()) -> any().
146 stop(HostType) ->
147 440 gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_ROSTER, ejabberd_sm).
148
149 -spec config_spec() -> mongoose_config_spec:config_section().
150 config_spec() ->
151 208 #section{
152 items = #{<<"iqdisc">> => mongoose_config_spec:iqdisc(),
153 <<"versioning">> => #option{type = boolean},
154 <<"store_current_id">> => #option{type = boolean},
155 <<"backend">> => #option{type = atom,
156 validate = {module, mod_roster}}
157 },
158 defaults = #{<<"iqdisc">> => one_queue,
159 <<"versioning">> => false,
160 <<"store_current_id">> => false,
161 <<"backend">> => mnesia}
162 }.
163
164 204 supported_features() -> [dynamic_domains].
165
166 -spec instrumentation(mongooseim:host_type()) -> [mongoose_instrument:spec()].
167 instrumentation(HostType) ->
168 891 [{mod_roster_get, #{host_type => HostType}, #{metrics => #{count => spiral}}},
169 {mod_roster_set, #{host_type => HostType}, #{metrics => #{count => spiral}}},
170 {mod_roster_push, #{host_type => HostType}, #{metrics => #{count => spiral}}}].
171
172 hooks(HostType) ->
173 890 [{roster_get, HostType, fun ?MODULE:get_user_roster/3, #{}, 50},
174 {roster_in_subscription, HostType, fun ?MODULE:in_subscription/3, #{}, 50},
175 {roster_out_subscription, HostType, fun ?MODULE:out_subscription/3, #{}, 50},
176 {roster_get_subscription_lists, HostType, fun ?MODULE:get_subscription_lists/3, #{}, 50},
177 {roster_get_jid_info, HostType, fun ?MODULE:get_jid_info/3, #{}, 50},
178 {remove_user, HostType, fun ?MODULE:remove_user/3, #{}, 50},
179 {remove_domain, HostType, fun ?MODULE:remove_domain/3, #{}, 50},
180 {anonymous_purge, HostType, fun ?MODULE:remove_user/3, #{}, 50},
181 {roster_get_versioning_feature, HostType, fun ?MODULE:get_versioning_feature/3, #{}, 50},
182 {get_personal_data, HostType, fun ?MODULE:get_personal_data/3, #{}, 50}].
183
184 -spec process_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map()) ->
185 {mongoose_acc:t(), jlib:iq()}.
186 process_iq(Acc, From = #jid{lserver = LServer, luser = LUser},
187 To = #jid{lserver = LServer, luser = LUser}, IQ, _Extra) ->
188 55 process_local_iq(Acc, From, To, IQ);
189 process_iq(Acc, _From, _To, IQ = #iq{lang = Lang, sub_el = SubEl}, _Extra) ->
190 %% Error type 'forbidden' is specified in Section 2.3.3 of RFC6121 for iq set.
191 %% The behaviour is unspecified for iq get, but it makes sense to handle them consistently.
192 2 ErrorMsg = <<"It is forbidden to query the roster of another user">>,
193 2 ErrorEl = mongoose_xmpp_errors:forbidden(Lang, ErrorMsg),
194 2 {Acc, IQ#iq{type = error, sub_el = [SubEl, ErrorEl]}}.
195
196 -spec process_local_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq()) ->
197 {mongoose_acc:t(), jlib:iq()}.
198 process_local_iq(Acc, From, To, #iq{type = Type} = IQ) ->
199 55 HostType = mongoose_acc:host_type(Acc),
200 55 IQReply = case Type of
201 31 set -> process_iq_set(HostType, From, To, IQ);
202 24 get -> process_iq_get(HostType, From, To, IQ)
203 end,
204 55 {Acc, IQReply}.
205
206 roster_hash(Items) ->
207 4 L = [R#roster{groups = lists:sort(Grs)} ||
208 4 R = #roster{groups = Grs} <- Items],
209 4 mongoose_bin:encode_crypto(term_to_binary(lists:sort(L))).
210
211 -spec roster_versioning_enabled(mongooseim:host_type()) -> boolean().
212 roster_versioning_enabled(HostType) ->
213 7719 gen_mod:get_module_opt(HostType, ?MODULE, versioning, false).
214
215 -spec roster_version_on_db(mongooseim:host_type()) -> boolean().
216 roster_version_on_db(HostType) ->
217 1132 gen_mod:get_module_opt(HostType, ?MODULE, store_current_id, false).
218
219 %% Returns a list that may contain an xmlel with the XEP-237 feature if it's enabled.
220 -spec get_versioning_feature(Acc, Params, Extra) -> {ok, Acc} when
221 Acc :: [exml:element()],
222 Params :: map(),
223 Extra :: gen_hook:extra().
224 get_versioning_feature(Acc, _, #{host_type := HostType}) ->
225 6697 NewAcc = case roster_versioning_enabled(HostType) of
226 true ->
227 6 Feature = #xmlel{name = <<"ver">>,
228 attrs = [{<<"xmlns">>, ?NS_ROSTER_VER}]},
229 6 [Feature | Acc];
230 6691 false -> []
231 end,
232 6697 {ok, NewAcc}.
233
234 roster_version(HostType, #jid{luser = LUser, lserver = LServer} = JID) ->
235 3 case roster_version_on_db(HostType) of
236 true ->
237 2 case read_roster_version(HostType, LUser, LServer) of
238
:-(
error -> not_found;
239 2 V -> V
240 end;
241 false ->
242 1 R = get_roster_old(HostType, JID),
243 1 roster_hash(R)
244 end.
245
246 %% Load roster from DB only if neccesary.
247 %% It is neccesary if
248 %% - roster versioning is disabled in server OR
249 %% - roster versioning is not used by the client OR
250 %% - roster versioning is used by server and client,
251 %% BUT the server isn't storing versions on db OR
252 %% - the roster version from client don't match current version.
253 -spec process_iq_get(mongooseim:host_type(), jid:jid(), jid:jid(), jlib:iq()) -> jlib:iq().
254 process_iq_get(HostType, From, To, #iq{sub_el = SubEl} = IQ) ->
255 24 AttrVer = exml_query:attr(SubEl, <<"ver">>), %% type binary() | undefined
256 24 VersioningRequested = is_binary(AttrVer),
257 24 VersioningEnabled = roster_versioning_enabled(HostType),
258 24 VersionOnDb = roster_version_on_db(HostType),
259 24 Strategy = choose_get_user_roster_strategy(VersioningRequested, VersioningEnabled, VersionOnDb),
260 24 {ItemsToSend, VersionToSend} =
261 get_user_roster_based_on_version(HostType, Strategy, AttrVer, From, To),
262 24 IQ#iq{type = result, sub_el = create_sub_el(ItemsToSend, VersionToSend)}.
263
264 -spec choose_get_user_roster_strategy(VersioningRequested :: boolean(),
265 VersioningEnabled :: boolean(),
266 VersionOnDb :: boolean()) ->
267 get_user_roster_strategy().
268 3 choose_get_user_roster_strategy(true, true, true) -> db_versioning;
269 3 choose_get_user_roster_strategy(true, true, false) -> hash_versioning;
270 18 choose_get_user_roster_strategy(_, _, _) -> no_versioning.
271
272 get_user_roster_based_on_version(HostType, db_versioning, RequestedVersion, From, To) ->
273 3 get_user_roster_db_versioning(HostType, RequestedVersion, From, To);
274 get_user_roster_based_on_version(HostType, hash_versioning, RequestedVersion, From, To) ->
275 3 get_user_roster_hash_versioning(HostType, RequestedVersion, From, To);
276 get_user_roster_based_on_version(HostType, no_versioning, _RequestedVersion, From, To) ->
277 18 get_user_roster_no_versioning(HostType, From, To).
278
279 get_user_roster_db_versioning(HostType, RequestedVersion, From, To)
280 when is_binary(RequestedVersion) ->
281 3 LUser = From#jid.luser,
282 3 LServer = From#jid.lserver,
283 3 case read_roster_version(HostType, LUser, LServer) of
284 error ->
285 1 RosterVersion = write_roster_version(HostType, LUser, LServer),
286 1 {lists:map(fun item_to_xml/1,
287 get_roster_old(HostType, To#jid.lserver, From)),
288 RosterVersion};
289 RequestedVersion ->
290 1 {false, false};
291 NewVersion ->
292 1 {lists:map(fun item_to_xml/1,
293 get_roster_old(HostType, To#jid.lserver, From)),
294 NewVersion}
295 end.
296
297 get_user_roster_hash_versioning(HostType, RequestedVersion, From, To)
298 when is_binary(RequestedVersion) ->
299 3 RosterItems = get_roster_old(HostType, To#jid.lserver, From),
300 3 case roster_hash(RosterItems) of
301 RequestedVersion ->
302 1 {false, false};
303 New ->
304 2 {lists:map(fun item_to_xml/1, RosterItems), New}
305 end.
306
307 get_user_roster_no_versioning(HostType, From, To) ->
308 18 {lists:map(fun item_to_xml/1,
309 get_roster_old(HostType, To#jid.lserver, From)),
310 false}.
311
312 create_sub_el(false, false) ->
313 2 [];
314 create_sub_el(Items, false) ->
315 18 [#xmlel{name = <<"query">>,
316 attrs = [{<<"xmlns">>, ?NS_ROSTER}],
317 children = Items}];
318 create_sub_el(Items, Version) ->
319 4 [#xmlel{name = <<"query">>,
320 attrs = [{<<"xmlns">>, ?NS_ROSTER},
321 {<<"ver">>, Version}],
322 children = Items}].
323
324 -spec get_user_roster(Acc, Params, Extra) -> {ok, Acc} when
325 Acc :: [roster()],
326 Params :: #{show_full_roster := boolean(), jid := jid:jid()},
327 Extra :: gen_hook:extra().
328 get_user_roster(Items, #{show_full_roster := false, jid := JID}, #{host_type := HostType}) ->
329 98 NewItems = do_get_user_roster(HostType, JID),
330 98 {ok, Items ++ NewItems};
331 get_user_roster(Items, #{show_full_roster := true, jid := JID}, #{host_type := HostType}) ->
332 93 NewItems = do_get_user_full_roster(HostType, JID),
333 93 {ok, Items ++ NewItems}.
334
335 -spec do_get_user_full_roster(mongooseim:host_type(), jid:jid()) -> [roster()].
336 do_get_user_full_roster(HostType, #jid{luser = LUser, lserver = LServer}) ->
337 93 get_roster(HostType, LUser, LServer).
338
339 -spec do_get_user_roster(mongooseim:host_type(), jid:jid()) -> [roster()].
340 do_get_user_roster(HostType, #jid{luser = LUser, lserver = LServer}) ->
341 6344 Roster = get_roster(HostType, LUser, LServer),
342 6344 Fun = fun(#roster{subscription = S, ask = A}) -> not (none =:= S andalso in =:= A) end,
343 6344 lists:filter(Fun, Roster).
344
345 -spec item_to_xml(roster()) -> exml:element().
346 item_to_xml(Item) ->
347 850 Attrs0 = [{<<"jid">>, jid:to_binary(Item#roster.jid)},
348 {<<"subscription">>, subs_to_binary(Item#roster.subscription)}],
349 850 Attrs1 = maybe_append_ask(Attrs0, Item),
350 850 Attrs2 = maybe_append_name(Attrs1, Item),
351 850 Fold = fun(G, Acc) -> [group_el(G) | Acc] end,
352 850 SubEls = lists:foldl(Fold, Item#roster.xs, Item#roster.groups),
353 850 #xmlel{name = <<"item">>, attrs = Attrs2, children = SubEls}.
354
355 -spec maybe_append_name([exml:attr()], roster()) -> [exml:attr()].
356 maybe_append_name(Attrs, #roster{name = <<>>}) ->
357 410 Attrs;
358 maybe_append_name(Attrs, #roster{name = Name}) ->
359 440 [{<<"name">>, Name} | Attrs].
360
361 -spec maybe_append_ask([exml:attr()], roster()) -> [exml:attr()].
362 maybe_append_ask(Attrs, #roster{ask = Ask}) when Ask =:= subscribe; Ask =:= out; Ask =:= both ->
363 308 [{<<"ask">>, <<"subscribe">>} | Attrs];
364 maybe_append_ask(Attrs, _) ->
365 542 Attrs.
366
367 -spec group_el(binary()) -> exml:element().
368 group_el(Name) ->
369 291 #xmlel{name = <<"group">>, children = [#xmlcdata{content = Name}]}.
370
371 -spec process_iq_set(mongooseim:host_type(), jid:jid(), jid:jid(), jlib:iq()) -> jlib:iq().
372 process_iq_set(HostType, From, To, #iq{sub_el = SubEl} = IQ) ->
373 31 #xmlel{children = Els} = SubEl,
374 31 mongoose_instrument:execute(mod_roster_set, #{host_type => HostType},
375 #{count => 1, jid => From}),
376 31 mongoose_hooks:roster_set(HostType, From, To, SubEl),
377 31 lists:foreach(fun(El) -> process_item_set(HostType, From, To, El) end, Els),
378 31 IQ#iq{type = result, sub_el = []}.
379
380 -spec process_item_set(mongooseim:host_type(), jid:jid(), jid:jid(), exml:element()) -> ok.
381 process_item_set(HostType, From, To, El) ->
382 31 Jid = jid:from_binary(exml_query:attr(El, <<"jid">>)),
383 31 do_process_item_set(HostType, From, To, El, Jid).
384
385 -spec do_process_item_set(
386 mongooseim:host_type(), jid:jid(), jid:jid(), exml:element(), error | jid:jid()) -> ok.
387
:-(
do_process_item_set(_, _, _, _, error) -> ok;
388 do_process_item_set(HostType, From, To, #xmlel{attrs = Attrs, children = Els}, JID1) ->
389 31 MakeItem2 = fun(Item) ->
390 31 Item1 = process_item_attrs(Item, Attrs),
391 31 process_item_els(Item1, Els)
392 end,
393 31 set_roster_item(HostType, JID1, From, To, MakeItem2),
394 31 ok.
395
396 %% @doc this is run when a roster item is to be added, updated or removed
397 %% the interface of this func could probably be a bit simpler
398 -spec set_roster_item(mongooseim:host_type(),
399 ContactJID :: jid:jid(),
400 From :: jid:jid(),
401 To :: jid:jid(),
402 fun((roster()) -> roster())) -> ok | {error, any()}.
403 set_roster_item(HostType, ContactJID, From, To, MakeItem) ->
404 184 ContactLJID = jid:to_lower(ContactJID),
405 184 F = fun() -> set_roster_item_t(HostType, From, ContactLJID, MakeItem) end,
406 184 case transaction(HostType, F) of
407 {atomic, does_not_exist} ->
408 11 {error, does_not_exist};
409 {atomic, {OldItem, NewItem}} ->
410 173 push_item(HostType, From, To, NewItem),
411 173 case NewItem#roster.subscription of
412 remove ->
413 24 send_unsubscribing_presence(From, OldItem),
414 24 ok;
415 149 _ -> ok
416 end;
417 E ->
418
:-(
?LOG_ERROR(#{what => roster_set_item_failed, reason => E}),
419
:-(
{error, E}
420 end.
421
422 -spec set_roster_item_t(mongooseim:host_type(), jid:jid(), contact(),
423 fun((roster()) -> roster())) ->
424 does_not_exist | {roster(), roster()}.
425 set_roster_item_t(HostType, UserJid = #jid{lserver = LServer},
426 ContactLJID, MakeItem) ->
427 184 InitialItem = get_roster_entry_t(HostType, UserJid, ContactLJID, short),
428 184 Item1 = case InitialItem of
429 151 does_not_exist -> new_roster_item(UserJid, ContactLJID);
430 33 Item -> Item
431 end,
432 184 Item2 = MakeItem(Item1),
433 184 case {InitialItem, Item2} of
434 {does_not_exist, #roster{subscription = remove}} ->
435 %% Cannot remove a non-existing item
436 11 does_not_exist;
437 _ ->
438 173 case Item2#roster.subscription of
439 24 remove -> del_roster_t(HostType, UserJid, ContactLJID);
440 149 _ -> update_roster_t(HostType, Item2)
441 end,
442 173 Item3 = mongoose_hooks:roster_process_item(HostType, LServer, Item2),
443 173 case roster_version_on_db(HostType) of
444 2 true -> write_roster_version_t(HostType, UserJid);
445 171 false -> ok
446 end,
447 173 {Item1, Item3}
448 end.
449
450 -spec new_roster_item(jid:jid(), jid:simple_jid()) -> roster().
451 new_roster_item(UserJid, ContactLJID) ->
452 151 #roster{usj = {jid:to_lus(UserJid), ContactLJID},
453 us = jid:to_lus(UserJid),
454 jid = ContactLJID}.
455
456 process_item_attrs(Item, [{<<"jid">>, Val} | Attrs]) ->
457 31 case jid:from_binary(Val) of
458 error ->
459
:-(
process_item_attrs(Item, Attrs);
460 JID1 ->
461 31 JID = jid:to_lower(JID1),
462 31 process_item_attrs(Item#roster{jid = JID}, Attrs)
463 end;
464 process_item_attrs(Item, [{<<"name">>, Val} | Attrs]) ->
465 29 process_item_attrs(Item#roster{name = Val}, Attrs);
466 process_item_attrs(Item, [{<<"subscription">>, <<"remove">>} | Attrs]) ->
467 2 process_item_attrs(Item#roster{subscription = remove}, Attrs);
468 process_item_attrs(Item, [_ | Attrs]) ->
469
:-(
process_item_attrs(Item, Attrs);
470 process_item_attrs(Item, []) ->
471 31 Item.
472
473 process_item_els(Item, [#xmlel{name = Name} = El | Els]) ->
474 27 case Name of
475 <<"group">> ->
476 27 Groups = [exml_query:cdata(El) | Item#roster.groups],
477 27 process_item_els(Item#roster{groups = Groups}, Els);
478 _ ->
479
:-(
case exml_query:attr(El, <<"xmlns">>, <<>>) of
480
:-(
<<>> -> process_item_els(Item, Els);
481 _ ->
482
:-(
XEls = [El | Item#roster.xs],
483
:-(
process_item_els(Item#roster{xs = XEls}, Els)
484 end
485 end;
486 process_item_els(Item, [#xmlcdata{} | Els]) ->
487
:-(
process_item_els(Item, Els);
488 31 process_item_els(Item, []) -> Item.
489
490 push_item(HostType, JID, From, Item) ->
491 998 broadcast_item(JID, Item#roster.jid, Item#roster.subscription),
492 998 case roster_versioning_enabled(HostType) of
493 true ->
494 3 push_item_version(JID, From, Item, roster_version(HostType, JID));
495 false ->
496 995 lists:foreach(fun(Resource) ->
497 837 push_item_without_version(HostType, JID, Resource, From, Item)
498 end,
499 ejabberd_sm:get_user_resources(JID))
500 end.
501
502 -spec broadcast_item(jid:jid(), contact(), subscription_state()) -> ok.
503 broadcast_item(#jid{luser = LUser, lserver = LServer}, ContactJid, Subscription) ->
504 998 Item = {item, ContactJid, Subscription},
505 998 UserPids = ejabberd_sm:get_user_present_pids(LUser, LServer),
506 998 lists:foreach(fun({_, Pid}) -> mongoose_c2s:cast(Pid, ?MODULE, Item) end, UserPids).
507
508 push_item_without_version(HostType, JID, Resource, From, Item) ->
509 837 mongoose_instrument:execute(mod_roster_push, #{host_type => HostType},
510 #{count => 1, jid => From}),
511 837 mongoose_hooks:roster_push(HostType, From, Item),
512 837 push_item_final(jid:replace_resource(JID, Resource), From, Item, not_found).
513
514 push_item_version(JID, From, Item, RosterVersion) ->
515 3 lists:foreach(fun(Resource) ->
516 3 push_item_final(jid:replace_resource(JID, Resource),
517 From, Item, RosterVersion)
518 end, ejabberd_sm:get_user_resources(JID)).
519
520 push_item_final(JID, From, Item, RosterVersion) ->
521 840 ExtraAttrs = case RosterVersion of
522 837 not_found -> [];
523 3 _ -> [{<<"ver">>, RosterVersion}]
524 end,
525 840 ResIQ = #iq{type = set, xmlns = ?NS_ROSTER,
526 %% @doc Roster push, calculate and include the version attribute.
527 %% TODO: don't push to those who didn't load roster
528 id = <<"push", (mongoose_bin:gen_from_crypto())/binary>>,
529 sub_el =
530 [#xmlel{name = <<"query">>,
531 attrs = [{<<"xmlns">>, ?NS_ROSTER} | ExtraAttrs],
532 children = [item_to_xml(Item)]}]},
533 840 ejabberd_router:route(From, JID, jlib:iq_to_xml(ResIQ)).
534
535 -spec get_subscription_lists(Acc, Params, Extra) -> {ok, Acc} when
536 Acc :: mongoose_acc:t(),
537 Params :: #{jid := jid:jid()},
538 Extra :: gen_hook:extra().
539 get_subscription_lists(Acc, #{jid := #jid{luser = LUser, lserver = LServer} = JID}, _) ->
540 5941 Items = mod_roster_backend:get_subscription_lists(Acc, LUser, LServer),
541 5941 SubLists = fill_subscription_lists(JID, LServer, Items, [], [], []),
542 5941 {ok, mongoose_acc:set(roster, subscription_lists, SubLists, Acc)}.
543
544 fill_subscription_lists(JID, LServer, [#roster{} = I | Is], F, T, P) ->
545 11 J = I#roster.jid,
546 11 NewP = build_pending(I, JID, P),
547 11 case I#roster.subscription of
548 both ->
549 7 fill_subscription_lists(JID, LServer, Is, [J | F], [J | T], NewP);
550 from ->
551 1 fill_subscription_lists(JID, LServer, Is, [J | F], T, NewP);
552
:-(
to -> fill_subscription_lists(JID, LServer, Is, F, [J | T], NewP);
553 3 _ -> fill_subscription_lists(JID, LServer, Is, F, T, NewP)
554 end;
555 5941 fill_subscription_lists(_, _LServer, [], F, T, P) -> {F, T, P}.
556
557 build_pending(#roster{ask = Ask} = I, JID, P)
558 when Ask == in; Ask == both ->
559 3 Status = case I#roster.askmessage of
560 3 Message when is_binary(Message) -> Message;
561
:-(
true -> <<>>
562 end,
563 3 StatusEl = #xmlel{
564 name = <<"status">>,
565 children = [#xmlcdata{content = Status}]},
566 3 El = #xmlel{
567 name = <<"presence">>,
568 attrs = [{<<"from">>, jid:to_binary(I#roster.jid)},
569 {<<"to">>, jid:to_binary(JID)},
570 {<<"type">>, <<"subscribe">>}],
571 children = [StatusEl]},
572 3 [El | P];
573 build_pending(_, _, P) ->
574 8 P.
575
576 -spec subs_to_binary(subscription_state()) -> binary().
577 404 subs_to_binary(none) -> <<"none">>;
578 125 subs_to_binary(from) -> <<"from">>;
579 133 subs_to_binary(to) -> <<"to">>;
580 164 subs_to_binary(both) -> <<"both">>;
581 24 subs_to_binary(remove) -> <<"remove">>.
582
583 -spec in_subscription(Acc, Params, Extra) -> {ok, Acc} when
584 Acc :: mongoose_acc:t(),
585 Params :: #{to := jid:jid(),
586 from := jid:jid(),
587 type := sub_presence(),
588 reason := iodata()},
589 Extra :: gen_hook:extra().
590 in_subscription(Acc,
591 #{to := ToJID, from := FromJID, type := Type, reason := Reason},
592 #{host_type := HostType}) ->
593 567 Res = process_subscription(HostType, in, ToJID, FromJID, Type, Reason),
594 567 {ok, mongoose_acc:set(hook, result, Res, Acc)}.
595
596 -spec out_subscription(Acc, Params, Extra) -> {ok, Acc} when
597 Acc :: mongoose_acc:t(),
598 Params :: #{to := jid:jid(),
599 from := jid:jid(),
600 type := sub_presence()},
601 Extra :: gen_hook:extra().
602 out_subscription(Acc,
603 #{to := ToJID, from := FromJID, type := Type},
604 #{host_type := HostType}) ->
605 386 Res = process_subscription(HostType, out, FromJID, ToJID, Type, <<>>),
606 386 {ok, mongoose_acc:set(hook, result, Res, Acc)}.
607
608 -spec process_subscription(mongooseim:host_type(), in | out, jid:jid(), jid:jid(),
609 sub_presence(), iodata()) ->
610 boolean().
611 process_subscription(HostType, Direction, JID, ContactJID, Type, Reason) ->
612 953 TransactionFun =
613 fun() ->
614 954 process_subscription_t(HostType, Direction, JID, ContactJID, Type, Reason)
615 end,
616 953 case transaction(HostType, TransactionFun) of
617 {atomic, {Push, AutoReply}} ->
618 953 case AutoReply of
619 862 none -> ok;
620 _ ->
621 91 PresenceStanza = #xmlel{name = <<"presence">>,
622 attrs = [{<<"type">>, autoreply_to_type(AutoReply)}],
623 children = []},
624 91 ejabberd_router:route(JID, ContactJID, PresenceStanza)
625 end,
626 953 case Push of
627 {push, #roster{subscription = none, ask = in}} ->
628 107 true;
629 {push, Item} ->
630 825 push_item(HostType, JID, JID, Item),
631 825 true;
632 21 none -> false
633 end;
634
:-(
_ -> false
635 end.
636
637
:-(
autoreply_to_type(subscribed) -> <<"subscribed">>;
638 91 autoreply_to_type(unsubscribed) -> <<"unsubscribed">>.
639
640 -spec process_subscription_t(mongooseim:host_type(), in | out, jid:jid(), jid:jid(),
641 sub_presence(), iodata()) ->
642 {Push :: none | {push, roster()},
643 AutoReply :: none | subscribed | unsubscribed}.
644 process_subscription_t(HostType, Direction, JID, ContactJID, Type, Reason) ->
645 954 US = jid:to_lus(JID),
646 954 ContactLJID = jid:to_lower(ContactJID),
647 954 Item = case get_roster_entry_t(HostType, JID, ContactLJID, full) of
648 does_not_exist ->
649 118 #roster{usj = {US, ContactLJID},
650 us = US,
651 jid = ContactLJID};
652 836 R -> R
653 end,
654 954 NewState = case Direction of
655 out ->
656 387 out_state_change(Item#roster.subscription,
657 Item#roster.ask, Type);
658 in ->
659 567 in_state_change(Item#roster.subscription,
660 Item#roster.ask, Type)
661 end,
662 954 AutoReply = case Direction of
663 387 out -> none;
664 in ->
665 567 in_auto_reply(Item#roster.subscription,
666 Item#roster.ask, Type)
667 end,
668 954 AskMessage = case NewState of
669 118 {_, both} -> Reason;
670 189 {_, in} -> Reason;
671 647 _ -> <<"">>
672 end,
673 954 case NewState of
674 15 none -> {none, AutoReply};
675 {none, none}
676 when Item#roster.subscription == none,
677 Item#roster.ask == in ->
678 6 del_roster_t(HostType, JID, ContactLJID), {none, AutoReply};
679 {Subscription, Pending} ->
680 933 NewItem = Item#roster{subscription = Subscription,
681 ask = Pending,
682 askmessage = iolist_to_binary(AskMessage)},
683 933 roster_subscribe_t(HostType, NewItem),
684 932 case roster_version_on_db(HostType) of
685
:-(
true -> write_roster_version_t(HostType, JID);
686 932 false -> ok
687 end,
688 932 {{push, NewItem}, AutoReply}
689 end.
690
691 %% in_state_change(Subscription, Pending, Type) -> NewState
692 %% NewState = none | {NewSubscription, NewPending}
693 -ifdef(ROSTER_GATEWAY_WORKAROUND).
694
695 -define(NNSD, {to, none}).
696
697 -define(NISD, {to, in}).
698
699 -else.
700
701 -define(NNSD, none).
702
703 -define(NISD, none).
704
705 -endif.
706
707 107 in_state_change(none, none, subscribe) -> {none, in};
708
:-(
in_state_change(none, none, subscribed) -> ?NNSD;
709 1 in_state_change(none, none, unsubscribe) -> none;
710 11 in_state_change(none, none, unsubscribed) -> none;
711 59 in_state_change(none, out, subscribe) -> {none, both};
712 40 in_state_change(none, out, subscribed) -> {to, none};
713
:-(
in_state_change(none, out, unsubscribe) -> none;
714 6 in_state_change(none, out, unsubscribed) -> {none, none};
715
:-(
in_state_change(none, in, subscribe) -> none;
716
:-(
in_state_change(none, in, subscribed) -> ?NISD;
717
:-(
in_state_change(none, in, unsubscribe) -> {none, none};
718
:-(
in_state_change(none, in, unsubscribed) -> none;
719
:-(
in_state_change(none, both, subscribe) -> none;
720 59 in_state_change(none, both, subscribed) -> {to, in};
721
:-(
in_state_change(none, both, unsubscribe) -> {none, out};
722
:-(
in_state_change(none, both, unsubscribed) -> {none, in};
723 23 in_state_change(to, none, subscribe) -> {to, in};
724
:-(
in_state_change(to, none, subscribed) -> none;
725
:-(
in_state_change(to, none, unsubscribe) -> none;
726 86 in_state_change(to, none, unsubscribed) -> {none, none};
727
:-(
in_state_change(to, in, subscribe) -> none;
728
:-(
in_state_change(to, in, subscribed) -> none;
729
:-(
in_state_change(to, in, unsubscribe) -> {to, none};
730
:-(
in_state_change(to, in, unsubscribed) -> {none, in};
731
:-(
in_state_change(from, none, subscribe) -> none;
732
:-(
in_state_change(from, none, subscribed) -> {both, none};
733 12 in_state_change(from, none, unsubscribe) -> {none, none};
734
:-(
in_state_change(from, none, unsubscribed) -> none;
735
:-(
in_state_change(from, out, subscribe) -> none;
736 82 in_state_change(from, out, subscribed) -> {both, none};
737
:-(
in_state_change(from, out, unsubscribe) -> {none, out};
738
:-(
in_state_change(from, out, unsubscribed) -> {from, none};
739
:-(
in_state_change(both, none, subscribe) -> none;
740
:-(
in_state_change(both, none, subscribed) -> none;
741 79 in_state_change(both, none, unsubscribe) -> {to, none};
742 2 in_state_change(both, none, unsubscribed) -> {from, none}.
743
744 107 out_state_change(none, none, subscribe) -> {none, out};
745 2 out_state_change(none, none, subscribed) -> none;
746 1 out_state_change(none, none, unsubscribe) -> none;
747
:-(
out_state_change(none, none, unsubscribed) -> none;
748 out_state_change(none, out, subscribe) ->
749
:-(
{none, out}; %% We need to resend query (RFC3921, section 9.2)
750
:-(
out_state_change(none, out, subscribed) -> none;
751
:-(
out_state_change(none, out, unsubscribe) -> {none, none};
752
:-(
out_state_change(none, out, unsubscribed) -> none;
753 59 out_state_change(none, in, subscribe) -> {none, both};
754 40 out_state_change(none, in, subscribed) -> {from, none};
755
:-(
out_state_change(none, in, unsubscribe) -> none;
756 6 out_state_change(none, in, unsubscribed) -> {none, none};
757
:-(
out_state_change(none, both, subscribe) -> none;
758 59 out_state_change(none, both, subscribed) -> {from, out};
759
:-(
out_state_change(none, both, unsubscribe) -> {none, in};
760
:-(
out_state_change(none, both, unsubscribed) -> {none, out};
761
:-(
out_state_change(to, none, subscribe) -> none;
762
:-(
out_state_change(to, none, subscribed) -> {both, none};
763 5 out_state_change(to, none, unsubscribe) -> {none, none};
764
:-(
out_state_change(to, none, unsubscribed) -> none;
765
:-(
out_state_change(to, in, subscribe) -> none;
766 83 out_state_change(to, in, subscribed) -> {both, none};
767
:-(
out_state_change(to, in, unsubscribe) -> {none, in};
768
:-(
out_state_change(to, in, unsubscribed) -> {to, none};
769 23 out_state_change(from, none, subscribe) -> {from, out};
770
:-(
out_state_change(from, none, subscribed) -> none;
771
:-(
out_state_change(from, none, unsubscribe) -> none;
772
:-(
out_state_change(from, none, unsubscribed) -> {none, none};
773
:-(
out_state_change(from, out, subscribe) -> none;
774
:-(
out_state_change(from, out, subscribed) -> none;
775
:-(
out_state_change(from, out, unsubscribe) -> {from, none};
776
:-(
out_state_change(from, out, unsubscribed) -> {none, out};
777
:-(
out_state_change(both, none, subscribe) -> none;
778
:-(
out_state_change(both, none, subscribed) -> none;
779
:-(
out_state_change(both, none, unsubscribe) -> {from, none};
780 2 out_state_change(both, none, unsubscribed) -> {to, none}.
781
782
:-(
in_auto_reply(from, none, subscribe) -> subscribed;
783
:-(
in_auto_reply(from, out, subscribe) -> subscribed;
784
:-(
in_auto_reply(both, none, subscribe) -> subscribed;
785
:-(
in_auto_reply(none, in, unsubscribe) -> unsubscribed;
786
:-(
in_auto_reply(none, both, unsubscribe) -> unsubscribed;
787
:-(
in_auto_reply(to, in, unsubscribe) -> unsubscribed;
788 12 in_auto_reply(from, none, unsubscribe) -> unsubscribed;
789
:-(
in_auto_reply(from, out, unsubscribe) -> unsubscribed;
790 79 in_auto_reply(both, none, unsubscribe) -> unsubscribed;
791 476 in_auto_reply(_, _, _) -> none.
792
793 get_user_rosters_length(HostType, JID) ->
794 1 length(get_roster_old(HostType, JID)).
795
796 -spec remove_user(Acc, Params, Extra) -> {ok, Acc} when
797 Acc :: mongoose_acc:t(),
798 Params :: #{jid := jid:jid()},
799 Extra :: gen_hook:extra().
800 remove_user(Acc, #{jid := #jid{luser = LUser, lserver = LServer} = JID}, #{host_type := HostType}) ->
801 6246 Acc1 = try_send_unsubscription_to_rosteritems(Acc, JID),
802 6246 F = fun() -> mod_roster_backend:remove_user_t(HostType, LUser, LServer) end,
803 6246 case transaction(HostType, F) of
804 {atomic, ok} ->
805 6246 ok;
806 Result ->
807
:-(
?LOG_ERROR(#{what => remove_user_transaction_failed, reason => Result})
808 end,
809 6246 {ok, Acc1}.
810
811 -spec try_send_unsubscription_to_rosteritems(mongoose_acc:t(), jid:jid()) ->
812 mongoose_acc:t().
813 try_send_unsubscription_to_rosteritems(Acc, JID) ->
814 6246 try
815 6246 send_unsubscription_to_rosteritems(Acc, JID)
816 catch
817 E:R:S ->
818
:-(
?LOG_WARNING(#{what => roster_unsubcribe_failed,
819
:-(
class => E, reason => R, stacktrace => S}),
820
:-(
Acc
821 end.
822
823 %% For each contact with Subscription:
824 %% Both or From, send a "unsubscribed" presence stanza;
825 %% Both or To, send a "unsubscribe" presence stanza.
826 -spec send_unsubscription_to_rosteritems(mongoose_acc:t(), jid:jid()) -> mongoose_acc:t().
827 send_unsubscription_to_rosteritems(Acc, JID) ->
828 6246 RosterItems = do_get_user_roster(mongoose_acc:host_type(Acc), JID),
829 6246 lists:foreach(fun(RosterItem) ->
830 213 send_unsubscribing_presence(JID, RosterItem)
831 end, RosterItems),
832 6246 Acc.
833
834 -spec send_unsubscribing_presence(From :: jid:jid(), Item :: roster()) -> ok | mongoose_acc:t().
835 send_unsubscribing_presence(From, #roster{ subscription = Subscription } = Item) ->
836 237 BareFrom = jid:to_bare(From),
837 237 ContactJID = jid:make_noprep(Item#roster.jid),
838 237 IsTo = Subscription == both orelse Subscription == to,
839 237 IsFrom = Subscription == both orelse Subscription == from,
840 237 case IsTo of
841 86 true -> send_presence_type(BareFrom, ContactJID, <<"unsubscribe">>);
842 151 _ -> ok
843 end,
844 237 case IsFrom of
845 86 true -> send_presence_type(BareFrom, ContactJID, <<"unsubscribed">>);
846 151 _ -> ok
847 end.
848
849 send_presence_type(From, To, Type) ->
850 172 ejabberd_router:route(From, To,
851 #xmlel{name = <<"presence">>,
852 attrs = [{<<"type">>, Type}], children = []}).
853
854 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
855
856 -spec remove_domain(Acc, Params, Extra) -> {ok , Acc} when
857 Acc :: mongoose_domain_api:remove_domain_acc(),
858 Params :: #{domain := jid:lserver()},
859 Extra :: gen_hook:extra().
860 remove_domain(Acc, #{domain := Domain}, #{host_type := HostType}) ->
861 15 case mongoose_lib:is_exported(mod_roster_backend, remove_domain_t, 2) of
862 true ->
863 15 F = fun() -> mod_roster_backend:remove_domain_t(HostType, Domain) end,
864 15 case transaction(HostType, F) of
865 {atomic, ok} ->
866 15 ok;
867 Result ->
868
:-(
?LOG_ERROR(#{what => remove_domain_transaction_failed,
869
:-(
reason => Result})
870 end;
871 false ->
872
:-(
ok
873 end,
874 15 {ok, Acc}.
875
876 -spec set_items(mongooseim:host_type(), jid:jid(), exml:element()) -> ok | {error, any()}.
877 set_items(HostType, Jid, SubEl) ->
878
:-(
F = fun() -> set_items_t(HostType, Jid, SubEl) end,
879
:-(
case transaction(HostType, F) of
880 {atomic, _} ->
881
:-(
ok;
882 Result ->
883
:-(
{error, Result}
884 end.
885
886 set_items_t(HostType, JID, #xmlel{children = Els}) ->
887
:-(
lists:foreach(fun(El) ->
888
:-(
process_item_set_t(HostType, JID, El)
889 end, Els).
890
891 %% @doc add a contact to roster, or update
892 -spec set_roster_entry(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), [binary()]) ->
893 ok | {error, any()}.
894 set_roster_entry(HostType, UserJid, ContactJid, Name, Groups) ->
895 120 UpdateF = fun(Item) -> Item#roster{name = Name, groups = Groups} end,
896 120 set_roster_item(HostType, ContactJid, UserJid, UserJid, UpdateF).
897
898 %% @doc remove from roster - in practice it means changing subscription state to 'remove'
899 -spec remove_from_roster(mongooseim:host_type(), UserJid :: jid:jid(), ContactJid :: jid:jid()) ->
900 ok | {error, any()}.
901 remove_from_roster(HostType, UserJid, ContactJid) ->
902 33 UpdateF = fun(Item) -> Item#roster{subscription = remove} end,
903 33 set_roster_item(HostType, ContactJid, UserJid, UserJid, UpdateF).
904
905 process_item_set_t(HostType, JID,
906 #xmlel{attrs = Attrs, children = Els} = El) ->
907
:-(
case jid:from_binary(exml_query:attr(El, <<"jid">>)) of
908
:-(
error -> ok;
909 JID1 ->
910
:-(
LJID = jid:to_lower(JID1),
911
:-(
Item = #roster{usj = {jid:to_lus(JID), LJID},
912 us = jid:to_lus(JID), jid = LJID},
913
:-(
Item1 = process_item_attrs_ws(Item, Attrs),
914
:-(
Item2 = process_item_els(Item1, Els),
915
:-(
case Item2#roster.subscription of
916
:-(
remove -> del_roster_t(HostType, JID, LJID);
917
:-(
_ -> update_roster_t(HostType, Item2)
918 end
919 end;
920
:-(
process_item_set_t(_HostType, _Jid, _) -> ok.
921
922 process_item_attrs_ws(Item, [{<<"jid">>, Val} | Attrs]) ->
923
:-(
case jid:from_binary(Val) of
924 error ->
925
:-(
process_item_attrs_ws(Item, Attrs);
926 JID1 ->
927
:-(
JID = {JID1#jid.luser, JID1#jid.lserver, JID1#jid.lresource},
928
:-(
process_item_attrs_ws(Item#roster{jid = JID}, Attrs)
929 end;
930 process_item_attrs_ws(Item, [{<<"name">>, Val} | Attrs]) ->
931
:-(
process_item_attrs_ws(Item#roster{name = Val}, Attrs);
932 process_item_attrs_ws(Item, [{<<"subscription">>, <<"remove">>} | Attrs]) ->
933
:-(
process_item_attrs_ws(Item#roster{subscription = remove}, Attrs);
934 process_item_attrs_ws(Item, [{<<"subscription">>, <<"none">>} | Attrs]) ->
935
:-(
process_item_attrs_ws(Item#roster{subscription = none}, Attrs);
936 process_item_attrs_ws(Item, [{<<"subscription">>, <<"both">>} | Attrs]) ->
937
:-(
process_item_attrs_ws(Item#roster{subscription = both}, Attrs);
938 process_item_attrs_ws(Item, [{<<"subscription">>, <<"from">>} | Attrs]) ->
939
:-(
process_item_attrs_ws(Item#roster{subscription = from}, Attrs);
940 process_item_attrs_ws(Item, [{<<"subscription">>, <<"to">>} | Attrs]) ->
941
:-(
process_item_attrs_ws(Item#roster{subscription = to}, Attrs);
942 process_item_attrs_ws(Item, [_ | Attrs]) ->
943
:-(
process_item_attrs_ws(Item, Attrs);
944 process_item_attrs_ws(Item, []) ->
945
:-(
Item.
946
947 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
948
949 -spec get_jid_info(Acc, Params, Extra) -> {ok, Acc} when
950 Acc :: {subscription_state(), [binary()]},
951 Params :: #{to := jid:jid(), remote := jid:jid() | jid:simple_jid()},
952 Extra :: gen_hook:extra().
953 get_jid_info(_, #{to := ToJID, remote := JID}, #{host_type := HostType}) ->
954 143 ToRosterEntry = get_roster_entry(HostType, ToJID, JID, full),
955 143 RemoteRosterEntryGetter = fun() -> get_roster_entry(HostType, ToJID, jid:to_bare(jid:to_lower(JID)), full) end,
956 143 NewAcc = determine_subscription_state(ToRosterEntry, RemoteRosterEntryGetter),
957 143 {ok, NewAcc}.
958
959 -spec determine_subscription_state(RosterEntry, RosterEntryGetter) -> SubscriptionState when
960 RosterEntry :: roster() | does_not_exist | error,
961 RosterEntryGetter :: fun(() -> RosterEntry) | undefined,
962 SubscriptionState :: {subscription_state(), [binary()]}.
963
:-(
determine_subscription_state(error, _) -> {none, []};
964 68 determine_subscription_state(does_not_exist, undefined) -> {none, []};
965 91 determine_subscription_state(does_not_exist, Getter) -> determine_subscription_state(Getter(), undefined);
966 75 determine_subscription_state(R, _) -> {R#roster.subscription, R#roster.groups}.
967
968 get_roster_old(HostType, #jid{lserver = LServer} = JID) ->
969 2 get_roster_old(HostType, LServer, JID).
970
971 get_roster_old(HostType, DestServer, JID) ->
972 25 A = mongoose_acc:new(#{ location => ?LOCATION,
973 lserver => DestServer,
974 host_type => HostType,
975 element => undefined }),
976 25 mongoose_instrument:execute(mod_roster_get, #{host_type => HostType},
977 #{count => 1, jid => JID}),
978 25 mongoose_hooks:roster_get(A, JID, false).
979
980 -spec item_to_map(roster()) -> map().
981 item_to_map(#roster{} = Roster) ->
982 19 ContactJid = jid:make_noprep(jid:to_bare(Roster#roster.jid)),
983 19 ContactName = Roster#roster.name,
984 19 Subs = Roster#roster.subscription,
985 19 Groups = Roster#roster.groups,
986 19 Ask = Roster#roster.ask,
987 19 #{jid => ContactJid, name => ContactName, subscription => Subs,
988 groups => Groups, ask => Ask}.
989
990 -spec config_metrics(mongooseim:host_type()) -> [{gen_mod:opt_key(), gen_mod:opt_value()}].
991 config_metrics(HostType) ->
992 224 mongoose_module_metrics:opts_for_module(HostType, ?MODULE, [backend]).
993
994 %% Backend API wrappers
995
996 -spec transaction(mongooseim:host_type(), fun(() -> any())) ->
997 {aborted, any()} | {atomic, any()} | {error, any()}.
998 transaction(HostType, F) ->
999 7398 mod_roster_backend:transaction(HostType, F).
1000
1001 -spec read_roster_version(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
1002 binary() | error.
1003 read_roster_version(HostType, LUser, LServer) ->
1004 5 mod_roster_backend:read_roster_version(HostType, LUser, LServer).
1005
1006 -spec write_roster_version(mongooseim:host_type(), jid:luser(), jid:lserver()) -> version().
1007 write_roster_version(HostType, LUser, LServer) ->
1008 1 write_roster_version(HostType, LUser, LServer, no_transaction).
1009
1010 -spec write_roster_version_t(mongooseim:host_type(), jid:jid()) -> version().
1011 write_roster_version_t(HostType, #jid{luser = LUser, lserver = LServer}) ->
1012 2 write_roster_version(HostType, LUser, LServer, in_transaction).
1013
1014 -spec write_roster_version(mongooseim:host_type(), jid:luser(), jid:lserver(),
1015 transaction_state()) -> version().
1016 write_roster_version(HostType, LUser, LServer, TransactionState) ->
1017 3 Ver = mongoose_bin:encode_crypto(term_to_binary(os:timestamp())),
1018 3 mod_roster_backend:write_roster_version(HostType, LUser, LServer, TransactionState, Ver),
1019 3 Ver.
1020
1021 -spec get_roster(mongooseim:host_type(), jid:luser(), jid:lserver()) -> [roster()].
1022 get_roster(HostType, LUser, LServer) ->
1023 6524 mod_roster_backend:get_roster(HostType, LUser, LServer).
1024
1025 -spec get_roster_entry(mongooseim:host_type(), jid:jid(), contact(), entry_format()) ->
1026 roster() | does_not_exist | error.
1027 get_roster_entry(HostType, #jid{luser = LUser, lserver = LServer}, LJid, Format) ->
1028 278 mod_roster_backend:get_roster_entry(HostType, LUser, LServer, LJid, no_transaction, Format).
1029
1030 -spec get_roster_entry_t(mongooseim:host_type(), jid:jid(), contact(), entry_format()) ->
1031 roster() | does_not_exist | error.
1032 get_roster_entry_t(HostType, #jid{luser = LUser, lserver = LServer}, LJid, Format) ->
1033 1138 mod_roster_backend:get_roster_entry(HostType, LUser, LServer, LJid, in_transaction, Format).
1034
1035 -spec roster_subscribe_t(mongooseim:host_type(), roster()) -> ok.
1036 roster_subscribe_t(HostType, Item) ->
1037 933 mod_roster_backend:roster_subscribe_t(HostType, Item).
1038
1039 -spec update_roster_t(mongooseim:host_type(), roster()) -> ok.
1040 update_roster_t(HostType, Item) ->
1041 149 mod_roster_backend:update_roster_t(HostType, Item).
1042
1043 -spec del_roster_t(mongooseim:host_type(), jid:jid(), contact()) -> ok.
1044 del_roster_t(HostType, #jid{luser = LUser, lserver = LServer}, LJID) ->
1045 30 mod_roster_backend:del_roster_t(HostType, LUser, LServer, LJID).
Line Hits Source