• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

processone / ejabberd / 747

27 Jun 2024 01:43PM UTC coverage: 32.123% (+0.8%) from 31.276%
747

push

github

badlop
Set version to 24.06

14119 of 43953 relevant lines covered (32.12%)

614.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

40.96
/src/misc.erl
1
%%%-------------------------------------------------------------------
2
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
3
%%% @doc
4
%%%   This is the place for some unsorted auxiliary functions
5
%%%   Some functions from jlib.erl are moved here
6
%%%   Mild rubbish heap is accepted ;)
7
%%% @end
8
%%% Created : 30 Mar 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
9
%%%
10
%%%
11
%%% ejabberd, Copyright (C) 2002-2024   ProcessOne
12
%%%
13
%%% This program is free software; you can redistribute it and/or
14
%%% modify it under the terms of the GNU General Public License as
15
%%% published by the Free Software Foundation; either version 2 of the
16
%%% License, or (at your option) any later version.
17
%%%
18
%%% This program is distributed in the hope that it will be useful,
19
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
20
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21
%%% General Public License for more details.
22
%%%
23
%%% You should have received a copy of the GNU General Public License along
24
%%% with this program; if not, write to the Free Software Foundation, Inc.,
25
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26
%%%
27
%%%-------------------------------------------------------------------
28
-module(misc).
29

30
%% API
31
-export([add_delay_info/3, add_delay_info/4,
32
         unwrap_carbon/1, unwrap_mucsub_message/1, is_standalone_chat_state/1,
33
         tolower/1, term_to_base64/1, base64_to_term/1, ip_to_list/1,
34
         hex_to_bin/1, hex_to_base64/1, url_encode/1, expand_keyword/3,
35
         atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
36
         l2i/1, i2l/1, i2l/2, expr_to_term/1, term_to_expr/1,
37
         now_to_usec/1, usec_to_now/1, encode_pid/1, decode_pid/2,
38
         compile_exprs/2, join_atoms/2, try_read_file/1, get_descr/2,
39
         css_dir/0, img_dir/0, js_dir/0, msgs_dir/0, sql_dir/0, lua_dir/0,
40
         read_css/1, read_img/1, read_js/1, read_lua/1,
41
         intersection/2, format_val/1, cancel_timer/1, unique_timestamp/0,
42
         is_mucsub_message/1, best_match/2, pmap/2, peach/2, format_exception/4,
43
         get_my_ipv4_address/0, get_my_ipv6_address/0, parse_ip_mask/1,
44
         crypto_hmac/3, crypto_hmac/4, uri_parse/1, uri_parse/2, uri_quote/1,
45
         json_encode/1, json_decode/1,
46
         set_proc_label/1,
47
         match_ip_mask/3, format_hosts_list/1, format_cycle/1, delete_dir/1,
48
         semver_to_xxyy/1, logical_processors/0, get_mucsub_event_type/1]).
49

50
%% Deprecated functions
51
-export([decode_base64/1, encode_base64/1]).
52
-deprecated([{decode_base64, 1},
53
             {encode_base64, 1}]).
54

55
-include("logger.hrl").
56
-include_lib("xmpp/include/xmpp.hrl").
57
-include_lib("kernel/include/file.hrl").
58

59
%% Copied from erlang/otp/lib/stdlib/src/re.erl
60
-type re_mp() :: {re_pattern, _, _, _, _}.
61
-export_type([re_mp/0]).
62

63
-ifdef(OTP_BELOW_27).
64
-type json_value() :: jiffy:json_value().
65
-else.
66
-type json_value() :: json:encode_value().
67
-endif.
68
-export_type([json_value/0]).
69

70
-type distance_cache() :: #{{string(), string()} => non_neg_integer()}.
71

72
-spec uri_parse(binary()|string()) -> {ok, string(), string(), string(), number(), string(), string()} | {error, term()}.
73
-ifdef(USE_OLD_HTTP_URI).
74
uri_parse(URL) when is_binary(URL) ->
75
    uri_parse(binary_to_list(URL));
76
uri_parse(URL) ->
77
    uri_parse(URL, []).
78

79
uri_parse(URL, Protocols) when is_binary(URL) ->
80
    uri_parse(binary_to_list(URL), Protocols);
81
uri_parse(URL, Protocols) ->
82
    case http_uri:parse(URL, [{scheme_defaults, Protocols}]) of
83
        {ok, {Scheme, UserInfo, Host, Port, Path, Query}} ->
84
            {ok, atom_to_list(Scheme), UserInfo, Host, Port, Path, Query};
85
        {error, _} = E ->
86
            E
87
    end.
88

89
-else.
90
uri_parse(URL) when is_binary(URL) ->
91
    uri_parse(binary_to_list(URL));
6✔
92
uri_parse(URL) ->
93
    uri_parse(URL, [{http, 80}, {https, 443}]).
6✔
94

95
uri_parse(URL, Protocols) when is_binary(URL) ->
96
    uri_parse(binary_to_list(URL), Protocols);
×
97
uri_parse(URL, Protocols) ->
98
    case uri_string:parse(URL) of
6✔
99
        #{scheme := Scheme, host := Host, port := Port, path := Path} = M1 ->
100
            {ok, Scheme, maps:get(userinfo, M1, ""), Host, Port, Path, maps:get(query, M1, "")};
6✔
101
        #{scheme := Scheme, host := Host, path := Path} = M2 ->
102
            case lists:keyfind(list_to_atom(Scheme), 1, Protocols) of
×
103
                {_, Port} ->
104
                    {ok, Scheme, maps:get(userinfo, M2, ""), Host, Port, Path, maps:get(query, M2, "")};
×
105
                _ ->
106
                    {error, unknown_protocol}
×
107
            end;
108
        {error, Atom, _} ->
109
            {error, Atom}
×
110
    end.
111
-endif.
112

113
-ifdef(OTP_BELOW_25).
114
-ifdef(OTP_BELOW_24).
115
uri_quote(Data) ->
116
    Data.
117
-else.
118
uri_quote(Data) ->
119
    http_uri:encode(Data).
120
-endif.
121
-else.
122
uri_quote(Data) ->
123
    uri_string:quote(Data).
×
124
-endif.
125

126
-ifdef(USE_OLD_CRYPTO_HMAC).
127
crypto_hmac(Type, Key, Data) -> crypto:hmac(Type, Key, Data).
128
crypto_hmac(Type, Key, Data, MacL) -> crypto:hmac(Type, Key, Data, MacL).
129
-else.
130
crypto_hmac(Type, Key, Data) -> crypto:mac(hmac, Type, Key, Data).
12✔
131
crypto_hmac(Type, Key, Data, MacL) -> crypto:macN(hmac, Type, Key, Data, MacL).
×
132
-endif.
133

134
-ifdef(OTP_BELOW_27).
135
json_encode(Term) ->
136
    jiffy:encode(Term).
12✔
137
json_decode(Bin) ->
138
    jiffy:decode(Bin, [return_maps]).
6✔
139
-else.
140
json_encode(Term) ->
141
    iolist_to_binary(json:encode(Term)).
142
json_decode(Bin) ->
143
    json:decode(Bin).
144
-endif.
145

146
%%%===================================================================
147
%%% API
148
%%%===================================================================
149
-spec add_delay_info(stanza(), jid(), erlang:timestamp()) -> stanza().
150
add_delay_info(Stz, From, Time) ->
151
    add_delay_info(Stz, From, Time, <<"">>).
378✔
152

153
-spec add_delay_info(stanza(), jid(), erlang:timestamp(), binary()) -> stanza().
154
add_delay_info(Stz, From, Time, Desc) ->
155
    Delays = xmpp:get_subtags(Stz, #delay{stamp = {0,0,0}}),
3,254✔
156
    Matching = lists:any(
3,254✔
157
        fun(#delay{from = OldFrom}) when is_record(OldFrom, jid) ->
158
               jid:tolower(From) == jid:tolower(OldFrom);
1,582✔
159
           (_) ->
160
               false
×
161
        end, Delays),
162
    case Matching of
3,254✔
163
        true ->
164
            Stz;
1,582✔
165
        _ ->
166
            NewDelay = #delay{stamp = Time, from = From, desc = Desc},
1,672✔
167
            xmpp:append_subtags(Stz, [NewDelay])
1,672✔
168
    end.
169

170
-spec unwrap_carbon(stanza()) -> xmpp_element().
171
unwrap_carbon(#message{} = Msg) ->
172
    try
1,259✔
173
        case xmpp:get_subtag(Msg, #carbons_sent{forwarded = #forwarded{}}) of
1,259✔
174
            #carbons_sent{forwarded = #forwarded{sub_els = [El]}} ->
175
                xmpp:decode(El, ?NS_CLIENT, [ignore_els]);
×
176
            _ ->
177
                case xmpp:get_subtag(Msg, #carbons_received{
1,259✔
178
                                              forwarded = #forwarded{}}) of
179
                    #carbons_received{forwarded = #forwarded{sub_els = [El]}} ->
180
                        xmpp:decode(El, ?NS_CLIENT, [ignore_els]);
×
181
                    _ ->
182
                        Msg
1,259✔
183
                end
184
        end
185
    catch _:{xmpp_codec, _} ->
186
            Msg
×
187
    end;
188
unwrap_carbon(Stanza) -> Stanza.
×
189

190
-spec unwrap_mucsub_message(xmpp_element()) -> message() | false.
191
unwrap_mucsub_message(#message{} = OuterMsg) ->
192
    case xmpp:get_subtag(OuterMsg, #ps_event{}) of
9,578✔
193
        #ps_event{
194
            items = #ps_items{
195
                node = Node,
196
                items = [
197
                    #ps_item{
198
                        sub_els = [#message{} = InnerMsg]} | _]}}
199
            when Node == ?NS_MUCSUB_NODES_MESSAGES;
200
                 Node == ?NS_MUCSUB_NODES_SUBJECT ->
201
            InnerMsg;
105✔
202
        _ ->
203
            false
9,473✔
204
    end;
205
unwrap_mucsub_message(_Packet) ->
206
    false.
×
207

208
-spec is_mucsub_message(xmpp_element()) -> boolean().
209
is_mucsub_message(Packet) ->
210
    get_mucsub_event_type(Packet) /= false.
6,388✔
211

212
-spec get_mucsub_event_type(xmpp_element()) -> binary() | false.
213
get_mucsub_event_type(#message{} = OuterMsg) ->
214
    case xmpp:get_subtag(OuterMsg, #ps_event{}) of
8,248✔
215
        #ps_event{
216
            items = #ps_items{
217
                node = Node}}
218
            when Node == ?NS_MUCSUB_NODES_MESSAGES;
219
                 Node == ?NS_MUCSUB_NODES_SUBJECT;
220
                 Node == ?NS_MUCSUB_NODES_AFFILIATIONS;
221
                 Node == ?NS_MUCSUB_NODES_CONFIG;
222
                 Node == ?NS_MUCSUB_NODES_PARTICIPANTS;
223
                 Node == ?NS_MUCSUB_NODES_PRESENCE;
224
                 Node == ?NS_MUCSUB_NODES_SUBSCRIBERS ->
225
            Node;
×
226
        _ ->
227
            false
8,248✔
228
    end;
229
get_mucsub_event_type(_Packet) ->
230
    false.
×
231

232
-spec is_standalone_chat_state(stanza()) -> boolean().
233
is_standalone_chat_state(Stanza) ->
234
    case unwrap_carbon(Stanza) of
1,259✔
235
        #message{body = [], subject = [], sub_els = Els} ->
236
            IgnoreNS = [?NS_CHATSTATES, ?NS_DELAY, ?NS_EVENT, ?NS_HINTS],
312✔
237
            Stripped = [El || El <- Els,
312✔
238
                              not lists:member(xmpp:get_ns(El), IgnoreNS)],
452✔
239
            Stripped == [];
312✔
240
        _ ->
241
            false
947✔
242
    end.
243

244
-spec tolower(binary()) -> binary().
245
tolower(B) ->
246
    iolist_to_binary(tolower_s(binary_to_list(B))).
42✔
247

248
tolower_s([C | Cs]) ->
249
    if C >= $A, C =< $Z -> [C + 32 | tolower_s(Cs)];
420✔
250
       true -> [C | tolower_s(Cs)]
420✔
251
    end;
252
tolower_s([]) -> [].
42✔
253

254
-spec term_to_base64(term()) -> binary().
255
term_to_base64(Term) ->
256
    encode_base64(term_to_binary(Term)).
1,183✔
257

258
-spec base64_to_term(binary()) -> {term, term()} | error.
259
base64_to_term(Base64) ->
260
    try binary_to_term(base64:decode(Base64), [safe]) of
2✔
261
        Term -> {term, Term}
2✔
262
    catch _:_ ->
263
            error
×
264
    end.
265

266
-spec decode_base64(binary()) -> binary().
267
decode_base64(S) ->
268
    try base64:mime_decode(S)
×
269
    catch _:badarg -> <<>>
×
270
    end.
271

272
-spec encode_base64(binary()) -> binary().
273
encode_base64(Data) ->
274
    base64:encode(Data).
1,183✔
275

276
-spec ip_to_list(inet:ip_address() | undefined |
277
                 {inet:ip_address(), inet:port_number()}) -> binary().
278

279
ip_to_list({IP, _Port}) ->
280
    ip_to_list(IP);
1,307✔
281
%% This function clause could use inet_parse too:
282
ip_to_list(undefined) ->
283
    <<"unknown">>;
×
284
ip_to_list(local) ->
285
    <<"unix">>;
×
286
ip_to_list(IP) ->
287
    list_to_binary(inet_parse:ntoa(IP)).
1,313✔
288

289
-spec hex_to_bin(binary()) -> binary().
290
hex_to_bin(Hex) ->
291
    hex_to_bin(binary_to_list(Hex), []).
×
292

293
-spec hex_to_bin(list(), list()) -> binary().
294
hex_to_bin([], Acc) ->
295
    list_to_binary(lists:reverse(Acc));
×
296
hex_to_bin([H1, H2 | T], Acc) ->
297
    {ok, [V], []} = io_lib:fread("~16u", [H1, H2]),
×
298
    hex_to_bin(T, [V | Acc]).
×
299

300
-spec hex_to_base64(binary()) -> binary().
301
hex_to_base64(Hex) ->
302
    base64:encode(hex_to_bin(Hex)).
×
303

304
-spec url_encode(binary()) -> binary().
305
url_encode(A) ->
306
    url_encode(A, <<>>).
77✔
307

308
-spec expand_keyword(iodata(), iodata(), iodata()) -> binary().
309
expand_keyword(Keyword, Input, Replacement) ->
310
    re:replace(Input, Keyword, Replacement,
20✔
311
               [{return, binary}, global]).
312

313
binary_to_atom(Bin) ->
314
    erlang:binary_to_atom(Bin, utf8).
54,097✔
315

316
tuple_to_binary(T) ->
317
    iolist_to_binary(tuple_to_list(T)).
×
318

319
%% erlang:atom_to_binary/1 is available since OTP 23
320
%% https://www.erlang.org/doc/apps/erts/erlang#atom_to_binary/1
321
%% Let's use /2 for backwards compatibility.
322
atom_to_binary(A) ->
323
    erlang:atom_to_binary(A, utf8).
27,664✔
324

325
expr_to_term(Expr) ->
326
    Str = binary_to_list(<<Expr/binary, ".">>),
×
327
    {ok, Tokens, _} = erl_scan:string(Str),
×
328
    {ok, Term} = erl_parse:parse_term(Tokens),
×
329
    Term.
×
330

331
term_to_expr(Term) ->
332
    list_to_binary(io_lib:print(Term, 1, 999999, -1)).
4,137✔
333

334
-spec now_to_usec(erlang:timestamp()) -> non_neg_integer().
335
now_to_usec({MSec, Sec, USec}) ->
336
    (MSec*1000000 + Sec)*1000000 + USec.
118✔
337

338
-spec usec_to_now(non_neg_integer()) -> erlang:timestamp().
339
usec_to_now(Int) ->
340
    Secs = Int div 1000000,
2,795✔
341
    USec = Int rem 1000000,
2,795✔
342
    MSec = Secs div 1000000,
2,795✔
343
    Sec = Secs rem 1000000,
2,795✔
344
    {MSec, Sec, USec}.
2,795✔
345

346
l2i(I) when is_integer(I) -> I;
×
347
l2i(L) when is_binary(L) -> binary_to_integer(L).
×
348

349
i2l(I) when is_integer(I) -> integer_to_binary(I);
6,079✔
350
i2l(L) when is_binary(L) -> L.
×
351

352
i2l(I, N) when is_integer(I) -> i2l(i2l(I), N);
540✔
353
i2l(L, N) when is_binary(L) ->
354
    case str:len(L) of
948✔
355
      N -> L;
540✔
356
      C when C > N -> L;
×
357
      _ -> i2l(<<$0, L/binary>>, N)
408✔
358
    end.
359

360
-spec encode_pid(pid()) -> binary().
361
encode_pid(Pid) ->
362
    list_to_binary(erlang:pid_to_list(Pid)).
1,179✔
363

364
-spec decode_pid(binary(), binary()) -> pid().
365
decode_pid(PidBin, NodeBin) ->
366
    PidStr = binary_to_list(PidBin),
2,257✔
367
    Pid = erlang:list_to_pid(PidStr),
2,257✔
368
    case erlang:binary_to_atom(NodeBin, latin1) of
2,257✔
369
        Node when Node == node() ->
370
            Pid;
2,257✔
371
        Node ->
372
            try set_node_id(PidStr, NodeBin)
×
373
            catch _:badarg ->
374
                    erlang:error({bad_node, Node})
×
375
            end
376
    end.
377

378
-spec compile_exprs(module(), [string()]) -> ok | {error, any()}.
379
compile_exprs(Mod, Exprs) ->
380
    try
×
381
        Forms = lists:map(
×
382
                  fun(Expr) ->
383
                          {ok, Tokens, _} = erl_scan:string(lists:flatten(Expr)),
×
384
                          {ok, Form} = erl_parse:parse_form(Tokens),
×
385
                          Form
×
386
                  end, Exprs),
387
        {ok, Code} = case compile:forms(Forms, []) of
×
388
                         {ok, Mod, Bin} -> {ok, Bin};
×
389
                         {ok, Mod, Bin, _Warnings} -> {ok, Bin};
×
390
                         Error -> Error
×
391
                     end,
392
        {module, Mod} = code:load_binary(Mod, "nofile", Code),
×
393
        ok
×
394
    catch _:{badmatch, {error, ErrInfo, _ErrLocation}} ->
395
            {error, ErrInfo};
×
396
          _:{badmatch, {error, _} = Err} ->
397
            Err;
×
398
          _:{badmatch, error} ->
399
            {error, compile_failed}
×
400
    end.
401

402
-spec join_atoms([atom()], binary()) -> binary().
403
join_atoms(Atoms, Sep) ->
404
    str:join([io_lib:format("~p", [A]) || A <- lists:sort(Atoms)], Sep).
×
405

406
%% @doc Checks if the file is readable and converts its name to binary.
407
%%      Fails with `badarg' otherwise. The function is intended for usage
408
%%      in configuration validators only.
409
-spec try_read_file(file:filename_all()) -> binary().
410
try_read_file(Path) ->
411
    case file:open(Path, [read]) of
×
412
        {ok, Fd} ->
413
            file:close(Fd),
×
414
            iolist_to_binary(Path);
×
415
        {error, Why} ->
416
            ?ERROR_MSG("Failed to read ~ts: ~ts", [Path, file:format_error(Why)]),
×
417
            erlang:error(badarg)
×
418
    end.
419

420
-spec css_dir() -> file:filename().
421
css_dir() ->
422
    get_dir("css").
×
423

424
-spec img_dir() -> file:filename().
425
img_dir() ->
426
    get_dir("img").
×
427

428
-spec js_dir() -> file:filename().
429
js_dir() ->
430
    get_dir("js").
×
431

432
-spec msgs_dir() -> file:filename().
433
msgs_dir() ->
434
    get_dir("msgs").
1✔
435

436
-spec sql_dir() -> file:filename().
437
sql_dir() ->
438
    get_dir("sql").
1✔
439

440
-spec lua_dir() -> file:filename().
441
lua_dir() ->
442
    get_dir("lua").
1✔
443

444
-spec read_css(file:filename()) -> {ok, binary()} | {error, file:posix()}.
445
read_css(File) ->
446
    read_file(filename:join(css_dir(), File)).
×
447

448
-spec read_img(file:filename()) -> {ok, binary()} | {error, file:posix()}.
449
read_img(File) ->
450
    read_file(filename:join(img_dir(), File)).
×
451

452
-spec read_js(file:filename()) -> {ok, binary()} | {error, file:posix()}.
453
read_js(File) ->
454
    read_file(filename:join(js_dir(), File)).
×
455

456
-spec read_lua(file:filename()) -> {ok, binary()} | {error, file:posix()}.
457
read_lua(File) ->
458
    read_file(filename:join(lua_dir(), File)).
1✔
459

460
-spec get_descr(binary(), binary()) -> binary().
461
get_descr(Lang, Text) ->
462
    Desc = translate:translate(Lang, Text),
×
463
    Copyright = ejabberd_config:get_copyright(),
×
464
    <<Desc/binary, $\n, Copyright/binary>>.
×
465

466
-spec intersection(list(), list()) -> list().
467
intersection(L1, L2) ->
468
    lists:filter(
1✔
469
      fun(E) ->
470
              lists:member(E, L2)
9✔
471
      end, L1).
472

473
-spec format_val(any()) -> iodata().
474
format_val({yaml, S}) when is_integer(S); is_binary(S); is_atom(S) ->
475
    format_val(S);
×
476
format_val({yaml, YAML}) ->
477
    S = try fast_yaml:encode(YAML)
×
478
        catch _:_ -> YAML
×
479
        end,
480
    format_val(S);
×
481
format_val(I) when is_integer(I) ->
482
    integer_to_list(I);
×
483
format_val(B) when is_atom(B) ->
484
    erlang:atom_to_binary(B, utf8);
×
485
format_val(Term) ->
486
    S = try iolist_to_binary(Term)
×
487
        catch _:_ -> list_to_binary(io_lib:format("~p", [Term]))
×
488
        end,
489
    case binary:match(S, <<"\n">>) of
×
490
        nomatch -> S;
×
491
        _ -> [io_lib:nl(), S]
×
492
    end.
493

494
-spec cancel_timer(reference() | undefined) -> ok.
495
cancel_timer(TRef) when is_reference(TRef) ->
496
    case erlang:cancel_timer(TRef) of
31✔
497
        false ->
498
            receive {timeout, TRef, _} -> ok
×
499
            after 0 -> ok
×
500
            end;
501
        _ ->
502
            ok
31✔
503
    end;
504
cancel_timer(_) ->
505
    ok.
2✔
506

507
-spec best_match(atom() | binary() | string(),
508
                 [atom() | binary() | string()]) -> string().
509
best_match(Pattern, []) ->
510
    Pattern;
×
511
best_match(Pattern, Opts) ->
512
    String = to_string(Pattern),
×
513
    {Ds, _} = lists:mapfoldl(
×
514
                fun(Opt, Cache) ->
515
                        SOpt = to_string(Opt),
×
516
                        {Distance, Cache1} = ld(String, SOpt, Cache),
×
517
                        {{Distance, SOpt}, Cache1}
×
518
                end, #{}, Opts),
519
    element(2, lists:min(Ds)).
×
520

521
-spec logical_processors() -> non_neg_integer().
522
logical_processors() ->
523
    case erlang:system_info(logical_processors) of
297✔
524
        V when is_integer(V), V >= 2  -> V;
297✔
525
        _ -> 1
×
526
    end.
527

528
-spec pmap(fun((T1) -> T2), [T1]) -> [T2].
529
pmap(Fun, [_,_|_] = List) ->
530
    case logical_processors() of
1✔
531
        1 -> lists:map(Fun, List);
×
532
        _ ->
533
            Self = self(),
1✔
534
            lists:map(
1✔
535
              fun({Pid, Ref}) ->
536
                      receive
30✔
537
                          {Pid, Ret} ->
538
                              receive
30✔
539
                                  {'DOWN', Ref, _, _, _} ->
540
                                      Ret
30✔
541
                              end;
542
                          {'DOWN', Ref, _, _, Reason} ->
543
                              exit(Reason)
×
544
                      end
545
              end, [spawn_monitor(
30✔
546
                      fun() -> Self ! {self(), Fun(X)} end)
30✔
547
                    || X <- List])
1✔
548
    end;
549
pmap(Fun, List) ->
550
    lists:map(Fun, List).
×
551

552
-spec peach(fun((T) -> any()), [T]) -> ok.
553
peach(Fun, [_,_|_] = List) ->
554
    case logical_processors() of
1✔
555
        1 -> lists:foreach(Fun, List);
×
556
        _ ->
557
            Self = self(),
1✔
558
            lists:foreach(
1✔
559
              fun({Pid, Ref}) ->
560
                      receive
36✔
561
                          Pid ->
562
                              receive
36✔
563
                                  {'DOWN', Ref, _, _, _} ->
564
                                      ok
36✔
565
                              end;
566
                          {'DOWN', Ref, _, _, Reason} ->
567
                              exit(Reason)
×
568
                      end
569
              end, [spawn_monitor(
36✔
570
                      fun() -> Fun(X), Self ! self() end)
36✔
571
                    || X <- List])
1✔
572
    end;
573
peach(Fun, List) ->
574
    lists:foreach(Fun, List).
×
575

576
-ifdef(HAVE_ERL_ERROR).
577
format_exception(Level, Class, Reason, Stacktrace) ->
578
    erl_error:format_exception(
×
579
      Level, Class, Reason, Stacktrace,
580
      fun(_M, _F, _A) -> false end,
×
581
      fun(Term, I) ->
582
              io_lib:print(Term, I, 80, -1)
×
583
      end).
584
-else.
585
format_exception(Level, Class, Reason, Stacktrace) ->
586
    lib:format_exception(
587
      Level, Class, Reason, Stacktrace,
588
      fun(_M, _F, _A) -> false end,
589
      fun(Term, I) ->
590
              io_lib:print(Term, I, 80, -1)
591
      end).
592
-endif.
593

594
-spec get_my_ipv4_address() -> inet:ip4_address().
595
get_my_ipv4_address() ->
596
    {ok, MyHostName} = inet:gethostname(),
11✔
597
    case inet:getaddr(MyHostName, inet) of
11✔
598
        {ok, Addr} -> Addr;
11✔
599
        {error, _} -> {127, 0, 0, 1}
×
600
    end.
601

602
-spec get_my_ipv6_address() -> inet:ip6_address().
603
get_my_ipv6_address() ->
604
    {ok, MyHostName} = inet:gethostname(),
×
605
    case inet:getaddr(MyHostName, inet6) of
×
606
        {ok, Addr} -> Addr;
×
607
        {error, _} -> {0, 0, 0, 0, 0, 0, 0, 1}
×
608
    end.
609

610
-spec parse_ip_mask(binary()) -> {ok, {inet:ip4_address(), 0..32}} |
611
                                 {ok, {inet:ip6_address(), 0..128}} |
612
                                 error.
613
parse_ip_mask(S) ->
614
    case econf:validate(econf:ip_mask(), S) of
×
615
        {ok, _} = Ret -> Ret;
×
616
        _ -> error
×
617
    end.
618

619
-spec match_ip_mask(inet:ip_address(), inet:ip_address(), 0..128) -> boolean().
620
match_ip_mask({_, _, _, _} = IP, {_, _, _, _} = Net, Mask) ->
621
    IPInt = ip_to_integer(IP),
×
622
    NetInt = ip_to_integer(Net),
×
623
    M = bnot (1 bsl (32 - Mask) - 1),
×
624
    IPInt band M =:= NetInt band M;
×
625
match_ip_mask({_, _, _, _, _, _, _, _} = IP,
626
                {_, _, _, _, _, _, _, _} = Net, Mask) ->
627
    IPInt = ip_to_integer(IP),
×
628
    NetInt = ip_to_integer(Net),
×
629
    M = bnot (1 bsl (128 - Mask) - 1),
×
630
    IPInt band M =:= NetInt band M;
×
631
match_ip_mask({_, _, _, _} = IP,
632
                {0, 0, 0, 0, 0, 16#FFFF, _, _} = Net, Mask) ->
633
    IPInt = ip_to_integer({0, 0, 0, 0, 0, 16#FFFF, 0, 0}) + ip_to_integer(IP),
×
634
    NetInt = ip_to_integer(Net),
×
635
    M = bnot (1 bsl (128 - Mask) - 1),
×
636
    IPInt band M =:= NetInt band M;
×
637
match_ip_mask({0, 0, 0, 0, 0, 16#FFFF, _, _} = IP,
638
                {_, _, _, _} = Net, Mask) ->
639
    IPInt = ip_to_integer(IP) - ip_to_integer({0, 0, 0, 0, 0, 16#FFFF, 0, 0}),
×
640
    NetInt = ip_to_integer(Net),
×
641
    M = bnot (1 bsl (32 - Mask) - 1),
×
642
    IPInt band M =:= NetInt band M;
×
643
match_ip_mask(_, _, _) ->
644
    false.
×
645

646
-spec format_hosts_list([binary(), ...]) -> iolist().
647
format_hosts_list([Host]) ->
648
    Host;
×
649
format_hosts_list([H1, H2]) ->
650
    [H1, " and ", H2];
×
651
format_hosts_list([H1, H2, H3]) ->
652
    [H1, ", ", H2, " and ", H3];
×
653
format_hosts_list([H1, H2|Hs]) ->
654
    io_lib:format("~ts, ~ts and ~B more hosts",
1✔
655
                  [H1, H2, length(Hs)]).
656

657
-spec format_cycle([atom(), ...]) -> iolist().
658
format_cycle([M1]) ->
659
    atom_to_list(M1);
×
660
format_cycle([M1, M2]) ->
661
    [atom_to_list(M1), " and ", atom_to_list(M2)];
×
662
format_cycle([M|Ms]) ->
663
    atom_to_list(M) ++ ", " ++ format_cycle(Ms).
×
664

665
-spec delete_dir(file:filename_all()) -> ok | {error, file:posix()}.
666
delete_dir(Dir) ->
667
    try
1✔
668
        {ok, Entries} = file:list_dir(Dir),
1✔
669
        lists:foreach(fun(Path) ->
×
670
                              case filelib:is_dir(Path) of
×
671
                                  true ->
672
                                      ok = delete_dir(Path);
×
673
                                  false ->
674
                                      ok = file:delete(Path)
×
675
                              end
676
                      end, [filename:join(Dir, Entry) || Entry <- Entries]),
×
677
        ok = file:del_dir(Dir)
×
678
    catch
679
        _:{badmatch, {error, Error}} ->
680
            {error, Error}
1✔
681
    end.
682

683
-spec semver_to_xxyy(binary()) -> binary().
684
semver_to_xxyy(<<Y1, Y2, $., M2, $., $0>>) ->
685
    <<Y1, Y2, $., $0, M2>>;
×
686
semver_to_xxyy(<<Y1, Y2, $., M2, $., Patch/binary>>) ->
687
    <<Y1, Y2, $., $0, M2, $., Patch/binary>>;
×
688
semver_to_xxyy(<<Y1, Y2, $., M1, M2, $., $0>>) ->
689
    <<Y1, Y2, $., M1, M2>>;
×
690
semver_to_xxyy(Version) when is_binary(Version) ->
691
    Version.
×
692

693
%%%===================================================================
694
%%% Internal functions
695
%%%===================================================================
696
-spec url_encode(binary(), binary()) -> binary().
697
url_encode(<<H:8, T/binary>>, Acc) when
698
  (H >= $a andalso H =< $z) orelse
699
  (H >= $A andalso H =< $Z) orelse
700
  (H >= $0 andalso H =< $9) orelse
701
  H == $_ orelse
702
  H == $. orelse
703
  H == $- orelse
704
  H == $/ orelse
705
  H == $: ->
706
    url_encode(T, <<Acc/binary, H>>);
2,611✔
707
url_encode(<<H:8, T/binary>>, Acc) ->
708
    case integer_to_list(H, 16) of
725✔
709
        [X, Y] -> url_encode(T, <<Acc/binary, $%, X, Y>>);
725✔
710
        [X] -> url_encode(T, <<Acc/binary, $%, $0, X>>)
×
711
    end;
712
url_encode(<<>>, Acc) ->
713
    Acc.
77✔
714

715
-spec set_node_id(string(), binary()) -> pid().
716
set_node_id(PidStr, NodeBin) ->
717
    ExtPidStr = erlang:pid_to_list(
×
718
                  binary_to_term(
719
                    <<131,103,100,(size(NodeBin)):16,NodeBin/binary,0:72>>)),
720
    [H|_] = string:tokens(ExtPidStr, "."),
×
721
    [_|T] = string:tokens(PidStr, "."),
×
722
    erlang:list_to_pid(string:join([H|T], ".")).
×
723

724
-spec read_file(file:filename()) -> {ok, binary()} | {error, file:posix()}.
725
read_file(Path) ->
726
    case file:read_file(Path) of
1✔
727
        {ok, Data} ->
728
            {ok, Data};
1✔
729
        {error, Why} = Err ->
730
            ?ERROR_MSG("Failed to read file ~ts: ~ts",
×
731
                       [Path, file:format_error(Why)]),
×
732
            Err
×
733
    end.
734

735
-spec get_dir(string()) -> file:filename().
736
get_dir(Type) ->
737
    Env = "EJABBERD_" ++ string:to_upper(Type) ++ "_PATH",
3✔
738
    case os:getenv(Env) of
3✔
739
        false ->
740
            case code:priv_dir(ejabberd) of
3✔
741
                {error, _} -> filename:join(["priv", Type]);
×
742
                Path -> filename:join([Path, Type])
3✔
743
            end;
744
        Path ->
745
            Path
×
746
    end.
747

748
%% Generates erlang:timestamp() that is guaranteed to unique
749
-spec unique_timestamp() -> erlang:timestamp().
750
unique_timestamp() ->
751
    {MS, S, _} = erlang:timestamp(),
1,230✔
752
    {MS, S, erlang:unique_integer([positive, monotonic]) rem 1000000}.
1,230✔
753

754
%% Levenshtein distance
755
-spec ld(string(), string(), distance_cache()) -> {non_neg_integer(), distance_cache()}.
756
ld([] = S, T, Cache) ->
757
    {length(T), maps:put({S, T}, length(T), Cache)};
×
758
ld(S, [] = T, Cache) ->
759
    {length(S), maps:put({S, T}, length(S), Cache)};
×
760
ld([X|S], [X|T], Cache) ->
761
    ld(S, T, Cache);
×
762
ld([_|ST] = S, [_|TT] = T, Cache) ->
763
    try {maps:get({S, T}, Cache), Cache}
×
764
    catch _:{badkey, _} ->
765
            {L1, C1} = ld(S, TT, Cache),
×
766
            {L2, C2} = ld(ST, T, C1),
×
767
            {L3, C3} = ld(ST, TT, C2),
×
768
            L = 1 + lists:min([L1, L2, L3]),
×
769
            {L, maps:put({S, T}, L, C3)}
×
770
    end.
771

772
-spec ip_to_integer(inet:ip_address()) -> non_neg_integer().
773
ip_to_integer({IP1, IP2, IP3, IP4}) ->
774
    IP1 bsl 8 bor IP2 bsl 8 bor IP3 bsl 8 bor IP4;
×
775
ip_to_integer({IP1, IP2, IP3, IP4, IP5, IP6, IP7,
776
               IP8}) ->
777
    IP1 bsl 16 bor IP2 bsl 16 bor IP3 bsl 16 bor IP4 bsl 16
778
        bor IP5 bsl 16 bor IP6 bsl 16 bor IP7 bsl 16 bor IP8.
×
779

780
-spec to_string(atom() | binary() | string()) -> string().
781
to_string(A) when is_atom(A) ->
782
    atom_to_list(A);
×
783
to_string(B) when is_binary(B) ->
784
    binary_to_list(B);
×
785
to_string(S) ->
786
    S.
×
787

788
-ifdef(OTP_BELOW_27).
789
set_proc_label(_Label) ->
790
    ok.
2,818✔
791
-else.
792
set_proc_label(Label) ->
793
    proc_lib:set_label(Label).
794
-endif.
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc