diff options
Diffstat (limited to 'src/misc.erl')
-rw-r--r-- | src/misc.erl | 678 |
1 files changed, 678 insertions, 0 deletions
diff --git a/src/misc.erl b/src/misc.erl new file mode 100644 index 000000000..e29f73fc1 --- /dev/null +++ b/src/misc.erl @@ -0,0 +1,678 @@ +%%%------------------------------------------------------------------- +%%% @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> +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2019 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(misc). + +%% API +-export([add_delay_info/3, add_delay_info/4, + unwrap_carbon/1, unwrap_mucsub_message/1, is_standalone_chat_state/1, + tolower/1, term_to_base64/1, base64_to_term/1, ip_to_list/1, + hex_to_bin/1, hex_to_base64/1, url_encode/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, + now_to_usec/1, usec_to_now/1, encode_pid/1, decode_pid/2, + compile_exprs/2, join_atoms/2, try_read_file/1, get_descr/2, + 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, + intersection/2, format_val/1, cancel_timer/1, unique_timestamp/0, + is_mucsub_message/1, best_match/2, pmap/2, peach/2, format_exception/4, + parse_ip_mask/1, match_ip_mask/3, format_hosts_list/1, format_cycle/1, + delete_dir/1]). + +%% Deprecated functions +-export([decode_base64/1, encode_base64/1]). +-deprecated([{decode_base64, 1}, + {encode_base64, 1}]). + +-include("logger.hrl"). +-include("xmpp.hrl"). +-include_lib("kernel/include/file.hrl"). + +-type distance_cache() :: #{{string(), string()} => non_neg_integer()}. + +%%%=================================================================== +%%% API +%%%=================================================================== +-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) -> + Delays = xmpp:get_subtags(Stz, #delay{stamp = {0,0,0}}), + 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; + _ -> + NewDelay = #delay{stamp = Time, from = From, desc = Desc}, + 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. + +-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. + +-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. + +-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. + +-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) -> + try binary_to_term(base64:decode(Base64), [safe]) of + Term -> {term, Term} + catch _:_ -> + error + end. + +-spec decode_base64(binary()) -> binary(). +decode_base64(S) -> + try base64:mime_decode(S) + catch _:badarg -> <<>> + end. + +-spec encode_base64(binary()) -> binary(). +encode_base64(Data) -> + base64:encode(Data). + +-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) -> + base64:encode(hex_to_bin(Hex)). + +-spec url_encode(binary()) -> binary(). +url_encode(A) -> + url_encode(A, <<>>). + +-spec expand_keyword(iodata(), iodata(), iodata()) -> binary(). +expand_keyword(Keyword, Input, Replacement) -> + re:replace(Input, Keyword, Replacement, + [{return, binary}, global]). + +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)). + +-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}. + +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. + +-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 -> + erlang:error({bad_node, Node}) + end + end. + +-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. + +-spec join_atoms([atom()], binary()) -> binary(). +join_atoms(Atoms, Sep) -> + str:join([io_lib:format("~p", [A]) || A <- lists:sort(Atoms)], Sep). + +%% @doc Checks if the file is readable and converts its name to binary. +%% Fails with `badarg` otherwise. The function is intended for usage +%% in configuration validators only. +-spec try_read_file(file:filename_all()) -> binary(). +try_read_file(Path) -> + case file:open(Path, [read]) of + {ok, Fd} -> + file:close(Fd), + iolist_to_binary(Path); + {error, Why} -> + ?ERROR_MSG("Failed to read ~ts: ~ts", [Path, file:format_error(Why)]), + erlang:error(badarg) + end. + +%% @doc Checks if the URL is valid HTTP(S) URL and converts its name to binary. +%% Fails with `badarg` otherwise. The function is intended for usage +%% in configuration validators only. +-spec try_url(binary() | string()) -> binary(). +try_url(URL0) -> + URL = case URL0 of + V when is_binary(V) -> binary_to_list(V); + _ -> URL0 + end, + case http_uri:parse(URL) of + {ok, {Scheme, _, _, _, _, _}} when Scheme /= http, Scheme /= https -> + ?ERROR_MSG("Unsupported URI scheme: ~ts", [URL]), + erlang:error(badarg); + {ok, {_, _, Host, _, _, _}} when Host == ""; Host == <<"">> -> + ?ERROR_MSG("Invalid URL: ~ts", [URL]), + erlang:error(badarg); + {ok, _} -> + iolist_to_binary(URL); + {error, _} -> + ?ERROR_MSG("Invalid URL: ~ts", [URL]), + erlang:error(badarg) + end. + +-spec css_dir() -> file:filename(). +css_dir() -> + get_dir("css"). + +-spec img_dir() -> file:filename(). +img_dir() -> + get_dir("img"). + +-spec js_dir() -> file:filename(). +js_dir() -> + get_dir("js"). + +-spec msgs_dir() -> file:filename(). +msgs_dir() -> + get_dir("msgs"). + +-spec sql_dir() -> file:filename(). +sql_dir() -> + get_dir("sql"). + +-spec lua_dir() -> file:filename(). +lua_dir() -> + get_dir("lua"). + +-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)). + +-spec read_lua(file:filename()) -> {ok, binary()} | {error, file:posix()}. +read_lua(File) -> + read_file(filename:join(lua_dir(), File)). + +-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>>. + +-spec intersection(list(), list()) -> list(). +intersection(L1, L2) -> + lists:filter( + fun(E) -> + lists:member(E, L2) + end, L1). + +-spec format_val(any()) -> iodata(). +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); +format_val(I) when is_integer(I) -> + integer_to_list(I); +format_val(B) when is_atom(B) -> + erlang:atom_to_binary(B, utf8); +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] + end. + +-spec cancel_timer(reference() | undefined) -> ok. +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. + +-spec best_match(atom() | binary() | string(), + [atom() | binary() | string()]) -> string(). +best_match(Pattern, []) -> + Pattern; +best_match(Pattern, Opts) -> + String = to_string(Pattern), + {Ds, _} = lists:mapfoldl( + fun(Opt, Cache) -> + SOpt = to_string(Opt), + {Distance, Cache1} = ld(String, SOpt, Cache), + {{Distance, SOpt}, Cache1} + end, #{}, Opts), + element(2, lists:min(Ds)). + +-spec pmap(fun((T1) -> T2), [T1]) -> [T2]. +pmap(Fun, [_,_|_] = List) -> + case erlang:system_info(logical_processors) of + 1 -> lists:map(Fun, List); + _ -> + Self = self(), + lists:map( + fun({Pid, Ref}) -> + receive + {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; +pmap(Fun, List) -> + lists:map(Fun, List). + +-spec peach(fun((T) -> any()), [T]) -> ok. +peach(Fun, [_,_|_] = List) -> + case erlang:system_info(logical_processors) of + 1 -> lists:foreach(Fun, List); + _ -> + Self = self(), + lists:foreach( + fun({Pid, Ref}) -> + receive + Pid -> + receive + {'DOWN', Ref, _, _, _} -> + ok + end; + {'DOWN', Ref, _, _, Reason} -> + exit(Reason) + end + end, [spawn_monitor( + fun() -> Fun(X), Self ! self() end) + || X <- List]) + end; +peach(Fun, List) -> + lists:foreach(Fun, List). + +-ifdef(HAVE_ERL_ERROR). +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). +-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. + +-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. + +-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]) -> + io_lib:format("~ts, ~ts and ~B more hosts", + [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. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +-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) -> + case integer_to_list(H, 16) of + [X, Y] -> url_encode(T, <<Acc/binary, $%, X, Y>>); + [X] -> url_encode(T, <<Acc/binary, $%, $0, X>>) + end; +url_encode(<<>>, Acc) -> + Acc. + +-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], ".")). + +-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 -> + ?ERROR_MSG("Failed to read file ~ts: ~ts", + [Path, file:format_error(Why)]), + Err + end. + +-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. + +%% Generates erlang:timestamp() that is guaranteed to unique +-spec unique_timestamp() -> erlang:timestamp(). +unique_timestamp() -> + {MS, S, _} = erlang:timestamp(), + {MS, S, erlang:unique_integer([positive, monotonic]) rem 1000000}. + +%% Levenshtein distance +-spec ld(string(), string(), distance_cache()) -> {non_neg_integer(), distance_cache()}. +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. + +-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. + +-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. |