mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-26 16:26:24 +01:00
1004 lines
30 KiB
Erlang
1004 lines
30 KiB
Erlang
%%%----------------------------------------------------------------------
|
|
%%% File : jlib.erl
|
|
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
|
%%% Purpose : General XMPP library.
|
|
%%% Created : 23 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
|
|
%%%
|
|
%%%
|
|
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
|
|
%%%
|
|
%%% This program is free software; you can redistribute it and/or
|
|
%%% modify it under the terms of the GNU General Public License as
|
|
%%% published by the Free Software Foundation; either version 2 of the
|
|
%%% License, or (at your option) any later version.
|
|
%%%
|
|
%%% This program is distributed in the hope that it will be useful,
|
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
%%% General Public License for more details.
|
|
%%%
|
|
%%% You should have received a copy of the GNU General Public License along
|
|
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
|
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
%%%
|
|
%%%----------------------------------------------------------------------
|
|
|
|
-module(jlib).
|
|
|
|
-author('alexey@process-one.net').
|
|
|
|
-protocol({xep, 59, '1.0'}).
|
|
-protocol({xep, 82, '1.1'}).
|
|
-protocol({xep, 203, '2.0'}).
|
|
|
|
-compile({no_auto_import, [atom_to_binary/2,
|
|
binary_to_integer/1,
|
|
integer_to_binary/1]}).
|
|
|
|
%% The following functions are deprected: use functions from misc.erl
|
|
-export([tolower/1, term_to_base64/1, base64_to_term/1,
|
|
decode_base64/1, encode_base64/1, ip_to_list/1,
|
|
hex_to_bin/1, hex_to_base64/1, expand_keyword/3,
|
|
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
|
|
l2i/1, i2l/1, i2l/2, expr_to_term/1, term_to_expr/1]).
|
|
|
|
%% The following functions are used by gen_iq_handler.erl for providing backward
|
|
%% compatibility and must not be used in other parts of the code
|
|
%% Use xmpp:decode() and xmpp:encode() instead
|
|
-export([iq_query_info/1, iq_to_xml/1]).
|
|
|
|
%% The following functions are deprecated and will be removed soon
|
|
%% Use functions from xmpp.erl and xmpp_util.erl instead
|
|
-export([make_result_iq_reply/1, make_error_reply/3,
|
|
make_error_reply/2, make_error_element/2,
|
|
make_correct_from_to_attrs/3, replace_from_to_attrs/3,
|
|
replace_from_to/3, replace_from_attrs/2, replace_from/2,
|
|
remove_attr/2, get_iq_namespace/1,
|
|
iq_query_or_response_info/1, is_iq_request_type/1,
|
|
parse_xdata_submit/1, unwrap_carbon/1, is_standalone_chat_state/1,
|
|
add_delay_info/3, add_delay_info/4,
|
|
timestamp_to_legacy/1, timestamp_to_iso_basic/1, timestamp_to_iso/2,
|
|
now_to_utc_string/1, now_to_local_string/1,
|
|
datetime_string_to_timestamp/1,
|
|
rsm_encode/1, rsm_encode/2, rsm_decode/1,
|
|
binary_to_integer/1, binary_to_integer/2,
|
|
integer_to_binary/1, integer_to_binary/2]).
|
|
|
|
%% The following functions are deprecated and will be removed soon
|
|
%% Use corresponding functions from jid.erl instead
|
|
-export([make_jid/3, make_jid/1, split_jid/1, string_to_jid/1,
|
|
jid_to_string/1, is_nodename/1, nodeprep/1,
|
|
nameprep/1, resourceprep/1, jid_tolower/1,
|
|
jid_remove_resource/1, jid_replace_resource/2]).
|
|
|
|
-deprecated([{make_jid, '_'},
|
|
{split_jid, 1},
|
|
{string_to_jid, 1},
|
|
{jid_to_string, 1},
|
|
{is_nodename, 1},
|
|
{nodeprep, 1},
|
|
{nameprep, 1},
|
|
{resourceprep, 1},
|
|
{jid_tolower, 1},
|
|
{jid_remove_resource, 1},
|
|
{jid_replace_resource, 2},
|
|
{add_delay_info, 3},
|
|
{add_delay_info, 4},
|
|
{make_result_iq_reply, 1},
|
|
{make_error_reply, 3},
|
|
{make_error_reply, 2},
|
|
{make_error_element, 2},
|
|
{make_correct_from_to_attrs, 3},
|
|
{replace_from_to_attrs, 3},
|
|
{replace_from_to, 3},
|
|
{replace_from_attrs, 2},
|
|
{replace_from, 2},
|
|
{remove_attr, 2},
|
|
{get_iq_namespace, 1},
|
|
{iq_query_or_response_info, 1},
|
|
{is_iq_request_type, 1},
|
|
{parse_xdata_submit, 1},
|
|
{unwrap_carbon, 1},
|
|
{is_standalone_chat_state, 1},
|
|
{timestamp_to_legacy, 1},
|
|
{timestamp_to_iso_basic, 1},
|
|
{timestamp_to_iso, 2},
|
|
{now_to_utc_string, 1},
|
|
{now_to_local_string, 1},
|
|
{datetime_string_to_timestamp, 1},
|
|
{rsm_encode, 1},
|
|
{rsm_encode, 2},
|
|
{rsm_decode, 1},
|
|
{binary_to_integer, 1},
|
|
{binary_to_integer, 2},
|
|
{integer_to_binary, 1},
|
|
{integer_to_binary, 2},
|
|
{tolower, 1},
|
|
{term_to_base64, 1},
|
|
{base64_to_term, 1},
|
|
{decode_base64, 1},
|
|
{encode_base64, 1},
|
|
{ip_to_list, 1},
|
|
{hex_to_bin, 1},
|
|
{hex_to_base64, 1},
|
|
{expand_keyword, 3},
|
|
{atom_to_binary, 1},
|
|
{binary_to_atom, 1},
|
|
{tuple_to_binary, 1},
|
|
{l2i, 1},
|
|
{i2l, 1},
|
|
{i2l, 2},
|
|
{expr_to_term, 1},
|
|
{term_to_expr, 1}]).
|
|
|
|
-include("ejabberd.hrl").
|
|
-include("jlib.hrl").
|
|
|
|
%send_iq(From, To, ID, SubTags) ->
|
|
% ok.
|
|
|
|
-spec make_result_iq_reply(xmlel()) -> xmlel().
|
|
|
|
make_result_iq_reply(#xmlel{name = Name, attrs = Attrs,
|
|
children = SubTags}) ->
|
|
NewAttrs = make_result_iq_reply_attrs(Attrs),
|
|
#xmlel{name = Name, attrs = NewAttrs,
|
|
children = SubTags}.
|
|
|
|
-spec make_result_iq_reply_attrs([attr()]) -> [attr()].
|
|
|
|
make_result_iq_reply_attrs(Attrs) ->
|
|
To = fxml:get_attr(<<"to">>, Attrs),
|
|
From = fxml:get_attr(<<"from">>, Attrs),
|
|
Attrs1 = lists:keydelete(<<"to">>, 1, Attrs),
|
|
Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1),
|
|
Attrs3 = case To of
|
|
{value, ToVal} -> [{<<"from">>, ToVal} | Attrs2];
|
|
_ -> Attrs2
|
|
end,
|
|
Attrs4 = case From of
|
|
{value, FromVal} -> [{<<"to">>, FromVal} | Attrs3];
|
|
_ -> Attrs3
|
|
end,
|
|
Attrs5 = lists:keydelete(<<"type">>, 1, Attrs4),
|
|
Attrs6 = [{<<"type">>, <<"result">>} | Attrs5],
|
|
Attrs6.
|
|
|
|
-spec make_error_reply(xmlel(), binary(), binary()) -> xmlel().
|
|
|
|
make_error_reply(#xmlel{name = Name, attrs = Attrs,
|
|
children = SubTags},
|
|
Code, Desc) ->
|
|
NewAttrs = make_error_reply_attrs(Attrs),
|
|
#xmlel{name = Name, attrs = NewAttrs,
|
|
children =
|
|
SubTags ++
|
|
[#xmlel{name = <<"error">>,
|
|
attrs = [{<<"code">>, Code}],
|
|
children = [{xmlcdata, Desc}]}]}.
|
|
|
|
-spec make_error_reply(xmlel(), xmlel()) -> xmlel().
|
|
|
|
make_error_reply(#xmlel{name = Name, attrs = Attrs,
|
|
children = SubTags},
|
|
Error) ->
|
|
NewAttrs = make_error_reply_attrs(Attrs),
|
|
#xmlel{name = Name, attrs = NewAttrs,
|
|
children = SubTags ++ [Error]}.
|
|
|
|
-spec make_error_reply_attrs([attr()]) -> [attr()].
|
|
|
|
make_error_reply_attrs(Attrs) ->
|
|
To = fxml:get_attr(<<"to">>, Attrs),
|
|
From = fxml:get_attr(<<"from">>, Attrs),
|
|
Attrs1 = lists:keydelete(<<"to">>, 1, Attrs),
|
|
Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1),
|
|
Attrs3 = case To of
|
|
{value, ToVal} -> [{<<"from">>, ToVal} | Attrs2];
|
|
_ -> Attrs2
|
|
end,
|
|
Attrs4 = case From of
|
|
{value, FromVal} -> [{<<"to">>, FromVal} | Attrs3];
|
|
_ -> Attrs3
|
|
end,
|
|
Attrs5 = lists:keydelete(<<"type">>, 1, Attrs4),
|
|
Attrs6 = [{<<"type">>, <<"error">>} | Attrs5],
|
|
Attrs6.
|
|
|
|
-spec make_error_element(binary(), binary()) -> xmlel().
|
|
|
|
make_error_element(Code, Desc) ->
|
|
#xmlel{name = <<"error">>, attrs = [{<<"code">>, Code}],
|
|
children = [{xmlcdata, Desc}]}.
|
|
|
|
-spec make_correct_from_to_attrs(binary(), binary(), [attr()]) -> [attr()].
|
|
|
|
make_correct_from_to_attrs(From, To, Attrs) ->
|
|
Attrs1 = lists:keydelete(<<"from">>, 1, Attrs),
|
|
Attrs2 = case fxml:get_attr(<<"to">>, Attrs) of
|
|
{value, _} -> Attrs1;
|
|
_ -> [{<<"to">>, To} | Attrs1]
|
|
end,
|
|
Attrs3 = [{<<"from">>, From} | Attrs2],
|
|
Attrs3.
|
|
|
|
-spec replace_from_to_attrs(binary(), binary(), [attr()]) -> [attr()].
|
|
|
|
replace_from_to_attrs(From, To, Attrs) ->
|
|
Attrs1 = lists:keydelete(<<"to">>, 1, Attrs),
|
|
Attrs2 = lists:keydelete(<<"from">>, 1, Attrs1),
|
|
Attrs3 = [{<<"to">>, To} | Attrs2],
|
|
Attrs4 = [{<<"from">>, From} | Attrs3],
|
|
Attrs4.
|
|
|
|
-spec replace_from_to(jid(), jid(), xmlel()) -> xmlel().
|
|
|
|
replace_from_to(From, To,
|
|
#xmlel{name = Name, attrs = Attrs, children = Els}) ->
|
|
NewAttrs =
|
|
replace_from_to_attrs(jid:encode(From),
|
|
jid:encode(To), Attrs),
|
|
#xmlel{name = Name, attrs = NewAttrs, children = Els}.
|
|
|
|
-spec replace_from_attrs(binary(), [attr()]) -> [attr()].
|
|
|
|
replace_from_attrs(From, Attrs) ->
|
|
Attrs1 = lists:keydelete(<<"from">>, 1, Attrs),
|
|
[{<<"from">>, From} | Attrs1].
|
|
|
|
-spec replace_from(jid(), xmlel()) -> xmlel().
|
|
|
|
replace_from(From,
|
|
#xmlel{name = Name, attrs = Attrs, children = Els}) ->
|
|
NewAttrs = replace_from_attrs(jid:encode(From),
|
|
Attrs),
|
|
#xmlel{name = Name, attrs = NewAttrs, children = Els}.
|
|
|
|
-spec remove_attr(binary(), xmlel()) -> xmlel().
|
|
|
|
remove_attr(Attr,
|
|
#xmlel{name = Name, attrs = Attrs, children = Els}) ->
|
|
NewAttrs = lists:keydelete(Attr, 1, Attrs),
|
|
#xmlel{name = Name, attrs = NewAttrs, children = Els}.
|
|
|
|
-spec make_jid(binary(), binary(), binary()) -> jid() | error.
|
|
|
|
make_jid(User, Server, Resource) ->
|
|
jid:make(User, Server, Resource).
|
|
|
|
-spec make_jid({binary(), binary(), binary()}) -> jid() | error.
|
|
|
|
make_jid({User, Server, Resource}) ->
|
|
jid:make({User, Server, Resource}).
|
|
|
|
%% This is the reverse of make_jid/1
|
|
-spec split_jid(jid()) -> {binary(), binary(), binary()} | error.
|
|
split_jid(J) ->
|
|
jid:split(J).
|
|
|
|
-spec string_to_jid(binary()) -> jid() | error.
|
|
|
|
string_to_jid(S) ->
|
|
try jid:decode(S)
|
|
catch _:_ -> error end.
|
|
|
|
-spec jid_to_string(jid() | ljid()) -> binary().
|
|
|
|
jid_to_string(J) ->
|
|
jid:encode(J).
|
|
|
|
-spec is_nodename(binary()) -> boolean().
|
|
|
|
is_nodename(Node) ->
|
|
jid:is_nodename(Node).
|
|
|
|
%tolower_c(C) when C >= $A, C =< $Z ->
|
|
% C + 32;
|
|
%tolower_c(C) ->
|
|
% C.
|
|
|
|
-define(LOWER(Char),
|
|
if Char >= $A, Char =< $Z -> Char + 32;
|
|
true -> Char
|
|
end).
|
|
|
|
%tolower(S) ->
|
|
% lists:map(fun tolower_c/1, S).
|
|
|
|
%tolower(S) ->
|
|
% [?LOWER(Char) || Char <- S].
|
|
|
|
-spec tolower(binary()) -> binary().
|
|
|
|
tolower(B) ->
|
|
iolist_to_binary(tolower_s(binary_to_list(B))).
|
|
|
|
tolower_s([C | Cs]) ->
|
|
if C >= $A, C =< $Z -> [C + 32 | tolower_s(Cs)];
|
|
true -> [C | tolower_s(Cs)]
|
|
end;
|
|
tolower_s([]) -> [].
|
|
|
|
%tolower([C | Cs]) when C >= $A, C =< $Z ->
|
|
% [C + 32 | tolower(Cs)];
|
|
%tolower([C | Cs]) ->
|
|
% [C | tolower(Cs)];
|
|
%tolower([]) ->
|
|
% [].
|
|
|
|
-spec nodeprep(binary()) -> binary() | error.
|
|
|
|
nodeprep(S) -> jid:nodeprep(S).
|
|
|
|
-spec nameprep(binary()) -> binary() | error.
|
|
|
|
nameprep(S) -> jid:nameprep(S).
|
|
|
|
-spec resourceprep(binary()) -> binary() | error.
|
|
|
|
resourceprep(S) -> jid:resourceprep(S).
|
|
|
|
-spec jid_tolower(jid() | ljid()) -> error | ljid().
|
|
|
|
jid_tolower(J) ->
|
|
jid:tolower(J).
|
|
|
|
-spec jid_remove_resource(jid()) -> jid();
|
|
(ljid()) -> ljid().
|
|
|
|
jid_remove_resource(J) -> jid:remove_resource(J).
|
|
|
|
-spec jid_replace_resource(jid(), binary()) -> error | jid().
|
|
|
|
jid_replace_resource(JID, Resource) ->
|
|
jid:replace_resource(JID, Resource).
|
|
|
|
-spec get_iq_namespace(xmlel()) -> binary().
|
|
|
|
get_iq_namespace(#xmlel{name = <<"iq">>, children = Els}) ->
|
|
case fxml:remove_cdata(Els) of
|
|
[#xmlel{attrs = Attrs}] -> fxml:get_attr_s(<<"xmlns">>, Attrs);
|
|
_ -> <<"">>
|
|
end;
|
|
get_iq_namespace(_) -> <<"">>.
|
|
|
|
%%
|
|
-spec iq_query_info(Xmlel :: xmlel()) ->
|
|
iq_request() | 'reply' | 'invalid' | 'not_iq'.
|
|
|
|
%% @spec (xmlelement()) -> iq() | reply | invalid | not_iq
|
|
iq_query_info(El) -> iq_info_internal(El, request).
|
|
|
|
%%
|
|
-spec iq_query_or_response_info(Xmlel :: xmlel()) ->
|
|
iq_request() | iq_reply() |
|
|
'reply' | 'invalid' | 'not_iq'.
|
|
|
|
iq_query_or_response_info(El) ->
|
|
iq_info_internal(El, any).
|
|
|
|
iq_info_internal(#xmlel{name = <<"iq">>, attrs = Attrs, children = Els}, Filter) ->
|
|
ID = fxml:get_attr_s(<<"id">>, Attrs),
|
|
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
|
|
{Type, Class} = case fxml:get_attr_s(<<"type">>, Attrs) of
|
|
<<"set">> -> {set, request};
|
|
<<"get">> -> {get, request};
|
|
<<"result">> -> {result, reply};
|
|
<<"error">> -> {error, reply};
|
|
_ -> {invalid, invalid}
|
|
end,
|
|
if Type == invalid -> invalid; Class == request; Filter == any ->
|
|
FilteredEls = fxml:remove_cdata(Els),
|
|
{XMLNS, SubEl} = case {Class, FilteredEls} of
|
|
{request, [#xmlel{attrs = Attrs2}]} ->
|
|
{fxml:get_attr_s(<<"xmlns">>, Attrs2), hd(FilteredEls)};
|
|
{reply, _} ->
|
|
NonErrorEls = [El || #xmlel{name = SubName} = El <- FilteredEls,
|
|
SubName /= <<"error">>],
|
|
{case NonErrorEls of
|
|
[NonErrorEl] -> fxml:get_tag_attr_s(<<"xmlns">>, NonErrorEl);
|
|
_ -> <<"">>
|
|
end,
|
|
FilteredEls};
|
|
_ ->
|
|
{<<"">>, []}
|
|
end,
|
|
if XMLNS == <<"">>, Class == request ->
|
|
invalid;
|
|
true ->
|
|
#iq{id = ID, type = Type, xmlns = XMLNS, lang = Lang, sub_el = SubEl}
|
|
end;
|
|
Class == reply, Filter /= any ->
|
|
reply
|
|
end;
|
|
iq_info_internal(_, _) -> not_iq.
|
|
|
|
-spec is_iq_request_type(set | get | result | error) -> boolean().
|
|
|
|
is_iq_request_type(set) -> true;
|
|
is_iq_request_type(get) -> true;
|
|
is_iq_request_type(_) -> false.
|
|
|
|
iq_type_to_string(set) -> <<"set">>;
|
|
iq_type_to_string(get) -> <<"get">>;
|
|
iq_type_to_string(result) -> <<"result">>;
|
|
iq_type_to_string(error) -> <<"error">>.
|
|
|
|
-spec iq_to_xml(IQ :: iq()) -> xmlel().
|
|
|
|
iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) ->
|
|
Children =
|
|
if
|
|
is_list(SubEl) -> SubEl;
|
|
true -> [SubEl]
|
|
end,
|
|
if ID /= <<"">> ->
|
|
#xmlel{name = <<"iq">>,
|
|
attrs =
|
|
[{<<"id">>, ID}, {<<"type">>, iq_type_to_string(Type)}],
|
|
children = Children};
|
|
true ->
|
|
#xmlel{name = <<"iq">>,
|
|
attrs = [{<<"type">>, iq_type_to_string(Type)}],
|
|
children = Children}
|
|
end.
|
|
|
|
-spec parse_xdata_submit(El :: xmlel()) ->
|
|
[{Var::binary(), Values::[binary()]}] | 'invalid'.
|
|
|
|
parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
|
|
case fxml:get_attr_s(<<"type">>, Attrs) of
|
|
<<"submit">> ->
|
|
lists:reverse(parse_xdata_fields(Els, []));
|
|
<<"form">> -> %% This is a workaround to accept Psi's wrong forms
|
|
lists:reverse(parse_xdata_fields(Els, []));
|
|
_ ->
|
|
invalid
|
|
end.
|
|
|
|
-spec parse_xdata_fields(Xmlels :: [xmlel() | cdata()],
|
|
Res :: [{Var::binary(), Values :: [binary()]}]) ->
|
|
[{Var::binary(), Values::[binary()]}].
|
|
|
|
parse_xdata_fields([], Res) -> Res;
|
|
parse_xdata_fields([#xmlel{name = <<"field">>, attrs = Attrs, children = SubEls}
|
|
| Els], Res) ->
|
|
case fxml:get_attr_s(<<"var">>, Attrs) of
|
|
<<>> ->
|
|
parse_xdata_fields(Els, Res);
|
|
Var ->
|
|
Field = {Var, lists:reverse(parse_xdata_values(SubEls, []))},
|
|
parse_xdata_fields(Els, [Field | Res])
|
|
end;
|
|
parse_xdata_fields([_ | Els], Res) ->
|
|
parse_xdata_fields(Els, Res).
|
|
|
|
-spec parse_xdata_values(Xmlels :: [xmlel() | cdata()],
|
|
Res :: [binary()]) -> [binary()].
|
|
|
|
parse_xdata_values([], Res) -> Res;
|
|
parse_xdata_values([#xmlel{name = <<"value">>, children = SubEls} | Els], Res) ->
|
|
Val = fxml:get_cdata(SubEls),
|
|
parse_xdata_values(Els, [Val | Res]);
|
|
parse_xdata_values([_ | Els], Res) ->
|
|
parse_xdata_values(Els, Res).
|
|
|
|
-spec rsm_decode(iq() | xmlel()) -> none | rsm_in().
|
|
|
|
rsm_decode(#iq{sub_el = SubEl}) -> rsm_decode(SubEl);
|
|
rsm_decode(#xmlel{} = SubEl) ->
|
|
case fxml:get_subtag(SubEl, <<"set">>) of
|
|
false -> none;
|
|
#xmlel{name = <<"set">>, children = SubEls} ->
|
|
lists:foldl(fun rsm_parse_element/2, #rsm_in{}, SubEls)
|
|
end.
|
|
|
|
rsm_parse_element(#xmlel{name = <<"max">>, attrs = []} =
|
|
Elem,
|
|
RsmIn) ->
|
|
CountStr = fxml:get_tag_cdata(Elem),
|
|
{Count, _} = str:to_integer(CountStr),
|
|
RsmIn#rsm_in{max = Count};
|
|
rsm_parse_element(#xmlel{name = <<"before">>,
|
|
attrs = []} =
|
|
Elem,
|
|
RsmIn) ->
|
|
UID = fxml:get_tag_cdata(Elem),
|
|
RsmIn#rsm_in{direction = before, id = UID};
|
|
rsm_parse_element(#xmlel{name = <<"after">>,
|
|
attrs = []} =
|
|
Elem,
|
|
RsmIn) ->
|
|
UID = fxml:get_tag_cdata(Elem),
|
|
RsmIn#rsm_in{direction = aft, id = UID};
|
|
rsm_parse_element(#xmlel{name = <<"index">>,
|
|
attrs = []} =
|
|
Elem,
|
|
RsmIn) ->
|
|
IndexStr = fxml:get_tag_cdata(Elem),
|
|
{Index, _} = str:to_integer(IndexStr),
|
|
RsmIn#rsm_in{index = Index};
|
|
rsm_parse_element(_, RsmIn) -> RsmIn.
|
|
|
|
-spec rsm_encode(iq(), rsm_out()) -> iq().
|
|
|
|
rsm_encode(#iq{sub_el = SubEl} = IQ, RsmOut) ->
|
|
Set = #xmlel{name = <<"set">>,
|
|
attrs = [{<<"xmlns">>, ?NS_RSM}],
|
|
children = lists:reverse(rsm_encode_out(RsmOut))},
|
|
#xmlel{name = Name, attrs = Attrs, children = SubEls} =
|
|
SubEl,
|
|
New = #xmlel{name = Name, attrs = Attrs,
|
|
children = [Set | SubEls]},
|
|
IQ#iq{sub_el = New}.
|
|
|
|
-spec rsm_encode(none | rsm_out()) -> [xmlel()].
|
|
|
|
rsm_encode(none) -> [];
|
|
rsm_encode(RsmOut) ->
|
|
[#xmlel{name = <<"set">>,
|
|
attrs = [{<<"xmlns">>, ?NS_RSM}],
|
|
children = lists:reverse(rsm_encode_out(RsmOut))}].
|
|
|
|
rsm_encode_out(#rsm_out{count = Count, index = Index,
|
|
first = First, last = Last}) ->
|
|
El = rsm_encode_first(First, Index, []),
|
|
El2 = rsm_encode_last(Last, El),
|
|
rsm_encode_count(Count, El2).
|
|
|
|
rsm_encode_first(undefined, undefined, Arr) -> Arr;
|
|
rsm_encode_first(First, undefined, Arr) ->
|
|
[#xmlel{name = <<"first">>, attrs = [],
|
|
children = [{xmlcdata, First}]}
|
|
| Arr];
|
|
rsm_encode_first(First, Index, Arr) ->
|
|
[#xmlel{name = <<"first">>,
|
|
attrs = [{<<"index">>, i2l(Index)}],
|
|
children = [{xmlcdata, First}]}
|
|
| Arr].
|
|
|
|
rsm_encode_last(undefined, Arr) -> Arr;
|
|
rsm_encode_last(Last, Arr) ->
|
|
[#xmlel{name = <<"last">>, attrs = [],
|
|
children = [{xmlcdata, Last}]}
|
|
| Arr].
|
|
|
|
rsm_encode_count(undefined, Arr) -> Arr;
|
|
rsm_encode_count(Count, Arr) ->
|
|
[#xmlel{name = <<"count">>, attrs = [],
|
|
children = [{xmlcdata, i2l(Count)}]}
|
|
| Arr].
|
|
|
|
-spec unwrap_carbon(xmlel()) -> xmlel().
|
|
|
|
unwrap_carbon(#xmlel{name = <<"message">>} = Stanza) ->
|
|
case unwrap_carbon(Stanza, <<"sent">>) of
|
|
#xmlel{} = Payload ->
|
|
Payload;
|
|
false ->
|
|
case unwrap_carbon(Stanza, <<"received">>) of
|
|
#xmlel{} = Payload ->
|
|
Payload;
|
|
false ->
|
|
Stanza
|
|
end
|
|
end;
|
|
unwrap_carbon(Stanza) -> Stanza.
|
|
|
|
-spec unwrap_carbon(xmlel(), binary()) -> xmlel() | false.
|
|
|
|
unwrap_carbon(Stanza, Direction) ->
|
|
case fxml:get_subtag(Stanza, Direction) of
|
|
#xmlel{name = Direction, attrs = Attrs} = El ->
|
|
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
|
|
NS when NS == ?NS_CARBONS_2;
|
|
NS == ?NS_CARBONS_1 ->
|
|
case fxml:get_subtag_with_xmlns(El, <<"forwarded">>,
|
|
?NS_FORWARD) of
|
|
#xmlel{children = Els} ->
|
|
case fxml:remove_cdata(Els) of
|
|
[#xmlel{} = Payload] ->
|
|
Payload;
|
|
_ ->
|
|
false
|
|
end;
|
|
false ->
|
|
false
|
|
end;
|
|
_NS ->
|
|
false
|
|
end;
|
|
false ->
|
|
false
|
|
end.
|
|
|
|
-spec is_standalone_chat_state(xmlel()) -> boolean().
|
|
|
|
is_standalone_chat_state(Stanza) ->
|
|
case unwrap_carbon(Stanza) of
|
|
#xmlel{name = <<"message">>, children = Els} ->
|
|
IgnoreNS = [?NS_CHATSTATES, ?NS_DELAY],
|
|
Stripped = [El || #xmlel{name = Name, attrs = Attrs} = El <- Els,
|
|
not lists:member(fxml:get_attr_s(<<"xmlns">>,
|
|
Attrs),
|
|
IgnoreNS),
|
|
Name /= <<"thread">>],
|
|
Stripped == [];
|
|
#xmlel{} ->
|
|
false
|
|
end.
|
|
|
|
-spec add_delay_info(xmlel(), jid() | ljid() | binary(), erlang:timestamp())
|
|
-> xmlel().
|
|
|
|
add_delay_info(El, From, Time) ->
|
|
add_delay_info(El, From, Time, <<"">>).
|
|
|
|
-spec add_delay_info(xmlel(), jid() | ljid() | binary(), erlang:timestamp(),
|
|
binary()) -> xmlel().
|
|
|
|
add_delay_info(El, From, Time, Desc) ->
|
|
DelayTag = create_delay_tag(Time, From, Desc),
|
|
fxml:append_subtags(El, [DelayTag]).
|
|
|
|
-spec create_delay_tag(erlang:timestamp(), jid() | ljid() | binary(), binary())
|
|
-> xmlel() | error.
|
|
|
|
create_delay_tag(TimeStamp, FromJID, Desc) when is_tuple(FromJID) ->
|
|
From = jid:encode(FromJID),
|
|
Stamp = now_to_utc_string(TimeStamp, 3),
|
|
Children = case Desc of
|
|
<<"">> -> [];
|
|
_ -> [{xmlcdata, Desc}]
|
|
end,
|
|
#xmlel{name = <<"delay">>,
|
|
attrs =
|
|
[{<<"xmlns">>, ?NS_DELAY}, {<<"from">>, From},
|
|
{<<"stamp">>, Stamp}],
|
|
children = Children};
|
|
create_delay_tag(DateTime, Host, Desc) when is_binary(Host) ->
|
|
FromJID = jid:make(Host),
|
|
create_delay_tag(DateTime, FromJID, Desc).
|
|
|
|
-type tz() :: {binary(), {integer(), integer()}} | {integer(), integer()} | utc.
|
|
|
|
%% Timezone = utc | {Sign::string(), {Hours, Minutes}} | {Hours, Minutes}
|
|
%% Hours = integer()
|
|
%% Minutes = integer()
|
|
-spec timestamp_to_iso(calendar:datetime(), tz()) -> {binary(), binary()}.
|
|
|
|
%% This is the XEP-0082 date and time format
|
|
%% http://xmpp.org/extensions/xep-0082.html
|
|
|
|
timestamp_to_iso({{Year, Month, Day},
|
|
{Hour, Minute, Second}},
|
|
Timezone) ->
|
|
Timestamp_string =
|
|
lists:flatten(io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0B",
|
|
[Year, Month, Day, Hour, Minute, Second])),
|
|
Timezone_string = case Timezone of
|
|
utc -> "Z";
|
|
{Sign, {TZh, TZm}} ->
|
|
io_lib:format("~s~2..0B:~2..0B", [Sign, TZh, TZm]);
|
|
{TZh, TZm} ->
|
|
Sign = case TZh >= 0 of
|
|
true -> "+";
|
|
false -> "-"
|
|
end,
|
|
io_lib:format("~s~2..0B:~2..0B",
|
|
[Sign, abs(TZh), TZm])
|
|
end,
|
|
{iolist_to_binary(Timestamp_string), iolist_to_binary(Timezone_string)}.
|
|
|
|
|
|
-spec timestamp_to_legacy(calendar:datetime()) -> binary().
|
|
%% This is the jabber legacy format
|
|
%% http://xmpp.org/extensions/xep-0091.html#time
|
|
timestamp_to_legacy({{Year, Month, Day},
|
|
{Hour, Minute, Second}}) ->
|
|
(str:format("~4..0B~2..0B~2..0BT~2..0B:~2..0B:~2..0B",
|
|
[Year, Month, Day, Hour, Minute, Second])).
|
|
|
|
-spec timestamp_to_iso_basic(calendar:datetime()) -> binary().
|
|
%% This is the ISO 8601 basic bormat
|
|
timestamp_to_iso_basic({{Year, Month, Day},
|
|
{Hour, Minute, Second}}) ->
|
|
(str:format("~4..0B~2..0B~2..0BT~2..0B~2..0B~2..0B",
|
|
[Year, Month, Day, Hour, Minute, Second])).
|
|
|
|
-spec now_to_utc_string(erlang:timestamp()) -> binary().
|
|
|
|
now_to_utc_string({MegaSecs, Secs, MicroSecs}) ->
|
|
now_to_utc_string({MegaSecs, Secs, MicroSecs}, 6).
|
|
|
|
-spec now_to_utc_string(erlang:timestamp(), 1..6) -> binary().
|
|
|
|
now_to_utc_string({MegaSecs, Secs, MicroSecs}, Precision) ->
|
|
{{Year, Month, Day}, {Hour, Minute, Second}} =
|
|
calendar:now_to_universal_time({MegaSecs, Secs,
|
|
MicroSecs}),
|
|
Max = round(math:pow(10, Precision)),
|
|
case round(MicroSecs / math:pow(10, 6 - Precision)) of
|
|
Max ->
|
|
now_to_utc_string({MegaSecs, Secs + 1, 0}, Precision);
|
|
FracOfSec ->
|
|
(str:format("~4..0B-~2..0B-~2..0BT"
|
|
"~2..0B:~2..0B:~2..0B.~*..0BZ",
|
|
[Year, Month, Day, Hour, Minute, Second,
|
|
Precision, FracOfSec]))
|
|
end.
|
|
|
|
-spec now_to_local_string(erlang:timestamp()) -> binary().
|
|
|
|
now_to_local_string({MegaSecs, Secs, MicroSecs}) ->
|
|
LocalTime = calendar:now_to_local_time({MegaSecs, Secs,
|
|
MicroSecs}),
|
|
UTCTime = calendar:now_to_universal_time({MegaSecs,
|
|
Secs, MicroSecs}),
|
|
Seconds =
|
|
calendar:datetime_to_gregorian_seconds(LocalTime) -
|
|
calendar:datetime_to_gregorian_seconds(UTCTime),
|
|
{{H, M, _}, Sign} = if Seconds < 0 ->
|
|
{calendar:seconds_to_time(-Seconds), "-"};
|
|
true -> {calendar:seconds_to_time(Seconds), "+"}
|
|
end,
|
|
{{Year, Month, Day}, {Hour, Minute, Second}} =
|
|
LocalTime,
|
|
(str:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0B.~6."
|
|
".0B~s~2..0B:~2..0B",
|
|
[Year, Month, Day, Hour, Minute, Second,
|
|
MicroSecs, Sign, H, M])).
|
|
|
|
-spec datetime_string_to_timestamp(binary()) -> undefined | erlang:timestamp().
|
|
|
|
datetime_string_to_timestamp(TimeStr) ->
|
|
case catch parse_datetime(TimeStr) of
|
|
{'EXIT', _Err} -> undefined;
|
|
TimeStamp -> TimeStamp
|
|
end.
|
|
|
|
parse_datetime(TimeStr) ->
|
|
[Date, Time] = str:tokens(TimeStr, <<"T">>),
|
|
D = parse_date(Date),
|
|
{T, MS, TZH, TZM} = parse_time(Time),
|
|
S = calendar:datetime_to_gregorian_seconds({D, T}),
|
|
S1 = calendar:datetime_to_gregorian_seconds({{1970, 1,
|
|
1},
|
|
{0, 0, 0}}),
|
|
Seconds = S - S1 - TZH * 60 * 60 - TZM * 60,
|
|
{Seconds div 1000000, Seconds rem 1000000, MS}.
|
|
|
|
% yyyy-mm-dd
|
|
parse_date(Date) ->
|
|
[Y, M, D] = str:tokens(Date, <<"-">>),
|
|
Date1 = {binary_to_integer(Y), binary_to_integer(M),
|
|
binary_to_integer(D)},
|
|
case calendar:valid_date(Date1) of
|
|
true -> Date1;
|
|
_ -> false
|
|
end.
|
|
|
|
% hh:mm:ss[.sss]TZD
|
|
parse_time(Time) ->
|
|
case str:str(Time, <<"Z">>) of
|
|
0 -> parse_time_with_timezone(Time);
|
|
_ ->
|
|
[T | _] = str:tokens(Time, <<"Z">>),
|
|
{TT, MS} = parse_time1(T),
|
|
{TT, MS, 0, 0}
|
|
end.
|
|
|
|
parse_time_with_timezone(Time) ->
|
|
case str:str(Time, <<"+">>) of
|
|
0 ->
|
|
case str:str(Time, <<"-">>) of
|
|
0 -> false;
|
|
_ -> parse_time_with_timezone(Time, <<"-">>)
|
|
end;
|
|
_ -> parse_time_with_timezone(Time, <<"+">>)
|
|
end.
|
|
|
|
parse_time_with_timezone(Time, Delim) ->
|
|
[T, TZ] = str:tokens(Time, Delim),
|
|
{TZH, TZM} = parse_timezone(TZ),
|
|
{TT, MS} = parse_time1(T),
|
|
case Delim of
|
|
<<"-">> -> {TT, MS, -TZH, -TZM};
|
|
<<"+">> -> {TT, MS, TZH, TZM}
|
|
end.
|
|
|
|
parse_timezone(TZ) ->
|
|
[H, M] = str:tokens(TZ, <<":">>),
|
|
{[H1, M1], true} = check_list([{H, 12}, {M, 60}]),
|
|
{H1, M1}.
|
|
|
|
parse_time1(Time) ->
|
|
[HMS | T] = str:tokens(Time, <<".">>),
|
|
MS = case T of
|
|
[] -> 0;
|
|
[Val] -> binary_to_integer(str:left(Val, 6, $0))
|
|
end,
|
|
[H, M, S] = str:tokens(HMS, <<":">>),
|
|
{[H1, M1, S1], true} = check_list([{H, 24}, {M, 60},
|
|
{S, 60}]),
|
|
{{H1, M1, S1}, MS}.
|
|
|
|
check_list(List) ->
|
|
lists:mapfoldl(fun ({L, N}, B) ->
|
|
V = binary_to_integer(L),
|
|
if (V >= 0) and (V =< N) -> {V, B};
|
|
true -> {false, false}
|
|
end
|
|
end,
|
|
true, List).
|
|
|
|
%
|
|
% Base64 stuff (based on httpd_util.erl)
|
|
%
|
|
|
|
-spec term_to_base64(term()) -> binary().
|
|
|
|
term_to_base64(Term) ->
|
|
encode_base64(term_to_binary(Term)).
|
|
|
|
-spec base64_to_term(binary()) -> {term, term()} | error.
|
|
|
|
base64_to_term(Base64) ->
|
|
case catch binary_to_term(decode_base64(Base64), [safe]) of
|
|
{'EXIT', _} ->
|
|
error;
|
|
Term ->
|
|
{term, Term}
|
|
end.
|
|
|
|
-spec decode_base64(binary()) -> binary().
|
|
|
|
decode_base64(S) ->
|
|
case catch binary:last(S) of
|
|
C when C == $\n; C == $\s ->
|
|
decode_base64(binary:part(S, 0, byte_size(S) - 1));
|
|
_ ->
|
|
decode_base64_bin(S, <<>>)
|
|
end.
|
|
|
|
take_without_spaces(Bin, Count) ->
|
|
take_without_spaces(Bin, Count, <<>>).
|
|
|
|
take_without_spaces(Bin, 0, Acc) ->
|
|
{Acc, Bin};
|
|
take_without_spaces(<<>>, _, Acc) ->
|
|
{Acc, <<>>};
|
|
take_without_spaces(<<$\s, Tail/binary>>, Count, Acc) ->
|
|
take_without_spaces(Tail, Count, Acc);
|
|
take_without_spaces(<<$\t, Tail/binary>>, Count, Acc) ->
|
|
take_without_spaces(Tail, Count, Acc);
|
|
take_without_spaces(<<$\n, Tail/binary>>, Count, Acc) ->
|
|
take_without_spaces(Tail, Count, Acc);
|
|
take_without_spaces(<<$\r, Tail/binary>>, Count, Acc) ->
|
|
take_without_spaces(Tail, Count, Acc);
|
|
take_without_spaces(<<Char:8, Tail/binary>>, Count, Acc) ->
|
|
take_without_spaces(Tail, Count-1, <<Acc/binary, Char:8>>).
|
|
|
|
decode_base64_bin(<<>>, Acc) ->
|
|
Acc;
|
|
decode_base64_bin(Bin, Acc) ->
|
|
case take_without_spaces(Bin, 4) of
|
|
{<<A, B, $=, $=>>, _} ->
|
|
<<Acc/binary, (d(A)):6, (d(B) bsr 4):2>>;
|
|
{<<A, B, C, $=>>, _} ->
|
|
<<Acc/binary, (d(A)):6, (d(B)):6, (d(C) bsr 2):4>>;
|
|
{<<A, B, C, D>>, Tail} ->
|
|
Acc2 = <<Acc/binary, (d(A)):6, (d(B)):6, (d(C)):6, (d(D)):6>>,
|
|
decode_base64_bin(Tail, Acc2);
|
|
_ ->
|
|
<<"">>
|
|
end.
|
|
|
|
d(X) when X >= $A, X =< $Z -> X - 65;
|
|
d(X) when X >= $a, X =< $z -> X - 71;
|
|
d(X) when X >= $0, X =< $9 -> X + 4;
|
|
d($+) -> 62;
|
|
d($/) -> 63;
|
|
d(_) -> 63.
|
|
|
|
|
|
%% Convert Erlang inet IP to list
|
|
-spec encode_base64(binary()) -> binary().
|
|
|
|
encode_base64(Data) ->
|
|
encode_base64_bin(Data, <<>>).
|
|
|
|
encode_base64_bin(<<A:6, B:6, C:6, D:6, Tail/binary>>, Acc) ->
|
|
encode_base64_bin(Tail, <<Acc/binary, (e(A)):8, (e(B)):8, (e(C)):8, (e(D)):8>>);
|
|
encode_base64_bin(<<A:6, B:6, C:4>>, Acc) ->
|
|
<<Acc/binary, (e(A)):8, (e(B)):8, (e(C bsl 2)):8, $=>>;
|
|
encode_base64_bin(<<A:6, B:2>>, Acc) ->
|
|
<<Acc/binary, (e(A)):8, (e(B bsl 4)):8, $=, $=>>;
|
|
encode_base64_bin(<<>>, Acc) ->
|
|
Acc.
|
|
|
|
e(X) when X >= 0, X < 26 -> X + 65;
|
|
e(X) when X > 25, X < 52 -> X + 71;
|
|
e(X) when X > 51, X < 62 -> X - 4;
|
|
e(62) -> $+;
|
|
e(63) -> $/;
|
|
e(X) -> exit({bad_encode_base64_token, X}).
|
|
|
|
-spec ip_to_list(inet:ip_address() | undefined |
|
|
{inet:ip_address(), inet:port_number()}) -> binary().
|
|
|
|
ip_to_list({IP, _Port}) ->
|
|
ip_to_list(IP);
|
|
%% This function clause could use inet_parse too:
|
|
ip_to_list(undefined) ->
|
|
<<"unknown">>;
|
|
ip_to_list(IP) ->
|
|
list_to_binary(inet_parse:ntoa(IP)).
|
|
|
|
-spec hex_to_bin(binary()) -> binary().
|
|
|
|
hex_to_bin(Hex) ->
|
|
hex_to_bin(binary_to_list(Hex), []).
|
|
|
|
-spec hex_to_bin(list(), list()) -> binary().
|
|
|
|
hex_to_bin([], Acc) ->
|
|
list_to_binary(lists:reverse(Acc));
|
|
hex_to_bin([H1, H2 | T], Acc) ->
|
|
{ok, [V], []} = io_lib:fread("~16u", [H1, H2]),
|
|
hex_to_bin(T, [V | Acc]).
|
|
|
|
-spec hex_to_base64(binary()) -> binary().
|
|
|
|
hex_to_base64(Hex) -> encode_base64(hex_to_bin(Hex)).
|
|
|
|
-spec expand_keyword(binary(), binary(), binary()) -> binary().
|
|
|
|
expand_keyword(Keyword, Input, Replacement) ->
|
|
Parts = binary:split(Input, Keyword, [global]),
|
|
str:join(Parts, Replacement).
|
|
|
|
binary_to_atom(Bin) ->
|
|
erlang:binary_to_atom(Bin, utf8).
|
|
|
|
binary_to_integer(Bin) ->
|
|
erlang:binary_to_integer(Bin).
|
|
|
|
binary_to_integer(Bin, Base) ->
|
|
erlang:binary_to_integer(Bin, Base).
|
|
|
|
integer_to_binary(I) ->
|
|
erlang:integer_to_binary(I).
|
|
|
|
integer_to_binary(I, Base) ->
|
|
erlang:integer_to_binary(I, Base).
|
|
|
|
tuple_to_binary(T) ->
|
|
iolist_to_binary(tuple_to_list(T)).
|
|
|
|
atom_to_binary(A) ->
|
|
erlang:atom_to_binary(A, utf8).
|
|
|
|
expr_to_term(Expr) ->
|
|
Str = binary_to_list(<<Expr/binary, ".">>),
|
|
{ok, Tokens, _} = erl_scan:string(Str),
|
|
{ok, Term} = erl_parse:parse_term(Tokens),
|
|
Term.
|
|
|
|
term_to_expr(Term) ->
|
|
list_to_binary(io_lib:print(Term)).
|
|
|
|
l2i(I) when is_integer(I) -> I;
|
|
l2i(L) when is_binary(L) -> binary_to_integer(L).
|
|
|
|
i2l(I) when is_integer(I) -> integer_to_binary(I);
|
|
i2l(L) when is_binary(L) -> L.
|
|
|
|
i2l(I, N) when is_integer(I) -> i2l(i2l(I), N);
|
|
i2l(L, N) when is_binary(L) ->
|
|
case str:len(L) of
|
|
N -> L;
|
|
C when C > N -> L;
|
|
_ -> i2l(<<$0, L/binary>>, N)
|
|
end.
|