./ct_report/coverage/mod_amp.COVER.html

1 %% @doc MongooseIM/Ejabberd module for (a subset of) XEP-0079 support.
2 %% This work was sponsored by Grindr LLC
3 %% @reference <a href="http://xmpp.org/extensions/xep-0079.html">XEP-0079</a>
4 %% @author <mongooseim@erlang-solutions.com>
5 %% @copyright 2014 Erlang Solutions, Ltd.
6 -module(mod_amp).
7
8 -behavior(gen_mod).
9 -behaviour(mongoose_module_metrics).
10 -xep([{xep, 79}, {version, "1.2"}, {status, partial}]).
11 -export([start/2, stop/1, supported_features/0]).
12 -export([user_send_message/3,
13 check_packet/2,
14 disco_local_features/3,
15 c2s_stream_features/3,
16 xmpp_send_element/3]).
17
18 -export_type([amp_event/0, amp_rule/0, amp_rules/0, amp_rule_support/0,
19 amp_match_result/0, amp_strategy/0]).
20
21 -include("amp.hrl").
22 -include("mongoose.hrl").
23 -include("jlib.hrl").
24
25 -define(AMP_FEATURE,
26 #xmlel{name = <<"amp">>, attrs = [{<<"xmlns">>, ?NS_AMP_FEATURE}]}).
27
28 -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
29 start(HostType, _Opts) ->
30 167 gen_hook:add_handlers(hooks(HostType)).
31
32 -spec stop(mongooseim:host_type()) -> ok.
33 stop(HostType) ->
34 163 gen_hook:delete_handlers(hooks(HostType)).
35
36 -spec supported_features() -> [atom()].
37 84 supported_features() -> [dynamic_domains].
38
39 -spec c2s_hooks(mongooseim:host_type()) -> gen_hook:hook_list(mongoose_c2s_hooks:fn()).
40 c2s_hooks(HostType) ->
41 330 [{user_send_message, HostType, fun ?MODULE:user_send_message/3, #{}, 5}].
42
43 hooks(HostType) ->
44 330 [
45 {disco_local_features, HostType, fun ?MODULE:disco_local_features/3, #{}, 99},
46 {c2s_stream_features, HostType, fun ?MODULE:c2s_stream_features/3, #{}, 50},
47 {xmpp_send_element, HostType, fun ?MODULE:xmpp_send_element/3, #{}, 10},
48 {amp_verify_support, HostType, fun amp_resolver:verify_support/3, #{}, 10},
49 {amp_check_condition, HostType, fun amp_resolver:check_condition/3, #{}, 10},
50 {amp_determine_strategy, HostType, fun amp_strategy:determine_strategy/3, #{}, 10}
51 | c2s_hooks(HostType)].
52
53 %% API
54
55 -spec user_send_message(mongoose_acc:t(), mongoose_c2s_hooks:params(), gen_hook:extra()) ->
56 mongoose_c2s_hooks:result().
57 user_send_message(Acc, _, _) ->
58 1668 {From, To, Element} = mongoose_acc:packet(Acc),
59 1668 run_initial_check(Acc, From, To, Element).
60
61 -spec check_packet(mongoose_acc:t(), amp_event()) -> mongoose_acc:t().
62 check_packet(Acc, Event) ->
63 26203 case mongoose_acc:get(amp, rules, none, Acc) of
64 26050 none -> Acc;
65 153 Rules -> process_event(Acc, Rules, Event)
66 end.
67
68 -spec disco_local_features(mongoose_disco:feature_acc(),
69 map(),
70 map()) -> {ok, mongoose_disco:feature_acc()}.
71 disco_local_features(Acc = #{node := Node}, _, _) ->
72 221 NewAcc = case amp_features(Node) of
73 4 [] -> Acc;
74 217 Features -> mongoose_disco:add_features(Features, Acc)
75 end,
76 221 {ok, NewAcc}.
77
78 -spec c2s_stream_features(Acc, Params, Extra) -> {ok, Acc} when
79 Acc :: [exml:element()],
80 Params :: map(),
81 Extra :: gen_hook:extra().
82 c2s_stream_features(Acc, _, _) ->
83 6573 {ok, lists:keystore(<<"amp">>, #xmlel.name, Acc, ?AMP_FEATURE)}.
84
85 -spec xmpp_send_element(mongoose_acc:t(), mongoose_c2s_hooks:params(), gen_hook:extra()) ->
86 gen_hook:hook_fn_ret(mongoose_acc:t()).
87 xmpp_send_element(Acc, _Params, _Extra) ->
88 25983 Event = case mongoose_acc:get(c2s, send_result, undefined, Acc) of
89 25859 ok -> delivered;
90 124 _ -> delivery_failed
91 end,
92 25983 {ok, check_packet(Acc, Event)}.
93
94 %% Internal
95 -spec run_initial_check(mongoose_acc:t(), jid:jid(), jid:jid(), exml:element()) ->
96 mongoose_c2s_hooks:result().
97 run_initial_check(Acc, From, To, Packet) ->
98 1668 Result = case amp:extract_requested_rules(Packet) of
99 1492 none -> nothing_to_do;
100 175 {rules, Rules} -> validate_and_process_rules(Packet, From, Rules, Acc);
101 1 {errors, Errors} -> send_errors_and_drop(Packet, From, Errors, Acc)
102 end,
103 1668 case Result of
104 nothing_to_do ->
105 1492 {ok, Acc};
106 drop ->
107 39 {stop, Acc};
108 NewRules ->
109 137 Acc1 = mongoose_acc:set_permanent(amp, rules, NewRules, Acc),
110 137 Acc2 = mongoose_acc:update_stanza(#{element => amp:strip_amp_el(Packet),
111 from_jid => From, to_jid => To}, Acc1),
112 137 {ok, Acc2}
113 end.
114
115 -spec validate_and_process_rules(exml:element(), jid:jid(), amp_rules(), mongoose_acc:t()) ->
116 amp_rules() | drop.
117 validate_and_process_rules(Packet, From, Rules, Acc) ->
118 175 VerifiedRules = verify_support(mongoose_acc:host_type(Acc), Rules),
119 175 {Good, Bad} = lists:partition(fun is_supported_rule/1, VerifiedRules),
120 175 ValidRules = [ Rule || {supported, Rule} <- Good ],
121 175 case Bad of
122 [{error, ValidationError, InvalidRule} | _] ->
123 2 send_error_and_drop(Packet, From, ValidationError, InvalidRule, Acc);
124 [] ->
125 173 process_rules(Packet, From, initial_check, ValidRules, Acc)
126 end.
127
128 -spec process_event(mongoose_acc:t(), amp_rules(), amp_event()) -> mongoose_acc:t().
129 process_event(Acc, Rules, Event) when Event =/= initial_check ->
130 153 Packet = mongoose_acc:element(Acc),
131 153 From = mongoose_acc:from_jid(Acc),
132 153 NewRules = process_rules(Packet, From, Event, Rules, Acc),
133 153 mongoose_acc:set_permanent(amp, rules, NewRules, Acc).
134
135 -spec amp_features(binary()) -> [mongoose_disco:feature()].
136 amp_features(?NS_AMP) ->
137 1 [<<?NS_AMP/binary, Suffix/binary>> || Suffix <- amp_feature_suffixes()];
138 amp_features(<<>>) ->
139 216 [?NS_AMP];
140 amp_features(_) ->
141 4 [].
142
143 %% @doc This may eventually be configurable, but for now we return a constant list.
144 amp_feature_suffixes() ->
145 1 [<<>>,
146 <<"?action=drop">>,
147 <<"?action=notify">>,
148 <<"?action=error">>,
149 <<"?condition=deliver">>,
150 <<"?condition=match-resource">>].
151
152 -spec process_rules(exml:element(), jid:jid(), amp_event(), amp_rules(), mongoose_acc:t()) ->
153 amp_rules() | drop.
154 process_rules(Packet, From, Event, Rules, Acc) ->
155 326 HostType = mongoose_acc:host_type(Acc),
156 326 To = mongoose_acc:to_jid(Acc),
157 326 Strategy = determine_strategy(HostType, Packet, From, To, Event),
158 326 RulesWithResults = apply_rules(fun(Rule) ->
159 332 resolve_condition(HostType, Strategy, Event, Rule)
160 end, Rules),
161 326 PacketResult = take_action(Packet, From, RulesWithResults, Acc),
162 326 return_result(PacketResult, Event, RulesWithResults).
163
164 %% @doc hooks helpers
165 -spec verify_support(mongooseim:host_type(), amp_rules()) -> [amp_rule_support()].
166 verify_support(HostType, Rules) ->
167 175 mongoose_hooks:amp_verify_support(HostType, Rules).
168
169 -spec determine_strategy(mongooseim:host_type(), exml:element(), jid:jid(), jid:jid(),
170 amp_event()) ->
171 amp_strategy().
172 determine_strategy(HostType, Packet, From, To, Event) ->
173 326 mongoose_hooks:amp_determine_strategy(HostType, From, To, Packet, Event).
174
175 apply_rules(F, Rules) ->
176 326 [Rule#amp_rule{result = F(Rule)} || Rule <- Rules].
177
178 -spec resolve_condition(mongooseim:host_type(), amp_strategy(), amp_event(), amp_rule()) ->
179 amp_match_result().
180 resolve_condition(HostType, Strategy, Event, Rule) ->
181 332 Result = mongoose_hooks:amp_check_condition(HostType, Strategy, Rule),
182 332 match_undecided_for_final_event(Rule, Event, Result).
183
184 match_undecided_for_final_event(#amp_rule{condition = deliver}, Event, undecided)
185 when Event =:= delivered;
186 12 Event =:= delivery_failed -> match;
187 320 match_undecided_for_final_event(_, _, Result) -> Result.
188
189 -spec take_action(exml:element(), jid:jid(), amp_rules(), mongoose_acc:t()) -> pass | drop.
190 take_action(Packet, From, Rules, Acc) ->
191 326 case find(fun(#amp_rule{result = Result}) -> Result =:= match end, Rules) of
192 237 not_found -> pass;
193 89 {found, Rule} -> take_action_for_matched_rule(Packet, From, Rule, Acc)
194 end.
195
196 36 return_result(drop, initial_check, _Rules) -> drop;
197 return_result(pass, _Event, Rules) ->
198 290 lists:filter(fun(#amp_rule{result = Result}) ->
199 260 Result =:= undecided
200 end, Rules).
201
202 -spec take_action_for_matched_rule(exml:element(), jid:jid(), amp_rule(), mongoose_acc:t()) ->
203 pass | drop.
204 take_action_for_matched_rule(Packet, From, #amp_rule{action = notify} = Rule, _Acc) ->
205 53 reply_to_sender(Rule, server_jid(From), From, Packet),
206 53 pass;
207 take_action_for_matched_rule(Packet, From, #amp_rule{action = error} = Rule, Acc) ->
208 18 send_error_and_drop(Packet, From, 'undefined-condition', Rule, Acc);
209 take_action_for_matched_rule(Packet, From, #amp_rule{action = drop}, Acc) ->
210 18 update_metric_and_drop(Packet, From, Acc).
211
212 -spec reply_to_sender(amp_rule(), jid:jid(), jid:jid(), exml:element()) -> mongoose_acc:t().
213 reply_to_sender(MatchedRule, ServerJid, OriginalSender, OriginalPacket) ->
214 53 Response = amp:make_response(MatchedRule, OriginalSender, OriginalPacket),
215 53 ejabberd_router:route(ServerJid, OriginalSender, Response).
216
217 -spec send_error_and_drop(exml:element(), jid:jid(), amp_error(), amp_rule(), mongoose_acc:t()) -> drop.
218 send_error_and_drop(Packet, From, AmpError, MatchedRule, Acc) ->
219 20 send_errors_and_drop(Packet, From, [{AmpError, MatchedRule}], Acc).
220
221 -spec send_errors_and_drop(exml:element(), jid:jid(), [{amp_error(), amp_rule()}], mongoose_acc:t()) -> drop.
222 send_errors_and_drop(Packet, From, [], Acc) ->
223
:-(
?LOG_ERROR(#{what => empty_list_of_errors_generated,
224 text => <<"This shouldn't happen!">>,
225
:-(
exml_packet => Packet, from => From}),
226
:-(
update_metric_and_drop(Packet, From, Acc);
227 send_errors_and_drop(Packet, From, ErrorRules, Acc) ->
228 21 {Errors, Rules} = lists:unzip(ErrorRules),
229 21 ErrorResponse = amp:make_error_response(Errors, Rules, From, Packet),
230 21 ejabberd_router:route(server_jid(From), From, ErrorResponse),
231 21 update_metric_and_drop(Packet, From, Acc).
232
233 -spec update_metric_and_drop(exml:element(), jid:jid(), mongoose_acc:t()) -> drop.
234 update_metric_and_drop(Packet, From, Acc) ->
235 39 mongoose_hooks:xmpp_stanza_dropped(Acc, From, mongoose_acc:to_jid(Acc), Packet),
236 39 drop.
237
238 -spec is_supported_rule(amp_rule_support()) -> boolean().
239 259 is_supported_rule({supported, _}) -> true;
240 2 is_supported_rule(_) -> false.
241
242 server_jid(#jid{lserver = Host}) ->
243 74 jid:from_binary(Host).
244
245 237 find(_Pred, []) -> not_found;
246 find(Pred, [H|T]) ->
247 309 case Pred(H) of
248 89 true -> {found, H};
249 220 false -> find(Pred, T)
250 end.
Line Hits Source