/home/runner/work/klsn/klsn/_build/test/cover/ct/klsn_binstr.html

1 -module(klsn_binstr).
2
3 -export([
4 urlencode/1
5 , urldecode/1
6 , from_any/1
7 , replace/2
8 , hash/1
9 , uuid/0
10 , join/1
11 , join/2
12 ]).
13
14 %% UTF-8 binary string used as the canonical textual representation across
15 %% the klsn_* helper modules.
16 -export_type([
17 binstr/0
18 ]).
19
20 -type binstr() :: unicode:unicode_binary().
21 -type from_any_input() :: integer() | float() | atom() | iolist().
22
23
24 %% @doc
25 %% Return a lowercase hexadecimal SHA-256 digest of Binary.
26 -spec hash(binstr()) -> binstr().
27 hash(Binary) ->
28
:-(
list_to_binary(
29 string:lowercase(
30 binary_to_list(
31 binary:encode_hex(
32 crypto:hash(sha256, Binary)
33 )
34 )
35 )
36 ).
37
38 %% @doc
39 %% Apply each replacement rule {Before,After} to Sub using
40 %% binary:replace/4 with the global option. Rules are processed from
41 %% left to right so earlier replacements feed into later ones.
42 %%
43 %% Returns the transformed binary.
44 -spec replace([{binstr(), binstr()}], binstr()) -> binstr().
45 replace(Rule, Sub) ->
46
:-(
lists:foldl(fun({Before, After}, Acc) ->
47
:-(
binary:replace(Acc, Before, After, [global])
48 end, Sub, Rule).
49
50 %% @doc
51 %% Convert an integer, float, atom or arbitrary iolist to a UTF-8 binary.
52 %%
53 %% If the value is already a binary it is returned unchanged (through the
54 %% iolist clause).
55 -spec from_any(from_any_input()) -> binstr().
56 from_any(Integer) when is_integer(Integer) ->
57 13 integer_to_binary(Integer);
58 from_any(Float) when is_float(Float) ->
59 1 float_to_binary(Float);
60 from_any(Atom) when is_atom(Atom) ->
61 48532735 atom_to_binary(Atom);
62 from_any(IOList) ->
63 615437 iolist_to_binary(IOList).
64
65 %% @doc
66 %% Percent-encode Bin so that it becomes safe to embed inside a URL.
67 %% The implementation keeps alphanumerics and the unreserved characters
68 %% - . _ ~ intact and encodes everything else as %XX.
69 -spec urlencode(binstr()) -> binstr().
70 urlencode(Bin) ->
71 69 IsSafe = fun
72 15 ($-) -> true;
73
:-(
($.) -> true;
74 525 ($_) -> true;
75
:-(
($~) -> true;
76
:-(
(C) when $A =< C, C =< $Z -> true;
77 917 (C) when $a =< C, C =< $z -> true;
78 1000 (C) when $0 =< C, C =< $9 -> true;
79 2 (_) -> false
80 end,
81 69 urlencode(Bin, IsSafe, <<>>).
82
83 urlencode(<<>>, _, Acc) ->
84 69 Acc;
85 urlencode(<<Char/utf8, Rest/binary>>, IsSafe, Acc) ->
86 2459 Encoded = case IsSafe(Char) of
87 false ->
88 2 percent_encode(<<Char/utf8>>, <<>>);
89 true ->
90 2457 <<Char/utf8>>
91 end,
92 2459 urlencode(Rest, IsSafe, <<Acc/binary, Encoded/binary>>);
93 urlencode(<<Char:1/binary, Rest/binary>>, IsSafe, Acc) ->
94
:-(
Encoded = percent_encode(<<Char/binary>>, <<>>),
95
:-(
urlencode(Rest, IsSafe, <<Acc/binary, Encoded/binary>>);
96 urlencode(_, _, _) ->
97
:-(
error(badarg).
98
99 percent_encode(<<>>, Acc) ->
100 2 Acc;
101 percent_encode(<<A:4, B:4, Rest/binary>>, Acc) ->
102 2 EncodedLeft = encode_half_char(A),
103 2 EncodedRight = encode_half_char(B),
104 2 percent_encode(Rest, <<
105 Acc/binary
106 , $%
107 , EncodedLeft
108 , EncodedRight
109 >>).
110
111 encode_half_char(HalfChar) ->
112 4 if
113 2 ((HalfChar) >= 0) andalso ((HalfChar) =< 9) -> (HalfChar) + $0;
114 2 ((HalfChar) >= 10) andalso ((HalfChar) =< 15) -> (HalfChar) + $A - 10
115 end.
116
117 -spec urldecode(binstr()) -> binstr().
118 urldecode(Bin) when is_binary(Bin) ->
119
:-(
urldecode(Bin, <<>>);
120 urldecode(_) ->
121
:-(
error(badarg).
122
123 urldecode(<<>>, Acc) ->
124
:-(
Acc;
125 urldecode(<<"%", H, L, Rest/binary>>, Acc) ->
126
:-(
V0 = hex_val(H),
127
:-(
V1 = hex_val(L),
128
:-(
Byte = V0 * 16 + V1,
129
:-(
urldecode(Rest, <<Acc/binary, Byte>>);
130 urldecode(<<Byte, Rest/binary>>, Acc) ->
131
:-(
urldecode(Rest, <<Acc/binary, Byte>>).
132
133 -spec hex_val(integer()) -> integer().
134
:-(
hex_val(H) when $0 =< H, H =< $9 -> H - $0;
135
:-(
hex_val(H) when $A =< H, H =< $F -> H - $A + 10;
136
:-(
hex_val(H) when $a =< H, H =< $f -> H - $a + 10.
137
138
139 -spec uuid() -> binstr().
140 uuid() ->
141 1 <<R1:48, _:4, R2:12, _:2, R3:62>> = crypto:strong_rand_bytes(16),
142 1 <<U1:32, U2:16, U3:16, U4:16, U5:48>> = <<R1:48, 0:1, 1:1, 0:1, 0:1, R2:12, 1:1, 0:1, R3:62>>,
143 1 iolist_to_binary(io_lib:format(
144 "~8.16.0b-~4.16.0b-~4.16.0b-~4.16.0b-~12.16.0b",
145 [U1, U2, U3, U4, U5])
146 ).
147
148 %% @doc
149 %% Join a list of binstr values without a separator (equivalent to join(List, &lt;&lt;&gt;&gt;)).
150 -spec join([binstr()]) -> binstr().
151 join(List) when is_list(List) ->
152
:-(
join(List, <<>>);
153 join(_) ->
154
:-(
error(badarg).
155
156 %% @doc
157 %% Join a list of binstr values inserting the given binary Separator
158 %% between each element.
159 -spec join([binstr()], binstr()) -> binstr().
160 join(List, Separator) when is_list(List), is_binary(Separator) ->
161
:-(
iolist_to_binary(join_bin(List, Separator, true, []));
162 join(_, _) ->
163
:-(
error(badarg).
164
165 join_bin([], _Sep, _IsFirst, Acc) ->
166
:-(
lists:reverse(Acc);
167 join_bin([Head | Tail], Sep, true, Acc) when is_binary(Head) ->
168
:-(
join_bin(Tail, Sep, false, [Head | Acc]);
169 join_bin([Head | Tail], Sep, false, Acc) when is_binary(Head) ->
170
:-(
join_bin(Tail, Sep, false, [Head, Sep | Acc]);
171 join_bin(_, _, _, _) ->
172
:-(
error(badarg).
Line Hits Source