2017-03-30 13:17:13 +02:00
|
|
|
%%%-------------------------------------------------------------------
|
|
|
|
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
|
|
|
%%% @doc
|
|
|
|
%%% This is the place for some unsorted auxiliary functions
|
|
|
|
%%% Some functions from jlib.erl are moved here
|
|
|
|
%%% Mild rubbish heap is accepted ;)
|
|
|
|
%%% @end
|
|
|
|
%%% Created : 30 Mar 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
|
|
|
%%%
|
|
|
|
%%%
|
2020-01-28 13:34:02 +01:00
|
|
|
%%% ejabberd, Copyright (C) 2002-2020 ProcessOne
|
2017-03-30 13:17:13 +02:00
|
|
|
%%%
|
|
|
|
%%% 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.
|
|
|
|
%%%
|
|
|
|
%%%-------------------------------------------------------------------
|
2017-04-11 12:13:58 +02:00
|
|
|
-module(misc).
|
2017-03-30 13:17:13 +02:00
|
|
|
|
|
|
|
%% API
|
2018-10-25 01:05:45 +02:00
|
|
|
-export([add_delay_info/3, add_delay_info/4,
|
2018-10-25 01:22:57 +02:00
|
|
|
unwrap_carbon/1, unwrap_mucsub_message/1, is_standalone_chat_state/1,
|
2018-10-25 01:05:45 +02:00
|
|
|
tolower/1, term_to_base64/1, base64_to_term/1, ip_to_list/1,
|
2018-04-03 00:12:43 +02:00
|
|
|
hex_to_bin/1, hex_to_base64/1, url_encode/1, expand_keyword/3,
|
2017-03-30 13:17:13 +02:00
|
|
|
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
|
2017-03-30 16:44:43 +02:00
|
|
|
l2i/1, i2l/1, i2l/2, expr_to_term/1, term_to_expr/1,
|
2017-07-20 20:22:50 +02:00
|
|
|
now_to_usec/1, usec_to_now/1, encode_pid/1, decode_pid/2,
|
2018-06-14 13:00:47 +02:00
|
|
|
compile_exprs/2, join_atoms/2, try_read_file/1, get_descr/2,
|
2018-07-14 16:53:00 +02:00
|
|
|
css_dir/0, img_dir/0, js_dir/0, msgs_dir/0, sql_dir/0, lua_dir/0,
|
|
|
|
read_css/1, read_img/1, read_js/1, read_lua/1, try_url/1,
|
2019-03-14 15:12:39 +01:00
|
|
|
intersection/2, format_val/1, cancel_timer/1, unique_timestamp/0,
|
2019-06-14 11:33:26 +02:00
|
|
|
is_mucsub_message/1, best_match/2, pmap/2, peach/2, format_exception/4,
|
2020-05-29 18:40:19 +02:00
|
|
|
get_my_ipv4_address/0, get_my_ipv6_address/0, parse_ip_mask/1,
|
2020-06-01 10:35:28 +02:00
|
|
|
crypto_hmac/3, crypto_hmac/4, uri_parse/1,
|
2020-05-29 18:40:19 +02:00
|
|
|
match_ip_mask/3, format_hosts_list/1, format_cycle/1, delete_dir/1]).
|
2017-05-12 08:34:57 +02:00
|
|
|
|
2017-05-23 09:43:26 +02:00
|
|
|
%% Deprecated functions
|
|
|
|
-export([decode_base64/1, encode_base64/1]).
|
|
|
|
-deprecated([{decode_base64, 1},
|
|
|
|
{encode_base64, 1}]).
|
|
|
|
|
2017-05-12 08:34:57 +02:00
|
|
|
-include("logger.hrl").
|
2018-10-25 01:05:45 +02:00
|
|
|
-include("xmpp.hrl").
|
2017-05-12 08:34:57 +02:00
|
|
|
-include_lib("kernel/include/file.hrl").
|
2017-03-30 13:17:13 +02:00
|
|
|
|
2019-06-27 14:22:27 +02:00
|
|
|
-type distance_cache() :: #{{string(), string()} => non_neg_integer()}.
|
|
|
|
|
2020-06-01 10:35:28 +02:00
|
|
|
-ifdef(USE_OLD_HTTP_URI).
|
|
|
|
uri_parse(URL) when is_binary(URL) ->
|
|
|
|
uri_parse(binary_to_list(URL));
|
|
|
|
uri_parse(URL) ->
|
|
|
|
{ok, {Scheme, _UserInfo, Host, Port, Path, _Query}} = http_uri:parse(URL),
|
|
|
|
{ok, Scheme, Host, Port, Path}.
|
|
|
|
-else.
|
2020-06-03 12:02:29 +02:00
|
|
|
uri_parse(URL) when is_binary(URL) ->
|
|
|
|
uri_parse(binary_to_list(URL));
|
2020-06-01 10:35:28 +02:00
|
|
|
uri_parse(URL) ->
|
2020-06-03 12:22:14 +02:00
|
|
|
case uri_string:parse(URL) of
|
|
|
|
#{scheme := Scheme, host := Host, port := Port, path := Path} ->
|
|
|
|
{ok, Scheme, Host, Port, Path};
|
|
|
|
#{scheme := "https", host := Host, path := Path} ->
|
|
|
|
{ok, "https", Host, 443, Path};
|
|
|
|
#{scheme := "http", host := Host, path := Path} ->
|
|
|
|
{ok, "http", Host, 80, Path}
|
|
|
|
end.
|
2020-06-01 10:35:28 +02:00
|
|
|
-endif.
|
|
|
|
|
|
|
|
-ifdef(USE_OLD_CRYPTO_HMAC).
|
|
|
|
crypto_hmac(Type, Key, Data) -> crypto:hmac(Type, Key, Data).
|
|
|
|
crypto_hmac(Type, Key, Data, MacL) -> crypto:hmac(Type, Key, Data, MacL).
|
|
|
|
-else.
|
|
|
|
crypto_hmac(Type, Key, Data) -> crypto:mac(hmac, Type, Key, Data).
|
|
|
|
crypto_hmac(Type, Key, Data, MacL) -> crypto:macN(hmac, Type, Key, Data, MacL).
|
|
|
|
-endif.
|
|
|
|
|
2017-03-30 13:17:13 +02:00
|
|
|
%%%===================================================================
|
|
|
|
%%% API
|
|
|
|
%%%===================================================================
|
2018-10-25 01:05:45 +02:00
|
|
|
-spec add_delay_info(stanza(), jid(), erlang:timestamp()) -> stanza().
|
|
|
|
add_delay_info(Stz, From, Time) ->
|
|
|
|
add_delay_info(Stz, From, Time, <<"">>).
|
|
|
|
|
|
|
|
-spec add_delay_info(stanza(), jid(), erlang:timestamp(), binary()) -> stanza().
|
|
|
|
add_delay_info(Stz, From, Time, Desc) ->
|
2019-04-01 09:47:29 +02:00
|
|
|
Delays = xmpp:get_subtags(Stz, #delay{stamp = {0,0,0}}),
|
2019-03-29 11:25:35 +01:00
|
|
|
Matching = lists:any(
|
|
|
|
fun(#delay{from = OldFrom}) when is_record(OldFrom, jid) ->
|
|
|
|
jid:tolower(From) == jid:tolower(OldFrom);
|
|
|
|
(_) ->
|
|
|
|
false
|
|
|
|
end, Delays),
|
|
|
|
case Matching of
|
|
|
|
true ->
|
|
|
|
Stz;
|
2018-10-25 01:05:45 +02:00
|
|
|
_ ->
|
2019-03-29 11:25:35 +01:00
|
|
|
NewDelay = #delay{stamp = Time, from = From, desc = Desc},
|
2018-10-25 01:05:45 +02:00
|
|
|
xmpp:append_subtags(Stz, [NewDelay])
|
|
|
|
end.
|
|
|
|
|
|
|
|
-spec unwrap_carbon(stanza()) -> xmpp_element().
|
|
|
|
unwrap_carbon(#message{} = Msg) ->
|
|
|
|
try
|
|
|
|
case xmpp:get_subtag(Msg, #carbons_sent{forwarded = #forwarded{}}) of
|
|
|
|
#carbons_sent{forwarded = #forwarded{sub_els = [El]}} ->
|
|
|
|
xmpp:decode(El, ?NS_CLIENT, [ignore_els]);
|
|
|
|
_ ->
|
|
|
|
case xmpp:get_subtag(Msg, #carbons_received{
|
|
|
|
forwarded = #forwarded{}}) of
|
|
|
|
#carbons_received{forwarded = #forwarded{sub_els = [El]}} ->
|
|
|
|
xmpp:decode(El, ?NS_CLIENT, [ignore_els]);
|
|
|
|
_ ->
|
|
|
|
Msg
|
|
|
|
end
|
|
|
|
end
|
|
|
|
catch _:{xmpp_codec, _} ->
|
|
|
|
Msg
|
|
|
|
end;
|
|
|
|
unwrap_carbon(Stanza) -> Stanza.
|
|
|
|
|
2018-10-25 01:22:57 +02:00
|
|
|
-spec unwrap_mucsub_message(xmpp_element()) -> message() | false.
|
|
|
|
unwrap_mucsub_message(#message{} = OuterMsg) ->
|
|
|
|
case xmpp:get_subtag(OuterMsg, #ps_event{}) of
|
|
|
|
#ps_event{
|
|
|
|
items = #ps_items{
|
|
|
|
node = Node,
|
|
|
|
items = [
|
|
|
|
#ps_item{
|
|
|
|
sub_els = [#message{} = InnerMsg]} | _]}}
|
|
|
|
when Node == ?NS_MUCSUB_NODES_MESSAGES;
|
|
|
|
Node == ?NS_MUCSUB_NODES_SUBJECT ->
|
|
|
|
InnerMsg;
|
|
|
|
_ ->
|
|
|
|
false
|
|
|
|
end;
|
|
|
|
unwrap_mucsub_message(_Packet) ->
|
|
|
|
false.
|
|
|
|
|
2019-03-14 15:12:39 +01:00
|
|
|
-spec is_mucsub_message(xmpp_element()) -> boolean().
|
|
|
|
is_mucsub_message(#message{} = OuterMsg) ->
|
|
|
|
case xmpp:get_subtag(OuterMsg, #ps_event{}) of
|
|
|
|
#ps_event{
|
|
|
|
items = #ps_items{
|
|
|
|
node = Node}}
|
|
|
|
when Node == ?NS_MUCSUB_NODES_MESSAGES;
|
|
|
|
Node == ?NS_MUCSUB_NODES_SUBJECT;
|
|
|
|
Node == ?NS_MUCSUB_NODES_AFFILIATIONS;
|
|
|
|
Node == ?NS_MUCSUB_NODES_CONFIG;
|
|
|
|
Node == ?NS_MUCSUB_NODES_PARTICIPANTS;
|
|
|
|
Node == ?NS_MUCSUB_NODES_PRESENCE;
|
|
|
|
Node == ?NS_MUCSUB_NODES_SUBSCRIBERS ->
|
|
|
|
true;
|
|
|
|
_ ->
|
|
|
|
false
|
|
|
|
end;
|
|
|
|
is_mucsub_message(_Packet) ->
|
|
|
|
false.
|
|
|
|
|
2018-10-25 01:05:45 +02:00
|
|
|
-spec is_standalone_chat_state(stanza()) -> boolean().
|
|
|
|
is_standalone_chat_state(Stanza) ->
|
|
|
|
case unwrap_carbon(Stanza) of
|
|
|
|
#message{body = [], subject = [], sub_els = Els} ->
|
|
|
|
IgnoreNS = [?NS_CHATSTATES, ?NS_DELAY, ?NS_EVENT],
|
|
|
|
Stripped = [El || El <- Els,
|
|
|
|
not lists:member(xmpp:get_ns(El), IgnoreNS)],
|
|
|
|
Stripped == [];
|
|
|
|
_ ->
|
|
|
|
false
|
|
|
|
end.
|
|
|
|
|
2017-03-30 13:17:13 +02:00
|
|
|
-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([]) -> [].
|
|
|
|
|
|
|
|
-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) ->
|
2017-05-23 09:43:26 +02:00
|
|
|
try binary_to_term(base64:decode(Base64), [safe]) of
|
|
|
|
Term -> {term, Term}
|
2018-04-24 18:29:10 +02:00
|
|
|
catch _:_ ->
|
2017-05-23 09:43:26 +02:00
|
|
|
error
|
2017-03-30 13:17:13 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
-spec decode_base64(binary()) -> binary().
|
|
|
|
decode_base64(S) ->
|
2017-05-23 09:43:26 +02:00
|
|
|
try base64:mime_decode(S)
|
|
|
|
catch _:badarg -> <<>>
|
2017-03-30 13:17:13 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
-spec encode_base64(binary()) -> binary().
|
|
|
|
encode_base64(Data) ->
|
2017-05-23 09:43:26 +02:00
|
|
|
base64:encode(Data).
|
2017-03-30 13:17:13 +02:00
|
|
|
|
|
|
|
-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) ->
|
2017-05-23 09:43:26 +02:00
|
|
|
base64:encode(hex_to_bin(Hex)).
|
2017-03-30 13:17:13 +02:00
|
|
|
|
2018-04-03 00:12:43 +02:00
|
|
|
-spec url_encode(binary()) -> binary().
|
|
|
|
url_encode(A) ->
|
|
|
|
url_encode(A, <<>>).
|
|
|
|
|
2019-06-14 11:33:26 +02:00
|
|
|
-spec expand_keyword(iodata(), iodata(), iodata()) -> binary().
|
2017-03-30 13:17:13 +02:00
|
|
|
expand_keyword(Keyword, Input, Replacement) ->
|
2019-06-14 11:33:26 +02:00
|
|
|
re:replace(Input, Keyword, Replacement,
|
|
|
|
[{return, binary}, global]).
|
2017-03-30 13:17:13 +02:00
|
|
|
|
|
|
|
binary_to_atom(Bin) ->
|
|
|
|
erlang:binary_to_atom(Bin, utf8).
|
|
|
|
|
|
|
|
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)).
|
|
|
|
|
2017-07-20 20:22:50 +02:00
|
|
|
-spec now_to_usec(erlang:timestamp()) -> non_neg_integer().
|
|
|
|
now_to_usec({MSec, Sec, USec}) ->
|
|
|
|
(MSec*1000000 + Sec)*1000000 + USec.
|
|
|
|
|
|
|
|
-spec usec_to_now(non_neg_integer()) -> erlang:timestamp().
|
|
|
|
usec_to_now(Int) ->
|
|
|
|
Secs = Int div 1000000,
|
|
|
|
USec = Int rem 1000000,
|
|
|
|
MSec = Secs div 1000000,
|
|
|
|
Sec = Secs rem 1000000,
|
|
|
|
{MSec, Sec, USec}.
|
|
|
|
|
2017-03-30 13:17:13 +02:00
|
|
|
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.
|
|
|
|
|
2017-03-30 16:44:43 +02:00
|
|
|
-spec encode_pid(pid()) -> binary().
|
|
|
|
encode_pid(Pid) ->
|
|
|
|
list_to_binary(erlang:pid_to_list(Pid)).
|
|
|
|
|
|
|
|
-spec decode_pid(binary(), binary()) -> pid().
|
|
|
|
decode_pid(PidBin, NodeBin) ->
|
|
|
|
PidStr = binary_to_list(PidBin),
|
|
|
|
Pid = erlang:list_to_pid(PidStr),
|
|
|
|
case erlang:binary_to_atom(NodeBin, latin1) of
|
|
|
|
Node when Node == node() ->
|
|
|
|
Pid;
|
|
|
|
Node ->
|
|
|
|
try set_node_id(PidStr, NodeBin)
|
|
|
|
catch _:badarg ->
|
2017-03-30 16:51:37 +02:00
|
|
|
erlang:error({bad_node, Node})
|
2017-03-30 16:44:43 +02:00
|
|
|
end
|
|
|
|
end.
|
|
|
|
|
2017-04-16 19:05:46 +02:00
|
|
|
-spec compile_exprs(module(), [string()]) -> ok | {error, any()}.
|
|
|
|
compile_exprs(Mod, Exprs) ->
|
|
|
|
try
|
|
|
|
Forms = lists:map(
|
|
|
|
fun(Expr) ->
|
|
|
|
{ok, Tokens, _} = erl_scan:string(lists:flatten(Expr)),
|
|
|
|
{ok, Form} = erl_parse:parse_form(Tokens),
|
|
|
|
Form
|
|
|
|
end, Exprs),
|
|
|
|
{ok, Code} = case compile:forms(Forms, []) of
|
|
|
|
{ok, Mod, Bin} -> {ok, Bin};
|
|
|
|
{ok, Mod, Bin, _Warnings} -> {ok, Bin};
|
|
|
|
Error -> Error
|
|
|
|
end,
|
|
|
|
{module, Mod} = code:load_binary(Mod, "nofile", Code),
|
|
|
|
ok
|
|
|
|
catch _:{badmatch, {error, ErrInfo, _ErrLocation}} ->
|
|
|
|
{error, ErrInfo};
|
|
|
|
_:{badmatch, {error, _} = Err} ->
|
|
|
|
Err;
|
|
|
|
_:{badmatch, error} ->
|
|
|
|
{error, compile_failed}
|
|
|
|
end.
|
|
|
|
|
2017-04-30 18:01:47 +02:00
|
|
|
-spec join_atoms([atom()], binary()) -> binary().
|
|
|
|
join_atoms(Atoms, Sep) ->
|
2018-10-04 13:31:41 +02:00
|
|
|
str:join([io_lib:format("~p", [A]) || A <- lists:sort(Atoms)], Sep).
|
2017-04-30 18:01:47 +02:00
|
|
|
|
2017-05-12 08:34:57 +02:00
|
|
|
%% @doc Checks if the file is readable and converts its name to binary.
|
2020-05-11 19:37:47 +02:00
|
|
|
%% Fails with `badarg' otherwise. The function is intended for usage
|
2017-05-12 08:34:57 +02:00
|
|
|
%% in configuration validators only.
|
|
|
|
-spec try_read_file(file:filename_all()) -> binary().
|
|
|
|
try_read_file(Path) ->
|
2017-08-17 13:33:41 +02:00
|
|
|
case file:open(Path, [read]) of
|
|
|
|
{ok, Fd} ->
|
|
|
|
file:close(Fd),
|
2017-05-12 08:34:57 +02:00
|
|
|
iolist_to_binary(Path);
|
2017-08-17 13:33:41 +02:00
|
|
|
{error, Why} ->
|
2019-09-23 14:17:20 +02:00
|
|
|
?ERROR_MSG("Failed to read ~ts: ~ts", [Path, file:format_error(Why)]),
|
2017-05-12 08:34:57 +02:00
|
|
|
erlang:error(badarg)
|
|
|
|
end.
|
|
|
|
|
2018-06-29 10:06:24 +02:00
|
|
|
%% @doc Checks if the URL is valid HTTP(S) URL and converts its name to binary.
|
2020-05-11 19:37:47 +02:00
|
|
|
%% Fails with `badarg' otherwise. The function is intended for usage
|
2018-06-29 10:06:24 +02:00
|
|
|
%% in configuration validators only.
|
|
|
|
-spec try_url(binary() | string()) -> binary().
|
2018-06-29 11:13:16 +02:00
|
|
|
try_url(URL0) ->
|
|
|
|
URL = case URL0 of
|
|
|
|
V when is_binary(V) -> binary_to_list(V);
|
|
|
|
_ -> URL0
|
|
|
|
end,
|
2020-06-01 10:35:28 +02:00
|
|
|
case uri_parse(URL) of
|
2018-06-29 10:06:24 +02:00
|
|
|
{ok, {Scheme, _, _, _, _, _}} when Scheme /= http, Scheme /= https ->
|
2019-09-23 14:17:20 +02:00
|
|
|
?ERROR_MSG("Unsupported URI scheme: ~ts", [URL]),
|
2018-06-29 10:06:24 +02:00
|
|
|
erlang:error(badarg);
|
|
|
|
{ok, {_, _, Host, _, _, _}} when Host == ""; Host == <<"">> ->
|
2019-09-23 14:17:20 +02:00
|
|
|
?ERROR_MSG("Invalid URL: ~ts", [URL]),
|
2018-06-29 10:06:24 +02:00
|
|
|
erlang:error(badarg);
|
|
|
|
{ok, _} ->
|
|
|
|
iolist_to_binary(URL);
|
|
|
|
{error, _} ->
|
2019-09-23 14:17:20 +02:00
|
|
|
?ERROR_MSG("Invalid URL: ~ts", [URL]),
|
2018-06-29 10:06:24 +02:00
|
|
|
erlang:error(badarg)
|
|
|
|
end.
|
|
|
|
|
2017-10-09 20:46:38 +02:00
|
|
|
-spec css_dir() -> file:filename().
|
|
|
|
css_dir() ->
|
2018-06-14 13:00:47 +02:00
|
|
|
get_dir("css").
|
2017-10-09 20:46:38 +02:00
|
|
|
|
2017-10-09 22:35:42 +02:00
|
|
|
-spec img_dir() -> file:filename().
|
|
|
|
img_dir() ->
|
2018-06-14 13:00:47 +02:00
|
|
|
get_dir("img").
|
2017-10-09 22:35:42 +02:00
|
|
|
|
|
|
|
-spec js_dir() -> file:filename().
|
|
|
|
js_dir() ->
|
2018-06-14 13:00:47 +02:00
|
|
|
get_dir("js").
|
|
|
|
|
|
|
|
-spec msgs_dir() -> file:filename().
|
|
|
|
msgs_dir() ->
|
|
|
|
get_dir("msgs").
|
|
|
|
|
|
|
|
-spec sql_dir() -> file:filename().
|
|
|
|
sql_dir() ->
|
|
|
|
get_dir("sql").
|
2017-10-09 22:35:42 +02:00
|
|
|
|
2018-07-14 16:53:00 +02:00
|
|
|
-spec lua_dir() -> file:filename().
|
|
|
|
lua_dir() ->
|
|
|
|
get_dir("lua").
|
|
|
|
|
2017-10-09 22:35:42 +02:00
|
|
|
-spec read_css(file:filename()) -> {ok, binary()} | {error, file:posix()}.
|
|
|
|
read_css(File) ->
|
|
|
|
read_file(filename:join(css_dir(), File)).
|
|
|
|
|
|
|
|
-spec read_img(file:filename()) -> {ok, binary()} | {error, file:posix()}.
|
|
|
|
read_img(File) ->
|
|
|
|
read_file(filename:join(img_dir(), File)).
|
|
|
|
|
|
|
|
-spec read_js(file:filename()) -> {ok, binary()} | {error, file:posix()}.
|
|
|
|
read_js(File) ->
|
|
|
|
read_file(filename:join(js_dir(), File)).
|
|
|
|
|
2018-07-14 16:53:00 +02:00
|
|
|
-spec read_lua(file:filename()) -> {ok, binary()} | {error, file:posix()}.
|
|
|
|
read_lua(File) ->
|
|
|
|
read_file(filename:join(lua_dir(), File)).
|
|
|
|
|
2018-06-14 13:00:47 +02:00
|
|
|
-spec get_descr(binary(), binary()) -> binary().
|
|
|
|
get_descr(Lang, Text) ->
|
|
|
|
Desc = translate:translate(Lang, Text),
|
|
|
|
Copyright = ejabberd_config:get_copyright(),
|
|
|
|
<<Desc/binary, $\n, Copyright/binary>>.
|
|
|
|
|
2018-07-08 13:52:12 +02:00
|
|
|
-spec intersection(list(), list()) -> list().
|
|
|
|
intersection(L1, L2) ->
|
|
|
|
lists:filter(
|
|
|
|
fun(E) ->
|
|
|
|
lists:member(E, L2)
|
|
|
|
end, L1).
|
|
|
|
|
2018-07-08 19:42:53 +02:00
|
|
|
-spec format_val(any()) -> iodata().
|
2018-09-17 11:08:04 +02:00
|
|
|
format_val({yaml, S}) when is_integer(S); is_binary(S); is_atom(S) ->
|
|
|
|
format_val(S);
|
|
|
|
format_val({yaml, YAML}) ->
|
|
|
|
S = try fast_yaml:encode(YAML)
|
|
|
|
catch _:_ -> YAML
|
|
|
|
end,
|
|
|
|
format_val(S);
|
2018-07-08 19:42:53 +02:00
|
|
|
format_val(I) when is_integer(I) ->
|
|
|
|
integer_to_list(I);
|
|
|
|
format_val(B) when is_atom(B) ->
|
|
|
|
erlang:atom_to_binary(B, utf8);
|
2018-09-17 11:08:04 +02:00
|
|
|
format_val(Term) ->
|
|
|
|
S = try iolist_to_binary(Term)
|
|
|
|
catch _:_ -> list_to_binary(io_lib:format("~p", [Term]))
|
|
|
|
end,
|
|
|
|
case binary:match(S, <<"\n">>) of
|
|
|
|
nomatch -> S;
|
|
|
|
_ -> [io_lib:nl(), S]
|
2018-07-08 19:42:53 +02:00
|
|
|
end.
|
|
|
|
|
2019-06-14 11:33:26 +02:00
|
|
|
-spec cancel_timer(reference() | undefined) -> ok.
|
2018-07-17 20:50:58 +02:00
|
|
|
cancel_timer(TRef) when is_reference(TRef) ->
|
|
|
|
case erlang:cancel_timer(TRef) of
|
|
|
|
false ->
|
|
|
|
receive {timeout, TRef, _} -> ok
|
|
|
|
after 0 -> ok
|
|
|
|
end;
|
|
|
|
_ ->
|
|
|
|
ok
|
|
|
|
end;
|
|
|
|
cancel_timer(_) ->
|
|
|
|
ok.
|
|
|
|
|
2019-09-28 10:27:20 +02:00
|
|
|
-spec best_match(atom() | binary() | string(),
|
|
|
|
[atom() | binary() | string()]) -> string().
|
2019-04-30 08:36:38 +02:00
|
|
|
best_match(Pattern, []) ->
|
|
|
|
Pattern;
|
|
|
|
best_match(Pattern, Opts) ->
|
2019-09-28 10:27:20 +02:00
|
|
|
String = to_string(Pattern),
|
2019-04-30 08:36:38 +02:00
|
|
|
{Ds, _} = lists:mapfoldl(
|
|
|
|
fun(Opt, Cache) ->
|
2019-09-28 10:27:20 +02:00
|
|
|
SOpt = to_string(Opt),
|
|
|
|
{Distance, Cache1} = ld(String, SOpt, Cache),
|
|
|
|
{{Distance, SOpt}, Cache1}
|
2019-04-30 08:36:38 +02:00
|
|
|
end, #{}, Opts),
|
|
|
|
element(2, lists:min(Ds)).
|
|
|
|
|
2019-06-14 11:33:26 +02:00
|
|
|
-spec pmap(fun((T1) -> T2), [T1]) -> [T2].
|
|
|
|
pmap(Fun, [_,_|_] = List) ->
|
2019-07-10 09:28:37 +02:00
|
|
|
case erlang:system_info(logical_processors) of
|
|
|
|
1 -> lists:map(Fun, List);
|
|
|
|
_ ->
|
|
|
|
Self = self(),
|
|
|
|
lists:map(
|
|
|
|
fun({Pid, Ref}) ->
|
2019-06-14 11:33:26 +02:00
|
|
|
receive
|
2019-07-10 09:28:37 +02:00
|
|
|
{Pid, Ret} ->
|
|
|
|
receive
|
|
|
|
{'DOWN', Ref, _, _, _} ->
|
|
|
|
Ret
|
|
|
|
end;
|
|
|
|
{'DOWN', Ref, _, _, Reason} ->
|
|
|
|
exit(Reason)
|
|
|
|
end
|
|
|
|
end, [spawn_monitor(
|
|
|
|
fun() -> Self ! {self(), Fun(X)} end)
|
|
|
|
|| X <- List])
|
|
|
|
end;
|
2019-06-14 11:33:26 +02:00
|
|
|
pmap(Fun, List) ->
|
|
|
|
lists:map(Fun, List).
|
|
|
|
|
|
|
|
-spec peach(fun((T) -> any()), [T]) -> ok.
|
|
|
|
peach(Fun, [_,_|_] = List) ->
|
2019-07-10 09:28:37 +02:00
|
|
|
case erlang:system_info(logical_processors) of
|
|
|
|
1 -> lists:foreach(Fun, List);
|
|
|
|
_ ->
|
|
|
|
Self = self(),
|
|
|
|
lists:foreach(
|
|
|
|
fun({Pid, Ref}) ->
|
2019-06-14 11:33:26 +02:00
|
|
|
receive
|
2019-07-10 09:28:37 +02:00
|
|
|
Pid ->
|
|
|
|
receive
|
|
|
|
{'DOWN', Ref, _, _, _} ->
|
|
|
|
ok
|
|
|
|
end;
|
|
|
|
{'DOWN', Ref, _, _, Reason} ->
|
|
|
|
exit(Reason)
|
|
|
|
end
|
|
|
|
end, [spawn_monitor(
|
|
|
|
fun() -> Fun(X), Self ! self() end)
|
|
|
|
|| X <- List])
|
|
|
|
end;
|
2019-06-14 11:33:26 +02:00
|
|
|
peach(Fun, List) ->
|
|
|
|
lists:foreach(Fun, List).
|
|
|
|
|
2019-06-14 15:58:39 +02:00
|
|
|
-ifdef(HAVE_ERL_ERROR).
|
2019-06-14 11:33:26 +02:00
|
|
|
format_exception(Level, Class, Reason, Stacktrace) ->
|
|
|
|
erl_error:format_exception(
|
|
|
|
Level, Class, Reason, Stacktrace,
|
|
|
|
fun(_M, _F, _A) -> false end,
|
|
|
|
fun(Term, I) ->
|
|
|
|
io_lib:print(Term, I, 80, -1)
|
|
|
|
end).
|
2019-06-14 15:58:39 +02:00
|
|
|
-else.
|
|
|
|
format_exception(Level, Class, Reason, Stacktrace) ->
|
|
|
|
lib:format_exception(
|
|
|
|
Level, Class, Reason, Stacktrace,
|
|
|
|
fun(_M, _F, _A) -> false end,
|
|
|
|
fun(Term, I) ->
|
|
|
|
io_lib:print(Term, I, 80, -1)
|
|
|
|
end).
|
|
|
|
-endif.
|
2019-06-14 11:33:26 +02:00
|
|
|
|
2020-05-29 18:40:19 +02:00
|
|
|
-spec get_my_ipv4_address() -> inet:ip4_address().
|
|
|
|
get_my_ipv4_address() ->
|
2020-04-20 08:42:32 +02:00
|
|
|
{ok, MyHostName} = inet:gethostname(),
|
|
|
|
case inet:getaddr(MyHostName, inet) of
|
|
|
|
{ok, Addr} -> Addr;
|
|
|
|
{error, _} -> {127, 0, 0, 1}
|
|
|
|
end.
|
|
|
|
|
2020-05-29 18:40:19 +02:00
|
|
|
-spec get_my_ipv6_address() -> inet:ip6_address().
|
|
|
|
get_my_ipv6_address() ->
|
2020-05-19 22:55:12 +02:00
|
|
|
{ok, MyHostName} = inet:gethostname(),
|
|
|
|
case inet:getaddr(MyHostName, inet6) of
|
|
|
|
{ok, Addr} -> Addr;
|
|
|
|
{error, _} -> {0, 0, 0, 0, 0, 0, 0, 1}
|
|
|
|
end.
|
|
|
|
|
2019-06-14 11:33:26 +02:00
|
|
|
-spec parse_ip_mask(binary()) -> {ok, {inet:ip4_address(), 0..32}} |
|
|
|
|
{ok, {inet:ip6_address(), 0..128}} |
|
|
|
|
error.
|
|
|
|
parse_ip_mask(S) ->
|
|
|
|
case econf:validate(econf:ip_mask(), S) of
|
|
|
|
{ok, _} = Ret -> Ret;
|
|
|
|
_ -> error
|
|
|
|
end.
|
|
|
|
|
|
|
|
-spec match_ip_mask(inet:ip_address(), inet:ip_address(), 0..128) -> boolean().
|
|
|
|
match_ip_mask({_, _, _, _} = IP, {_, _, _, _} = Net, Mask) ->
|
|
|
|
IPInt = ip_to_integer(IP),
|
|
|
|
NetInt = ip_to_integer(Net),
|
|
|
|
M = bnot (1 bsl (32 - Mask) - 1),
|
|
|
|
IPInt band M =:= NetInt band M;
|
|
|
|
match_ip_mask({_, _, _, _, _, _, _, _} = IP,
|
|
|
|
{_, _, _, _, _, _, _, _} = Net, Mask) ->
|
|
|
|
IPInt = ip_to_integer(IP),
|
|
|
|
NetInt = ip_to_integer(Net),
|
|
|
|
M = bnot (1 bsl (128 - Mask) - 1),
|
|
|
|
IPInt band M =:= NetInt band M;
|
|
|
|
match_ip_mask({_, _, _, _} = IP,
|
|
|
|
{0, 0, 0, 0, 0, 16#FFFF, _, _} = Net, Mask) ->
|
|
|
|
IPInt = ip_to_integer({0, 0, 0, 0, 0, 16#FFFF, 0, 0}) + ip_to_integer(IP),
|
|
|
|
NetInt = ip_to_integer(Net),
|
|
|
|
M = bnot (1 bsl (128 - Mask) - 1),
|
|
|
|
IPInt band M =:= NetInt band M;
|
|
|
|
match_ip_mask({0, 0, 0, 0, 0, 16#FFFF, _, _} = IP,
|
|
|
|
{_, _, _, _} = Net, Mask) ->
|
|
|
|
IPInt = ip_to_integer(IP) - ip_to_integer({0, 0, 0, 0, 0, 16#FFFF, 0, 0}),
|
|
|
|
NetInt = ip_to_integer(Net),
|
|
|
|
M = bnot (1 bsl (32 - Mask) - 1),
|
|
|
|
IPInt band M =:= NetInt band M;
|
|
|
|
match_ip_mask(_, _, _) ->
|
|
|
|
false.
|
|
|
|
|
2019-09-20 11:36:31 +02:00
|
|
|
-spec format_hosts_list([binary(), ...]) -> iolist().
|
|
|
|
format_hosts_list([Host]) ->
|
|
|
|
Host;
|
|
|
|
format_hosts_list([H1, H2]) ->
|
|
|
|
[H1, " and ", H2];
|
|
|
|
format_hosts_list([H1, H2, H3]) ->
|
|
|
|
[H1, ", ", H2, " and ", H3];
|
|
|
|
format_hosts_list([H1, H2|Hs]) ->
|
2019-09-23 14:17:20 +02:00
|
|
|
io_lib:format("~ts, ~ts and ~B more hosts",
|
2019-09-20 11:36:31 +02:00
|
|
|
[H1, H2, length(Hs)]).
|
|
|
|
|
|
|
|
-spec format_cycle([atom(), ...]) -> iolist().
|
|
|
|
format_cycle([M1]) ->
|
|
|
|
atom_to_list(M1);
|
|
|
|
format_cycle([M1, M2]) ->
|
|
|
|
[atom_to_list(M1), " and ", atom_to_list(M2)];
|
|
|
|
format_cycle([M|Ms]) ->
|
|
|
|
atom_to_list(M) ++ ", " ++ format_cycle(Ms).
|
|
|
|
|
|
|
|
-spec delete_dir(file:filename_all()) -> ok | {error, file:posix()}.
|
|
|
|
delete_dir(Dir) ->
|
|
|
|
try
|
|
|
|
{ok, Entries} = file:list_dir(Dir),
|
|
|
|
lists:foreach(fun(Path) ->
|
|
|
|
case filelib:is_dir(Path) of
|
|
|
|
true ->
|
|
|
|
ok = delete_dir(Path);
|
|
|
|
false ->
|
|
|
|
ok = file:delete(Path)
|
|
|
|
end
|
|
|
|
end, [filename:join(Dir, Entry) || Entry <- Entries]),
|
|
|
|
ok = file:del_dir(Dir)
|
|
|
|
catch
|
|
|
|
_:{badmatch, {error, Error}} ->
|
|
|
|
{error, Error}
|
|
|
|
end.
|
|
|
|
|
2017-03-30 13:17:13 +02:00
|
|
|
%%%===================================================================
|
|
|
|
%%% Internal functions
|
|
|
|
%%%===================================================================
|
2018-04-03 00:12:43 +02:00
|
|
|
-spec url_encode(binary(), binary()) -> binary().
|
|
|
|
url_encode(<<H:8, T/binary>>, Acc) when
|
|
|
|
(H >= $a andalso H =< $z) orelse
|
|
|
|
(H >= $A andalso H =< $Z) orelse
|
|
|
|
(H >= $0 andalso H =< $9) orelse
|
|
|
|
H == $_ orelse
|
|
|
|
H == $. orelse
|
|
|
|
H == $- orelse
|
|
|
|
H == $/ orelse
|
|
|
|
H == $: ->
|
|
|
|
url_encode(T, <<Acc/binary, H>>);
|
|
|
|
url_encode(<<H:8, T/binary>>, Acc) ->
|
2018-04-03 00:21:33 +02:00
|
|
|
case integer_to_list(H, 16) of
|
2018-04-03 00:12:43 +02:00
|
|
|
[X, Y] -> url_encode(T, <<Acc/binary, $%, X, Y>>);
|
|
|
|
[X] -> url_encode(T, <<Acc/binary, $%, $0, X>>)
|
|
|
|
end;
|
|
|
|
url_encode(<<>>, Acc) ->
|
|
|
|
Acc.
|
|
|
|
|
2017-03-30 16:44:43 +02:00
|
|
|
-spec set_node_id(string(), binary()) -> pid().
|
|
|
|
set_node_id(PidStr, NodeBin) ->
|
|
|
|
ExtPidStr = erlang:pid_to_list(
|
|
|
|
binary_to_term(
|
|
|
|
<<131,103,100,(size(NodeBin)):16,NodeBin/binary,0:72>>)),
|
|
|
|
[H|_] = string:tokens(ExtPidStr, "."),
|
|
|
|
[_|T] = string:tokens(PidStr, "."),
|
|
|
|
erlang:list_to_pid(string:join([H|T], ".")).
|
2017-10-09 22:35:42 +02:00
|
|
|
|
|
|
|
-spec read_file(file:filename()) -> {ok, binary()} | {error, file:posix()}.
|
|
|
|
read_file(Path) ->
|
|
|
|
case file:read_file(Path) of
|
|
|
|
{ok, Data} ->
|
|
|
|
{ok, Data};
|
|
|
|
{error, Why} = Err ->
|
2019-09-23 14:17:20 +02:00
|
|
|
?ERROR_MSG("Failed to read file ~ts: ~ts",
|
2017-10-09 22:35:42 +02:00
|
|
|
[Path, file:format_error(Why)]),
|
|
|
|
Err
|
|
|
|
end.
|
2018-06-14 13:00:47 +02:00
|
|
|
|
|
|
|
-spec get_dir(string()) -> file:filename().
|
|
|
|
get_dir(Type) ->
|
|
|
|
Env = "EJABBERD_" ++ string:to_upper(Type) ++ "_PATH",
|
|
|
|
case os:getenv(Env) of
|
|
|
|
false ->
|
|
|
|
case code:priv_dir(ejabberd) of
|
|
|
|
{error, _} -> filename:join(["priv", Type]);
|
|
|
|
Path -> filename:join([Path, Type])
|
|
|
|
end;
|
|
|
|
Path ->
|
|
|
|
Path
|
|
|
|
end.
|
2019-02-27 11:00:02 +01:00
|
|
|
|
|
|
|
%% Generates erlang:timestamp() that is guaranteed to unique
|
|
|
|
-spec unique_timestamp() -> erlang:timestamp().
|
|
|
|
unique_timestamp() ->
|
2019-04-16 11:20:55 +02:00
|
|
|
{MS, S, _} = erlang:timestamp(),
|
|
|
|
{MS, S, erlang:unique_integer([positive, monotonic]) rem 1000000}.
|
2019-04-30 08:36:38 +02:00
|
|
|
|
|
|
|
%% Levenshtein distance
|
2019-06-27 14:22:27 +02:00
|
|
|
-spec ld(string(), string(), distance_cache()) -> {non_neg_integer(), distance_cache()}.
|
2019-04-30 08:36:38 +02:00
|
|
|
ld([] = S, T, Cache) ->
|
|
|
|
{length(T), maps:put({S, T}, length(T), Cache)};
|
|
|
|
ld(S, [] = T, Cache) ->
|
|
|
|
{length(S), maps:put({S, T}, length(S), Cache)};
|
|
|
|
ld([X|S], [X|T], Cache) ->
|
|
|
|
ld(S, T, Cache);
|
|
|
|
ld([_|ST] = S, [_|TT] = T, Cache) ->
|
|
|
|
try {maps:get({S, T}, Cache), Cache}
|
|
|
|
catch _:{badkey, _} ->
|
|
|
|
{L1, C1} = ld(S, TT, Cache),
|
|
|
|
{L2, C2} = ld(ST, T, C1),
|
|
|
|
{L3, C3} = ld(ST, TT, C2),
|
|
|
|
L = 1 + lists:min([L1, L2, L3]),
|
|
|
|
{L, maps:put({S, T}, L, C3)}
|
|
|
|
end.
|
2019-06-14 11:33:26 +02:00
|
|
|
|
|
|
|
-spec ip_to_integer(inet:ip_address()) -> non_neg_integer().
|
|
|
|
ip_to_integer({IP1, IP2, IP3, IP4}) ->
|
|
|
|
IP1 bsl 8 bor IP2 bsl 8 bor IP3 bsl 8 bor IP4;
|
|
|
|
ip_to_integer({IP1, IP2, IP3, IP4, IP5, IP6, IP7,
|
|
|
|
IP8}) ->
|
|
|
|
IP1 bsl 16 bor IP2 bsl 16 bor IP3 bsl 16 bor IP4 bsl 16
|
|
|
|
bor IP5 bsl 16 bor IP6 bsl 16 bor IP7 bsl 16 bor IP8.
|
2019-09-28 10:27:20 +02:00
|
|
|
|
|
|
|
-spec to_string(atom() | binary() | string()) -> string().
|
|
|
|
to_string(A) when is_atom(A) ->
|
|
|
|
atom_to_list(A);
|
|
|
|
to_string(B) when is_binary(B) ->
|
|
|
|
binary_to_list(B);
|
|
|
|
to_string(S) ->
|
|
|
|
S.
|