diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index e61b9e987..7e1ee9509 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -310,11 +310,7 @@ wait_for_stream({xmlstreamstart, #xmlel{ns = NS} = Opening}, StateData) -> _ -> case StateData#state.resource of undefined -> - RosterVersioningFeature = - case roster_versioning:is_enabled(ServerB) of - true -> [roster_versioning:stream_feature()]; - false -> [] - end, + RosterVersioningFeature = ejabberd_hooks:run_fold(roster_get_versioning_feature, Server, [], [Server]), send_element( StateData, exmpp_stream:features([ diff --git a/src/mod_roster.erl b/src/mod_roster.erl index 95c9e9247..e2c017d54 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -24,6 +24,15 @@ %%% %%%---------------------------------------------------------------------- +%%% @doc Roster management (Mnesia storage). +%%% +%%% Includes support for XEP-0237: Roster Versioning. +%%% The roster versioning follows an all-or-nothing strategy: +%%% - If the version supplied by the client is the latest, return an empty response. +%%% - If not, return the entire new roster (with updated version string). +%%% Roster version is a hash digest of the entire roster. +%%% No additional data is stored in DB. + -module(mod_roster). -author('alexey@process-one.net'). @@ -43,6 +52,7 @@ item_to_xml/1, webadmin_page/3, webadmin_user/4, + get_versioning_feature/2, roster_versioning_enabled/1]). -include_lib("exmpp/include/exmpp.hrl"). @@ -52,6 +62,8 @@ -include("web/ejabberd_http.hrl"). -include("web/ejabberd_web_admin.hrl"). +-define(NS_ROSTER_VER, "urn:xmpp:features:rosterver"). + %% @type rosteritem() = {roster, USJ, US, Contact_JID, Name, Subscription, Ask, Groups, Askmessage, Xs} %% USJ = {LUser, LServer, Prepd_Contact_JID} %% LUser = binary() @@ -96,6 +108,8 @@ start(Host, Opts) when is_list(Host) -> ?MODULE, remove_user, 50), ejabberd_hooks:add(resend_subscription_requests_hook, HostB, ?MODULE, get_in_pending_subscriptions, 50), + ejabberd_hooks:add(roster_get_versioning_feature, HostB, + ?MODULE, get_versioning_feature, 50), ejabberd_hooks:add(webadmin_page_host, HostB, ?MODULE, webadmin_page, 50), ejabberd_hooks:add(webadmin_user, HostB, @@ -124,6 +138,8 @@ stop(Host) when is_list(Host) -> ?MODULE, remove_user, 50), ejabberd_hooks:delete(resend_subscription_requests_hook, HostB, ?MODULE, get_in_pending_subscriptions, 50), + ejabberd_hooks:delete(roster_get_versioning_feature, HostB, + ?MODULE, get_versioning_feature, 50), ejabberd_hooks:delete(webadmin_page_host, HostB, ?MODULE, webadmin_page, 50), ejabberd_hooks:delete(webadmin_user, HostB, @@ -131,16 +147,6 @@ stop(Host) when is_list(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_ROSTER). -%% @spec (Host) -> true | false -%% @type Host = binary() -roster_versioning_enabled(Host) -> - gen_mod:get_module_opt(binary_to_list(Host), ?MODULE, versioning, false). - -%% @spec (Host) -> true | false -%% @type Host = binary() -roster_version_on_db(Host) -> - gen_mod:get_module_opt(binary_to_list(Host), ?MODULE, store_current_id, false). - %% @spec (From, To, IQ_Rec) -> IQ_Result %% From = exmpp_jid:jid() %% To = exmpp_jid:jid() @@ -176,6 +182,26 @@ roster_hash(Items) -> [R#roster{groups = lists:sort(Grs)} || R = #roster{groups = Grs} <- Items]))). +%% @spec (Host) -> true | false +%% @type Host = binary() +roster_versioning_enabled(Host) -> + gen_mod:get_module_opt(binary_to_list(Host), ?MODULE, versioning, false). + +%% @spec (Host) -> true | false +%% @type Host = binary() +roster_version_on_db(Host) -> + gen_mod:get_module_opt(binary_to_list(Host), ?MODULE, store_current_id, false). + +%% Returns a list that may contain an xmlelement with the XEP-237 feature if it's enabled. +get_versioning_feature(Acc, Host) -> + case roster_versioning_enabled(Host) of + true -> + Feature = exmpp_xml:element(?NS_ROSTER_VER, 'ver', [], + [exmpp_xml:element(?NS_ROSTER_VER, 'optional')]), + [Feature | Acc]; + false -> [] + end. + roster_version(LServer ,LUser) -> US = {LUser, LServer}, case roster_version_on_db(LServer) of @@ -477,7 +503,7 @@ push_item(User, Server, From, Item) case roster_versioning_enabled(Server) of true -> - roster_versioning:push_item(Server, User, From, Item, roster_version(Server, User)); + push_item_version(Server, User, From, Item, roster_version(Server, User)); false -> lists:foreach(fun(Resource) -> push_item(User, Server, Resource, From, Item) @@ -504,6 +530,23 @@ push_item(User, Server, Resource, From, Item) exmpp_jid:make(User, Server, Resource), ResIQ). +%% @doc Roster push, calculate and include the version attribute. +%% TODO: don't push to those who didn't load roster +push_item_version(Server, User, From, Item, RosterVersion) -> + lists:foreach(fun(Resource) -> + push_item_version(User, Server, Resource, From, Item, RosterVersion) + end, ejabberd_sm:get_user_resources(User, Server)). + +push_item_version(User, Server, Resource, From, Item, RosterVersion) -> + Request = #xmlel{ns = ?NS_ROSTER, name = 'query', attrs = [?XMLATTR('ver', RosterVersion)], + children = [mod_roster:item_to_xml(Item)]}, + ResIQ = exmpp_iq:set(?NS_JABBER_CLIENT, Request, + "push" ++ randoms:get_string()), + ejabberd_router:route( + From, + exmpp_jid:make(User, Server, Resource), + ResIQ). + %% @spec (Ignored, User, Server) -> Subscription_Lists %% Ignored = term() %% User = binary() diff --git a/src/mod_roster_odbc.erl b/src/mod_roster_odbc.erl index 0564e78c8..790862b0a 100644 --- a/src/mod_roster_odbc.erl +++ b/src/mod_roster_odbc.erl @@ -24,6 +24,15 @@ %%% %%%---------------------------------------------------------------------- +%%% @doc Roster management (Mnesia storage). +%%% +%%% Includes support for XEP-0237: Roster Versioning. +%%% The roster versioning follows an all-or-nothing strategy: +%%% - If the version supplied by the client is the latest, return an empty response. +%%% - If not, return the entire new roster (with updated version string). +%%% Roster version is a hash digest of the entire roster. +%%% No additional data is stored in DB. + -module(mod_roster_odbc). -author('alexey@process-one.net'). @@ -42,6 +51,7 @@ get_jid_info/4, webadmin_page/3, webadmin_user/4, + get_versioning_feature/2, roster_versioning_enabled/1]). -include_lib("exmpp/include/exmpp.hrl"). @@ -51,6 +61,7 @@ -include("web/ejabberd_http.hrl"). -include("web/ejabberd_web_admin.hrl"). +-define(NS_ROSTER_VER, "urn:xmpp:features:rosterver"). start(Host, Opts) -> HostB = list_to_binary(Host), @@ -71,6 +82,8 @@ start(Host, Opts) -> ?MODULE, remove_user, 50), ejabberd_hooks:add(resend_subscription_requests_hook, HostB, ?MODULE, get_in_pending_subscriptions, 50), + ejabberd_hooks:add(roster_get_versioning_feature, HostB, + ?MODULE, get_versioning_feature, 50), ejabberd_hooks:add(webadmin_page_host, HostB, ?MODULE, webadmin_page, 50), ejabberd_hooks:add(webadmin_user, HostB, @@ -96,6 +109,8 @@ stop(Host) -> ?MODULE, remove_user, 50), ejabberd_hooks:delete(resend_subscription_requests_hook, HostB, ?MODULE, get_in_pending_subscriptions, 50), + ejabberd_hooks:delete(roster_get_versioning_feature, HostB, + ?MODULE, get_versioning_feature, 50), ejabberd_hooks:delete(webadmin_page_host, HostB, ?MODULE, webadmin_page, 50), ejabberd_hooks:delete(webadmin_user, HostB, @@ -103,12 +118,6 @@ stop(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_ROSTER). -roster_versioning_enabled(Host) -> - gen_mod:get_module_opt(binary_to_list(Host), ?MODULE, versioning, false). - -roster_version_on_db(Host) -> - gen_mod:get_module_opt(binary_to_list(Host), ?MODULE, store_current_id, false). - process_iq(From, To, IQ_Rec) -> LServer = exmpp_jid:prep_domain_as_list(From), case lists:member(LServer, ?MYHOSTS) of @@ -131,6 +140,26 @@ roster_hash(Items) -> [R#roster{groups = lists:sort(Grs)} || R = #roster{groups = Grs} <- Items]))). +%% @spec (Host) -> true | false +%% @type Host = binary() +roster_versioning_enabled(Host) -> + gen_mod:get_module_opt(binary_to_list(Host), ?MODULE, versioning, false). + +%% @spec (Host) -> true | false +%% @type Host = binary() +roster_version_on_db(Host) -> + gen_mod:get_module_opt(binary_to_list(Host), ?MODULE, store_current_id, false). + +%% Returns a list that may contain an xmlelement with the XEP-237 feature if it's enabled. +get_versioning_feature(Acc, Host) -> + case roster_versioning_enabled(Host) of + true -> + Feature = exmpp_xml:element(?NS_ROSTER_VER, 'ver', [], + [exmpp_xml:element(?NS_ROSTER_VER, 'optional')]), + [Feature | Acc]; + false -> [] + end. + roster_version(LServer ,LUser) -> US = {LUser, LServer}, case roster_version_on_db(LServer) of @@ -433,7 +462,7 @@ push_item(User, Server, From, Item) when is_binary(User), is_binary(Server) -> Item#roster.subscription}]}), case roster_versioning_enabled(Server) of true -> - roster_versioning:push_item(Server, User, From, Item, roster_version(Server, User)); + push_item_version(Server, User, From, Item, roster_version(Server, User)); false -> lists:foreach(fun(Resource) -> push_item(User, Server, Resource, From, Item) @@ -452,6 +481,23 @@ push_item(User, Server, Resource, From, Item) -> exmpp_jid:make(User, Server, Resource), ResIQ). +%% @doc Roster push, calculate and include the version attribute. +%% TODO: don't push to those who didn't load roster +push_item_version(Server, User, From, Item, RosterVersion) -> + lists:foreach(fun(Resource) -> + push_item_version(User, Server, Resource, From, Item, RosterVersion) + end, ejabberd_sm:get_user_resources(User, Server)). + +push_item_version(User, Server, Resource, From, Item, RosterVersion) -> + Request = #xmlel{ns = ?NS_ROSTER, name = 'query', attrs = [?XMLATTR('ver', RosterVersion)], + children = [mod_roster:item_to_xml(Item)]}, + ResIQ = exmpp_iq:set(?NS_JABBER_CLIENT, Request, + "push" ++ randoms:get_string()), + ejabberd_router:route( + From, + exmpp_jid:make(User, Server, Resource), + ResIQ). + get_subscription_lists(_, User, Server) when is_binary(User), is_binary(Server) -> try diff --git a/src/roster_versioning.erl b/src/roster_versioning.erl deleted file mode 100644 index 1aa7e4e3b..000000000 --- a/src/roster_versioning.erl +++ /dev/null @@ -1,75 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_roster.erl -%%% Author : Pablo Polvorin -%%% Purpose : Common utility functions for XEP-0237 (Roster Versioning) -%%% Created : 19 Jul 2009 by Pablo Polvorin -%%% -%%% -%%% ejabberd, Copyright (C) 2009 ProcessOne -%%% -%%% This program is free software; you can redistribute it and/or -%%% modify it under the terms of the GNU General Public License as -%%% published by the Free Software Foundation; either version 2 of the -%%% License, or (at your option) any later version. -%%% -%%% This program is distributed in the hope that it will be useful, -%%% but WITHOUT ANY WARRANTY; without even the implied warranty of -%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -%%% General Public License for more details. -%%% -%%% You should have received a copy of the GNU General Public License -%%% along with this program; if not, write to the Free Software -%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA -%%% 02111-1307 USA -%%% -%%% -%%% @doc The roster versioning follows an all-or-nothing strategy: -%%% - If the version supplied by the client is the lastest, return an empty response -%%% - If not, return the entire new roster (with updated version string). -%%% Roster version is a hash digest of the entire roster. -%%% No additional data is stored in DB. -%%%---------------------------------------------------------------------- --module(roster_versioning). --author('pablo.polvorin@process-one.net'). - -%%API --export([is_enabled/1, - stream_feature/0, - push_item/5]). - - --include("mod_roster.hrl"). --include_lib("exmpp/include/exmpp.hrl"). - --define(NS_ROSTER_VER, "urn:xmpp:features:rosterver"). - -%%@doc is roster versioning enabled? -is_enabled(Host) -> - case gen_mod:is_loaded(binary_to_list(Host), mod_roster) of - true -> mod_roster:roster_versioning_enabled(Host); - false -> mod_roster_odbc:roster_versioning_enabled(Host) - end. - -stream_feature() -> - exmpp_xml:element(?NS_ROSTER_VER, 'ver', [], [exmpp_xml:element(?NS_ROSTER_VER, 'optional')]). - - - - -%% @doc Roster push, calculate and include the version attribute. -%% TODO: don't push to those who didn't load roster -push_item(Server, User, From, Item, RosterVersion) -> - lists:foreach(fun(Resource) -> - push_item(User, Server, Resource, From, Item, RosterVersion) - end, ejabberd_sm:get_user_resources(User, Server)). - -push_item(User, Server, Resource, From, Item, RosterVersion) -> - Request = #xmlel{ns = ?NS_ROSTER, name = 'query', attrs = [?XMLATTR('ver', RosterVersion)], - children = [mod_roster:item_to_xml(Item)]}, - ResIQ = exmpp_iq:set(?NS_JABBER_CLIENT, Request, - "push" ++ randoms:get_string()), - ejabberd_router:route( - From, - exmpp_jid:make(User, Server, Resource), - ResIQ). -