./ct_report/coverage/jlib.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : jlib.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : General XMPP library.
5 %%% Created : 23 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2011 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
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 %%%
24 %%%----------------------------------------------------------------------
25
26 -module(jlib).
27 -author('alexey@process-one.net').
28 -xep([{xep, 59}, {version, "1.0"}]).
29 -xep([{xep, 86}, {version, "1.0"}]).
30 -export([make_result_iq_reply/1,
31 make_error_reply/2,
32 make_error_reply/3,
33 make_invitation/3,
34 make_config_change_message/1,
35 replace_from_to_attrs/3,
36 replace_from_to/3,
37 remove_attr/2,
38 iq_query_info/1,
39 iq_query_or_response_info/1,
40 iq_to_xml/1,
41 timestamp_to_xml/3,
42 decode_base64/1,
43 encode_base64/1,
44 rsm_encode/1,
45 rsm_decode/1,
46 stanza_error/3,
47 stanza_error/5,
48 stanza_errort/5,
49 stream_error/1,
50 stream_errort/3,
51 maybe_append_delay/4,
52 remove_delay_tags/1]).
53
54 -ignore_xref([make_result_iq_reply/1]).
55
56 -include_lib("exml/include/exml.hrl").
57 -include_lib("exml/include/exml_stream.hrl"). % only used to define stream types
58 -include("jlib.hrl").
59 -include("mongoose.hrl").
60 -include("mongoose_rsm.hrl").
61
62 %% Stream types defined in exml/include/exml_stream.hrl
63 -type xmlstreamstart() :: #xmlstreamstart{}.
64 -type xmlstreamend() :: #xmlstreamend{}.
65 -type xmlstreamel() :: exml:element() | xmlstreamstart() | xmlstreamend().
66
67 -type xmlcdata() :: #xmlcdata{}.
68
69 -type xmlch() :: exml:element() | xmlcdata(). % (XML ch)ild
70
71 -type binary_pair() :: {binary(), binary()}.
72
73 -type iq() :: #iq{}.
74
75 -type rsm_in() :: #rsm_in{}.
76 -type rsm_out() :: #rsm_out{}.
77
78 %% Copied from calendar:rfc3339_string() (because it is not exported)
79 -type rfc3339_string() :: [byte(), ...].
80
81 -export_type([xmlstreamstart/0, xmlstreamend/0, xmlstreamel/0,
82 binary_pair/0,
83 rsm_in/0, rsm_out/0,
84 xmlcdata/0,
85 xmlch/0,
86 iq/0,
87 rfc3339_string/0]).
88
89 -spec make_result_iq_reply(exml:element()) -> exml:element();
90 (iq()) -> iq().
91 make_result_iq_reply(XE = #xmlel{attrs = Attrs}) ->
92
:-(
NewAttrs = make_result_iq_reply_attrs(Attrs),
93
:-(
XE#xmlel{attrs = NewAttrs};
94 make_result_iq_reply(IQ = #iq{}) ->
95 1 IQ#iq{ type = result }.
96
97
98 -spec make_result_iq_reply_attrs([binary_pair()]) -> [binary_pair(), ...].
99 make_result_iq_reply_attrs(Attrs) ->
100
:-(
To = xml:get_attr(<<"to">>, Attrs),
101
:-(
From = xml:get_attr(<<"from">>, Attrs),
102
:-(
Attrs1 = lists:keydelete(<<"to">>, 1, Attrs),
103
:-(
Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1),
104
:-(
Attrs3 = case To of
105 {value, ToVal} ->
106
:-(
[{<<"from">>, ToVal} | Attrs2];
107 _ ->
108
:-(
Attrs2
109 end,
110
:-(
Attrs4 = case From of
111 {value, FromVal} ->
112
:-(
[{<<"to">>, FromVal} | Attrs3];
113 _ ->
114
:-(
Attrs3
115 end,
116
:-(
Attrs5 = lists:keydelete(<<"type">>, 1, Attrs4),
117
:-(
[{<<"type">>, <<"result">>} | Attrs5].
118
119
120 -spec make_error_reply(exml:element() | mongoose_acc:t(),
121 xmlcdata() | exml:element()) ->
122 exml:element() | {mongoose_acc:t(), exml:element() | {error, {already_an_error, _, _}}}.
123 make_error_reply(#xmlel{name = Name, attrs = Attrs,
124 children = SubTags}, Error) ->
125 363 NewAttrs = make_error_reply_attrs(Attrs),
126 363 #xmlel{name = Name, attrs = NewAttrs, children = SubTags ++ [Error]};
127 make_error_reply(Acc, Error) ->
128 47 make_error_reply(Acc, mongoose_acc:element(Acc), Error).
129
130 make_error_reply(Acc, Packet, Error) ->
131 257 case mongoose_acc:get(flag, error, false, Acc) of
132 true ->
133
:-(
?LOG_ERROR(#{what => error_reply_to_error, exml_packet => Packet,
134
:-(
reason => Error}),
135
:-(
{Acc, {error, {already_an_error, Packet, Error}}};
136 _ ->
137 257 {mongoose_acc:set(flag, error, true, Acc),
138 make_error_reply(Packet, Error)}
139 end.
140
141 -spec make_error_reply_attrs([binary_pair()]) -> [binary_pair(), ...].
142 make_error_reply_attrs(Attrs) ->
143 363 To = xml:get_attr(<<"to">>, Attrs),
144 363 From = xml:get_attr(<<"from">>, Attrs),
145 363 Attrs1 = lists:keydelete(<<"to">>, 1, Attrs),
146 363 Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1),
147 363 Attrs3 = case To of
148 {value, ToVal} ->
149 340 [{<<"from">>, ToVal} | Attrs2];
150 _ ->
151 23 Attrs2
152 end,
153 363 Attrs4 = case From of
154 {value, FromVal} ->
155 125 [{<<"to">>, FromVal} | Attrs3];
156 _ ->
157 238 Attrs3
158 end,
159 363 Attrs5 = lists:keydelete(<<"type">>, 1, Attrs4),
160 363 Attrs6 = [{<<"type">>, <<"error">>} | Attrs5],
161 363 Attrs6.
162
163
164 -spec make_config_change_message(binary()) -> exml:element().
165 make_config_change_message(Status) ->
166 4 #xmlel{name = <<"message">>, attrs = [{<<"type">>, <<"groupchat">>}],
167 children = [#xmlel{name = <<"x">>,
168 attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
169 children = [#xmlel{name = <<"status">>,
170 attrs = [{<<"code">>, Status}]}]}]}.
171
172
173 -spec make_invitation(From :: jid:jid(), Password :: binary(),
174 Reason :: binary()) -> exml:element().
175 make_invitation(From, Password, Reason) ->
176 7 Children = case Reason of
177 6 <<>> -> [];
178 1 _ -> [#xmlel{name = <<"reason">>,
179 children = [#xmlcdata{content = Reason}]}]
180 end,
181 7 Elements = [#xmlel{name = <<"invite">>,
182 attrs = [{<<"from">>, jid:to_binary(From)}],
183 children = Children}],
184
185 7 Elements2 = case Password of
186 7 <<>> -> Elements;
187
:-(
_ -> [#xmlel{name = <<"password">>,
188 children = [#xmlcdata{content = Password}]} | Elements]
189 end,
190
191 7 #xmlel{name = <<"message">>,
192 children = [#xmlel{name = <<"x">>,
193 attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
194 children = Elements2}]}.
195
196 -spec replace_from_to_attrs(From :: binary(),
197 To :: binary() | undefined,
198 [binary_pair()]) -> [binary_pair()].
199 replace_from_to_attrs(From, To, Attrs) ->
200 38882 Attrs1 = lists:keydelete(<<"to">>, 1, Attrs),
201 38882 Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1),
202 38882 Attrs3 = case To of
203 1634 undefined -> Attrs2;
204 37248 _ -> [{<<"to">>, To} | Attrs2]
205 end,
206 38882 Attrs4 = [{<<"from">>, From} | Attrs3],
207 38882 Attrs4.
208
209
210 -spec replace_from_to(From :: jid:simple_jid() | jid:jid(),
211 To :: jid:simple_jid() | jid:jid(),
212 XE :: exml:element()) -> exml:element().
213 replace_from_to(From, To, XE = #xmlel{attrs = Attrs}) ->
214 37248 NewAttrs = replace_from_to_attrs(jid:to_binary(From),
215 jid:to_binary(To),
216 Attrs),
217 37248 XE#xmlel{attrs = NewAttrs}.
218
219
220 -spec remove_attr(binary(), exml:element()) -> exml:element().
221 remove_attr(Attr, XE = #xmlel{attrs = Attrs}) ->
222
:-(
NewAttrs = lists:keydelete(Attr, 1, Attrs),
223
:-(
XE#xmlel{attrs = NewAttrs}.
224
225 -spec iq_query_info(exml:element()) -> 'invalid' | 'not_iq' | 'reply' | iq().
226 iq_query_info(El) ->
227 12260 iq_info_internal(El, request).
228
229
230 -spec iq_query_or_response_info(exml:element()) ->
231 'invalid' | 'not_iq' | 'reply' | iq().
232 iq_query_or_response_info(El) ->
233 25999 iq_info_internal(El, any).
234
235 -spec make_reply_from_type(binary()) -> {atom(), atom()}.
236 make_reply_from_type(<<"set">>) ->
237 25906 {set, request};
238 make_reply_from_type(<<"get">>) ->
239 1583 {get, request};
240 make_reply_from_type(<<"result">>) ->
241 10435 {result, reply};
242 make_reply_from_type(<<"error">>) ->
243 335 {error, reply};
244 make_reply_from_type(_) ->
245
:-(
{invalid, invalid}.
246
247 -spec extract_xmlns([exml:element()]) -> binary().
248 extract_xmlns([Element]) ->
249 9624 xml:get_tag_attr_s(<<"xmlns">>, Element);
250 extract_xmlns(_) ->
251 1146 <<>>.
252
253 -spec iq_info_internal(exml:element(), Filter :: 'any' | 'request') ->
254 'invalid' | 'not_iq' | 'reply' | iq().
255 iq_info_internal(#xmlel{name = Name, attrs = Attrs,
256 children = Els}, Filter) when Name == <<"iq">> ->
257 %% Filter is either request or any. If it is request, any replies
258 %% are converted to the atom reply.
259 38259 ID = xml:get_attr_s(<<"id">>, Attrs),
260 38259 Type = xml:get_attr_s(<<"type">>, Attrs),
261 38259 Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
262 38259 {Type1, Class} = make_reply_from_type(Type),
263 38259 case {Type1, Class, Filter} of
264 {invalid, _, _} ->
265
:-(
invalid;
266 {_, Class, Filter} when Class == request; Filter == any ->
267 %% The iq record is a bit strange. The sub_el field is an
268 %% XML tuple for requests, but a list of XML tuples for
269 %% responses.
270 38259 FilteredEls = xml:remove_cdata(Els),
271 38259 {XMLNS, SubEl} =
272 case {Class, FilteredEls} of
273 {request, [#xmlel{attrs = Attrs2}]} ->
274 27489 {xml:get_attr_s(<<"xmlns">>, Attrs2),
275 hd(FilteredEls)};
276 {reply, _} ->
277 %% Find the namespace of the first non-error
278 %% element, if there is one.
279 10770 NonErrorEls = [El ||
280 #xmlel{name = SubName} = El
281 10770 <- FilteredEls,
282 9955 SubName /= <<"error">>],
283 10770 {extract_xmlns(NonErrorEls), FilteredEls};
284 _ ->
285
:-(
{<<>>, []}
286 end,
287 38259 case {XMLNS, Class} of
288 {<<>>, request} ->
289
:-(
invalid;
290 _ ->
291 38259 #iq{id = ID,
292 type = Type1,
293 xmlns = XMLNS,
294 lang = Lang,
295 sub_el = SubEl}
296 end;
297 {_, reply, _} ->
298
:-(
reply
299 end;
300 iq_info_internal(_, _) ->
301
:-(
not_iq.
302
303 -spec iq_type_to_binary(set|get|result|error) -> invalid | binary().
304 861 iq_type_to_binary(set) -> <<"set">>;
305 10 iq_type_to_binary(get) -> <<"get">>;
306 15703 iq_type_to_binary(result) -> <<"result">>;
307 283 iq_type_to_binary(error) -> <<"error">>;
308
:-(
iq_type_to_binary(_) -> invalid.
309
310 -spec iq_to_xml(iq()) -> exml:element().
311 iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) when ID /= "" ->
312 16857 #xmlel{name = <<"iq">>,
313 attrs = [{<<"id">>, ID}, {<<"type">>, iq_type_to_binary(Type)}],
314 children = sub_el_to_els(SubEl)};
315 iq_to_xml(#iq{type = Type, sub_el = SubEl}) ->
316
:-(
#xmlel{name = <<"iq">>,
317 attrs = [{<<"type">>, iq_type_to_binary(Type)}],
318 children = sub_el_to_els(SubEl)}.
319
320 %% @doc Convert `#iq.sub_el' back to `#xmlel.children'.
321 %% @end
322 -spec sub_el_to_els([exml:element()] | exml:element()) -> [exml:element()].
323 %% for requests.
324 67 sub_el_to_els(#xmlel{}=E) -> [E];
325 %% for replies.
326 16790 sub_el_to_els(Es) when is_list(Es) -> Es.
327
328 -spec rsm_decode(exml:element() | iq()) -> none | #rsm_in{}.
329 rsm_decode(#iq{sub_el = SubEl})->
330 65 rsm_decode(SubEl);
331 rsm_decode(#xmlel{}=SubEl) ->
332 2603 case xml:get_subtag(SubEl, <<"set">>) of
333 false ->
334 1833 none;
335 #xmlel{name = <<"set">>, children = SubEls} ->
336 770 lists:foldl(fun rsm_parse_element/2, #rsm_in{}, SubEls)
337 end.
338
339 -spec rsm_parse_element(exml:element(), rsm_in()) -> rsm_in().
340 rsm_parse_element(#xmlel{name = <<"max">>, attrs = []} = Elem, RsmIn) ->
341 754 CountStr = xml:get_tag_cdata(Elem),
342 754 {Count, _} = string:to_integer(binary_to_list(CountStr)),
343 754 RsmIn#rsm_in{max = Count};
344 rsm_parse_element(#xmlel{name = <<"before">>, attrs = []} = Elem, RsmIn) ->
345 322 UID = xml:get_tag_cdata(Elem),
346 322 RsmIn#rsm_in{direction = before, id = UID};
347 rsm_parse_element(#xmlel{name = <<"after">>, attrs = []} = Elem, RsmIn) ->
348 262 UID = xml:get_tag_cdata(Elem),
349 262 RsmIn#rsm_in{direction = aft, id = UID};
350 rsm_parse_element(#xmlel{name = <<"index">>, attrs = []} = Elem, RsmIn) ->
351 52 IndexStr = xml:get_tag_cdata(Elem),
352 52 {Index, _} = string:to_integer(binary_to_list(IndexStr)),
353 52 RsmIn#rsm_in{index = Index};
354 rsm_parse_element(_, RsmIn)->
355
:-(
RsmIn.
356
357 -spec rsm_encode(none | rsm_out()) -> [exml:element()].
358 rsm_encode(none) ->
359 6 [];
360 rsm_encode(RsmOut) ->
361 48 [#xmlel{name = <<"set">>, attrs = [{<<"xmlns">>, ?NS_RSM}],
362 children = lists:reverse(rsm_encode_out(RsmOut))}].
363
364 -spec rsm_encode_out(rsm_out()) -> [exml:element()].
365 rsm_encode_out(#rsm_out{count = Count, index = Index, first = First, last = Last})->
366 48 El = rsm_encode_first(First, Index, []),
367 48 El2 = rsm_encode_last(Last, El),
368 48 rsm_encode_count(Count, El2).
369
370 -spec rsm_encode_first(First :: undefined | binary(),
371 Index :: 'undefined' | integer(),
372 Arr::[exml:element()]) -> [exml:element()].
373 rsm_encode_first(undefined, undefined, Arr) ->
374 12 Arr;
375 rsm_encode_first(First, undefined, Arr) ->
376 9 [#xmlel{name = <<"first">>, children = [#xmlcdata{content = First}]} | Arr];
377 rsm_encode_first(First, Index, Arr) ->
378 27 [#xmlel{name = <<"first">>, attrs = [{<<"index">>, i2b(Index)}],
379 children = [#xmlcdata{content = First}]}|Arr].
380
381 -spec rsm_encode_last(Last :: 'undefined', Arr :: [exml:element()]) -> [exml:element()].
382 12 rsm_encode_last(undefined, Arr) -> Arr;
383 rsm_encode_last(Last, Arr) ->
384 36 [#xmlel{name = <<"last">>, children = [#xmlcdata{content = Last}]} | Arr].
385
386 -spec rsm_encode_count(Count :: 'undefined' | pos_integer(),
387 Arr :: [exml:element()]) -> [exml:element()].
388
:-(
rsm_encode_count(undefined, Arr) -> Arr;
389 rsm_encode_count(Count, Arr) ->
390 48 [#xmlel{name = <<"count">>, children = [#xmlcdata{content = i2b(Count)}]} | Arr].
391
392 -spec i2b(integer()) -> binary().
393 75 i2b(I) when is_integer(I) -> list_to_binary(integer_to_list(I)).
394
395 -spec timestamp_to_xml(TimestampString :: rfc3339_string(),
396 FromJID :: jid:simple_jid() | jid:jid() | undefined,
397 Desc :: iodata() | undefined) -> exml:element().
398 timestamp_to_xml(TimestampString, FromJID, Desc) ->
399 11324 Text = case Desc of
400 963 undefined -> [];
401 10361 _ -> [#xmlcdata{content = Desc}]
402 end,
403 11324 From = case FromJID of
404 963 undefined -> [];
405 10361 _ -> [{<<"from">>, jid:to_binary(FromJID)}]
406 end,
407 11324 #xmlel{name = <<"delay">>,
408 attrs = [{<<"xmlns">>, ?NS_DELAY},
409 {<<"stamp">>, list_to_binary(TimestampString)} | From],
410 children = Text}.
411
412 -spec decode_base64(binary() | string()) -> binary().
413 decode_base64(S) ->
414
:-(
base64:mime_decode(S).
415
416 -spec encode_base64(binary() | string()) -> binary().
417 encode_base64(B) ->
418 160 base64:encode(B).
419
420 -spec stanza_error( Code :: binary()
421 , Type :: binary()
422 , Condition :: binary()
423 , SpecTag :: binary()
424 , SpecNs :: binary() | undefined) -> exml:element().
425 stanza_error(Code, Type, Condition, SpecTag, SpecNs) ->
426 4 Er = stanza_error(Code, Type, Condition),
427 4 Spec = #xmlel{ name = SpecTag, attrs = [{<<"xmlns">>, SpecNs}]},
428 4 NCh = [Spec | Er#xmlel.children],
429 4 Er#xmlel{children = NCh}.
430
431 %% TODO: remove `code' attribute (currently it used for backward-compatibility)
432
433 -spec stanza_error( Code :: binary()
434 , Type :: binary()
435 , Condition :: binary() | undefined) -> exml:element().
436 stanza_error(Code, Type, Condition) ->
437 332 #xmlel{ name = <<"error">>
438 , attrs = [{<<"code">>, Code}, {<<"type">>, Type}]
439 , children = [ #xmlel{ name = Condition
440 , attrs = [{<<"xmlns">>, ?NS_STANZAS}]
441 }]
442 }.
443
444 -spec stanza_errort( Code :: binary()
445 , Type :: binary()
446 , Condition :: binary()
447 , Lang :: ejabberd:lang()
448 , Text :: binary()) -> exml:element().
449 stanza_errort(Code, Type, Condition, Lang, Text) ->
450 648 Txt = translate:translate(Lang, Text),
451 648 #xmlel{ name = <<"error">>
452 , attrs = [{<<"code">>, Code}, {<<"type">>, Type}]
453 , children = [ #xmlel{ name = Condition
454 , attrs = [{<<"xmlns">>, ?NS_STANZAS}]
455 }
456 , #xmlel{ name = <<"text">>
457 , attrs = [{<<"xmlns">>, ?NS_STANZAS}]
458 , children = [#xmlcdata{ content = Txt }]
459 }]
460 }.
461
462 -spec stream_error(Condition :: binary()) -> exml:element().
463 stream_error(Condition) ->
464 37 #xmlel{ name = <<"stream:error">>
465 , children = [ #xmlel{ name = Condition
466 , attrs = [{<<"xmlns">>, ?NS_STREAMS}]
467 }
468 ]
469 }.
470
471 -spec stream_errort( Condition :: binary()
472 , Lang :: ejabberd:lang()
473 , Text :: binary()) -> exml:element().
474 stream_errort(Condition, Lang, Text) ->
475 89 Txt = translate:translate(Lang, Text),
476 89 #xmlel{ name = <<"stream:error">>
477 , children = [ #xmlel{ name = Condition
478 , attrs = [{<<"xmlns">>, ?NS_STREAMS}] }
479 , #xmlel{ name = <<"text">>
480 , attrs = [ {<<"xml:lang">>, Lang}
481 , {<<"xmlns">>, ?NS_STREAMS}]
482 , children = [ #xmlcdata{ content = Txt} ]}
483 ]
484 }.
485
486 -spec maybe_append_delay(Packet :: exml:element(),
487 From :: jid:jid(),
488 TS :: integer(),
489 Desc :: undefined | iodata()) -> exml:element().
490 maybe_append_delay(Packet = #xmlel{children = Children}, From, TS, Desc) ->
491 5331 case exml_query:path(Packet, [{element, <<"delay">>}]) of
492 undefined ->
493 5331 TsString = calendar:system_time_to_rfc3339(TS, [{offset, "Z"}, {unit, microsecond}]),
494 5331 DelayTag = jlib:timestamp_to_xml(TsString, From, Desc),
495 5331 Packet#xmlel{children = [DelayTag | Children]};
496 _ ->
497
:-(
Packet
498 end.
499
500 remove_delay_tags(#xmlel{children = Els} = Packet) ->
501 168 NEl = lists:foldl(
502 fun(#xmlel{name= <<"delay">>, attrs = Attrs} = R, El)->
503
:-(
case xml:get_attr_s(<<"xmlns">>, Attrs) of
504 ?NS_DELAY ->
505
:-(
El;
506 _ ->
507
:-(
El ++ [R]
508 end;
509 (#xmlel{name= <<"x">>, attrs = Attrs } = R, El) ->
510 3 case xml:get_attr_s(<<"xmlns">>, Attrs) of
511 ?NS_DELAY91 ->
512
:-(
El;
513 _ ->
514 3 El ++ [R]
515 end;
516 (R, El) ->
517 171 El ++ [R]
518 end, [], Els),
519 168 Packet#xmlel{children=NEl}.
Line Hits Source