aboutsummaryrefslogtreecommitdiff
path: root/src/mod_caps.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_caps.erl')
-rw-r--r--src/mod_caps.erl1001
1 files changed, 539 insertions, 462 deletions
diff --git a/src/mod_caps.erl b/src/mod_caps.erl
index c56ebc2eb..24af7e0ea 100644
--- a/src/mod_caps.erl
+++ b/src/mod_caps.erl
@@ -27,64 +27,65 @@
%%%----------------------------------------------------------------------
-module(mod_caps).
+
-author('henoch@dtek.chalmers.se').
-behaviour(gen_server).
+
-behaviour(gen_mod).
--export([read_caps/1,
- caps_stream_features/2,
- disco_features/5,
- disco_identity/5,
- disco_info/5,
- get_features/1]).
+-export([read_caps/1, caps_stream_features/2,
+ disco_features/5, disco_identity/5, disco_info/5,
+ get_features/2, export/1]).
%% gen_mod callbacks
--export([start/2, start_link/2,
- stop/1]).
+-export([start/2, start_link/2, stop/1]).
%% gen_server callbacks
--export([init/1,
- handle_info/2,
- handle_call/3,
- handle_cast/2,
- terminate/2,
- code_change/3
- ]).
+-export([init/1, handle_info/2, handle_call/3,
+ handle_cast/2, terminate/2, code_change/3]).
%% hook handlers
--export([user_send_packet/4,
- user_receive_packet/5,
- c2s_presence_in/2,
- c2s_broadcast_recipients/5]).
+-export([user_send_packet/4, user_receive_packet/5,
+ c2s_presence_in/2, c2s_broadcast_recipients/6]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
-define(PROCNAME, ejabberd_mod_caps).
--define(BAD_HASH_LIFETIME, 600). %% in seconds
--record(caps, {node, version, hash, exts}).
--record(caps_features, {node_pair, features = []}).
+-define(BAD_HASH_LIFETIME, 600).
+
+-record(caps,
+{
+ node = <<"">> :: binary(),
+ version = <<"">> :: binary(),
+ hash = <<"">> :: binary(),
+ exts = [] :: [binary()]
+}).
+
+-type caps() :: #caps{}.
--record(state, {host}).
+-export_type([caps/0]).
+
+-record(caps_features,
+{
+ node_pair = {<<"">>, <<"">>} :: {binary(), binary()},
+ features = [] :: [binary()] | pos_integer()
+}).
+
+-record(state, {host = <<"">> :: binary()}).
-%%====================================================================
-%% API
-%%====================================================================
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+ gen_server:start_link({local, Proc}, ?MODULE,
+ [Host, Opts], []).
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- ChildSpec =
- {Proc,
- {?MODULE, start_link, [Host, Opts]},
- transient,
- 1000,
- worker,
- [?MODULE]},
+ ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ transient, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -93,232 +94,242 @@ stop(Host) ->
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
-%% get_features returns a list of features implied by the given caps
-%% record (as extracted by read_caps) or 'unknown' if features are
-%% not completely collected at the moment.
-get_features(nothing) ->
- [];
-get_features(#caps{node = Node, version = Version, exts = Exts}) ->
+get_features(_Host, nothing) -> [];
+get_features(Host, #caps{node = Node, version = Version,
+ exts = Exts}) ->
SubNodes = [Version | Exts],
- lists:foldl(
- fun(SubNode, Acc) ->
- BinaryNode = node_to_binary(Node, SubNode),
- case cache_tab:lookup(caps_features, BinaryNode,
- caps_read_fun(BinaryNode)) of
- {ok, Features} when is_list(Features) ->
- binary_to_features(Features) ++ Acc;
- _ ->
- Acc
- end
- end, [], SubNodes).
-
-%% read_caps takes a list of XML elements (the child elements of a
-%% <presence/> stanza) and returns an opaque value representing the
-%% Entity Capabilities contained therein, or the atom nothing if no
-%% capabilities are advertised.
-read_caps(Els) ->
- read_caps(Els, nothing).
-
-read_caps([{xmlelement, "c", Attrs, _Els} | Tail], Result) ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_CAPS ->
- Node = xml:get_attr_s("node", Attrs),
- Version = xml:get_attr_s("ver", Attrs),
- Hash = xml:get_attr_s("hash", Attrs),
- Exts = string:tokens(xml:get_attr_s("ext", Attrs), " "),
- read_caps(Tail, #caps{node = Node, hash = Hash,
- version = Version, exts = Exts});
- _ ->
- read_caps(Tail, Result)
+ lists:foldl(fun (SubNode, Acc) ->
+ NodePair = {Node, SubNode},
+ case cache_tab:lookup(caps_features, NodePair,
+ caps_read_fun(Host, NodePair))
+ of
+ {ok, Features} when is_list(Features) ->
+ Features ++ Acc;
+ _ -> Acc
+ end
+ end,
+ [], SubNodes).
+
+-spec read_caps([xmlel()]) -> nothing | caps().
+
+read_caps(Els) -> read_caps(Els, nothing).
+
+read_caps([#xmlel{name = <<"c">>, attrs = Attrs}
+ | Tail],
+ Result) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_CAPS ->
+ Node = xml:get_attr_s(<<"node">>, Attrs),
+ Version = xml:get_attr_s(<<"ver">>, Attrs),
+ Hash = xml:get_attr_s(<<"hash">>, Attrs),
+ Exts = str:tokens(xml:get_attr_s(<<"ext">>, Attrs),
+ <<" ">>),
+ read_caps(Tail,
+ #caps{node = Node, hash = Hash, version = Version,
+ exts = Exts});
+ _ -> read_caps(Tail, Result)
end;
-read_caps([{xmlelement, "x", Attrs, _Els} | Tail], Result) ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_MUC_USER ->
- nothing;
- _ ->
- read_caps(Tail, Result)
+read_caps([#xmlel{name = <<"x">>, attrs = Attrs}
+ | Tail],
+ Result) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_MUC_USER -> nothing;
+ _ -> read_caps(Tail, Result)
end;
read_caps([_ | Tail], Result) ->
read_caps(Tail, Result);
-read_caps([], Result) ->
- Result.
+read_caps([], Result) -> Result.
-%%====================================================================
-%% Hooks
-%%====================================================================
user_send_packet(_DebugFlag,
#jid{luser = User, lserver = Server} = From,
- #jid{luser = User, lserver = Server, lresource = ""},
- {xmlelement, "presence", Attrs, Els}) ->
- Type = xml:get_attr_s("type", Attrs),
- if Type == ""; Type == "available" ->
- case read_caps(Els) of
- nothing ->
- ok;
- #caps{version = Version, exts = Exts} = Caps ->
- feature_request(Server, From, Caps, [Version | Exts])
- end;
- true ->
- ok
+ #jid{luser = User, lserver = Server,
+ lresource = <<"">>},
+ #xmlel{name = <<"presence">>, attrs = Attrs,
+ children = Els}) ->
+ Type = xml:get_attr_s(<<"type">>, Attrs),
+ if Type == <<"">>; Type == <<"available">> ->
+ case read_caps(Els) of
+ nothing -> ok;
+ #caps{version = Version, exts = Exts} = Caps ->
+ feature_request(Server, From, Caps, [Version | Exts])
+ end;
+ true -> ok
end;
-user_send_packet(_DebugFlag, _From, _To, _Packet) ->
- ok.
+user_send_packet(_DebugFlag, _From, _To, _Packet) -> ok.
-user_receive_packet(_DebugFlag,
- #jid{lserver = Server}, From, _To,
- {xmlelement, "presence", Attrs, Els}) ->
- Type = xml:get_attr_s("type", Attrs),
+user_receive_packet(_DebugFlag, #jid{lserver = Server},
+ From, _To,
+ #xmlel{name = <<"presence">>, attrs = Attrs,
+ children = Els}) ->
+ Type = xml:get_attr_s(<<"type">>, Attrs),
IsRemote = not lists:member(From#jid.lserver, ?MYHOSTS),
- %% Local users presence caps are already handled by user_send_packet.
- %% Otherwise we could send multiple request when broadcasting presence
- %% to every local subscriber.
- if IsRemote and ((Type == "") or (Type == "available")) ->
- case read_caps(Els) of
- nothing ->
- ok;
- #caps{version = Version, exts = Exts} = Caps ->
- feature_request(Server, From, Caps, [Version | Exts])
- end;
- true ->
- ok
+ if IsRemote and
+ ((Type == <<"">>) or (Type == <<"available">>)) ->
+ case read_caps(Els) of
+ nothing -> ok;
+ #caps{version = Version, exts = Exts} = Caps ->
+ feature_request(Server, From, Caps, [Version | Exts])
+ end;
+ true -> ok
end;
-user_receive_packet(_DebugFlag, _JID, _From, _To, _Packet) ->
+user_receive_packet(_DebugFlag, _JID, _From, _To,
+ _Packet) ->
ok.
+-spec caps_stream_features([xmlel()], binary()) -> [xmlel()].
+
caps_stream_features(Acc, MyHost) ->
case make_my_disco_hash(MyHost) of
- "" ->
- Acc;
- Hash ->
- [{xmlelement, "c", [{"xmlns", ?NS_CAPS},
- {"hash", "sha-1"},
- {"node", ?EJABBERD_URI},
- {"ver", Hash}], []} | Acc]
+ <<"">> -> Acc;
+ Hash ->
+ [#xmlel{name = <<"c">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_CAPS}, {<<"hash">>, <<"sha-1">>},
+ {<<"node">>, ?EJABBERD_URI}, {<<"ver">>, Hash}],
+ children = []}
+ | Acc]
end.
-disco_features(_Acc, From, To, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) ->
- ejabberd_hooks:run_fold(disco_local_features,
- To#jid.lserver,
- empty,
- [From, To, "", Lang]);
-disco_features(Acc, _From, _To, _Node, _Lang) ->
- Acc.
-
-disco_identity(_Acc, From, To, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) ->
- ejabberd_hooks:run_fold(disco_local_identity,
- To#jid.lserver,
- [],
- [From, To, "", Lang]);
-disco_identity(Acc, _From, _To, _Node, _Lang) ->
- Acc.
-
-disco_info(_Acc, Host, Module, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) ->
- ejabberd_hooks:run_fold(disco_info,
- Host,
- [],
- [Host, Module, "", Lang]);
-disco_info(Acc, _Host, _Module, _Node, _Lang) ->
- Acc.
-
-c2s_presence_in(C2SState, {From, To, {_, _, Attrs, Els}}) ->
- Type = xml:get_attr_s("type", Attrs),
- Subscription = ejabberd_c2s:get_subscription(From, C2SState),
- Insert = ((Type == "") or (Type == "available"))
- and ((Subscription == both) or (Subscription == to)),
- Delete = (Type == "unavailable") or (Type == "error") or (Type == "invisible"),
+disco_features(Acc, From, To, Node, Lang) ->
+ case is_valid_node(Node) of
+ true ->
+ ejabberd_hooks:run_fold(disco_local_features,
+ To#jid.lserver, empty,
+ [From, To, <<"">>, Lang]);
+ false ->
+ Acc
+ end.
+
+disco_identity(Acc, From, To, Node, Lang) ->
+ case is_valid_node(Node) of
+ true ->
+ ejabberd_hooks:run_fold(disco_local_identity,
+ To#jid.lserver, [],
+ [From, To, <<"">>, Lang]);
+ false ->
+ Acc
+ end.
+
+disco_info(Acc, Host, Module, Node, Lang) ->
+ case is_valid_node(Node) of
+ true ->
+ ejabberd_hooks:run_fold(disco_info, Host, [],
+ [Host, Module, <<"">>, Lang]);
+ false ->
+ Acc
+ end.
+
+c2s_presence_in(C2SState,
+ {From, To, {_, _, Attrs, Els}}) ->
+ Type = xml:get_attr_s(<<"type">>, Attrs),
+ Subscription = ejabberd_c2s:get_subscription(From,
+ C2SState),
+ Insert = ((Type == <<"">>) or (Type == <<"available">>))
+ and ((Subscription == both) or (Subscription == to)),
+ Delete = (Type == <<"unavailable">>) or
+ (Type == <<"error">>),
if Insert or Delete ->
- LFrom = jlib:jid_tolower(From),
- Rs = case ejabberd_c2s:get_aux_field(caps_resources, C2SState) of
- {ok, Rs1} ->
- Rs1;
- error ->
- gb_trees:empty()
- end,
- Caps = read_caps(Els),
- {CapsUpdated, NewRs} =
- case Caps of
- nothing when Insert == true ->
- {false, Rs};
- _ when Insert == true ->
- case gb_trees:lookup(LFrom, Rs) of
- {value, Caps} ->
- {false, Rs};
- none ->
- {true, gb_trees:insert(LFrom, Caps, Rs)};
- _ ->
- {true, gb_trees:update(LFrom, Caps, Rs)}
- end;
- _ ->
- {false, gb_trees:delete_any(LFrom, Rs)}
+ LFrom = jlib:jid_tolower(From),
+ Rs = case ejabberd_c2s:get_aux_field(caps_resources,
+ C2SState)
+ of
+ {ok, Rs1} -> Rs1;
+ error -> gb_trees:empty()
end,
- if CapsUpdated ->
- ejabberd_hooks:run(caps_update, To#jid.lserver,
- [From, To, get_features(Caps)]);
- true ->
- ok
- end,
- ejabberd_c2s:set_aux_field(caps_resources, NewRs, C2SState);
- true ->
- C2SState
+ Caps = read_caps(Els),
+ {CapsUpdated, NewRs} = case Caps of
+ nothing when Insert == true -> {false, Rs};
+ _ when Insert == true ->
+ case gb_trees:lookup(LFrom, Rs) of
+ {value, Caps} -> {false, Rs};
+ none ->
+ {true,
+ gb_trees:insert(LFrom, Caps,
+ Rs)};
+ _ ->
+ {true,
+ gb_trees:update(LFrom, Caps, Rs)}
+ end;
+ _ -> {false, gb_trees:delete_any(LFrom, Rs)}
+ end,
+ if CapsUpdated ->
+ ejabberd_hooks:run(caps_update, To#jid.lserver,
+ [From, To,
+ get_features(To#jid.lserver, Caps)]);
+ true -> ok
+ end,
+ ejabberd_c2s:set_aux_field(caps_resources, NewRs,
+ C2SState);
+ true -> C2SState
end.
-c2s_broadcast_recipients(InAcc, C2SState, {pep_message, Feature},
- _From, _Packet) ->
- case ejabberd_c2s:get_aux_field(caps_resources, C2SState) of
- {ok, Rs} ->
- gb_trees_fold(
- fun(USR, Caps, Acc) ->
- case lists:member(Feature, get_features(Caps)) of
- true ->
- [USR|Acc];
- false ->
- Acc
- end
- end, InAcc, Rs);
- _ ->
- InAcc
+c2s_broadcast_recipients(InAcc, Host, C2SState,
+ {pep_message, Feature}, _From, _Packet) ->
+ case ejabberd_c2s:get_aux_field(caps_resources,
+ C2SState)
+ of
+ {ok, Rs} ->
+ gb_trees_fold(fun (USR, Caps, Acc) ->
+ case lists:member(Feature,
+ get_features(Host, Caps))
+ of
+ true -> [USR | Acc];
+ false -> Acc
+ end
+ end,
+ InAcc, Rs);
+ _ -> InAcc
end;
-c2s_broadcast_recipients(Acc, _, _, _, _) ->
- Acc.
+c2s_broadcast_recipients(Acc, _, _, _, _, _) -> Acc.
-%%====================================================================
-%% gen_server callbacks
-%%====================================================================
init([Host, Opts]) ->
- case catch mnesia:table_info(caps_features, storage_type) of
- {'EXIT', _} ->
- ok;
- disc_only_copies ->
- ok;
- _ ->
- mnesia:delete_table(caps_features)
+ case gen_mod:db_type(Opts) of
+ mnesia ->
+ case catch mnesia:table_info(caps_features, storage_type) of
+ {'EXIT', _} ->
+ ok;
+ disc_only_copies ->
+ ok;
+ _ ->
+ mnesia:delete_table(caps_features)
+ end,
+ mnesia:create_table(caps_features,
+ [{disc_only_copies, [node()]},
+ {local_content, true},
+ {attributes,
+ record_info(fields, caps_features)}]),
+ update_table(),
+ mnesia:add_table_copy(caps_features, node(),
+ disc_only_copies);
+ _ ->
+ ok
end,
- mnesia:create_table(caps_features,
- [{disc_only_copies, [node()]},
- {local_content, true},
- {attributes, record_info(fields, caps_features)}]),
- mnesia:add_table_copy(caps_features, node(), disc_only_copies),
- MaxSize = gen_mod:get_opt(cache_size, Opts, 1000),
- LifeTime = gen_mod:get_opt(cache_life_time, Opts, timer:hours(24) div 1000),
- cache_tab:new(caps_features, [{max_size, MaxSize}, {life_time, LifeTime}]),
- ejabberd_hooks:add(c2s_presence_in, Host,
- ?MODULE, c2s_presence_in, 75),
+ MaxSize = gen_mod:get_opt(cache_size, Opts,
+ fun(I) when is_integer(I), I>0 -> I end,
+ 1000),
+ LifeTime = gen_mod:get_opt(cache_life_time, Opts,
+ fun(I) when is_integer(I), I>0 -> I end,
+ timer:hours(24) div 1000),
+ cache_tab:new(caps_features,
+ [{max_size, MaxSize}, {life_time, LifeTime}]),
+ ejabberd_hooks:add(c2s_presence_in, Host, ?MODULE,
+ c2s_presence_in, 75),
ejabberd_hooks:add(c2s_broadcast_recipients, Host,
?MODULE, c2s_broadcast_recipients, 75),
- ejabberd_hooks:add(user_send_packet, Host,
- ?MODULE, user_send_packet, 75),
- ejabberd_hooks:add(user_receive_packet, Host,
- ?MODULE, user_receive_packet, 75),
- ejabberd_hooks:add(c2s_stream_features, Host,
- ?MODULE, caps_stream_features, 75),
- ejabberd_hooks:add(s2s_stream_features, Host,
- ?MODULE, caps_stream_features, 75),
- ejabberd_hooks:add(disco_local_features, Host,
- ?MODULE, disco_features, 75),
- ejabberd_hooks:add(disco_local_identity, Host,
- ?MODULE, disco_identity, 75),
- ejabberd_hooks:add(disco_info, Host,
- ?MODULE, disco_info, 75),
+ ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
+ user_send_packet, 75),
+ ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
+ user_receive_packet, 75),
+ ejabberd_hooks:add(c2s_stream_features, Host, ?MODULE,
+ caps_stream_features, 75),
+ ejabberd_hooks:add(s2s_stream_features, Host, ?MODULE,
+ caps_stream_features, 75),
+ ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
+ disco_features, 75),
+ ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
+ disco_identity, 75),
+ ejabberd_hooks:add(disco_info, Host, ?MODULE,
+ disco_info, 75),
{ok, #state{host = Host}}.
handle_call(stop, _From, State) ->
@@ -326,20 +337,18 @@ handle_call(stop, _From, State) ->
handle_call(_Req, _From, State) ->
{reply, {error, badarg}, State}.
-handle_cast(_Msg, State) ->
- {noreply, State}.
+handle_cast(_Msg, State) -> {noreply, State}.
-handle_info(_Info, State) ->
- {noreply, State}.
+handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, State) ->
Host = State#state.host,
- ejabberd_hooks:delete(c2s_presence_in, Host,
- ?MODULE, c2s_presence_in, 75),
+ ejabberd_hooks:delete(c2s_presence_in, Host, ?MODULE,
+ c2s_presence_in, 75),
ejabberd_hooks:delete(c2s_broadcast_recipients, Host,
?MODULE, c2s_broadcast_recipients, 75),
- ejabberd_hooks:delete(user_send_packet, Host,
- ?MODULE, user_send_packet, 75),
+ ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
+ user_send_packet, 75),
ejabberd_hooks:delete(user_receive_packet, Host,
?MODULE, user_receive_packet, 75),
ejabberd_hooks:delete(c2s_stream_features, Host,
@@ -350,267 +359,283 @@ terminate(_Reason, State) ->
?MODULE, disco_features, 75),
ejabberd_hooks:delete(disco_local_identity, Host,
?MODULE, disco_identity, 75),
- ejabberd_hooks:delete(disco_info, Host,
- ?MODULE, disco_info, 75),
+ ejabberd_hooks:delete(disco_info, Host, ?MODULE,
+ disco_info, 75),
ok.
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
-%%====================================================================
-%% Aux functions
-%%====================================================================
-feature_request(Host, From, Caps, [SubNode | Tail] = SubNodes) ->
+feature_request(Host, From, Caps,
+ [SubNode | Tail] = SubNodes) ->
Node = Caps#caps.node,
- BinaryNode = node_to_binary(Node, SubNode),
- case cache_tab:lookup(caps_features, BinaryNode,
- caps_read_fun(BinaryNode)) of
- {ok, Fs} when is_list(Fs) ->
- feature_request(Host, From, Caps, Tail);
- Other ->
- NeedRequest = case Other of
- {ok, TS} ->
- now_ts() >= TS + ?BAD_HASH_LIFETIME;
- _ ->
- true
- end,
- if NeedRequest ->
- IQ = #iq{type = get,
- xmlns = ?NS_DISCO_INFO,
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_DISCO_INFO},
- {"node", Node ++ "#" ++ SubNode}],
- []}]},
- %% We cache current timestamp in order to avoid
- %% caps requests flood
- cache_tab:insert(caps_features, BinaryNode, now_ts(),
- caps_write_fun(BinaryNode, now_ts())),
- F = fun(IQReply) ->
- feature_response(
- IQReply, Host, From, Caps, SubNodes)
+ NodePair = {Node, SubNode},
+ case cache_tab:lookup(caps_features, NodePair,
+ caps_read_fun(Host, NodePair))
+ of
+ {ok, Fs} when is_list(Fs) ->
+ feature_request(Host, From, Caps, Tail);
+ Other ->
+ NeedRequest = case Other of
+ {ok, TS} -> now_ts() >= TS + (?BAD_HASH_LIFETIME);
+ _ -> true
end,
- ejabberd_local:route_iq(
- jlib:make_jid("", Host, ""), From, IQ, F);
- true ->
- feature_request(Host, From, Caps, Tail)
- end
+ if NeedRequest ->
+ IQ = #iq{type = get, xmlns = ?NS_DISCO_INFO,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, ?NS_DISCO_INFO},
+ {<<"node">>,
+ <<Node/binary, "#",
+ SubNode/binary>>}],
+ children = []}]},
+ cache_tab:insert(caps_features, NodePair, now_ts(),
+ caps_write_fun(Host, NodePair, now_ts())),
+ F = fun (IQReply) ->
+ feature_response(IQReply, Host, From, Caps,
+ SubNodes)
+ end,
+ ejabberd_local:route_iq(jlib:make_jid(<<"">>, Host,
+ <<"">>),
+ From, IQ, F);
+ true -> feature_request(Host, From, Caps, Tail)
+ end
end;
-feature_request(_Host, _From, _Caps, []) ->
- ok.
+feature_request(_Host, _From, _Caps, []) -> ok.
feature_response(#iq{type = result,
- sub_el = [{xmlelement, _, _, Els}]},
+ sub_el = [#xmlel{children = Els}]},
Host, From, Caps, [SubNode | SubNodes]) ->
- BinaryNode = node_to_binary(Caps#caps.node, SubNode),
+ NodePair = {Caps#caps.node, SubNode},
case check_hash(Caps, Els) of
- true ->
- Features = lists:flatmap(
- fun({xmlelement, "feature", FAttrs, _}) ->
- [xml:get_attr_s("var", FAttrs)];
- (_) ->
- []
- end, Els),
- BinaryFeatures = features_to_binary(Features),
- cache_tab:insert(
- caps_features, BinaryNode, BinaryFeatures,
- caps_write_fun(BinaryNode, BinaryFeatures));
- false ->
- ok
+ true ->
+ Features = lists:flatmap(fun (#xmlel{name =
+ <<"feature">>,
+ attrs = FAttrs}) ->
+ [xml:get_attr_s(<<"var">>, FAttrs)];
+ (_) -> []
+ end,
+ Els),
+ cache_tab:insert(caps_features, NodePair,
+ Features,
+ caps_write_fun(Host, NodePair, Features));
+ false -> ok
end,
feature_request(Host, From, Caps, SubNodes);
-feature_response(_IQResult, Host, From, Caps, [_SubNode | SubNodes]) ->
- %% We got type=error or invalid type=result stanza or timeout.
+feature_response(_IQResult, Host, From, Caps,
+ [_SubNode | SubNodes]) ->
feature_request(Host, From, Caps, SubNodes).
-node_to_binary(Node, SubNode) ->
- {list_to_binary(Node), list_to_binary(SubNode)}.
+caps_read_fun(Host, Node) ->
+ LServer = jlib:nameprep(Host),
+ DBType = gen_mod:db_type(LServer, ?MODULE),
+ caps_read_fun(LServer, Node, DBType).
-features_to_binary(L) -> [list_to_binary(I) || I <- L].
-binary_to_features(L) -> [binary_to_list(I) || I <- L].
-
-caps_read_fun(Node) ->
- fun() ->
+caps_read_fun(_LServer, Node, mnesia) ->
+ fun () ->
case mnesia:dirty_read({caps_features, Node}) of
- [#caps_features{features = Features}] ->
- {ok, Features};
- _ ->
- error
+ [#caps_features{features = Features}] -> {ok, Features};
+ _ -> error
end
+ end;
+caps_read_fun(LServer, {Node, SubNode}, odbc) ->
+ fun() ->
+ SNode = ejabberd_odbc:escape(Node),
+ SSubNode = ejabberd_odbc:escape(SubNode),
+ case ejabberd_odbc:sql_query(
+ LServer, [<<"select feature from caps_features where ">>,
+ <<"node='">>, SNode, <<"' and subnode='">>,
+ SSubNode, <<"';">>]) of
+ {selected, [<<"feature">>], [[H]|_] = Fs} ->
+ case catch jlib:binary_to_integer(H) of
+ Int when is_integer(Int), Int>=0 ->
+ {ok, Int};
+ _ ->
+ {ok, lists:flatten(Fs)}
+ end;
+ _ ->
+ error
+ end
end.
-caps_write_fun(Node, Features) ->
- fun() ->
- mnesia:dirty_write(
- #caps_features{node_pair = Node,
- features = Features})
+caps_write_fun(Host, Node, Features) ->
+ LServer = jlib:nameprep(Host),
+ DBType = gen_mod:db_type(LServer, ?MODULE),
+ caps_write_fun(LServer, Node, Features, DBType).
+
+caps_write_fun(_LServer, Node, Features, mnesia) ->
+ fun () ->
+ mnesia:dirty_write(#caps_features{node_pair = Node,
+ features = Features})
+ end;
+caps_write_fun(LServer, NodePair, Features, odbc) ->
+ fun () ->
+ ejabberd_odbc:sql_transaction(
+ LServer,
+ sql_write_features_t(NodePair, Features))
end.
make_my_disco_hash(Host) ->
- JID = jlib:make_jid("", Host, ""),
+ JID = jlib:make_jid(<<"">>, Host, <<"">>),
case {ejabberd_hooks:run_fold(disco_local_features,
- Host,
- empty,
- [JID, JID, "", ""]),
- ejabberd_hooks:run_fold(disco_local_identity,
- Host,
- [],
- [JID, JID, "", ""]),
- ejabberd_hooks:run_fold(disco_info,
- Host,
- [],
- [Host, undefined, "", ""])} of
- {{result, Features}, Identities, Info} ->
- Feats = lists:map(
- fun({{Feat, _Host}}) ->
- {xmlelement, "feature", [{"var", Feat}], []};
- (Feat) ->
- {xmlelement, "feature", [{"var", Feat}], []}
- end, Features),
- make_disco_hash(Identities ++ Info ++ Feats, sha1);
- _Err ->
- ""
+ Host, empty, [JID, JID, <<"">>, <<"">>]),
+ ejabberd_hooks:run_fold(disco_local_identity, Host, [],
+ [JID, JID, <<"">>, <<"">>]),
+ ejabberd_hooks:run_fold(disco_info, Host, [],
+ [Host, undefined, <<"">>, <<"">>])}
+ of
+ {{result, Features}, Identities, Info} ->
+ Feats = lists:map(fun ({{Feat, _Host}}) ->
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, Feat}],
+ children = []};
+ (Feat) ->
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, Feat}],
+ children = []}
+ end,
+ Features),
+ make_disco_hash(Identities ++ Info ++ Feats, sha1);
+ _Err -> <<"">>
end.
-ifdef(HAVE_MD2).
+
make_disco_hash(DiscoEls, Algo) ->
- Concat = [concat_identities(DiscoEls),
- concat_features(DiscoEls),
- concat_info(DiscoEls)],
- base64:encode_to_string(
- if Algo == md2 ->
- sha:md2(Concat);
- Algo == md5 ->
- crypto:md5(Concat);
- Algo == sha1 ->
- crypto:sha(Concat);
- Algo == sha224 ->
- sha:sha224(Concat);
- Algo == sha256 ->
- sha:sha256(Concat);
- Algo == sha384 ->
- sha:sha384(Concat);
- Algo == sha512 ->
- sha:sha512(Concat)
- end).
+ Concat = list_to_binary([concat_identities(DiscoEls),
+ concat_features(DiscoEls), concat_info(DiscoEls)]),
+ jlib:encode_base64(case Algo of
+ md2 -> sha:md2(Concat);
+ md5 -> crypto:md5(Concat);
+ sha1 -> crypto:sha(Concat);
+ sha224 -> sha:sha224(Concat);
+ sha256 -> sha:sha256(Concat);
+ sha384 -> sha:sha384(Concat);
+ sha512 -> sha:sha512(Concat)
+ end).
check_hash(Caps, Els) ->
case Caps#caps.hash of
- "md2" ->
- Caps#caps.version == make_disco_hash(Els, md2);
- "md5" ->
- Caps#caps.version == make_disco_hash(Els, md5);
- "sha-1" ->
- Caps#caps.version == make_disco_hash(Els, sha1);
- "sha-224" ->
- Caps#caps.version == make_disco_hash(Els, sha224);
- "sha-256" ->
- Caps#caps.version == make_disco_hash(Els, sha256);
- "sha-384" ->
- Caps#caps.version == make_disco_hash(Els, sha384);
- "sha-512" ->
- Caps#caps.version == make_disco_hash(Els, sha512);
- _ ->
- true
+ <<"md2">> ->
+ Caps#caps.version == make_disco_hash(Els, md2);
+ <<"md5">> ->
+ Caps#caps.version == make_disco_hash(Els, md5);
+ <<"sha-1">> ->
+ Caps#caps.version == make_disco_hash(Els, sha1);
+ <<"sha-224">> ->
+ Caps#caps.version == make_disco_hash(Els, sha224);
+ <<"sha-256">> ->
+ Caps#caps.version == make_disco_hash(Els, sha256);
+ <<"sha-384">> ->
+ Caps#caps.version == make_disco_hash(Els, sha384);
+ <<"sha-512">> ->
+ Caps#caps.version == make_disco_hash(Els, sha512);
+ _ -> true
end.
+
-else.
+
make_disco_hash(DiscoEls, Algo) ->
- Concat = [concat_identities(DiscoEls),
- concat_features(DiscoEls),
- concat_info(DiscoEls)],
- base64:encode_to_string(
- if Algo == md5 ->
- crypto:md5(Concat);
- Algo == sha1 ->
- crypto:sha(Concat);
- Algo == sha224 ->
- sha:sha224(Concat);
- Algo == sha256 ->
- sha:sha256(Concat);
- Algo == sha384 ->
- sha:sha384(Concat);
- Algo == sha512 ->
- sha:sha512(Concat)
- end).
+ Concat = list_to_binary([concat_identities(DiscoEls),
+ concat_features(DiscoEls), concat_info(DiscoEls)]),
+ jlib:encode_base64(case Algo of
+ md5 -> crypto:md5(Concat);
+ sha1 -> crypto:sha(Concat);
+ sha224 -> sha:sha224(Concat);
+ sha256 -> sha:sha256(Concat);
+ sha384 -> sha:sha384(Concat);
+ sha512 -> sha:sha512(Concat)
+ end).
check_hash(Caps, Els) ->
case Caps#caps.hash of
- "md5" ->
- Caps#caps.version == make_disco_hash(Els, md5);
- "sha-1" ->
- Caps#caps.version == make_disco_hash(Els, sha1);
- "sha-224" ->
- Caps#caps.version == make_disco_hash(Els, sha224);
- "sha-256" ->
- Caps#caps.version == make_disco_hash(Els, sha256);
- "sha-384" ->
- Caps#caps.version == make_disco_hash(Els, sha384);
- "sha-512" ->
- Caps#caps.version == make_disco_hash(Els, sha512);
- _ ->
- true
+ <<"md5">> ->
+ Caps#caps.version == make_disco_hash(Els, md5);
+ <<"sha-1">> ->
+ Caps#caps.version == make_disco_hash(Els, sha1);
+ <<"sha-224">> ->
+ Caps#caps.version == make_disco_hash(Els, sha224);
+ <<"sha-256">> ->
+ Caps#caps.version == make_disco_hash(Els, sha256);
+ <<"sha-384">> ->
+ Caps#caps.version == make_disco_hash(Els, sha384);
+ <<"sha-512">> ->
+ Caps#caps.version == make_disco_hash(Els, sha512);
+ _ -> true
end.
+
-endif.
concat_features(Els) ->
- lists:usort(
- lists:flatmap(
- fun({xmlelement, "feature", Attrs, _}) ->
- [[xml:get_attr_s("var", Attrs), $<]];
- (_) ->
- []
- end, Els)).
+ lists:usort(lists:flatmap(fun (#xmlel{name =
+ <<"feature">>,
+ attrs = Attrs}) ->
+ [[xml:get_attr_s(<<"var">>, Attrs), $<]];
+ (_) -> []
+ end,
+ Els)).
concat_identities(Els) ->
- lists:sort(
- lists:flatmap(
- fun({xmlelement, "identity", Attrs, _}) ->
- [[xml:get_attr_s("category", Attrs), $/,
- xml:get_attr_s("type", Attrs), $/,
- xml:get_attr_s("xml:lang", Attrs), $/,
- xml:get_attr_s("name", Attrs), $<]];
- (_) ->
- []
- end, Els)).
+ lists:sort(lists:flatmap(fun (#xmlel{name =
+ <<"identity">>,
+ attrs = Attrs}) ->
+ [[xml:get_attr_s(<<"category">>, Attrs),
+ $/, xml:get_attr_s(<<"type">>, Attrs),
+ $/,
+ xml:get_attr_s(<<"xml:lang">>, Attrs),
+ $/, xml:get_attr_s(<<"name">>, Attrs),
+ $<]];
+ (_) -> []
+ end,
+ Els)).
concat_info(Els) ->
- lists:sort(
- lists:flatmap(
- fun({xmlelement, "x", Attrs, Fields}) ->
- case {xml:get_attr_s("xmlns", Attrs),
- xml:get_attr_s("type", Attrs)} of
- {?NS_XDATA, "result"} ->
- [concat_xdata_fields(Fields)];
- _ ->
- []
- end;
- (_) ->
- []
- end, Els)).
+ lists:sort(lists:flatmap(fun (#xmlel{name = <<"x">>,
+ attrs = Attrs, children = Fields}) ->
+ case {xml:get_attr_s(<<"xmlns">>, Attrs),
+ xml:get_attr_s(<<"type">>, Attrs)}
+ of
+ {?NS_XDATA, <<"result">>} ->
+ [concat_xdata_fields(Fields)];
+ _ -> []
+ end;
+ (_) -> []
+ end,
+ Els)).
concat_xdata_fields(Fields) ->
- [Form, Res] =
- lists:foldl(
- fun({xmlelement, "field", Attrs, Els} = El,
- [FormType, VarFields] = Acc) ->
- case xml:get_attr_s("var", Attrs) of
- "" ->
- Acc;
- "FORM_TYPE" ->
- [xml:get_subtag_cdata(El, "value"), VarFields];
- Var ->
- [FormType,
- [[[Var, $<],
- lists:sort(
- lists:flatmap(
- fun({xmlelement, "value", _, VEls}) ->
- [[xml:get_cdata(VEls), $<]];
- (_) ->
- []
- end, Els))] | VarFields]]
- end;
- (_, Acc) ->
- Acc
- end, ["", []], Fields),
+ [Form, Res] = lists:foldl(fun (#xmlel{name =
+ <<"field">>,
+ attrs = Attrs, children = Els} =
+ El,
+ [FormType, VarFields] = Acc) ->
+ case xml:get_attr_s(<<"var">>, Attrs) of
+ <<"">> -> Acc;
+ <<"FORM_TYPE">> ->
+ [xml:get_subtag_cdata(El,
+ <<"value">>),
+ VarFields];
+ Var ->
+ [FormType,
+ [[[Var, $<],
+ lists:sort(lists:flatmap(fun
+ (#xmlel{name
+ =
+ <<"value">>,
+ children
+ =
+ VEls}) ->
+ [[xml:get_cdata(VEls),
+ $<]];
+ (_) ->
+ []
+ end,
+ Els))]
+ | VarFields]]
+ end;
+ (_, Acc) -> Acc
+ end,
+ [<<"">>, []], Fields),
[Form, $<, lists:sort(Res)].
gb_trees_fold(F, Acc, Tree) ->
@@ -619,13 +644,65 @@ gb_trees_fold(F, Acc, Tree) ->
gb_trees_fold_iter(F, Acc, Iter) ->
case gb_trees:next(Iter) of
- {Key, Val, NewIter} ->
- NewAcc = F(Key, Val, Acc),
- gb_trees_fold_iter(F, NewAcc, NewIter);
- _ ->
- Acc
+ {Key, Val, NewIter} ->
+ NewAcc = F(Key, Val, Acc),
+ gb_trees_fold_iter(F, NewAcc, NewIter);
+ _ -> Acc
end.
now_ts() ->
- {MegaSecs, Secs, _} = now(),
- MegaSecs*1000000 + Secs.
+ {MegaSecs, Secs, _} = now(), MegaSecs * 1000000 + Secs.
+
+is_valid_node(Node) ->
+ case str:tokens(Node, <<"#">>) of
+ [?EJABBERD_URI|_] ->
+ true;
+ _ ->
+ false
+ end.
+
+update_table() ->
+ Fields = record_info(fields, caps_features),
+ case mnesia:table_info(caps_features, attributes) of
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ caps_features, Fields, set,
+ fun(#caps_features{node_pair = {N, _}}) -> N end,
+ fun(#caps_features{node_pair = {N, P},
+ features = Fs} = R) ->
+ NewFs = if is_integer(Fs) ->
+ Fs;
+ true ->
+ [iolist_to_binary(F) || F <- Fs]
+ end,
+ R#caps_features{node_pair = {iolist_to_binary(N),
+ iolist_to_binary(P)},
+ features = NewFs}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating caps_features table", []),
+ mnesia:transform_table(caps_features, ignore, Fields)
+ end.
+
+sql_write_features_t({Node, SubNode}, Features) ->
+ SNode = ejabberd_odbc:escape(Node),
+ SSubNode = ejabberd_odbc:escape(SubNode),
+ NewFeatures = if is_integer(Features) ->
+ [jlib:integer_to_binary(Features)];
+ true ->
+ Features
+ end,
+ [[<<"delete from caps_features where node='">>,
+ SNode, <<"' and subnode='">>, SSubNode, <<"';">>]|
+ [[<<"insert into caps_features(node, subnode, feature) ">>,
+ <<"values ('">>, SNode, <<"', '">>, SSubNode, <<"', '">>,
+ ejabberd_odbc:escape(F), <<"');">>] || F <- NewFeatures]].
+
+export(_Server) ->
+ [{caps_features,
+ fun(_Host, #caps_features{node_pair = NodePair,
+ features = Features}) ->
+ sql_write_features_t(NodePair, Features);
+ (_Host, _R) ->
+ []
+ end}].