./ct_report/coverage/amp.COVER.html

1 %% @doc Models and business logic for XEP-0079: Advanced Message Processing
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(amp).
7
8 -include("amp.hrl").
9 -include("mongoose.hrl").
10 -include("jlib.hrl").
11
12 -export([extract_requested_rules/1,
13 make_response/3,
14 make_error_response/4,
15 rule_to_xmlel/1,
16 strip_amp_el/1,
17
18 binaries_to_rule/3,
19 is_amp_request/1
20 ]).
21
22 -ignore_xref([binaries_to_rule/3, is_amp_request/1, rule_to_xmlel/1]).
23
24 -export_type([amp_rule/0,
25 amp_rules/0]).
26
27
28 -spec binaries_to_rule(binary(), binary(), binary()) -> amp_rule() | amp_invalid_rule().
29 binaries_to_rule(<<"deliver">> = Condition, Value, Action) ->
30 400 case are_valid_deliver_params(Value, Action) of
31 400 true -> mk_amp_rule('deliver', from_bin_(Value), from_bin_(Action));
32
:-(
false -> mk_amp_invalid_rule(Condition, Value, Action)
33 end;
34 binaries_to_rule(<<"match-resource">> = Condition, Value, Action) ->
35 4 case are_valid_match_resource_params(Value, Action) of
36 4 true -> mk_amp_rule('match-resource', from_bin_(Value), from_bin_(Action));
37
:-(
false -> mk_amp_invalid_rule(Condition, Value, Action)
38 end;
39 binaries_to_rule(<<"expire-at">> = Condition, Value, Action) ->
40 1 case are_valid_expire_at_params(Value, Action) of
41 1 true -> mk_amp_rule('expire-at', Value, from_bin_(Action)); %% Value is binary here!
42
:-(
false -> mk_amp_invalid_rule(Condition, Value, Action)
43 end;
44 binaries_to_rule(Condition, Value, Action) ->
45 2 mk_amp_invalid_rule(Condition, Value, Action).
46
47
48 -spec extract_requested_rules(#xmlel{}) -> 'none'
49 | {rules, amp_rules()}
50 | {errors, [{amp_error(), amp_invalid_rule()}]}.
51 extract_requested_rules(#xmlel{} = Stanza) ->
52 3733 case is_amp_request(Stanza) of
53 272 true -> parse_rules(Stanza);
54 3461 _ -> none
55 end.
56
57 -spec make_response(amp_rule(), jid:jid(), #xmlel{}) -> #xmlel{}.
58 make_response(Rule, User, Packet) ->
59 77 OriginalId = exml_query:attr(Packet, <<"id">>, <<"original-id-missing">>),
60 77 OriginalSender = jid:to_binary(User),
61 77 OriginalRecipient = exml_query:attr(Packet, <<"to">>),
62
63 77 Amp = #xmlel{name = <<"amp">>,
64 attrs = [{<<"xmlns">>, ?NS_AMP},
65 {<<"status">>, to_bin_(Rule#amp_rule.action)},
66 {<<"to">>, OriginalRecipient},
67 {<<"from">>, OriginalSender}],
68 children = [rule_to_xmlel(Rule)]},
69 77 #xmlel{name = <<"message">>,
70 attrs = [{<<"id">>, OriginalId}],
71 children = [Amp]}.
72
73
74 -spec make_error_response([amp_error()], [amp_any_rule()], jid:jid(), #xmlel{})
75 -> #xmlel{}.
76 make_error_response([E|_] = Errors, [_|_] = Rules, User, Packet) ->
77 33 OriginalId = exml_query:attr(Packet, <<"id">>, <<"original-id-missing">>),
78 33 Error = make_error_el(Errors, Rules),
79 33 Amp = #xmlel{name = <<"amp">>,
80 attrs = [{<<"xmlns">>, ?NS_AMP} |
81 error_amp_attrs(E, User, Packet)],
82 34 children = [rule_to_xmlel(R) || R <- Rules]},
83 33 #xmlel{name = <<"message">>,
84 attrs = [{<<"id">>, OriginalId},
85 {<<"type">>, <<"error">>}],
86 children = [Error, Amp]};
87 make_error_response(Errors, Rules, User, Packet) ->
88
:-(
?LOG_ERROR(#{what => make_error_response_failed,
89 text => <<"amp:make_error_response/4 got invalid data">>,
90 reason => invalid_data, errors => Errors, rules => Rules,
91 user => User#jid.luser, server => User#jid.lserver,
92
:-(
exml_packet => Packet}),
93
:-(
error(invalid_data).
94
95 error_amp_attrs('undefined-condition', User, Packet) ->
96 30 OriginalSender = jid:to_binary(User),
97 30 OriginalRecipient = exml_query:attr(Packet, <<"to">>),
98 30 [{<<"status">>, <<"error">>},
99 {<<"to">>, OriginalRecipient},
100 {<<"from">>, OriginalSender}];
101 3 error_amp_attrs(_, _, _) -> [].
102
103
104 %% The lists are guaranteed to be non-empty and of equal
105 %% length by make_error_message/4
106 -spec make_error_el([amp_error()], [amp_any_rule()]) -> #xmlel{}.
107 make_error_el(Errors, Rules) ->
108 33 ErrorMarker = #xmlel{name = error_marker_name(hd(Errors)),
109 attrs = [{<<"xmlns">>, ?NS_STANZAS}]},
110 33 RuleContainer = #xmlel{name = rule_container_name(hd(Errors)),
111 attrs = [{<<"xmlns">>, ?NS_AMP}],
112 34 children = [ rule_to_xmlel(R) || R <- Rules ]},
113 33 #xmlel{name = <<"error">>,
114 attrs = [{<<"type">>, <<"modify">>},
115 {<<"code">>, error_code(hd(Errors))}],
116 children = [ErrorMarker, RuleContainer]}.
117
118 -spec rule_to_xmlel(amp_any_rule()) -> #xmlel{}.
119 rule_to_xmlel(#amp_rule{condition=C, value=V, action=A}) ->
120 141 #xmlel{name = <<"rule">>,
121 attrs = [{<<"condition">>, to_bin_(C)},
122 {<<"value">>, to_bin_(V)},
123 {<<"action">>, to_bin_(A)}]};
124 rule_to_xmlel(#amp_invalid_rule{condition=C, value=V, action=A}) ->
125 4 #xmlel{name = <<"rule">>,
126 attrs = [{<<"condition">>, C},
127 {<<"value">>, V},
128 {<<"action">>, A}]}.
129
130 -spec strip_amp_el(#xmlel{}) -> #xmlel{}.
131 strip_amp_el(#xmlel{children = Children} = Elem) ->
132 209 NewChildren = [ C || C <- Children, not is_amp_el(C) ],
133 209 Elem#xmlel{children = NewChildren}.
134
135
136 %% Internal
137 %% @doc We want to keep `client->server' AMPed messages,
138 %% but filter out `server->client' AMPed responses.
139 %% We can distinguish them by the fact that s2c messages MUST have
140 %% a `status' attr on the `<amp>' element.
141 %% @end
142 -spec is_amp_request(exml:element()) -> boolean().
143 is_amp_request(Stanza) ->
144 3733 Amp = exml_query:subelement(Stanza, <<"amp">>),
145 3733 (undefined =/= Amp)
146 andalso
147 272 (undefined == exml_query:attr(Amp, <<"status">>))
148 andalso
149 272 (undefined == exml_query:subelement(Stanza, <<"error">>)).
150
151 -spec is_amp_el(#xmlel{}) -> boolean().
152 209 is_amp_el(#xmlel{name = <<"amp">>}) -> true;
153 209 is_amp_el(_) -> false.
154
155 -spec parse_rules(#xmlel{}) -> none
156 | {rules, amp_rules()}
157 | {errors, [{amp_error(), amp_rule()}]}.
158 parse_rules(Stanza) ->
159 272 Amp = exml_query:subelement(Stanza, <<"amp">>),
160 272 RuleElems = exml_query:subelements(Amp, <<"rule">>),
161 272 MaybeRules = [ parse_rule(R) || R <- RuleElems ],
162 272 case lists:partition(fun is_valid_rule/1, MaybeRules) of
163
:-(
{[], []} -> none;
164 271 {Valid, []} -> {rules, Valid};
165 1 {_, Invalid} -> {errors, [ {'not-acceptable', R} || R <- Invalid ]}
166 end.
167
168 -spec parse_rule(#xmlel{}) -> amp_rule() | amp_invalid_rule().
169 parse_rule(#xmlel{attrs = Attrs}) ->
170 407 GetF = fun(Value) -> proplists:get_value(Value, Attrs, <<"attribute-missing">>) end,
171 407 {C, V, A} = {GetF(<<"condition">>),
172 GetF(<<"value">>),
173 GetF(<<"action">>)},
174 407 binaries_to_rule(C, V, A).
175
176 -spec is_valid_rule(amp_rule() | amp_invalid_rule()) -> boolean().
177 405 is_valid_rule(#amp_rule{}) -> true;
178 2 is_valid_rule(#amp_invalid_rule{}) -> false.
179
180 is_valid_action(Action) ->
181 405 lists:member(Action, ?AMP_LEGAL_ACTIONS).
182
183 are_valid_deliver_params(Value, Action) ->
184 400 lists:member(Value, ?AMP_LEGAL_DELIVER_VALUES) andalso
185 400 is_valid_action(Action).
186
187 are_valid_match_resource_params(Value, Action) ->
188 4 lists:member(Value, ?AMP_LEGAL_MATCH_RESOURCE_VALUES) andalso
189 4 is_valid_action(Action).
190
191 are_valid_expire_at_params(_Value, Action) ->
192 %% We may check the value with a regexp for a proper date in the future.
193 1 is_valid_action(Action).
194
195 mk_amp_rule(C, V, A) ->
196 405 #amp_rule{condition = C, value = V, action = A}.
197 mk_amp_invalid_rule(C, V, A) ->
198 2 #amp_invalid_rule{condition = C, value = V, action = A}.
199
200 1 error_code('not-acceptable') -> <<"405">>;
201 30 error_code('undefined-condition') -> <<"500">>;
202 2 error_code(_) -> <<"400">>.
203
204 1 error_marker_name('not-acceptable') -> <<"not-acceptable">>;
205 1 error_marker_name('unsupported-actions') -> <<"bad-request">>;
206 1 error_marker_name('unsupported-conditions') -> <<"bad-request">>;
207 30 error_marker_name('undefined-condition') -> <<"undefined-condition">>.
208
209 1 rule_container_name('not-acceptable') -> <<"invalid-rules">>;
210 1 rule_container_name('unsupported-actions') -> <<"unsupported-actions">>;
211 1 rule_container_name('unsupported-conditions') -> <<"unsupported-conditions">>;
212 30 rule_container_name('undefined-condition') -> <<"failed-rules">>.
213
214 -spec to_bin_(amp_action() | amp_condition() | amp_value() | amp_error()) ->
215 binary().
216 498 to_bin_(A) when is_atom(A) -> atom_to_binary(A, utf8);
217 2 to_bin_(X) when is_binary(X) -> X.
218
219 -spec from_bin_(binary()) -> amp_action() | amp_condition() |
220 amp_value() | amp_error().
221 %% @doc WARNING! This is a partial function. Only values that have been
222 %% verified as legal in binaries_to_rule/3 should be passed here.
223 %% DO NOT EXPORT!!!
224 %% conditons
225
:-(
from_bin_(<<"deliver">>) -> 'deliver';
226
:-(
from_bin_(<<"expire-at">>) -> 'expire-at';
227
:-(
from_bin_(<<"match-resource">>) -> 'match-resource';
228 %% non-date values
229 134 from_bin_(<<"direct">>) -> 'direct';
230
:-(
from_bin_(<<"forward">>) -> 'forward';
231
:-(
from_bin_(<<"gateway">>) -> 'gateway';
232 133 from_bin_(<<"none">>) -> 'none';
233 133 from_bin_(<<"stored">>) -> 'stored';
234 1 from_bin_(<<"any">>) -> 'any';
235 1 from_bin_(<<"exact">>) -> 'exact';
236 2 from_bin_(<<"other">>) -> 'other';
237 %% actions
238 1 from_bin_(<<"alert">>) -> 'alert';
239 90 from_bin_(<<"drop">>) -> 'drop';
240 92 from_bin_(<<"error">>) -> 'error';
241 222 from_bin_(<<"notify">>) -> 'notify';
242 %% amp error types
243
:-(
from_bin_(<<"unsupported-actions">>) -> 'unsupported-actions';
244
:-(
from_bin_(<<"unsupported-conditions">>) -> 'unsupported-conditions'.
Line Hits Source