1%% -*- mode: erlang; tab-width: 4; indent-tabs-mode: 1; st-rulers: [70] -*- 2%% vim: ts=4 sw=4 ft=erlang noet 3%%%------------------------------------------------------------------- 4%%% @author Andrew Bennett <potatosaladx@gmail.com> 5%%% @copyright 2017-2019, Andrew Bennett 6%%% @doc RFC 4648, Section 4: https://tools.ietf.org/html/rfc4648#section-4 7%%% 8%%% @end 9%%% Created : 11 May 2017 by Andrew Bennett <potatosaladx@gmail.com> 10%%%------------------------------------------------------------------- 11-module(jose_base64). 12-compile({parse_transform, jose_base}). 13 14-include("jose_base.hrl"). 15 16%% API 17-export([decode/1]). 18-export([decode/2]). 19-export(['decode!'/1]). 20-export(['decode!'/2]). 21-export([encode/1]). 22-export([encode/2]). 23-export([random/1]). 24-export([random/2]). 25 26% Macros 27-define(B64_TO_INT(C), 28 case C of 29 $A -> 16#00; 30 $B -> 16#01; 31 $C -> 16#02; 32 $D -> 16#03; 33 $E -> 16#04; 34 $F -> 16#05; 35 $G -> 16#06; 36 $H -> 16#07; 37 $I -> 16#08; 38 $J -> 16#09; 39 $K -> 16#0A; 40 $L -> 16#0B; 41 $M -> 16#0C; 42 $N -> 16#0D; 43 $O -> 16#0E; 44 $P -> 16#0F; 45 $Q -> 16#10; 46 $R -> 16#11; 47 $S -> 16#12; 48 $T -> 16#13; 49 $U -> 16#14; 50 $V -> 16#15; 51 $W -> 16#16; 52 $X -> 16#17; 53 $Y -> 16#18; 54 $Z -> 16#19; 55 $a -> 16#1A; 56 $b -> 16#1B; 57 $c -> 16#1C; 58 $d -> 16#1D; 59 $e -> 16#1E; 60 $f -> 16#1F; 61 $g -> 16#20; 62 $h -> 16#21; 63 $i -> 16#22; 64 $j -> 16#23; 65 $k -> 16#24; 66 $l -> 16#25; 67 $m -> 16#26; 68 $n -> 16#27; 69 $o -> 16#28; 70 $p -> 16#29; 71 $q -> 16#2A; 72 $r -> 16#2B; 73 $s -> 16#2C; 74 $t -> 16#2D; 75 $u -> 16#2E; 76 $v -> 16#2F; 77 $w -> 16#30; 78 $x -> 16#31; 79 $y -> 16#32; 80 $z -> 16#33; 81 $0 -> 16#34; 82 $1 -> 16#35; 83 $2 -> 16#36; 84 $3 -> 16#37; 85 $4 -> 16#38; 86 $5 -> 16#39; 87 $6 -> 16#3A; 88 $7 -> 16#3B; 89 $8 -> 16#3C; 90 $9 -> 16#3D; 91 $+ -> 16#3E; 92 $/ -> 16#3F 93 end). 94 95-define(INT_TO_B64(C), 96 case C of 97 16#00 -> $A; 98 16#01 -> $B; 99 16#02 -> $C; 100 16#03 -> $D; 101 16#04 -> $E; 102 16#05 -> $F; 103 16#06 -> $G; 104 16#07 -> $H; 105 16#08 -> $I; 106 16#09 -> $J; 107 16#0A -> $K; 108 16#0B -> $L; 109 16#0C -> $M; 110 16#0D -> $N; 111 16#0E -> $O; 112 16#0F -> $P; 113 16#10 -> $Q; 114 16#11 -> $R; 115 16#12 -> $S; 116 16#13 -> $T; 117 16#14 -> $U; 118 16#15 -> $V; 119 16#16 -> $W; 120 16#17 -> $X; 121 16#18 -> $Y; 122 16#19 -> $Z; 123 16#1A -> $a; 124 16#1B -> $b; 125 16#1C -> $c; 126 16#1D -> $d; 127 16#1E -> $e; 128 16#1F -> $f; 129 16#20 -> $g; 130 16#21 -> $h; 131 16#22 -> $i; 132 16#23 -> $j; 133 16#24 -> $k; 134 16#25 -> $l; 135 16#26 -> $m; 136 16#27 -> $n; 137 16#28 -> $o; 138 16#29 -> $p; 139 16#2A -> $q; 140 16#2B -> $r; 141 16#2C -> $s; 142 16#2D -> $t; 143 16#2E -> $u; 144 16#2F -> $v; 145 16#30 -> $w; 146 16#31 -> $x; 147 16#32 -> $y; 148 16#33 -> $z; 149 16#34 -> $0; 150 16#35 -> $1; 151 16#36 -> $2; 152 16#37 -> $3; 153 16#38 -> $4; 154 16#39 -> $5; 155 16#3A -> $6; 156 16#3B -> $7; 157 16#3C -> $8; 158 16#3D -> $9; 159 16#3E -> $+; 160 16#3F -> $/ 161 end). 162 163%%%=================================================================== 164%%% API functions 165%%%=================================================================== 166 167decode(Input) when ?is_iodata(Input) -> 168 decode(Input, #{}). 169 170decode(Input, Opts) when ?is_iodata(Input) andalso is_map(Opts) -> 171 try 'decode!'(Input, Opts) of 172 Output when is_binary(Output) -> 173 {ok, Output} 174 catch 175 _:_ -> 176 error 177 end; 178decode(Input, Opts) when ?is_iodata(Input) andalso is_list(Opts) -> 179 decode(Input, maps:from_list(Opts)). 180 181'decode!'(Input) when ?is_iodata(Input) -> 182 'decode!'(Input, #{}). 183 184'decode!'([], #{}) -> 185 <<>>; 186'decode!'(<<>>, #{}) -> 187 <<>>; 188'decode!'(Input, Opts) when ?is_iodata(Input) andalso is_map(Opts) -> 189 Padding = maps:get('padding', Opts, nil), 190 Size = erlang:iolist_size(Input), 191 Offset = 192 case Padding of 193 _ when (Padding == false orelse Padding == nil) andalso Size =< 4 -> 194 0; 195 _ when (Padding == false orelse Padding == nil) andalso (Size rem 4) =/= 0 -> 196 Size - (Size rem 4); 197 _ when (Padding == false orelse Padding == nil) -> 198 Size - 4; 199 _ when (Padding == true orelse Padding == nil) andalso Size >= 4 -> 200 Size - 4; 201 _ -> 202 erlang:error({badarg, [Input, Opts]}) 203 end, 204 << Head0:Offset/binary, Tail0/binary >> = ?to_binary(Input), 205 Head = << << (?B64_TO_INT(V)):6 >> || << V >> <= Head0 >>, 206 Tail = 207 case Padding of 208 false -> 209 case Tail0 of 210 << T0:8, T1:8 >> -> 211 << (?B64_TO_INT(T0)):6, (?B64_TO_INT(T1) bsr 4):2 >>; 212 << T0:8, T1:8, T2:8 >> -> 213 << (?B64_TO_INT(T0)):6, (?B64_TO_INT(T1)):6, (?B64_TO_INT(T2) bsr 2):4 >>; 214 << T:4/binary >> -> 215 << << (?B64_TO_INT(V)):6 >> || << V >> <= T >>; 216 <<>> -> 217 <<>>; 218 _ -> 219 erlang:error({badarg, [Input, Opts]}) 220 end; 221 nil -> 222 case Tail0 of 223 << T0:8, T1:8, $=, $= >> -> 224 << (?B64_TO_INT(T0)):6, (?B64_TO_INT(T1) bsr 4):2 >>; 225 << T0:8, T1:8, T2:8, $= >> -> 226 << (?B64_TO_INT(T0)):6, (?B64_TO_INT(T1)):6, (?B64_TO_INT(T2) bsr 2):4 >>; 227 << T0:8, T1:8 >> -> 228 << (?B64_TO_INT(T0)):6, (?B64_TO_INT(T1) bsr 4):2 >>; 229 << T0:8, T1:8, T2:8 >> -> 230 << (?B64_TO_INT(T0)):6, (?B64_TO_INT(T1)):6, (?B64_TO_INT(T2) bsr 2):4 >>; 231 << T:4/binary >> -> 232 << << (?B64_TO_INT(V)):6 >> || << V >> <= T >>; 233 <<>> -> 234 <<>> 235 end; 236 true -> 237 case Tail0 of 238 << T0:8, T1:8, $=, $= >> -> 239 << (?B64_TO_INT(T0)):6, (?B64_TO_INT(T1) bsr 4):2 >>; 240 << T0:8, T1:8, T2:8, $= >> -> 241 << (?B64_TO_INT(T0)):6, (?B64_TO_INT(T1)):6, (?B64_TO_INT(T2) bsr 2):4 >>; 242 << T:4/binary >> -> 243 << << (?B64_TO_INT(V)):6 >> || << V >> <= T >>; 244 <<>> -> 245 <<>>; 246 _ -> 247 erlang:error({badarg, [Input, Opts]}) 248 end 249 end, 250 << Head/binary, Tail/binary >>; 251'decode!'(Input, Opts) when ?is_iodata(Input) andalso is_list(Opts) -> 252 'decode!'(Input, maps:from_list(Opts)). 253 254encode(Input) when ?is_iodata(Input) -> 255 encode(Input, #{}). 256 257encode(Input, Opts) when ?is_iodata(Input) andalso is_map(Opts) -> 258 Padding = maps:get('padding', Opts, true), 259 Offset = 6 * (erlang:iolist_size(Input) div 6), 260 << Head:Offset/binary, Tail/binary >> = ?to_binary(Input), 261 H = << << (encode_pair(V0)):16, (encode_pair(V1)):16, (encode_pair(V2)):16, (encode_pair(V3)):16 >> || << V0:12, V1:12, V2:12, V3:12 >> <= Head >>, 262 {T, Pad} = 263 case Tail of 264 << T0:12, T1:12, T2:12, T3:4 >> -> 265 {<< (encode_pair(T0)):16, (encode_pair(T1)):16, (encode_pair(T2)):16, (encode_char(T3 bsl 2)):8 >>, << $= >>}; 266 << T0:12, T1:12, T2:8 >> -> 267 {<< (encode_pair(T0)):16, (encode_pair(T1)):16, (encode_pair(T2 bsl 4)):16 >>, << $=, $= >>}; 268 << T0:12, T1:12 >> -> 269 {<< (encode_pair(T0)):16, (encode_pair(T1)):16 >>, <<>>}; 270 << T0:12, T1:4 >> -> 271 {<< (encode_pair(T0)):16, (encode_char(T1 bsl 2)):8 >>, <<>>}; 272 << T0:8 >> -> 273 {<< (encode_pair(T0 bsl 4)):16 >>, << $=, $= >>}; 274 <<>> -> 275 {<<>>, <<>>} 276 end, 277 case Padding of 278 true -> 279 << H/binary, T/binary, Pad/binary >>; 280 false -> 281 << H/binary, T/binary >>; 282 _ -> 283 erlang:error({badarg, [Input, Opts]}) 284 end; 285encode(Input, Opts) when ?is_iodata(Input) andalso is_list(Opts) -> 286 encode(Input, maps:from_list(Opts)). 287 288random(Bytes) when is_integer(Bytes) andalso Bytes >= 0 -> 289 random(Bytes, #{}). 290 291random(0, Opts) when is_map(Opts) -> 292 <<>>; 293random(Bytes, Opts) when (Bytes =:= 1) andalso is_map(Opts) -> 294 erlang:error({badarg, [Bytes, Opts]}); 295random(Bytes, Opts) when is_integer(Bytes) andalso Bytes > 0 andalso is_map(Opts) -> 296 Padding = maps:get('padding', Opts, true), 297 R = (Bytes rem 4), 298 Size = 299 case Padding of 300 true when R =:= 0 -> 301 (Bytes * 3) div 4; 302 false when R =:= 0 orelse R =:= 2 orelse R =:= 3 -> 303 (Bytes * 3) div 4; 304 _ -> 305 erlang:error({badarg, [Bytes, Opts]}) 306 end, 307 Binary = crypto:strong_rand_bytes(Size), 308 encode(Binary, Opts); 309random(Bytes, Opts) when is_integer(Bytes) andalso Bytes >= 0 andalso is_list(Opts) -> 310 random(Bytes, maps:from_list(Opts)). 311 312%%%------------------------------------------------------------------- 313%%% Internal functions 314%%%------------------------------------------------------------------- 315 316%% @private 317encode_char(V) -> 318 jose_base:encode_char(?INT_TO_B64(V)). 319 320%% @private 321encode_pair(V) -> 322 jose_base:encode_pair(?INT_TO_B64(V), sensitive). 323