diff options
Diffstat (limited to 'src/mod_caps.erl')
-rw-r--r-- | src/mod_caps.erl | 764 |
1 files changed, 382 insertions, 382 deletions
diff --git a/src/mod_caps.erl b/src/mod_caps.erl index 6641e956f..4bec5f29e 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -27,30 +27,23 @@ %%%---------------------------------------------------------------------- -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, +-export([read_caps/1, caps_stream_features/2, + disco_features/5, disco_identity/5, disco_info/5, get_features/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/3, @@ -59,32 +52,45 @@ c2s_broadcast_recipients/5]). -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()] +}). --record(state, {host}). +-type caps() :: #caps{}. + +-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) -> @@ -96,164 +102,170 @@ stop(Host) -> %% 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(nothing) -> []; get_features(#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(NodePair)) + of + {ok, Features} when is_list(Features) -> + Features ++ Acc; + _ -> Acc + end + end, + [], SubNodes). + +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(#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 +user_send_packet( + #jid{luser = User, lserver = Server} = From, + #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(_From, _To, _Packet) -> - ok. +user_send_packet(_From, _To, _Packet) -> ok. user_receive_packet(#jid{lserver = Server}, From, _To, - {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 + #xmlel{name = <<"presence">>, attrs = Attrs, + children = Els}) -> + Type = xml:get_attr_s(<<"type">>, Attrs), + IsRemote = not lists:member(From#jid.lserver, ?MYHOSTS), + 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(_JID, _From, _To, _Packet) -> ok. 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_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, ?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_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, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) -> - ejabberd_hooks:run_fold(disco_info, - Host, - [], - [Host, Module, "", Lang]); -disco_info(Acc, _Host, _Module, _Node, _Lang) -> - Acc. +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") or (Type == "invisible"), +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(Caps)]); + true -> ok + end, + ejabberd_c2s:set_aux_field(caps_resources, NewRs, + C2SState); + true -> C2SState end. c2s_broadcast_recipients(InAcc, C2SState, {pep_message, Feature}, @@ -292,8 +304,8 @@ init([Host, Opts]) -> {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), + MaxSize = gen_mod:get_opt(cache_size, Opts, fun(CS) when is_integer(CS) -> CS end, 1000), + LifeTime = gen_mod:get_opt(cache_life_time, Opts, fun(CL) when is_integer(CL) -> CL 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), @@ -320,20 +332,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, @@ -344,267 +354,251 @@ 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(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(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(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)}. - -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() -> + 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_write_fun(Node, Features) -> - fun() -> - mnesia:dirty_write( - #caps_features{node_pair = Node, - features = Features}) + fun () -> + mnesia:dirty_write(#caps_features{node_pair = Node, + features = 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) -> @@ -613,13 +607,19 @@ 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. |