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

processone / ejabberd / 1195

14 Nov 2025 07:30AM UTC coverage: 14.534% (-19.2%) from 33.775%
1195

Pull #4493

github

web-flow
Merge 53c116bc1 into 026bd24a5
Pull Request #4493: Great invitations

0 of 521 new or added lines in 8 files covered. (0.0%)

2195 existing lines in 61 files now uncovered.

6752 of 46457 relevant lines covered (14.53%)

47.62 hits per line

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

28.04
/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-2025   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
         get_home/0, warn_unset_home/0,
40
         css_dir/0, img_dir/0, js_dir/0, msgs_dir/0, sql_dir/0, lua_dir/0,
41
         read_css/1, read_img/1, read_js/1, read_lua/1,
42
         intersection/2, format_val/1, cancel_timer/1, unique_timestamp/0,
43
         is_mucsub_message/1, best_match/2, pmap/2, peach/2, format_exception/4,
44
         get_my_ipv4_address/0, get_my_ipv6_address/0, parse_ip_mask/1,
45
         uri_parse/1, uri_parse/2,
46
         json_encode/1, json_decode/1,
47
         set_proc_label/1,
48
         match_ip_mask/3, format_hosts_list/1, format_cycle/1, delete_dir/1,
49
         semver_to_xxyy/1, logical_processors/0, get_mucsub_event_type/1]).
50

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

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

60
-ifdef(OTP_BELOW_27).
61
%% Copied from erlang/otp/lib/stdlib/src/re.erl
62
-type re_mp() :: {re_pattern, _, _, _, _}.
63
-type json_value() :: jiffy:json_value().
64
-else.
65
-type re_mp() :: re:mp().
66
-type json_value() :: json:encode_value().
67
-endif.
68
-export_type([re_mp/0]).
69
-export_type([json_value/0]).
70

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

73
-spec uri_parse(binary()|string()) -> {ok, string(), string(), string(), number(), string(), string()} | {error, term()}.
74
uri_parse(URL) ->
75
    yconf:parse_uri(URL).
52✔
76

77
uri_parse(URL, Protocols) ->
78
    yconf:parse_uri(URL, Protocols).
×
79

80
-ifdef(OTP_BELOW_27).
81
json_encode(Term) ->
82
    jiffy:encode(Term).
83
json_decode(Bin) ->
84
    jiffy:decode(Bin, [return_maps]).
85
-else.
86
json_encode({[]}) ->
87
    %% Jiffy was able to handle this case, but Json library does not
88
    <<"{}">>;
×
89
json_encode(Term) ->
UNCOV
90
    iolist_to_binary(json:encode(Term,
42✔
91
                     fun({Val}, Encoder) when is_list(Val) ->
92
                         json:encode_key_value_list(Val, Encoder);
×
93
                        (Val, Encoder) ->
UNCOV
94
                         json:encode_value(Val, Encoder)
326✔
95
                     end)).
96
json_decode(Bin) ->
UNCOV
97
    json:decode(Bin).
41✔
98
-endif.
99

100
%%%===================================================================
101
%%% API
102
%%%===================================================================
103
-spec add_delay_info(stanza(), jid(), erlang:timestamp()) -> stanza().
104
add_delay_info(Stz, From, Time) ->
UNCOV
105
    add_delay_info(Stz, From, Time, <<"">>).
29✔
106

107
-spec add_delay_info(stanza(), jid(), erlang:timestamp(), binary()) -> stanza().
108
add_delay_info(Stz, From, Time, Desc) ->
UNCOV
109
    Delays = xmpp:get_subtags(Stz, #delay{stamp = {0,0,0}}),
30✔
UNCOV
110
    Matching = lists:any(
30✔
111
        fun(#delay{from = OldFrom}) when is_record(OldFrom, jid) ->
112
               jid:tolower(From) == jid:tolower(OldFrom);
×
113
           (_) ->
114
               false
×
115
        end, Delays),
UNCOV
116
    case Matching of
30✔
117
        true ->
118
            Stz;
×
119
        _ ->
UNCOV
120
            NewDelay = #delay{stamp = Time, from = From, desc = Desc},
30✔
UNCOV
121
            xmpp:append_subtags(Stz, [NewDelay])
30✔
122
    end.
123

124
-spec unwrap_carbon(stanza()) -> xmpp_element().
125
unwrap_carbon(#message{} = Msg) ->
126
    try
×
127
        case xmpp:get_subtag(Msg, #carbons_sent{forwarded = #forwarded{}}) of
×
128
            #carbons_sent{forwarded = #forwarded{sub_els = [El]}} ->
129
                xmpp:decode(El, ?NS_CLIENT, [ignore_els]);
×
130
            _ ->
131
                case xmpp:get_subtag(Msg, #carbons_received{
×
132
                                              forwarded = #forwarded{}}) of
133
                    #carbons_received{forwarded = #forwarded{sub_els = [El]}} ->
134
                        xmpp:decode(El, ?NS_CLIENT, [ignore_els]);
×
135
                    _ ->
136
                        Msg
×
137
                end
138
        end
139
    catch _:{xmpp_codec, _} ->
140
            Msg
×
141
    end;
142
unwrap_carbon(Stanza) -> Stanza.
×
143

144
-spec unwrap_mucsub_message(xmpp_element()) -> message() | false.
145
unwrap_mucsub_message(#message{} = OuterMsg) ->
UNCOV
146
    case xmpp:get_subtag(OuterMsg, #ps_event{}) of
65✔
147
        #ps_event{
148
            items = #ps_items{
149
                node = Node,
150
                items = [
151
                    #ps_item{
152
                        sub_els = [#message{} = InnerMsg]} | _]}}
153
            when Node == ?NS_MUCSUB_NODES_MESSAGES;
154
                 Node == ?NS_MUCSUB_NODES_SUBJECT ->
155
            InnerMsg;
×
156
        _ ->
UNCOV
157
            false
65✔
158
    end;
159
unwrap_mucsub_message(_Packet) ->
160
    false.
×
161

162
-spec is_mucsub_message(xmpp_element()) -> boolean().
163
is_mucsub_message(Packet) ->
UNCOV
164
    get_mucsub_event_type(Packet) /= false.
65✔
165

166
-spec get_mucsub_event_type(xmpp_element()) -> binary() | false.
167
get_mucsub_event_type(#message{} = OuterMsg) ->
UNCOV
168
    case xmpp:get_subtag(OuterMsg, #ps_event{}) of
65✔
169
        #ps_event{
170
            items = #ps_items{
171
                node = Node}}
172
            when Node == ?NS_MUCSUB_NODES_MESSAGES;
173
                 Node == ?NS_MUCSUB_NODES_SUBJECT;
174
                 Node == ?NS_MUCSUB_NODES_AFFILIATIONS;
175
                 Node == ?NS_MUCSUB_NODES_CONFIG;
176
                 Node == ?NS_MUCSUB_NODES_PARTICIPANTS;
177
                 Node == ?NS_MUCSUB_NODES_PRESENCE;
178
                 Node == ?NS_MUCSUB_NODES_SUBSCRIBERS ->
179
            Node;
×
180
        _ ->
UNCOV
181
            false
65✔
182
    end;
183
get_mucsub_event_type(_Packet) ->
184
    false.
×
185

186
-spec is_standalone_chat_state(stanza()) -> boolean().
187
is_standalone_chat_state(Stanza) ->
188
    case unwrap_carbon(Stanza) of
×
189
        #message{body = [], subject = [], sub_els = Els} ->
190
            IgnoreNS = [?NS_CHATSTATES, ?NS_DELAY, ?NS_EVENT, ?NS_HINTS],
×
191
            Stripped = [El || El <- Els,
×
192
                              not lists:member(xmpp:get_ns(El), IgnoreNS)],
×
193
            Stripped == [];
×
194
        _ ->
195
            false
×
196
    end.
197

198
-spec tolower(binary()) -> binary().
199
tolower(B) ->
UNCOV
200
    iolist_to_binary(tolower_s(binary_to_list(B))).
27✔
201

202
tolower_s([C | Cs]) ->
UNCOV
203
    if C >= $A, C =< $Z -> [C + 32 | tolower_s(Cs)];
270✔
UNCOV
204
       true -> [C | tolower_s(Cs)]
270✔
205
    end;
UNCOV
206
tolower_s([]) -> [].
27✔
207

208
-spec term_to_base64(term()) -> binary().
209
term_to_base64(Term) ->
UNCOV
210
    encode_base64(term_to_binary(Term)).
258✔
211

212
-spec base64_to_term(binary()) -> {term, term()} | error.
213
base64_to_term(Base64) ->
UNCOV
214
    try binary_to_term(base64:decode(Base64), [safe]) of
2✔
UNCOV
215
        Term -> {term, Term}
2✔
216
    catch _:_ ->
217
            error
×
218
    end.
219

220
-spec decode_base64(binary()) -> binary().
221
decode_base64(S) ->
222
    try base64:mime_decode(S)
×
223
    catch _:badarg -> <<>>
×
224
    end.
225

226
-spec encode_base64(binary()) -> binary().
227
encode_base64(Data) ->
UNCOV
228
    base64:encode(Data).
258✔
229

230
-spec ip_to_list(inet:ip_address() | undefined |
231
                 {inet:ip_address(), inet:port_number()}) -> binary().
232

233
ip_to_list({IP, _Port}) ->
234
    ip_to_list(IP);
252✔
235
%% This function clause could use inet_parse too:
236
ip_to_list(undefined) ->
237
    <<"unknown">>;
×
238
ip_to_list(local) ->
239
    <<"unix">>;
×
240
ip_to_list(IP) ->
241
    list_to_binary(inet_parse:ntoa(IP)).
253✔
242

243
-spec hex_to_bin(binary()) -> binary().
244
hex_to_bin(Hex) ->
245
    hex_to_bin(binary_to_list(Hex), []).
×
246

247
-spec hex_to_bin(list(), list()) -> binary().
248
hex_to_bin([], Acc) ->
249
    list_to_binary(lists:reverse(Acc));
×
250
hex_to_bin([H1, H2 | T], Acc) ->
251
    {ok, [V], []} = io_lib:fread("~16u", [H1, H2]),
×
252
    hex_to_bin(T, [V | Acc]).
×
253

254
-spec hex_to_base64(binary()) -> binary().
255
hex_to_base64(Hex) ->
256
    base64:encode(hex_to_bin(Hex)).
×
257

258
-spec url_encode(binary()) -> binary().
259
url_encode(A) ->
260
    url_encode(A, <<>>).
629✔
261

262
-spec expand_keyword(iodata(), iodata(), iodata()) -> binary().
263
expand_keyword(Keyword, Input, Replacement) ->
264
    re:replace(Input, Keyword, Replacement,
218,637✔
265
               [{return, binary}, global]).
266

267
binary_to_atom(Bin) ->
268
    erlang:binary_to_atom(Bin, utf8).
3,123✔
269

270
tuple_to_binary(T) ->
271
    iolist_to_binary(tuple_to_list(T)).
×
272

273
%% erlang:atom_to_binary/1 is available since OTP 23
274
%% https://www.erlang.org/doc/apps/erts/erlang#atom_to_binary/1
275
%% Let's use /2 for backwards compatibility.
276
atom_to_binary(A) ->
277
    erlang:atom_to_binary(A, utf8).
3,143✔
278

279
expr_to_term(Expr) ->
280
    Str = binary_to_list(<<Expr/binary, ".">>),
×
281
    {ok, Tokens, _} = erl_scan:string(Str),
×
282
    {ok, Term} = erl_parse:parse_term(Tokens),
×
283
    Term.
×
284

285
term_to_expr(Term) ->
286
    list_to_binary(io_lib:print(Term, 1, 999999, -1)).
×
287

288
-spec now_to_usec(erlang:timestamp()) -> non_neg_integer().
289
now_to_usec({MSec, Sec, USec}) ->
290
    (MSec*1000000 + Sec)*1000000 + USec.
×
291

292
-spec usec_to_now(non_neg_integer()) -> erlang:timestamp().
293
usec_to_now(Int) ->
294
    Secs = Int div 1000000,
×
295
    USec = Int rem 1000000,
×
296
    MSec = Secs div 1000000,
×
297
    Sec = Secs rem 1000000,
×
298
    {MSec, Sec, USec}.
×
299

300
l2i(I) when is_integer(I) -> I;
×
301
l2i(L) when is_binary(L) -> binary_to_integer(L).
×
302

303
i2l(I) when is_integer(I) -> integer_to_binary(I);
×
304
i2l(L) when is_binary(L) -> L.
×
305

306
i2l(I, N) when is_integer(I) -> i2l(i2l(I), N);
×
307
i2l(L, N) when is_binary(L) ->
308
    case str:len(L) of
×
309
      N -> L;
×
310
      C when C > N -> L;
×
311
      _ -> i2l(<<$0, L/binary>>, N)
×
312
    end.
313

314
-spec encode_pid(pid()) -> binary().
315
encode_pid(Pid) ->
316
    list_to_binary(erlang:pid_to_list(Pid)).
×
317

318
-spec decode_pid(binary(), binary()) -> pid().
319
decode_pid(PidBin, NodeBin) ->
320
    PidStr = binary_to_list(PidBin),
×
321
    Pid = erlang:list_to_pid(PidStr),
×
322
    case erlang:binary_to_atom(NodeBin, latin1) of
×
323
        Node when Node == node() ->
324
            Pid;
×
325
        Node ->
326
            try set_node_id(PidStr, NodeBin)
×
327
            catch _:badarg ->
328
                    erlang:error({bad_node, Node})
×
329
            end
330
    end.
331

332
-spec compile_exprs(module(), [string()]) -> ok | {error, any()}.
333
compile_exprs(Mod, Exprs) ->
334
    try
×
335
        Forms = lists:map(
×
336
                  fun(Expr) ->
337
                          {ok, Tokens, _} = erl_scan:string(lists:flatten(Expr)),
×
338
                          {ok, Form} = erl_parse:parse_form(Tokens),
×
339
                          Form
×
340
                  end, Exprs),
341
        {ok, Code} = case compile:forms(Forms, []) of
×
342
                         {ok, Mod, Bin} -> {ok, Bin};
×
343
                         {ok, Mod, Bin, _Warnings} -> {ok, Bin};
×
344
                         Error -> Error
×
345
                     end,
346
        {module, Mod} = code:load_binary(Mod, "nofile", Code),
×
347
        ok
×
348
    catch _:{badmatch, {error, ErrInfo, _ErrLocation}} ->
349
            {error, ErrInfo};
×
350
          _:{badmatch, {error, _} = Err} ->
351
            Err;
×
352
          _:{badmatch, error} ->
353
            {error, compile_failed}
×
354
    end.
355

356
-spec join_atoms([atom()], binary()) -> binary().
357
join_atoms(Atoms, Sep) ->
358
    str:join([io_lib:format("~p", [A]) || A <- lists:sort(Atoms)], Sep).
×
359

360
%% @doc Checks if the file is readable and converts its name to binary.
361
%%      Fails with `badarg' otherwise. The function is intended for usage
362
%%      in configuration validators only.
363
-spec try_read_file(file:filename_all()) -> binary().
364
try_read_file(Path) ->
365
    case file:open(Path, [read]) of
×
366
        {ok, Fd} ->
367
            file:close(Fd),
×
368
            iolist_to_binary(Path);
×
369
        {error, Why} ->
370
            ?ERROR_MSG("Failed to read ~ts: ~ts", [Path, file:format_error(Why)]),
×
371
            erlang:error(badarg)
×
372
    end.
373

374
-spec css_dir() -> file:filename().
375
css_dir() ->
376
    get_dir("css").
×
377

378
-spec img_dir() -> file:filename().
379
img_dir() ->
380
    get_dir("img").
×
381

382
-spec js_dir() -> file:filename().
383
js_dir() ->
384
    get_dir("js").
×
385

386
-spec msgs_dir() -> file:filename().
387
msgs_dir() ->
388
    get_dir("msgs").
3✔
389

390
-spec sql_dir() -> file:filename().
391
sql_dir() ->
392
    get_dir("sql").
×
393

394
-spec lua_dir() -> file:filename().
395
lua_dir() ->
396
    get_dir("lua").
×
397

398
-spec read_css(file:filename()) -> {ok, binary()} | {error, file:posix()}.
399
read_css(File) ->
400
    read_file(filename:join(css_dir(), File)).
×
401

402
-spec read_img(file:filename()) -> {ok, binary()} | {error, file:posix()}.
403
read_img(File) ->
404
    read_file(filename:join(img_dir(), File)).
×
405

406
-spec read_js(file:filename()) -> {ok, binary()} | {error, file:posix()}.
407
read_js(File) ->
408
    read_file(filename:join(js_dir(), File)).
×
409

410
-spec read_lua(file:filename()) -> {ok, binary()} | {error, file:posix()}.
411
read_lua(File) ->
412
    read_file(filename:join(lua_dir(), File)).
×
413

414
-spec get_descr(binary(), binary()) -> binary().
415
get_descr(Lang, Text) ->
416
    Desc = translate:translate(Lang, Text),
×
417
    Copyright = ejabberd_config:get_copyright(),
×
418
    <<Desc/binary, $\n, Copyright/binary>>.
×
419

420
-spec get_home() -> string().
421
get_home() ->
422
    case init:get_argument(home) of
783✔
423
        {ok, [[Home]]} ->
424
            Home;
783✔
425
        error ->
426
            mnesia:system_info(directory)
×
427
    end.
428

429
warn_unset_home() ->
430
    case init:get_argument(home) of
3✔
431
        {ok, [[_Home]]} ->
432
            ok;
3✔
433
        error ->
434
            ?INFO_MSG("The 'HOME' environment variable is not set, "
×
435
                 "ejabberd will use as HOME the Mnesia directory: ~s.",
436
                 [mnesia:system_info(directory)])
×
437
    end.
438

439
-spec intersection(list(), list()) -> list().
440
intersection(L1, L2) ->
441
    lists:filter(
23✔
442
      fun(E) ->
443
              lists:member(E, L2)
25✔
444
      end, L1).
445

446
-spec format_val(any()) -> iodata().
447
format_val({yaml, S}) when is_integer(S); is_binary(S); is_atom(S) ->
448
    format_val(S);
×
449
format_val({yaml, YAML}) ->
450
    S = try fast_yaml:encode(YAML)
×
451
        catch _:_ -> YAML
×
452
        end,
453
    format_val(S);
×
454
format_val(I) when is_integer(I) ->
455
    integer_to_list(I);
×
456
format_val(B) when is_atom(B) ->
457
    erlang:atom_to_binary(B, utf8);
×
458
format_val(Term) ->
459
    S = try iolist_to_binary(Term)
×
460
        catch _:_ -> list_to_binary(io_lib:format("~p", [Term]))
×
461
        end,
462
    case binary:match(S, <<"\n">>) of
×
463
        nomatch -> S;
×
464
        _ -> [io_lib:nl(), S]
×
465
    end.
466

467
-spec cancel_timer(reference() | undefined) -> ok.
468
cancel_timer(TRef) when is_reference(TRef) ->
469
    case erlang:cancel_timer(TRef) of
20✔
470
        false ->
471
            receive {timeout, TRef, _} -> ok
×
472
            after 0 -> ok
×
473
            end;
474
        _ ->
475
            ok
20✔
476
    end;
477
cancel_timer(_) ->
478
    ok.
6✔
479

480
-spec best_match(atom() | binary() | string(),
481
                 [atom() | binary() | string()]) -> string().
482
best_match(Pattern, []) ->
483
    Pattern;
×
484
best_match(Pattern, Opts) ->
485
    String = to_string(Pattern),
×
486
    {Ds, _} = lists:mapfoldl(
×
487
                fun(Opt, Cache) ->
488
                        SOpt = to_string(Opt),
×
489
                        {Distance, Cache1} = ld(String, SOpt, Cache),
×
490
                        {{Distance, SOpt}, Cache1}
×
491
                end, #{}, Opts),
492
    element(2, lists:min(Ds)).
×
493

494
-spec logical_processors() -> non_neg_integer().
495
logical_processors() ->
496
    case erlang:system_info(logical_processors) of
84✔
497
        V when is_integer(V), V >= 2  -> V;
84✔
498
        _ -> 1
×
499
    end.
500

501
-spec pmap(fun((T1) -> T2), [T1]) -> [T2].
502
pmap(Fun, [_,_|_] = List) ->
503
    case logical_processors() of
3✔
504
        1 -> lists:map(Fun, List);
×
505
        _ ->
506
            Self = self(),
3✔
507
            lists:map(
3✔
508
              fun({Pid, Ref}) ->
509
                      receive
93✔
510
                          {Pid, Ret} ->
511
                              receive
93✔
512
                                  {'DOWN', Ref, _, _, _} ->
513
                                      Ret
93✔
514
                              end;
515
                          {'DOWN', Ref, _, _, Reason} ->
516
                              exit(Reason)
×
517
                      end
518
              end, [spawn_monitor(
93✔
519
                      fun() -> Self ! {self(), Fun(X)} end)
93✔
520
                    || X <- List])
3✔
521
    end;
522
pmap(Fun, List) ->
523
    lists:map(Fun, List).
×
524

525
-spec peach(fun((T) -> any()), [T]) -> ok.
526
peach(Fun, [_,_|_] = List) ->
527
    case logical_processors() of
3✔
528
        1 -> lists:foreach(Fun, List);
×
529
        _ ->
530
            Self = self(),
3✔
531
            lists:foreach(
3✔
532
              fun({Pid, Ref}) ->
533
                      receive
105✔
534
                          Pid ->
535
                              receive
105✔
536
                                  {'DOWN', Ref, _, _, _} ->
537
                                      ok
105✔
538
                              end;
539
                          {'DOWN', Ref, _, _, Reason} ->
540
                              exit(Reason)
×
541
                      end
542
              end, [spawn_monitor(
105✔
543
                      fun() -> Fun(X), Self ! self() end)
105✔
544
                    || X <- List])
3✔
545
    end;
546
peach(Fun, List) ->
547
    lists:foreach(Fun, List).
×
548

549
format_exception(Level, Class, Reason, Stacktrace) ->
550
    erl_error:format_exception(
×
551
      Level, Class, Reason, Stacktrace,
552
      fun(_M, _F, _A) -> false end,
×
553
      fun(Term, I) ->
554
              io_lib:print(Term, I, 80, -1)
×
555
      end).
556

557
-spec get_my_ipv4_address() -> inet:ip4_address().
558
get_my_ipv4_address() ->
559
    {ok, MyHostName} = inet:gethostname(),
31✔
560
    case inet:getaddr(MyHostName, inet) of
31✔
561
        {ok, Addr} -> Addr;
31✔
562
        {error, _} -> {127, 0, 0, 1}
×
563
    end.
564

565
-spec get_my_ipv6_address() -> inet:ip6_address().
566
get_my_ipv6_address() ->
567
    {ok, MyHostName} = inet:gethostname(),
×
568
    case inet:getaddr(MyHostName, inet6) of
×
569
        {ok, Addr} -> Addr;
×
570
        {error, _} -> {0, 0, 0, 0, 0, 0, 0, 1}
×
571
    end.
572

573
-spec parse_ip_mask(binary()) -> {ok, {inet:ip4_address(), 0..32}} |
574
                                 {ok, {inet:ip6_address(), 0..128}} |
575
                                 error.
576
parse_ip_mask(S) ->
577
    case econf:validate(econf:ip_mask(), S) of
×
578
        {ok, _} = Ret -> Ret;
×
579
        _ -> error
×
580
    end.
581

582
-spec match_ip_mask(inet:ip_address(), inet:ip_address(), 0..128) -> boolean().
583
match_ip_mask({_, _, _, _} = IP, {_, _, _, _} = Net, Mask) ->
584
    IPInt = ip_to_integer(IP),
×
585
    NetInt = ip_to_integer(Net),
×
586
    M = bnot (1 bsl (32 - Mask) - 1),
×
587
    IPInt band M =:= NetInt band M;
×
588
match_ip_mask({_, _, _, _, _, _, _, _} = IP,
589
                {_, _, _, _, _, _, _, _} = Net, Mask) ->
590
    IPInt = ip_to_integer(IP),
×
591
    NetInt = ip_to_integer(Net),
×
592
    M = bnot (1 bsl (128 - Mask) - 1),
×
593
    IPInt band M =:= NetInt band M;
×
594
match_ip_mask({_, _, _, _} = IP,
595
                {0, 0, 0, 0, 0, 16#FFFF, _, _} = Net, Mask) ->
596
    IPInt = ip_to_integer({0, 0, 0, 0, 0, 16#FFFF, 0, 0}) + ip_to_integer(IP),
×
597
    NetInt = ip_to_integer(Net),
×
598
    M = bnot (1 bsl (128 - Mask) - 1),
×
599
    IPInt band M =:= NetInt band M;
×
600
match_ip_mask({0, 0, 0, 0, 0, 16#FFFF, _, _} = IP,
601
                {_, _, _, _} = Net, Mask) ->
602
    IPInt = ip_to_integer(IP) - ip_to_integer({0, 0, 0, 0, 0, 16#FFFF, 0, 0}),
×
603
    NetInt = ip_to_integer(Net),
×
604
    M = bnot (1 bsl (32 - Mask) - 1),
×
605
    IPInt band M =:= NetInt band M;
×
606
match_ip_mask(_, _, _) ->
607
    false.
×
608

609
-spec format_hosts_list([binary(), ...]) -> iolist().
610
format_hosts_list([Host]) ->
611
    Host;
×
612
format_hosts_list([H1, H2]) ->
613
    [H1, " and ", H2];
×
614
format_hosts_list([H1, H2, H3]) ->
615
    [H1, ", ", H2, " and ", H3];
×
616
format_hosts_list([H1, H2|Hs]) ->
617
    io_lib:format("~ts, ~ts and ~B more hosts",
3✔
618
                  [H1, H2, length(Hs)]).
619

620
-spec format_cycle([atom(), ...]) -> iolist().
621
format_cycle([M1]) ->
622
    atom_to_list(M1);
×
623
format_cycle([M1, M2]) ->
624
    [atom_to_list(M1), " and ", atom_to_list(M2)];
×
625
format_cycle([M|Ms]) ->
626
    atom_to_list(M) ++ ", " ++ format_cycle(Ms).
×
627

628
-spec delete_dir(file:filename_all()) -> ok | {error, file:posix()}.
629
delete_dir(Dir) ->
630
    try
1✔
631
        {ok, Entries} = file:list_dir(Dir),
1✔
632
        lists:foreach(fun(Path) ->
×
633
                              case filelib:is_dir(Path) of
×
634
                                  true ->
635
                                      ok = delete_dir(Path);
×
636
                                  false ->
637
                                      ok = file:delete(Path)
×
638
                              end
639
                      end, [filename:join(Dir, Entry) || Entry <- Entries]),
×
640
        ok = file:del_dir(Dir)
×
641
    catch
642
        _:{badmatch, {error, Error}} ->
643
            {error, Error}
1✔
644
    end.
645

646
-spec semver_to_xxyy(binary()) -> binary().
647
semver_to_xxyy(<<Y1, Y2, $., M2, $., $0>>) ->
648
    <<Y1, Y2, $., $0, M2>>;
×
649
semver_to_xxyy(<<Y1, Y2, $., M2, $., Patch/binary>>) ->
650
    <<Y1, Y2, $., $0, M2, $., Patch/binary>>;
×
651
semver_to_xxyy(<<Y1, Y2, $., M1, M2, $., $0>>) ->
652
    <<Y1, Y2, $., M1, M2>>;
×
653
semver_to_xxyy(Version) when is_binary(Version) ->
654
    Version.
675✔
655

656
%%%===================================================================
657
%%% Internal functions
658
%%%===================================================================
659
-spec url_encode(binary(), binary()) -> binary().
660
url_encode(<<H:8, T/binary>>, Acc) when
661
  (H >= $a andalso H =< $z) orelse
662
  (H >= $A andalso H =< $Z) orelse
663
  (H >= $0 andalso H =< $9) orelse
664
  H == $_ orelse
665
  H == $. orelse
666
  H == $- orelse
667
  H == $/ orelse
668
  H == $: ->
669
    url_encode(T, <<Acc/binary, H>>);
10,934✔
670
url_encode(<<H:8, T/binary>>, Acc) ->
671
    case integer_to_list(H, 16) of
×
672
        [X, Y] -> url_encode(T, <<Acc/binary, $%, X, Y>>);
×
673
        [X] -> url_encode(T, <<Acc/binary, $%, $0, X>>)
×
674
    end;
675
url_encode(<<>>, Acc) ->
676
    Acc.
629✔
677

678
-spec set_node_id(string(), binary()) -> pid().
679
set_node_id(PidStr, NodeBin) ->
680
    ExtPidStr = erlang:pid_to_list(
×
681
                  binary_to_term(
682
                    <<131,103,100,(size(NodeBin)):16,NodeBin/binary,0:72>>)),
683
    [H|_] = string:tokens(ExtPidStr, "."),
×
684
    [_|T] = string:tokens(PidStr, "."),
×
685
    erlang:list_to_pid(string:join([H|T], ".")).
×
686

687
-spec read_file(file:filename()) -> {ok, binary()} | {error, file:posix()}.
688
read_file(Path) ->
689
    case file:read_file(Path) of
×
690
        {ok, Data} ->
691
            {ok, Data};
×
692
        {error, Why} = Err ->
693
            ?ERROR_MSG("Failed to read file ~ts: ~ts",
×
694
                       [Path, file:format_error(Why)]),
×
695
            Err
×
696
    end.
697

698
-spec get_dir(string()) -> file:filename().
699
get_dir(Type) ->
700
    Env = "EJABBERD_" ++ string:to_upper(Type) ++ "_PATH",
3✔
701
    case os:getenv(Env) of
3✔
702
        false ->
703
            case code:priv_dir(ejabberd) of
3✔
704
                {error, _} -> filename:join(["priv", Type]);
×
705
                Path -> filename:join([Path, Type])
3✔
706
            end;
707
        Path ->
708
            Path
×
709
    end.
710

711
%% Generates erlang:timestamp() that is guaranteed to unique
712
-spec unique_timestamp() -> erlang:timestamp().
713
unique_timestamp() ->
714
    {MS, S, _} = erlang:timestamp(),
180✔
715
    {MS, S, erlang:unique_integer([positive, monotonic]) rem 1000000}.
180✔
716

717
%% Levenshtein distance
718
-spec ld(string(), string(), distance_cache()) -> {non_neg_integer(), distance_cache()}.
719
ld([] = S, T, Cache) ->
720
    {length(T), maps:put({S, T}, length(T), Cache)};
×
721
ld(S, [] = T, Cache) ->
722
    {length(S), maps:put({S, T}, length(S), Cache)};
×
723
ld([X|S], [X|T], Cache) ->
724
    ld(S, T, Cache);
×
725
ld([_|ST] = S, [_|TT] = T, Cache) ->
726
    try {maps:get({S, T}, Cache), Cache}
×
727
    catch _:{badkey, _} ->
728
            {L1, C1} = ld(S, TT, Cache),
×
729
            {L2, C2} = ld(ST, T, C1),
×
730
            {L3, C3} = ld(ST, TT, C2),
×
731
            L = 1 + lists:min([L1, L2, L3]),
×
732
            {L, maps:put({S, T}, L, C3)}
×
733
    end.
734

735
-spec ip_to_integer(inet:ip_address()) -> non_neg_integer().
736
ip_to_integer({IP1, IP2, IP3, IP4}) ->
737
    IP1 bsl 8 bor IP2 bsl 8 bor IP3 bsl 8 bor IP4;
×
738
ip_to_integer({IP1, IP2, IP3, IP4, IP5, IP6, IP7,
739
               IP8}) ->
740
    IP1 bsl 16 bor IP2 bsl 16 bor IP3 bsl 16 bor IP4 bsl 16
741
        bor IP5 bsl 16 bor IP6 bsl 16 bor IP7 bsl 16 bor IP8.
×
742

743
-spec to_string(atom() | binary() | string()) -> string().
744
to_string(A) when is_atom(A) ->
745
    atom_to_list(A);
×
746
to_string(B) when is_binary(B) ->
747
    binary_to_list(B);
×
748
to_string(S) ->
749
    S.
×
750

751
-ifdef(OTP_BELOW_27).
752
set_proc_label(_Label) ->
753
    ok.
754
-else.
755
set_proc_label(Label) ->
756
    proc_lib:set_label(Label).
456✔
757
-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