./ct_report/coverage/mod_caps.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_caps.erl
3 %%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
4 %%% Purpose : Request and cache Entity Capabilities (XEP-0115)
5 %%% Created : 7 Oct 2006 by Magnus Henoch <henoch@dtek.chalmers.se>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2015 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 along
21 %%% with this program; if not, write to the Free Software Foundation, Inc.,
22 %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 %%%
24 %%% 2009, improvements from ProcessOne to support correct PEP handling
25 %%% through s2s, use less memory, and speedup global caps handling
26 %%%----------------------------------------------------------------------
27
28 -module(mod_caps).
29
30 -author('henoch@dtek.chalmers.se').
31
32 -xep([{xep, 115}, {version, "1.6.0"}]).
33
34 -behaviour(gen_server).
35 -behaviour(gen_mod).
36 -behaviour(mongoose_module_metrics).
37
38 -export([read_caps/1, caps_stream_features/3,
39 disco_local_features/3, disco_local_identity/3, disco_info/3]).
40
41 %% gen_mod callbacks
42 -export([start/2, start_link/2, stop/1, config_spec/0, supported_features/0]).
43
44 %% gen_server callbacks
45 -export([init/1, handle_info/2, handle_call/3,
46 handle_cast/2, terminate/2, code_change/3]).
47
48 -export([user_send_presence/3,
49 user_receive_presence/3,
50 get_pep_recipients/3,
51 filter_pep_recipient/3]).
52
53 %% for test cases
54 -export([delete_caps/2, make_disco_hash/2]).
55 -ignore_xref([delete_caps/2, make_disco_hash/2, read_caps/1, start_link/2]).
56
57 -include("mongoose.hrl").
58 -include("mongoose_config_spec.hrl").
59
60 -include("jlib.hrl").
61
62 -define(PROCNAME, ejabberd_mod_caps).
63
64 -define(BAD_HASH_LIFETIME, 600).
65
66 -record(caps,
67 {
68 node = <<>> :: binary(),
69 version = <<>> :: binary(),
70 hash = <<>> :: binary(),
71 exts = [] :: [binary()]
72 }).
73
74 -type caps() :: #caps{}.
75 -type caps_resources() :: gb_trees:tree(jid:simple_jid(), caps()).
76
77 -export_type([caps/0, node_pair/0, maybe_pending_features/0]).
78
79 -type features() :: [binary()].
80 -type maybe_pending_features() :: features() | pos_integer().
81 -type node_pair() :: {Node :: binary(), SubNode :: binary()}.
82
83 -record(state, {host_type :: mongooseim:host_type()}).
84
85 -type state() :: #state{}.
86
87 -spec start_link(mongooseim:host_type(), gen_mod:module_opts()) -> any().
88 start_link(HostType, Opts) ->
89 2 mod_caps_backend:init(HostType, Opts),
90 2 Proc = gen_mod:get_module_proc(HostType, ?PROCNAME),
91 2 gen_server:start_link({local, Proc}, ?MODULE,
92 [HostType, Opts], []).
93
94 -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> any().
95 start(HostType, Opts) ->
96 2 Proc = gen_mod:get_module_proc(HostType, ?PROCNAME),
97 2 ChildSpec = {Proc, {?MODULE, start_link, [HostType, Opts]},
98 transient, 1000, worker, [?MODULE]},
99 2 ejabberd_sup:start_child(ChildSpec).
100
101 -spec stop(mongooseim:host_type()) -> any().
102 stop(HostType) ->
103 2 Proc = gen_mod:get_module_proc(HostType, ?PROCNAME),
104 2 gen_server:call(Proc, stop),
105 2 ejabberd_sup:stop_child(Proc).
106
107 -spec config_spec() -> mongoose_config_spec:config_section().
108 config_spec() ->
109 186 #section{
110 items = #{<<"cache_size">> => #option{type = integer,
111 validate = positive},
112 <<"cache_life_time">> => #option{type = integer,
113 validate = positive},
114 <<"backend">> => #option{type = atom,
115 validate = {module, ?MODULE}}
116 },
117 defaults = #{<<"cache_size">> => 1000,
118 <<"cache_life_time">> => timer:hours(24) div 1000,
119 <<"backend">> => mnesia}
120 }.
121
122 2 supported_features() -> [dynamic_domains].
123
124 -spec get_features_list(mongooseim:host_type(), nothing | caps()) -> features().
125 get_features_list(HostType, Caps) ->
126
:-(
case get_features(HostType, Caps) of
127
:-(
unknown -> [];
128
:-(
Features -> Features
129 end.
130
131 -spec get_features(mongooseim:host_type(), nothing | caps()) -> unknown | features().
132
:-(
get_features(_HostType, nothing) -> [];
133 get_features(HostType, #caps{node = Node, version = Version, exts = Exts}) ->
134
:-(
SubNodes = [Version | Exts],
135
:-(
lists:foldl(fun (SubNode, Acc) ->
136
:-(
NodePair = {Node, SubNode},
137
:-(
case cache_tab:lookup(caps_features, NodePair,
138 caps_read_fun(HostType, NodePair))
139 of
140 {ok, Features} when is_list(Features) ->
141
:-(
Features ++ Acc;
142 _ when Acc == [] ->
143
:-(
unknown;
144 _ ->
145
:-(
Acc
146 end
147 end,
148 [], SubNodes).
149
150 -spec read_caps([exml:element()]) -> nothing | caps().
151 69 read_caps(Els) -> read_caps(Els, nothing).
152
153 read_caps([#xmlel{name = <<"c">>, attrs = Attrs} | Tail], Result) ->
154
:-(
case xml:get_attr_s(<<"xmlns">>, Attrs) of
155 ?NS_CAPS ->
156
:-(
Node = xml:get_attr_s(<<"node">>, Attrs),
157
:-(
Version = xml:get_attr_s(<<"ver">>, Attrs),
158
:-(
Hash = xml:get_attr_s(<<"hash">>, Attrs),
159
:-(
Exts = mongoose_bin:tokens(xml:get_attr_s(<<"ext">>, Attrs), <<" ">>),
160
:-(
read_caps(Tail, #caps{node = Node, hash = Hash, version = Version, exts = Exts});
161
:-(
_ -> read_caps(Tail, Result)
162 end;
163 read_caps([#xmlel{name = <<"x">>, attrs = Attrs} | Tail], Result) ->
164
:-(
case xml:get_attr_s(<<"xmlns">>, Attrs) of
165
:-(
?NS_MUC_USER -> nothing;
166
:-(
_ -> read_caps(Tail, Result)
167 end;
168 read_caps([_ | Tail], Result) ->
169 5 read_caps(Tail, Result);
170 69 read_caps([], Result) -> Result.
171
172 -spec user_send_presence(Acc, Params, Extra) -> {ok, Acc} when
173 Acc :: mongoose_acc:t(),
174 Params :: map(),
175 Extra :: map().
176 user_send_presence(Acc, _, _) ->
177 50 {From, To, Packet} = mongoose_acc:packet(Acc),
178 50 {ok, user_send_presence(Acc, From, To, Packet)}.
179
180 -spec user_send_presence(mongoose_acc:t(), jid:jid(), jid:jid(), exml:element()) -> mongoose_acc:t().
181 user_send_presence(Acc,
182 #jid{luser = User, lserver = LServer} = From,
183 #jid{luser = User, lserver = LServer, lresource = <<>>},
184 #xmlel{attrs = Attrs, children = Elements}) ->
185 26 Type = xml:get_attr_s(<<"type">>, Attrs),
186 26 handle_presence(Acc, LServer, From, Type, Elements);
187 user_send_presence(Acc, _, _, _) ->
188 24 Acc.
189
190 -spec caps_stream_features(Acc, Params, Extra) -> {ok, Acc} when
191 Acc :: [exml:element()],
192 Params :: #{lserver := jid:lserver()},
193 Extra :: #{host_type := mongooseim:host_type()}.
194 caps_stream_features(Acc, #{lserver := LServer}, #{host_type := HostType}) ->
195 58 NewAcc = case make_my_disco_hash(HostType, LServer) of
196 <<>> ->
197
:-(
Acc;
198 Hash ->
199 58 [#xmlel{name = <<"c">>,
200 attrs = [{<<"xmlns">>, ?NS_CAPS}, {<<"hash">>, <<"sha-1">>},
201 {<<"node">>, ?MONGOOSE_URI}, {<<"ver">>, Hash}],
202 children = []}
203 | Acc]
204 end,
205 58 {ok, NewAcc}.
206
207 -spec disco_local_features(mongoose_disco:feature_acc(),
208 map(),
209 map()) -> {ok, mongoose_disco:feature_acc()}.
210 disco_local_features(Acc = #{node := Node}, _, _) ->
211 63 NewAcc = case is_valid_node(Node) of
212 2 true -> Acc#{node := <<>>};
213 61 false -> Acc
214 end,
215 63 {ok, NewAcc}.
216
217 -spec disco_local_identity(Acc, Params, Extra) -> {ok, Acc} when
218 Acc :: mongoose_disco:identity_acc(),
219 Params :: map(),
220 Extra :: gen_hook:extra().
221 disco_local_identity(Acc = #{node := Node}, _, _) ->
222 63 NewAcc = case is_valid_node(Node) of
223 2 true -> Acc#{node := <<>>};
224 61 false -> Acc
225 end,
226 63 {ok, NewAcc}.
227
228 -spec disco_info(Acc, Params, Extra) -> {ok, Acc} when
229 Acc :: mongoose_disco:identity_acc(),
230 Params :: map(),
231 Extra :: gen_hook:extra().
232 disco_info(Acc = #{node := Node}, _, _) ->
233 63 NewAcc = case is_valid_node(Node) of
234 2 true -> Acc#{node := <<>>};
235 61 false -> Acc
236 end,
237 63 {ok, NewAcc}.
238
239 -spec handle_presence(mongoose_acc:t(), jid:lserver(), jid:jid(), binary(), [exml:element()]) ->
240 mongoose_acc:t().
241 handle_presence(Acc, LServer, From, Type, Elements) when Type =:= <<>>;
242 Type =:= <<"available">> ->
243 26 case read_caps(Elements) of
244 nothing ->
245 26 Acc;
246 #caps{version = Version, exts = Exts} = Caps ->
247
:-(
feature_request(Acc, LServer, From, Caps, [Version | Exts])
248 end;
249 handle_presence(Acc, _LServer, _From, _Type, _Elements) ->
250
:-(
Acc.
251
252 -spec user_receive_presence(mongoose_acc:t(), mongoose_c2s_hooks:params(), gen_hook:extra()) ->
253 mongoose_c2s_hooks:result().
254 user_receive_presence(Acc0, #{c2s_data := C2SData}, _Extra) ->
255 93 {From, To, #xmlel{attrs = Attrs, children = Els} = Packet} = mongoose_acc:packet(Acc0),
256 93 ?LOG_DEBUG(#{what => user_receive_presence,
257 to => jid:to_binary(To), from => jid:to_binary(From),
258 93 exml_packet => Packet, c2s_state => C2SData}),
259 93 Type = xml:get_attr_s(<<"type">>, Attrs),
260 93 #jid{lserver = LServer} = mongoose_c2s:get_jid(C2SData),
261 93 Acc = case mongoose_domain_api:get_host_type(From#jid.lserver) of
262 {error, not_found} ->
263
:-(
handle_presence(Acc0, LServer, From, Type, Els);
264 {ok, _} ->
265 93 Acc0 %% it was already handled in 'user_send_presence'
266 end,
267 93 case mongoose_c2s:get_mod_state(C2SData, mod_presence) of
268 {ok, Presences} ->
269 93 Subscription = get_subscription(From, Presences),
270 93 Insert = (Type == <<>> orelse Type == <<"available">>)
271 93 and (Subscription == both orelse Subscription == to),
272 93 Delete = Type == <<"unavailable">> orelse Type == <<"error">>,
273 93 case Insert orelse Delete of
274 true ->
275 43 LFrom = jid:to_lower(From),
276 43 Rs = case mongoose_c2s:get_mod_state(C2SData, ?MODULE) of
277 17 {ok, Rs1} -> Rs1;
278 26 {error, not_found} -> gb_trees:empty()
279 end,
280 43 Caps = read_caps(Els),
281 43 NewRs = case Caps of
282 nothing when Insert == true ->
283 38 Rs;
284 _ when Insert == true ->
285
:-(
?LOG_DEBUG(#{what => caps_set_caps,
286 caps => Caps,
287 to => jid:to_binary(To),
288 from => jid:to_binary(From),
289 exml_packet => Packet,
290
:-(
c2s_state => C2SData}),
291
:-(
upsert_caps(LFrom, Caps, Rs);
292 _ ->
293 5 gb_trees:delete_any(LFrom, Rs)
294 end,
295 43 {ok, mongoose_c2s_acc:to_acc(Acc, state_mod, {?MODULE, NewRs})};
296 false ->
297 50 {ok, Acc}
298 end;
299 {error, not_found} ->
300
:-(
{ok, Acc}
301 end.
302
303 get_subscription(From, Presences) ->
304 93 BareFrom = jid:to_bare(From),
305 93 F = mod_presence:is_subscribed_to_my_presence(From, BareFrom, Presences),
306 93 T = mod_presence:am_i_subscribed_to_presence(From, BareFrom, Presences),
307 93 case {F, T} of
308 69 {true, true} -> both;
309
:-(
{true, false} -> from;
310 12 {false, true} -> to;
311 12 {false, false} -> none
312 end.
313
314 -spec upsert_caps(jid:simple_jid(), caps(), caps_resources()) -> caps_resources().
315 upsert_caps(LFrom, Caps, Rs) ->
316
:-(
case gb_trees:lookup(LFrom, Rs) of
317
:-(
{value, Caps} -> Rs;
318 none ->
319
:-(
gb_trees:insert(LFrom, Caps, Rs);
320 _ ->
321
:-(
gb_trees:update(LFrom, Caps, Rs)
322 end.
323
324 -spec get_pep_recipients(Acc, Params, Extra) -> {ok, Acc} when
325 Acc :: [jid:simple_jid()],
326 Params :: #{c2s_data := mongoose_c2s:data(), feature := binary()},
327 Extra :: map().
328 get_pep_recipients(InAcc, #{c2s_data := C2SData, feature := Feature}, _) ->
329
:-(
HostType = mongoose_c2s:get_host_type(C2SData),
330
:-(
NewAcc = case mongoose_c2s:get_mod_state(C2SData, ?MODULE) of
331 {ok, Rs} ->
332
:-(
filter_recipients_by_caps(HostType, InAcc, Feature, Rs);
333
:-(
_ -> InAcc
334 end,
335
:-(
{ok, NewAcc};
336
:-(
get_pep_recipients(Acc, _, _) -> {ok, Acc}.
337
338 -spec filter_recipients_by_caps(mongooseim:host_type(), Acc, binary(), caps_resources()) -> Acc
339 when Acc :: [jid:simple_jid()].
340 filter_recipients_by_caps(HostType, InAcc, Feature, Rs) ->
341
:-(
gb_trees_fold(fun(USR, Caps, Acc) ->
342
:-(
case lists:member(Feature, get_features_list(HostType, Caps)) of
343
:-(
true -> [USR | Acc];
344
:-(
false -> Acc
345 end
346 end,
347 InAcc, Rs).
348
349 -spec filter_pep_recipient(Acc, Params, Extra) -> {ok | stop, Acc} when
350 Acc :: boolean(),
351 Params :: #{c2s_data := mongoose_c2s:data(), feature := binary(), to := jid:jid()},
352 Extra :: gen_hook:extra().
353 filter_pep_recipient(InAcc, #{c2s_data := C2SData, feature := Feature, to := To}, _) ->
354
:-(
case mongoose_c2s:get_mod_state(C2SData, ?MODULE) of
355 {ok, Rs} ->
356
:-(
?LOG_DEBUG(#{what => caps_lookup, text => <<"Look for CAPS for To jid">>,
357
:-(
acc => InAcc, c2s_state => C2SData, caps_resources => Rs}),
358
:-(
LTo = jid:to_lower(To),
359
:-(
case gb_trees:lookup(LTo, Rs) of
360 {value, Caps} ->
361
:-(
HostType = mongoose_c2s:get_host_type(C2SData),
362
:-(
Drop = not lists:member(Feature, get_features_list(HostType, Caps)),
363
:-(
{stop, Drop};
364 none ->
365
:-(
{stop, true}
366 end;
367
:-(
_ -> {ok, InAcc}
368 end.
369
370 -spec init(list()) -> {ok, state()}.
371 init([HostType, #{cache_size := MaxSize, cache_life_time := LifeTime}]) ->
372 2 cache_tab:new(caps_features, [{max_size, MaxSize}, {life_time, LifeTime}]),
373 2 gen_hook:add_handlers(hooks(HostType)),
374 2 {ok, #state{host_type = HostType}}.
375
376 -spec handle_call(term(), any(), state()) ->
377 {stop, normal, ok, state()} | {reply, {error, any()}, state()}.
378 handle_call(stop, _From, State) ->
379 2 {stop, normal, ok, State};
380 handle_call(_Req, _From, State) ->
381
:-(
{reply, {error, badarg}, State}.
382
383 -spec handle_cast(any(), state()) -> {noreply, state()}.
384
:-(
handle_cast(_Msg, State) -> {noreply, State}.
385
386 -spec handle_info(any(), state()) -> {noreply, state()}.
387
:-(
handle_info(_Info, State) -> {noreply, State}.
388
389 -spec terminate(any(), state()) -> ok.
390 terminate(_Reason, #state{host_type = HostType}) ->
391 2 gen_hook:delete_handlers(hooks(HostType)).
392
393 hooks(HostType) ->
394 4 [{disco_local_features, HostType, fun ?MODULE:disco_local_features/3, #{}, 1},
395 {get_pep_recipients, HostType, fun ?MODULE:get_pep_recipients/3, #{}, 75},
396 {filter_pep_recipient, HostType, fun ?MODULE:filter_pep_recipient/3, #{}, 75},
397 {user_send_presence, HostType, fun ?MODULE:user_send_presence/3, #{}, 75},
398 {user_receive_presence, HostType, fun ?MODULE:user_receive_presence/3, #{}, 1},
399 {c2s_stream_features, HostType, fun ?MODULE:caps_stream_features/3, #{}, 75},
400 {s2s_stream_features, HostType, fun ?MODULE:caps_stream_features/3, #{}, 75},
401 {disco_local_identity, HostType, fun ?MODULE:disco_local_identity/3, #{}, 1},
402 {disco_info, HostType, fun ?MODULE:disco_info/3, #{}, 1}
403 ].
404
405 -spec code_change(any(), state(), any()) -> {ok, state()}.
406
:-(
code_change(_OldVsn, State, _Extra) -> {ok, State}.
407
408 -spec feature_request(mongoose_acc:t(), jid:lserver(), jid:jid(), caps(), [binary()]) ->
409 mongoose_acc:t().
410 feature_request(Acc, LServer, From, Caps, [SubNode | Tail] = SubNodes) ->
411
:-(
Node = Caps#caps.node,
412
:-(
NodePair = {Node, SubNode},
413
:-(
HostType = mongoose_acc:host_type(Acc),
414
:-(
case cache_tab:lookup(caps_features, NodePair, caps_read_fun(HostType, NodePair)) of
415 {ok, Fs} when is_list(Fs) ->
416
:-(
feature_request(Acc, LServer, From, Caps, Tail);
417 Other ->
418
:-(
NeedRequest = case Other of
419
:-(
{ok, TS} -> os:system_time(second) >= TS + (?BAD_HASH_LIFETIME);
420
:-(
_ -> true
421 end,
422
:-(
F = fun (_From, _To, _Acc1, timeout) ->
423 %% IQ request timed out, skip this node
424
:-(
feature_request(Acc, LServer, From, Caps, Tail);
425 (_From, _To, Acc1, IQReply) ->
426
:-(
feature_response(Acc1, IQReply, LServer, From, Caps, SubNodes)
427 end,
428
:-(
case NeedRequest of
429 true ->
430
:-(
IQ = #iq{type = get, xmlns = ?NS_DISCO_INFO,
431 sub_el =
432 [#xmlel{name = <<"query">>,
433 attrs =
434 [{<<"xmlns">>, ?NS_DISCO_INFO},
435 {<<"node">>,
436 <<Node/binary, "#",
437 SubNode/binary>>}],
438 children = []}]},
439
:-(
cache_tab:insert(caps_features, NodePair, os:system_time(second),
440 caps_write_fun(HostType, NodePair, os:system_time(second))),
441
:-(
ejabberd_local:route_iq(jid:make_noprep(<<>>, LServer, <<>>), From, Acc, IQ, F),
442
:-(
Acc;
443
:-(
false -> feature_request(Acc, LServer, From, Caps, Tail)
444 end
445 end;
446 feature_request(Acc, _LServer, From, Caps, []) ->
447 %% feature_request is never executed with empty SubNodes list
448 %% so if we end up here, it means the caps are known
449
:-(
HostType = mongoose_acc:host_type(Acc),
450
:-(
mongoose_hooks:caps_recognised(Acc, From, self(), get_features_list(HostType, Caps)).
451
452 -spec feature_response(mongoose_acc:t(), jlib:iq(), jid:lserver(), jid:jid(), caps(), [binary()]) ->
453 mongoose_acc:t().
454 feature_response(Acc, #iq{type = result, sub_el = [#xmlel{children = Els}]},
455 LServer, From, Caps, [SubNode | SubNodes]) ->
456
:-(
HostType = mongoose_acc:host_type(Acc),
457
:-(
NodePair = {Caps#caps.node, SubNode},
458
:-(
case check_hash(Caps, Els) of
459 true ->
460
:-(
Features = lists:flatmap(fun (#xmlel{name = <<"feature">>,
461 attrs = FAttrs}) ->
462
:-(
[xml:get_attr_s(<<"var">>, FAttrs)];
463
:-(
(_) -> []
464 end,
465 Els),
466
:-(
cache_tab:insert(caps_features, NodePair,
467 Features,
468 caps_write_fun(HostType, NodePair, Features));
469
:-(
false -> ok
470 end,
471
:-(
feature_request(Acc, LServer, From, Caps, SubNodes);
472 feature_response(Acc, _IQResult, LServer, From, Caps, [_SubNode | SubNodes]) ->
473
:-(
feature_request(Acc, LServer, From, Caps, SubNodes).
474
475 -spec caps_read_fun(mongooseim:host_type(), node_pair()) ->
476 fun(() -> {ok, maybe_pending_features()} | error).
477 caps_read_fun(HostType, Node) ->
478
:-(
fun () ->
479
:-(
mod_caps_backend:read(HostType, Node)
480 end.
481
482 -spec caps_write_fun(mongooseim:host_type(), node_pair(), maybe_pending_features()) ->
483 fun(() -> ok).
484 caps_write_fun(HostType, Node, Features) ->
485
:-(
fun () ->
486
:-(
mod_caps_backend:write(HostType, Node, Features)
487 end.
488
489 -spec delete_caps(mongooseim:host_type(), node_pair()) -> ok.
490 delete_caps(HostType, Node) ->
491
:-(
cache_tab:delete(caps_features, Node, caps_delete_fun(HostType, Node)).
492
493 -spec caps_delete_fun(mongooseim:host_type(), node_pair()) -> fun(() -> ok).
494 caps_delete_fun(HostType, Node) ->
495
:-(
fun () ->
496
:-(
mod_caps_backend:delete_node(HostType, Node)
497 end.
498
499 -spec make_my_disco_hash(mongooseim:host_type(), jid:lserver()) -> binary().
500 make_my_disco_hash(HostType, LServer) ->
501 58 JID = jid:make(<<>>, LServer, <<>>),
502 58 case mongoose_disco:get_local_features(HostType, JID, JID, <<>>, <<>>) of
503 empty ->
504
:-(
<<>>;
505 {result, FeaturesXML} ->
506 58 IdentityXML = mongoose_disco:get_local_identity(HostType, JID, JID, <<>>, <<>>),
507 58 InfoXML = mongoose_disco:get_info(HostType, mod_disco, <<>>, <<>>),
508 58 make_disco_hash(IdentityXML ++ InfoXML ++ FeaturesXML, sha1)
509 end.
510
511 -spec make_disco_hash([exml:element()], HashAlgorithm :: atom()) -> binary().
512 make_disco_hash(DiscoEls, Algo) ->
513 58 Concat = list_to_binary([concat_identities(DiscoEls),
514 concat_features(DiscoEls), concat_info(DiscoEls)]),
515 58 jlib:encode_base64(case Algo of
516
:-(
md5 -> erlang:md5(Concat);
517 58 sha1 -> crypto:hash(sha, Concat);
518
:-(
sha224 -> crypto:hash(sha224, Concat);
519
:-(
sha256 -> crypto:hash(sha256, Concat);
520
:-(
sha384 -> crypto:hash(sha384, Concat);
521
:-(
sha512 -> crypto:hash(sha512, Concat)
522 end).
523
524 check_hash(Caps, Els) ->
525
:-(
case Caps#caps.hash of
526 <<"md5">> ->
527
:-(
Caps#caps.version == make_disco_hash(Els, md5);
528 <<"sha-1">> ->
529
:-(
Caps#caps.version == make_disco_hash(Els, sha1);
530 <<"sha-224">> ->
531
:-(
Caps#caps.version == make_disco_hash(Els, sha224);
532 <<"sha-256">> ->
533
:-(
Caps#caps.version == make_disco_hash(Els, sha256);
534 <<"sha-384">> ->
535
:-(
Caps#caps.version == make_disco_hash(Els, sha384);
536 <<"sha-512">> ->
537
:-(
Caps#caps.version == make_disco_hash(Els, sha512);
538
:-(
_ -> true
539 end.
540
541 concat_features(Els) ->
542 58 lists:usort(lists:flatmap(fun (#xmlel{name =
543 <<"feature">>,
544 attrs = Attrs}) ->
545 696 [[xml:get_attr_s(<<"var">>, Attrs), $<]];
546 116 (_) -> []
547 end,
548 Els)).
549
550 concat_identities(Els) ->
551 58 lists:sort(lists:flatmap(fun (#xmlel{name =
552 <<"identity">>,
553 attrs = Attrs}) ->
554 58 [[xml:get_attr_s(<<"category">>, Attrs),
555 $/, xml:get_attr_s(<<"type">>, Attrs),
556 $/,
557 xml:get_attr_s(<<"xml:lang">>, Attrs),
558 $/, xml:get_attr_s(<<"name">>, Attrs),
559 $<]];
560 754 (_) -> []
561 end,
562 Els)).
563
564 concat_info(Els) ->
565 58 lists:sort(lists:flatmap(fun(El) ->
566 812 concat_xdata_fields(mongoose_data_forms:parse_form(El))
567 end,
568 Els)).
569
570 concat_xdata_fields(#{type := <<"result">>, kvs := KVs, ns := NS}) ->
571 58 Res = maps:fold(fun(Var, Values, VarFields) ->
572 62 NewField = [[V, $<] || V <- [Var | lists:sort(Values)]],
573 62 [NewField | VarFields]
574 end, [], KVs),
575 58 [[NS, $<, lists:sort(Res)]];
576 concat_xdata_fields(_) ->
577 754 [].
578
579 gb_trees_fold(F, Acc, Tree) ->
580
:-(
Iter = gb_trees:iterator(Tree),
581
:-(
gb_trees_fold_iter(F, Acc, Iter).
582
583 gb_trees_fold_iter(F, Acc, Iter) ->
584
:-(
case gb_trees:next(Iter) of
585 {Key, Val, NewIter} ->
586
:-(
NewAcc = F(Key, Val, Acc),
587
:-(
gb_trees_fold_iter(F, NewAcc, NewIter);
588
:-(
_ -> Acc
589 end.
590
591 is_valid_node(Node) ->
592 189 case mongoose_bin:tokens(Node, <<"#">>) of
593 [?MONGOOSE_URI|_] ->
594 6 true;
595 _ ->
596 183 false
597 end.
Line Hits Source