out_subscription/1, set_items/3, remove_user/2,
get_jid_info/4, encode_item/1, webadmin_page/3,
webadmin_user/4, get_versioning_feature/2,
- roster_versioning_enabled/1, roster_version/2,
+ roster_version/2,
mod_opt_type/1, mod_options/1, set_roster/1, del_roster/3,
process_rosteritems/5,
depends/2, set_item_and_notify_clients/3]).
-define(ROSTER_ITEM_CACHE, roster_item_cache).
-define(ROSTER_VERSION_CACHE, roster_version_cache).
+-type c2s_state() :: ejabberd_c2s:state().
-export_type([subscription/0]).
-callback init(binary(), gen_mod:opts()) -> any().
-callback read_subscription_and_groups(binary(), binary(), ljid())
-> {ok, {subscription(), ask(), [binary()]}} | error.
-callback roster_subscribe(binary(), binary(), ljid(), #roster{}) -> any().
--callback transaction(binary(), function()) -> {atomic, any()} | {aborted, any()}.
+-callback transaction(binary(), fun(() -> T)) -> {atomic, T} | {aborted, any()}.
-callback remove_user(binary(), binary()) -> any().
-callback update_roster(binary(), binary(), ljid(), #roster{}) -> any().
-callback del_roster(binary(), binary(), ljid()) -> any().
depends(_Host, _Opts) ->
[].
+-spec process_iq(iq()) -> iq().
process_iq(#iq{from = #jid{luser = U, lserver = S},
to = #jid{luser = U, lserver = S}} = IQ) ->
process_local_iq(IQ);
process_local_iq(IQ)
end.
+-spec process_local_iq(iq()) -> iq().
process_local_iq(#iq{type = set,lang = Lang,
sub_els = [#roster_query{
items = [#roster_item{ask = Ask}]}]} = IQ)
Txt = ?T("No module is handling this query"),
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
+-spec roster_hash([#roster{}]) -> binary().
roster_hash(Items) ->
str:sha(term_to_binary(lists:sort([R#roster{groups =
lists:sort(Grs)}
|| R = #roster{groups = Grs}
<- Items]))).
-roster_versioning_enabled(Host) ->
- mod_roster_opt:versioning(Host).
-
-roster_version_on_db(Host) ->
- mod_roster_opt:store_current_id(Host).
-
%% Returns a list that may contain an xmlelement with the XEP-237 feature if it's enabled.
-spec get_versioning_feature([xmpp_element()], binary()) -> [xmpp_element()].
get_versioning_feature(Acc, Host) ->
case gen_mod:is_loaded(Host, ?MODULE) of
true ->
- case roster_versioning_enabled(Host) of
+ case mod_roster_opt:versioning(Host) of
true ->
[#rosterver_feature{}|Acc];
false ->
Acc
end.
+-spec roster_version(binary(), binary()) -> undefined | binary().
roster_version(LServer, LUser) ->
US = {LUser, LServer},
- case roster_version_on_db(LServer) of
+ case mod_roster_opt:store_current_id(LServer) of
true ->
case read_roster_version(LUser, LServer) of
error -> undefined;
[], [US]))
end.
+-spec read_roster_version(binary(), binary()) -> {ok, binary()} | error.
read_roster_version(LUser, LServer) ->
ets_cache:lookup(
?ROSTER_VERSION_CACHE, {LUser, LServer},
Mod:read_roster_version(LUser, LServer)
end).
+-spec write_roster_version(binary(), binary()) -> binary().
write_roster_version(LUser, LServer) ->
write_roster_version(LUser, LServer, false).
+-spec write_roster_version_t(binary(), binary()) -> binary().
write_roster_version_t(LUser, LServer) ->
write_roster_version(LUser, LServer, true).
+-spec write_roster_version(binary(), binary(), boolean()) -> binary().
write_roster_version(LUser, LServer, InTransaction) ->
Ver = str:sha(term_to_binary(erlang:unique_integer())),
Mod = gen_mod:db_mod(LServer, ?MODULE),
%% - roster versioning is not used by the client OR
%% - roster versioning is used by server and client, BUT the server isn't storing versions on db OR
%% - the roster version from client don't match current version.
+-spec process_iq_get(iq()) -> iq().
process_iq_get(#iq{to = To,
sub_els = [#roster_query{ver = RequestedVersion}]} = IQ) ->
LUser = To#jid.luser,
LServer = To#jid.lserver,
US = {LUser, LServer},
{ItemsToSend, VersionToSend} =
- case {roster_versioning_enabled(LServer),
- roster_version_on_db(LServer)} of
+ case {mod_roster_opt:versioning(LServer),
+ mod_roster_opt:store_current_id(LServer)} of
{true, true} when RequestedVersion /= undefined ->
case read_roster_version(LUser, LServer) of
error ->
Items)
++ Acc.
+-spec get_roster(binary(), binary()) -> [#roster{}].
get_roster(LUser, LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
R = case use_cache(Mod, LServer, roster) of
error -> []
end.
+-spec get_roster_item(binary(), binary(), ljid()) -> #roster{}.
get_roster_item(LUser, LServer, LJID) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:get_roster_item(LUser, LServer, LJID) of
us = {LUser, LServer}, jid = LBJID}
end.
+-spec get_subscription_and_groups(binary(), binary(), ljid()) ->
+ {subscription(), ask(), [binary()]}.
get_subscription_and_groups(LUser, LServer, LJID) ->
LBJID = jid:remove_resource(LJID),
Mod = gen_mod:db_mod(LServer, ?MODULE),
{none, none, []}
end.
+-spec set_roster(#roster{}) -> {atomic | aborted, any()}.
set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) ->
transaction(
LUser, LServer, [LJID],
update_roster_t(LUser, LServer, LJID, Item)
end).
+-spec del_roster(binary(), binary(), ljid()) -> {atomic | aborted, any()}.
del_roster(LUser, LServer, LJID) ->
transaction(
LUser, LServer, [LJID],
del_roster_t(LUser, LServer, LJID)
end).
+-spec encode_item(#roster{}) -> roster_item().
encode_item(Item) ->
#roster_item{jid = jid:make(Item#roster.jid),
name = Item#roster.name,
end,
groups = Item#roster.groups}.
+-spec decode_item(roster_item(), #roster{}, boolean()) -> #roster{}.
decode_item(#roster_item{subscription = remove} = Item, R, _) ->
R#roster{jid = jid:tolower(Item#roster_item.jid),
name = <<"">>,
end,
groups = Item#roster_item.groups}.
-process_iq_set(#iq{from = _From, to = To,
+-spec process_iq_set(iq()) -> iq().
+process_iq_set(#iq{from = _From, to = To, lang = Lang,
sub_els = [#roster_query{items = [QueryItem]}]} = IQ) ->
case set_item_and_notify_clients(To, QueryItem, false) of
ok ->
xmpp:make_iq_result(IQ);
- E ->
- ?ERROR_MSG("Roster set failed:~nIQ = ~s~nError = ~p",
- [xmpp:pp(IQ), E]),
- xmpp:make_error(IQ, xmpp:err_internal_server_error())
+ {error, _} ->
+ Txt = ?T("Database failure"),
+ Err = xmpp:err_internal_server_error(Txt, Lang),
+ xmpp:make_error(IQ, Err)
end.
--spec set_item_and_notify_clients(jid(), #roster_item{}, boolean()) -> ok | error.
+-spec set_item_and_notify_clients(jid(), #roster_item{}, boolean()) -> ok | {error, any()}.
set_item_and_notify_clients(To, #roster_item{jid = PeerJID} = RosterItem,
OverrideSubscription) ->
#jid{luser = LUser, lserver = LServer} = To,
PeerLJID = jid:tolower(PeerJID),
F = fun () ->
- Item = get_roster_item(LUser, LServer, PeerLJID),
- Item2 = decode_item(RosterItem, Item, OverrideSubscription),
- Item3 = ejabberd_hooks:run_fold(roster_process_item,
- LServer, Item2,
- [LServer]),
- case Item3#roster.subscription of
- remove -> del_roster_t(LUser, LServer, PeerLJID);
- _ -> update_roster_t(LUser, LServer, PeerLJID, Item3)
- end,
- case roster_version_on_db(LServer) of
- true -> write_roster_version_t(LUser, LServer);
- false -> ok
- end,
- {Item, Item3}
+ Item1 = get_roster_item(LUser, LServer, PeerLJID),
+ Item2 = decode_item(RosterItem, Item1, OverrideSubscription),
+ Item3 = ejabberd_hooks:run_fold(roster_process_item,
+ LServer, Item2,
+ [LServer]),
+ case Item3#roster.subscription of
+ remove -> del_roster_t(LUser, LServer, PeerLJID);
+ _ -> update_roster_t(LUser, LServer, PeerLJID, Item3)
+ end,
+ case mod_roster_opt:store_current_id(LServer) of
+ true -> write_roster_version_t(LUser, LServer);
+ false -> ok
+ end,
+ {Item1, Item3}
end,
case transaction(LUser, LServer, [PeerLJID], F) of
- {atomic, {OldItem, Item}} ->
- push_item(To, OldItem, Item),
- case Item#roster.subscription of
+ {atomic, {OldItem, NewItem}} ->
+ push_item(To, OldItem, NewItem),
+ case NewItem#roster.subscription of
remove ->
send_unsubscribing_presence(To, OldItem);
_ ->
ok
- end,
- ok;
- E ->
- E
+ end;
+ {aborted, Reason} ->
+ {error, Reason}
end.
+-spec push_item(jid(), #roster{}, #roster{}) -> ok.
push_item(To, OldItem, NewItem) ->
#jid{luser = LUser, lserver = LServer} = To,
- Ver = case roster_versioning_enabled(LServer) of
+ Ver = case mod_roster_opt:versioning(LServer) of
true -> roster_version(LServer, LUser);
false -> undefined
end,
push_item(To1, OldItem, NewItem, Ver)
end, ejabberd_sm:get_user_resources(LUser, LServer)).
+-spec push_item(jid(), #roster{}, #roster{}, undefined | binary()) -> ok.
push_item(To, OldItem, NewItem, Ver) ->
route_presence_change(To, OldItem, NewItem),
IQ = #iq{type = set, to = To,
ok
end.
+-spec ask_to_pending(ask()) -> none | in | out | both.
ask_to_pending(subscribe) -> out;
ask_to_pending(unsubscribe) -> none;
ask_to_pending(Ask) -> Ask.
+-spec roster_subscribe_t(binary(), binary(), ljid(), #roster{}) -> any().
roster_subscribe_t(LUser, LServer, LJID, Item) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:roster_subscribe(LUser, LServer, LJID, Item).
+-spec transaction(binary(), binary(), [ljid()], fun(() -> T)) -> {atomic, T} | {aborted, any()}.
transaction(LUser, LServer, LJIDs, F) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:transaction(LServer, F) of
#jid{user = User, server = Server} = From,
process_subscription(out, User, Server, JID, Type, <<"">>).
+-spec process_subscription(in | out, binary(), binary(), jid(),
+ subscribe | subscribed | unsubscribe | unsubscribed,
+ binary()) -> boolean().
process_subscription(Direction, User, Server, JID1,
Type, Reason) ->
LUser = jid:nodeprep(User),
ask = Pending,
askmessage = AskMessage},
roster_subscribe_t(LUser, LServer, LJID, NewItem),
- case roster_version_on_db(LServer) of
+ case mod_roster_opt:store_current_id(LServer) of
true -> write_roster_version_t(LUser, LServer);
false -> ok
end,
%% For each contact with Subscription:
%% Both or From, send a "unsubscribed" presence stanza;
%% Both or To, send a "unsubscribe" presence stanza.
+-spec send_unsubscription_to_rosteritems(binary(), binary(), [#roster{}]) -> ok.
send_unsubscription_to_rosteritems(LUser, LServer, RosterItems) ->
From = jid:make({LUser, LServer, <<"">>}),
lists:foreach(fun (RosterItem) ->
end,
RosterItems).
+-spec send_unsubscribing_presence(jid(), #roster{}) -> ok.
send_unsubscribing_presence(From, Item) ->
IsTo = case Item#roster.subscription of
both -> true;
from = jid:remove_resource(From),
to = jid:make(Item#roster.jid)});
true -> ok
- end,
- ok.
+ end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--spec set_items(binary(), binary(), roster_query()) -> any().
+-spec set_items(binary(), binary(), roster_query()) -> {atomic, ok} | {aborted, any()}.
set_items(User, Server, #roster_query{items = Items}) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
end,
transaction(LUser, LServer, LJIDs, F).
+-spec update_roster_t(binary(), binary(), ljid(), #roster{}) -> any().
update_roster_t(LUser, LServer, LJID, Item) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:update_roster(LUser, LServer, LJID, Item).
+-spec del_roster_t(binary(), binary(), ljid()) -> any().
del_roster_t(LUser, LServer, LJID) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:del_roster(LUser, LServer, LJID).
+-spec process_item_set_t(binary(), binary(), roster_item()) -> any().
process_item_set_t(LUser, LServer, #roster_item{jid = JID1} = QueryItem) ->
JID = {JID1#jid.user, JID1#jid.server, <<>>},
LJID = {JID1#jid.luser, JID1#jid.lserver, <<>>},
end;
process_item_set_t(_LUser, _LServer, _) -> ok.
--spec c2s_self_presence({presence(), ejabberd_c2s:state()})
- -> {presence(), ejabberd_c2s:state()}.
+-spec c2s_self_presence({presence(), c2s_state()}) -> {presence(), c2s_state()}.
c2s_self_presence({_, #{pres_last := _}} = Acc) ->
Acc;
c2s_self_presence({#presence{type = available} = Pkt, State}) ->
c2s_self_presence(Acc) ->
Acc.
--spec resend_pending_subscriptions(ejabberd_c2s:state()) -> ejabberd_c2s:state().
+-spec resend_pending_subscriptions(c2s_state()) -> c2s_state().
resend_pending_subscriptions(#{jid := JID} = State) ->
BareJID = jid:remove_resource(JID),
Result = get_roster(JID#jid.luser, JID#jid.lserver),
[?XE(<<"h3">>, [?ACT(<<"roster/">>, ?T("Roster"))])].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec has_duplicated_groups([binary()]) -> boolean().
has_duplicated_groups(Groups) ->
GroupsPrep = lists:usort([jid:resourceprep(G) || G <- Groups]),
not (length(GroupsPrep) == length(Groups)).