diff options
Diffstat (limited to 'lib/stdlib/src/base64.erl')
-rw-r--r-- | lib/stdlib/src/base64.erl | 743 |
1 files changed, 487 insertions, 256 deletions
diff --git a/lib/stdlib/src/base64.erl b/lib/stdlib/src/base64.erl index be4d9d42b4..62bcd0d24f 100644 --- a/lib/stdlib/src/base64.erl +++ b/lib/stdlib/src/base64.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2021. All Rights Reserved. +%% Copyright Ericsson AB 2007-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -21,12 +21,22 @@ -module(base64). --export([encode/1, decode/1, mime_decode/1, - encode_to_string/1, decode_to_string/1, mime_decode_to_string/1]). - +-export([encode/1, encode/2, + decode/1, decode/2, + mime_decode/1, mime_decode/2, + encode_to_string/1, encode_to_string/2, + decode_to_string/1, decode_to_string/2, + mime_decode_to_string/1, mime_decode_to_string/2, + format_error/2]). %% RFC 4648: Base 64 Encoding alphabet --type base64_alphabet() :: $A..$Z | $a..$z | $0..$9 | $+ | $/ | $=. +-type base64_alphabet() :: $A..$Z | $a..$z | $0..$9 | $+ | $/ | $- | $_ | $=. + +%% Selector for the Base 64 alphabet, `standard' for RFC 4648 +%% Section 4, `urlsafe' for RFC 4648 Section 5. +-type base64_mode() :: 'standard' | 'urlsafe'. + +-type options() :: #{padding => boolean(), mode => base64_mode()}. %% The following type is a subtype of string() for return values %% of encoding functions. @@ -40,67 +50,126 @@ Data :: byte_string() | binary(), Base64String :: base64_string(). -encode_to_string(Bin) when is_binary(Bin) -> - encode_to_string(binary_to_list(Bin)); -encode_to_string(List) when is_list(List) -> - encode_list_to_string(List). +encode_to_string(Data) -> + encode_to_string(Data, #{}). + +-spec encode_to_string(Data, Options) -> Base64String when + Data :: byte_string() | binary(), + Options :: options(), + Base64String :: base64_string(). + +encode_to_string(Bin, Options) when is_binary(Bin), is_map(Options) -> + encode_to_string(binary_to_list(Bin), Options); +encode_to_string(List, Options) when is_list(List), is_map(Options) -> + encode_list_to_string(get_encoding_offset(Options), get_padding(Options), List). -spec encode(Data) -> Base64 when Data :: byte_string() | binary(), Base64 :: base64_binary(). -encode(Bin) when is_binary(Bin) -> - encode_binary(Bin, <<>>); -encode(List) when is_list(List) -> - encode_list(List, <<>>). +encode(Data) -> + encode(Data, #{}). + +-spec encode(Data, Options) -> Base64 when + Data :: byte_string() | binary(), + Options :: options(), + Base64 :: base64_binary(). -encode_list_to_string([]) -> +encode(Bin, Options) when is_binary(Bin), is_map(Options) -> + encode_binary(get_encoding_offset(Options), get_padding(Options), Bin, <<>>); +encode(List, Options) when is_list(List) -> + encode_list(get_encoding_offset(Options), get_padding(Options), List, <<>>). + +encode_list_to_string(_ModeOffset, _Padding, []) -> []; -encode_list_to_string([B1]) -> - [b64e(B1 bsr 2), - b64e((B1 band 3) bsl 4), $=, $=]; -encode_list_to_string([B1,B2]) -> - [b64e(B1 bsr 2), - b64e(((B1 band 3) bsl 4) bor (B2 bsr 4)), - b64e((B2 band 15) bsl 2), $=]; -encode_list_to_string([B1,B2,B3|Ls]) -> +encode_list_to_string(ModeOffset, Padding, [B1]) -> + [b64e(B1 bsr 2, ModeOffset), + b64e((B1 band 3) bsl 4, ModeOffset) | + case Padding of + true -> "=="; + false -> "" + end]; +encode_list_to_string(ModeOffset, Padding, [B1,B2]) -> + [b64e(B1 bsr 2, ModeOffset), + b64e(((B1 band 3) bsl 4) bor (B2 bsr 4), ModeOffset), + b64e((B2 band 15) bsl 2, ModeOffset) | + case Padding of + true -> "="; + false -> "" + end]; +encode_list_to_string(ModeOffset, Padding, [B1,B2,B3|Ls]) -> BB = (B1 bsl 16) bor (B2 bsl 8) bor B3, - [b64e(BB bsr 18), - b64e((BB bsr 12) band 63), - b64e((BB bsr 6) band 63), - b64e(BB band 63) | encode_list_to_string(Ls)]. - -encode_binary(<<>>, A) -> + [b64e(BB bsr 18, ModeOffset), + b64e((BB bsr 12) band 63, ModeOffset), + b64e((BB bsr 6) band 63, ModeOffset), + b64e(BB band 63, ModeOffset) | encode_list_to_string(ModeOffset, Padding, Ls)]. + +encode_binary(ModeOffset, Padding, <<B1:6, B2:6, B3:6, B4:6, B5:6, B6:6, B7:6, B8:6, Ls/bits>>, A) -> + encode_binary(ModeOffset, + Padding, + Ls, + <<A/bits, + (b64e(B1, ModeOffset)):8, + (b64e(B2, ModeOffset)):8, + (b64e(B3, ModeOffset)):8, + (b64e(B4, ModeOffset)):8, + (b64e(B5, ModeOffset)):8, + (b64e(B6, ModeOffset)):8, + (b64e(B7, ModeOffset)):8, + (b64e(B8, ModeOffset)):8>>); +encode_binary(_ModeOffset, _Padding, <<>>, A) -> A; -encode_binary(<<B1:8>>, A) -> - <<A/bits,(b64e(B1 bsr 2)):8,(b64e((B1 band 3) bsl 4)):8,$=:8,$=:8>>; -encode_binary(<<B1:8, B2:8>>, A) -> - <<A/bits,(b64e(B1 bsr 2)):8, - (b64e(((B1 band 3) bsl 4) bor (B2 bsr 4))):8, - (b64e((B2 band 15) bsl 2)):8, $=:8>>; -encode_binary(<<B1:8, B2:8, B3:8, Ls/bits>>, A) -> - BB = (B1 bsl 16) bor (B2 bsl 8) bor B3, - encode_binary(Ls, - <<A/bits,(b64e(BB bsr 18)):8, - (b64e((BB bsr 12) band 63)):8, - (b64e((BB bsr 6) band 63)):8, - (b64e(BB band 63)):8>>). +encode_binary(ModeOffset, Padding, <<B1:6, B2:6, B3:6, B4:6, Ls/bits>>, A) -> + encode_binary(ModeOffset, + Padding, + Ls, + <<A/bits, + (b64e(B1, ModeOffset)):8, + (b64e(B2, ModeOffset)):8, + (b64e(B3, ModeOffset)):8, + (b64e(B4, ModeOffset)):8>>); +encode_binary(ModeOffset, Padding, <<B1:6, B2:2>>, A) -> + E1 = b64e(B1, ModeOffset), + E2 = b64e(B2 bsl 4, ModeOffset), + case Padding of + true -> <<A/bits,E1,E2,$=,$=>>; + _ -> <<A/bits,E1,E2>> + end; +encode_binary(ModeOffset, Padding, <<B1:6, B2:6, B3:4>>, A) -> + E1 = b64e(B1, ModeOffset), + E2 = b64e(B2, ModeOffset), + E3 = b64e(B3 bsl 2, ModeOffset), + case Padding of + true -> <<A/bits,E1,E2,E3,$=>>; + _ -> <<A/bits,E1,E2,E3>> + end. -encode_list([], A) -> +encode_list(_ModeOffset, _Padding, [], A) -> A; -encode_list([B1], A) -> - <<A/bits,(b64e(B1 bsr 2)):8,(b64e((B1 band 3) bsl 4)):8,$=:8,$=:8>>; -encode_list([B1,B2], A) -> - <<A/bits,(b64e(B1 bsr 2)):8, - (b64e(((B1 band 3) bsl 4) bor (B2 bsr 4))):8, - (b64e((B2 band 15) bsl 2)):8, $=:8>>; -encode_list([B1,B2,B3|Ls], A) -> +encode_list(ModeOffset, Padding, [B1], A) -> + E1 = b64e(B1 bsr 2, ModeOffset), + E2 = b64e((B1 band 3) bsl 4, ModeOffset), + case Padding of + true -> <<A/bits,E1,E2,$=,$=>>; + false -> <<A/bits,E1,E2>> + end; +encode_list(ModeOffset, Padding, [B1,B2], A) -> + E1 = b64e(B1 bsr 2, ModeOffset), + E2 = b64e(((B1 band 3) bsl 4) bor (B2 bsr 4), ModeOffset), + E3 = b64e((B2 band 15) bsl 2, ModeOffset), + case Padding of + true -> <<A/bits,E1,E2,E3,$=>>; + false -> <<A/bits,E1,E2,E3>> + end; +encode_list(ModeOffset, Padding, [B1,B2,B3|Ls], A) -> BB = (B1 bsl 16) bor (B2 bsl 8) bor B3, - encode_list(Ls, - <<A/bits,(b64e(BB bsr 18)):8, - (b64e((BB bsr 12) band 63)):8, - (b64e((BB bsr 6) band 63)):8, - (b64e(BB band 63)):8>>). + encode_list(ModeOffset, + Padding, + Ls, + <<A/bits,(b64e(BB bsr 18, ModeOffset)):8, + (b64e((BB bsr 12) band 63, ModeOffset)):8, + (b64e((BB bsr 6) band 63, ModeOffset)):8, + (b64e(BB band 63, ModeOffset)):8>>). %% mime_decode strips away all characters not Base64 before %% converting, whereas decode crashes if an illegal character is found @@ -109,19 +178,35 @@ encode_list([B1,B2,B3|Ls], A) -> Base64 :: base64_string() | base64_binary(), Data :: binary(). -decode(Bin) when is_binary(Bin) -> - decode_binary(Bin, <<>>); -decode(List) when is_list(List) -> - decode_list(List, <<>>). +decode(Base64) -> + decode(Base64, #{}). + +-spec decode(Base64, Options) -> Data when + Base64 :: base64_string() | base64_binary(), + Options :: options(), + Data :: binary(). + +decode(Bin, Options) when is_binary(Bin) -> + decode_binary(get_decoding_offset(Options), get_padding(Options), Bin, <<>>); +decode(List, Options) when is_list(List) -> + decode_list(get_decoding_offset(Options), get_padding(Options), List, <<>>). -spec mime_decode(Base64) -> Data when Base64 :: base64_string() | base64_binary(), Data :: binary(). -mime_decode(Bin) when is_binary(Bin) -> - mime_decode_binary(Bin, <<>>); -mime_decode(List) when is_list(List) -> - mime_decode_list(List, <<>>). +mime_decode(Base64) -> + mime_decode(Base64, #{}). + +-spec mime_decode(Base64, Options) -> Data when + Base64 :: base64_string() | base64_binary(), + Options :: options(), + Data :: binary(). + +mime_decode(Bin, Options) when is_binary(Bin) -> + mime_decode_binary(get_decoding_offset(Options), get_padding(Options), Bin, <<>>); +mime_decode(List, Options) when is_list(List) -> + mime_decode_list(get_decoding_offset(Options), get_padding(Options), List, <<>>). %% mime_decode_to_string strips away all characters not Base64 before %% converting, whereas decode_to_string crashes if an illegal @@ -131,324 +216,439 @@ mime_decode(List) when is_list(List) -> Base64 :: base64_string() | base64_binary(), DataString :: byte_string(). -decode_to_string(Bin) when is_binary(Bin) -> - decode_to_string(binary_to_list(Bin)); -decode_to_string(List) when is_list(List) -> - decode_list_to_string(List). +decode_to_string(Base64) -> + decode_to_string(Base64, #{}). + +-spec decode_to_string(Base64, Options) -> DataString when + Base64 :: base64_string() | base64_binary(), + Options :: options(), + DataString :: byte_string(). + +decode_to_string(Bin, Options) when is_binary(Bin) -> + decode_to_string(binary_to_list(Bin), Options); +decode_to_string(List, Options) when is_list(List) -> + decode_list_to_string(get_decoding_offset(Options), get_padding(Options), List). -spec mime_decode_to_string(Base64) -> DataString when Base64 :: base64_string() | base64_binary(), DataString :: byte_string(). -mime_decode_to_string(Bin) when is_binary(Bin) -> - mime_decode_to_string(binary_to_list(Bin)); -mime_decode_to_string(List) when is_list(List) -> - mime_decode_list_to_string(List). +mime_decode_to_string(Base64) -> + mime_decode_to_string(Base64, #{}). + +-spec mime_decode_to_string(Base64, Options) -> DataString when + Base64 :: base64_string() | base64_binary(), + Options :: options(), + DataString :: byte_string(). + +mime_decode_to_string(Bin, Options) when is_binary(Bin) -> + mime_decode_to_string(binary_to_list(Bin), Options); +mime_decode_to_string(List, Options) when is_list(List) -> + mime_decode_list_to_string(get_decoding_offset(Options), get_padding(Options), List). %% Skipping pad character if not at end of string. Also liberal about %% excess padding and skipping of other illegal (non-base64 alphabet) %% characters. See section 3.3 of RFC4648 -mime_decode_list([0 | Cs], A) -> - mime_decode_list(Cs, A); -mime_decode_list([C1 | Cs], A) -> - case b64d(C1) of - B1 when is_integer(B1) -> mime_decode_list(Cs, A, B1); - _ -> mime_decode_list(Cs, A) % eq is padding +mime_decode_list(ModeOffset, Padding, [C1 | Cs], A) -> + case b64d(C1, ModeOffset) of + B1 when is_integer(B1) -> mime_decode_list(ModeOffset, Padding, Cs, A, B1); + _ -> mime_decode_list(ModeOffset, Padding, Cs, A) % eq is padding end; -mime_decode_list([], A) -> +mime_decode_list(_ModeOffset, _Padding, [], A) -> A. -mime_decode_list([0 | Cs], A, B1) -> - mime_decode_list(Cs, A, B1); -mime_decode_list([C2 | Cs], A, B1) -> - case b64d(C2) of +mime_decode_list(ModeOffset, Padding, [C2 | Cs], A, B1) -> + case b64d(C2, ModeOffset) of B2 when is_integer(B2) -> - mime_decode_list(Cs, A, B1, B2); - _ -> mime_decode_list(Cs, A, B1) % eq is padding + mime_decode_list(ModeOffset, Padding, Cs, A, B1, B2); + _ -> mime_decode_list(ModeOffset, Padding, Cs, A, B1) % eq is padding end. -mime_decode_list([0 | Cs], A, B1, B2) -> - mime_decode_list(Cs, A, B1, B2); -mime_decode_list([C3 | Cs], A, B1, B2) -> - case b64d(C3) of +mime_decode_list(ModeOffset, Padding, [C3 | Cs], A, B1, B2) -> + case b64d(C3, ModeOffset) of B3 when is_integer(B3) -> - mime_decode_list(Cs, A, B1, B2, B3); + mime_decode_list(ModeOffset, Padding, Cs, A, B1, B2, B3); eq=B3 -> - mime_decode_list_after_eq(Cs, A, B1, B2, B3); - _ -> mime_decode_list(Cs, A, B1, B2) + mime_decode_list_after_eq(ModeOffset, Padding, Cs, A, B1, B2, B3); + _ -> mime_decode_list(ModeOffset, Padding, Cs, A, B1, B2) + end; +mime_decode_list(ModeOffset, Padding, [], A, B1, B2) -> + case Padding of + true -> missing_padding_error(); + false -> mime_decode_list_after_eq(ModeOffset, Padding, [], A, B1, B2, eq) end. -mime_decode_list([0 | Cs], A, B1, B2, B3) -> - mime_decode_list(Cs, A, B1, B2, B3); -mime_decode_list([C4 | Cs], A, B1, B2, B3) -> - case b64d(C4) of +mime_decode_list(ModeOffset, Padding, [C4 | Cs], A, B1, B2, B3) -> + case b64d(C4, ModeOffset) of B4 when is_integer(B4) -> - mime_decode_list(Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>); + mime_decode_list(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>); eq -> - mime_decode_list_after_eq(Cs, A, B1, B2, B3); - _ -> mime_decode_list(Cs, A, B1, B2, B3) + mime_decode_list_after_eq(ModeOffset, Padding, Cs, A, B1, B2, B3); + _ -> mime_decode_list(ModeOffset, Padding, Cs, A, B1, B2, B3) + end; +mime_decode_list(ModeOffset, Padding, [], A, B1, B2, B3) -> + case Padding of + true -> missing_padding_error(); + false -> mime_decode_list_after_eq(ModeOffset, Padding, [], A, B1, B2, B3) end. -mime_decode_list_after_eq([0 | Cs], A, B1, B2, B3) -> - mime_decode_list_after_eq(Cs, A, B1, B2, B3); -mime_decode_list_after_eq([C | Cs], A, B1, B2, B3) -> - case b64d(C) of +mime_decode_list_after_eq(ModeOffset, Padding, [C | Cs], A, B1, B2, B3) -> + case b64d(C, ModeOffset) of B when is_integer(B) -> %% More valid data, skip the eq as invalid case B3 of - eq -> mime_decode_list(Cs, A, B1, B2, B); - _ -> mime_decode_list(Cs, <<A/bits,B1:6,B2:6,B3:6,B:6>>) + eq -> mime_decode_list(ModeOffset, Padding, Cs, A, B1, B2, B); + _ -> mime_decode_list(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,B3:6,B:6>>) end; - _ -> mime_decode_list_after_eq(Cs, A, B1, B2, B3) + _ -> mime_decode_list_after_eq(ModeOffset, Padding, Cs, A, B1, B2, B3) end; -mime_decode_list_after_eq([], A, B1, B2, eq) -> +mime_decode_list_after_eq(_ModeOffset, _Padding, [], A, B1, B2, eq) -> <<A/bits,B1:6,(B2 bsr 4):2>>; -mime_decode_list_after_eq([], A, B1, B2, B3) -> +mime_decode_list_after_eq(_ModeOffset, _Padding, [], A, B1, B2, B3) -> <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>. -mime_decode_binary(<<0:8, Cs/bits>>, A) -> - mime_decode_binary(Cs, A); -mime_decode_binary(<<C1:8, Cs/bits>>, A) -> - case b64d(C1) of - B1 when is_integer(B1) -> mime_decode_binary(Cs, A, B1); - _ -> mime_decode_binary(Cs, A) % eq is padding +mime_decode_binary(ModeOffset, Padding, <<C1:8, Cs/bits>>, A) -> + case b64d(C1, ModeOffset) of + B1 when is_integer(B1) -> mime_decode_binary(ModeOffset, Padding, Cs, A, B1); + _ -> mime_decode_binary(ModeOffset, Padding, Cs, A) % eq is padding end; -mime_decode_binary(<<>>, A) -> +mime_decode_binary(_ModeOffset, _Padding, <<>>, A) -> A. -mime_decode_binary(<<0:8, Cs/bits>>, A, B1) -> - mime_decode_binary(Cs, A, B1); -mime_decode_binary(<<C2:8, Cs/bits>>, A, B1) -> - case b64d(C2) of +mime_decode_binary(ModeOffset, Padding, <<C2:8, Cs/bits>>, A, B1) -> + case b64d(C2, ModeOffset) of B2 when is_integer(B2) -> - mime_decode_binary(Cs, A, B1, B2); - _ -> mime_decode_binary(Cs, A, B1) % eq is padding + mime_decode_binary(ModeOffset, Padding, Cs, A, B1, B2); + _ -> mime_decode_binary(ModeOffset, Padding, Cs, A, B1) % eq is padding end. -mime_decode_binary(<<0:8, Cs/bits>>, A, B1, B2) -> - mime_decode_binary(Cs, A, B1, B2); -mime_decode_binary(<<C3:8, Cs/bits>>, A, B1, B2) -> - case b64d(C3) of +mime_decode_binary(ModeOffset, Padding, <<C3:8, Cs/bits>>, A, B1, B2) -> + case b64d(C3, ModeOffset) of B3 when is_integer(B3) -> - mime_decode_binary(Cs, A, B1, B2, B3); + mime_decode_binary(ModeOffset, Padding, Cs, A, B1, B2, B3); eq=B3 -> - mime_decode_binary_after_eq(Cs, A, B1, B2, B3); - _ -> mime_decode_binary(Cs, A, B1, B2) + mime_decode_binary_after_eq(ModeOffset, Padding, Cs, A, B1, B2, B3); + _ -> mime_decode_binary(ModeOffset, Padding, Cs, A, B1, B2) + end; +mime_decode_binary(ModeOffset, Padding, <<Cs/bits>>, A, B1, B2) -> + case Padding of + true -> missing_padding_error(); + false -> mime_decode_binary_after_eq(ModeOffset, Padding, Cs, A, B1, B2, eq) end. -mime_decode_binary(<<0:8, Cs/bits>>, A, B1, B2, B3) -> - mime_decode_binary(Cs, A, B1, B2, B3); -mime_decode_binary(<<C4:8, Cs/bits>>, A, B1, B2, B3) -> - case b64d(C4) of +mime_decode_binary(ModeOffset, Padding, <<C4:8, Cs/bits>>, A, B1, B2, B3) -> + case b64d(C4, ModeOffset) of B4 when is_integer(B4) -> - mime_decode_binary(Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>); + mime_decode_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>); eq -> - mime_decode_binary_after_eq(Cs, A, B1, B2, B3); - _ -> mime_decode_binary(Cs, A, B1, B2, B3) + mime_decode_binary_after_eq(ModeOffset, Padding, Cs, A, B1, B2, B3); + _ -> mime_decode_binary(ModeOffset, Padding, Cs, A, B1, B2, B3) + end; +mime_decode_binary(ModeOffset, Padding, <<Cs/bits>>, A, B1, B2, B3) -> + case Padding of + true -> missing_padding_error(); + false -> mime_decode_binary_after_eq(ModeOffset, Padding, Cs, A, B1, B2, B3) end. -mime_decode_binary_after_eq(<<0:8, Cs/bits>>, A, B1, B2, B3) -> - mime_decode_binary_after_eq(Cs, A, B1, B2, B3); -mime_decode_binary_after_eq(<<C:8, Cs/bits>>, A, B1, B2, B3) -> - case b64d(C) of +mime_decode_binary_after_eq(ModeOffset, Padding, <<C:8, Cs/bits>>, A, B1, B2, B3) -> + case b64d(C, ModeOffset) of B when is_integer(B) -> %% More valid data, skip the eq as invalid case B3 of - eq -> mime_decode_binary(Cs, A, B1, B2, B); - _ -> mime_decode_binary(Cs, <<A/bits,B1:6,B2:6,B3:6,B:6>>) + eq -> mime_decode_binary(ModeOffset, Padding, Cs, A, B1, B2, B); + _ -> mime_decode_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,B3:6,B:6>>) end; - _ -> mime_decode_binary_after_eq(Cs, A, B1, B2, B3) + _ -> mime_decode_binary_after_eq(ModeOffset, Padding, Cs, A, B1, B2, B3) end; -mime_decode_binary_after_eq(<<>>, A, B1, B2, eq) -> +mime_decode_binary_after_eq(_ModeOffset, _Padding, <<>>, A, B1, B2, eq) -> <<A/bits,B1:6,(B2 bsr 4):2>>; -mime_decode_binary_after_eq(<<>>, A, B1, B2, B3) -> +mime_decode_binary_after_eq(_ModeOffset, _Padding, <<>>, A, B1, B2, B3) -> <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>. -mime_decode_list_to_string([0 | Cs]) -> - mime_decode_list_to_string(Cs); -mime_decode_list_to_string([C1 | Cs]) -> - case b64d(C1) of - B1 when is_integer(B1) -> mime_decode_list_to_string(Cs, B1); - _ -> mime_decode_list_to_string(Cs) % eq is padding +mime_decode_list_to_string(ModeOffset, Padding, [C1 | Cs]) -> + case b64d(C1, ModeOffset) of + B1 when is_integer(B1) -> mime_decode_list_to_string(ModeOffset, Padding, Cs, B1); + _ -> mime_decode_list_to_string(ModeOffset, Padding, Cs) % eq is padding end; -mime_decode_list_to_string([]) -> +mime_decode_list_to_string(_ModeOffset, _Padding, []) -> []. -mime_decode_list_to_string([0 | Cs], B1) -> - mime_decode_list_to_string(Cs, B1); -mime_decode_list_to_string([C2 | Cs], B1) -> - case b64d(C2) of +mime_decode_list_to_string(ModeOffset, Padding, [C2 | Cs], B1) -> + case b64d(C2, ModeOffset) of B2 when is_integer(B2) -> - mime_decode_list_to_string(Cs, B1, B2); - _ -> mime_decode_list_to_string(Cs, B1) % eq is padding + mime_decode_list_to_string(ModeOffset, Padding, Cs, B1, B2); + _ -> mime_decode_list_to_string(ModeOffset, Padding, Cs, B1) % eq is padding end. -mime_decode_list_to_string([0 | Cs], B1, B2) -> - mime_decode_list_to_string(Cs, B1, B2); -mime_decode_list_to_string([C3 | Cs], B1, B2) -> - case b64d(C3) of +mime_decode_list_to_string(ModeOffset, Padding, [C3 | Cs], B1, B2) -> + case b64d(C3, ModeOffset) of B3 when is_integer(B3) -> - mime_decode_list_to_string(Cs, B1, B2, B3); - eq=B3 -> mime_decode_list_to_string_after_eq(Cs, B1, B2, B3); - _ -> mime_decode_list_to_string(Cs, B1, B2) + mime_decode_list_to_string(ModeOffset, Padding, Cs, B1, B2, B3); + eq=B3 -> mime_decode_list_to_string_after_eq(ModeOffset, Padding, Cs, B1, B2, B3); + _ -> mime_decode_list_to_string(ModeOffset, Padding, Cs, B1, B2) + end; +mime_decode_list_to_string(ModeOffset, Padding, [], B1, B2) -> + case Padding of + true -> missing_padding_error(); + false -> mime_decode_list_to_string_after_eq(ModeOffset, Padding, [], B1, B2, eq) end. -mime_decode_list_to_string([0 | Cs], B1, B2, B3) -> - mime_decode_list_to_string(Cs, B1, B2, B3); -mime_decode_list_to_string([C4 | Cs], B1, B2, B3) -> - case b64d(C4) of +mime_decode_list_to_string(ModeOffset, Padding, [C4 | Cs], B1, B2, B3) -> + case b64d(C4, ModeOffset) of B4 when is_integer(B4) -> Bits4x6 = (B1 bsl 18) bor (B2 bsl 12) bor (B3 bsl 6) bor B4, Octet1 = Bits4x6 bsr 16, Octet2 = (Bits4x6 bsr 8) band 16#ff, Octet3 = Bits4x6 band 16#ff, - [Octet1, Octet2, Octet3 | mime_decode_list_to_string(Cs)]; + [Octet1, Octet2, Octet3 | mime_decode_list_to_string(ModeOffset, Padding, Cs)]; eq -> - mime_decode_list_to_string_after_eq(Cs, B1, B2, B3); - _ -> mime_decode_list_to_string(Cs, B1, B2, B3) + mime_decode_list_to_string_after_eq(ModeOffset, Padding, Cs, B1, B2, B3); + _ -> mime_decode_list_to_string(ModeOffset, Padding, Cs, B1, B2, B3) + end; +mime_decode_list_to_string(ModeOffset, Padding, [], B1, B2, B3) -> + case Padding of + true -> missing_padding_error(); + false -> mime_decode_list_to_string_after_eq(ModeOffset, Padding, [], B1, B2, B3) end. -mime_decode_list_to_string_after_eq([0 | Cs], B1, B2, B3) -> - mime_decode_list_to_string_after_eq(Cs, B1, B2, B3); -mime_decode_list_to_string_after_eq([C | Cs], B1, B2, B3) -> - case b64d(C) of +mime_decode_list_to_string_after_eq(ModeOffset, Padding, [C | Cs], B1, B2, B3) -> + case b64d(C, ModeOffset) of B when is_integer(B) -> %% More valid data, skip the eq as invalid case B3 of - eq -> mime_decode_list_to_string(Cs, B1, B2, B); + eq -> mime_decode_list_to_string(ModeOffset, Padding, Cs, B1, B2, B); _ -> Bits4x6 = (B1 bsl 18) bor (B2 bsl 12) bor (B3 bsl 6) bor B, Octet1 = Bits4x6 bsr 16, Octet2 = (Bits4x6 bsr 8) band 16#ff, Octet3 = Bits4x6 band 16#ff, - [Octet1, Octet2, Octet3 | mime_decode_list_to_string(Cs)] + [Octet1, Octet2, Octet3 | mime_decode_list_to_string(ModeOffset, Padding, Cs)] end; - _ -> mime_decode_list_to_string_after_eq(Cs, B1, B2, B3) + _ -> mime_decode_list_to_string_after_eq(ModeOffset, Padding, Cs, B1, B2, B3) end; -mime_decode_list_to_string_after_eq([], B1, B2, eq) -> +mime_decode_list_to_string_after_eq(_ModeOffset, _Padding, [], B1, B2, eq) -> binary_to_list(<<B1:6,(B2 bsr 4):2>>); -mime_decode_list_to_string_after_eq([], B1, B2, B3) -> +mime_decode_list_to_string_after_eq(_ModeOffset, _Padding, [], B1, B2, B3) -> binary_to_list(<<B1:6,B2:6,(B3 bsr 2):4>>). -decode_list([C1 | Cs], A) -> - case b64d(C1) of - ws -> decode_list(Cs, A); - B1 -> decode_list(Cs, A, B1) +decode_list(ModeOffset, Padding, [C1 | Cs], A) -> + case b64d(C1, ModeOffset) of + ws -> decode_list(ModeOffset, Padding, Cs, A); + B1 -> decode_list(ModeOffset, Padding, Cs, A, B1) end; -decode_list([], A) -> +decode_list(_ModeOffset, _Padding, [], A) -> A. -decode_list([C2 | Cs], A, B1) -> - case b64d(C2) of - ws -> decode_list(Cs, A, B1); - B2 -> decode_list(Cs, A, B1, B2) +decode_list(ModeOffset, Padding, [C2 | Cs], A, B1) -> + case b64d(C2, ModeOffset) of + ws -> decode_list(ModeOffset, Padding, Cs, A, B1); + B2 -> decode_list(ModeOffset, Padding, Cs, A, B1, B2) end. -decode_list([C3 | Cs], A, B1, B2) -> - case b64d(C3) of - ws -> decode_list(Cs, A, B1, B2); - B3 -> decode_list(Cs, A, B1, B2, B3) +decode_list(ModeOffset, Padding, [C3 | Cs], A, B1, B2) -> + case b64d(C3, ModeOffset) of + ws -> decode_list(ModeOffset, Padding, Cs, A, B1, B2); + B3 -> decode_list(ModeOffset, Padding, Cs, A, B1, B2, B3) + end; +decode_list(ModeOffset, Padding, [], A, B1, B2) -> + case Padding of + true -> missing_padding_error(); + false -> decode_list(ModeOffset, Padding, [], A, B1, B2, eq) end. -decode_list([C4 | Cs], A, B1, B2, B3) -> - case b64d(C4) of - ws -> decode_list(Cs, A, B1, B2, B3); - eq when B3 =:= eq -> only_ws(Cs, <<A/bits,B1:6,(B2 bsr 4):2>>); - eq -> only_ws(Cs, <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>); - B4 -> decode_list(Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>) +decode_list(ModeOffset, Padding, [C4 | Cs], A, B1, B2, B3) -> + case b64d(C4, ModeOffset) of + ws -> decode_list(ModeOffset, Padding, Cs, A, B1, B2, B3); + eq when B3 =:= eq -> only_ws(ModeOffset, Padding, Cs, <<A/bits,B1:6,(B2 bsr 4):2>>); + eq -> only_ws(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>); + B4 -> decode_list(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>) + end; +decode_list(_ModeOffset, Padding, [], A, B1, B2, B3) -> + case Padding of + true -> missing_padding_error(); + false when B3 == eq -> <<A/bits,B1:6,(B2 bsr 4):2>>; + false -> <<A/bits,B1:6,B2:6,(B3 bsr 2):4>> end. -decode_binary(<<C1:8, Cs/bits>>, A) -> - case b64d(C1) of - ws -> decode_binary(Cs, A); - B1 -> decode_binary(Cs, A, B1) +decode_binary(ModeOffset, Padding, <<C1:8, C2:8, C3:8, C4:8, Cs/bits>>, A) -> + case {b64d(C1, ModeOffset), b64d(C2, ModeOffset), b64d(C3, ModeOffset), b64d(C4, ModeOffset)} of + {B1, B2, B3, B4} when is_integer(B1), is_integer(B2), + is_integer(B3), is_integer(B4) -> + decode_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>); + {B1, B2, B3, B4} -> + dec_bin(ModeOffset, Padding, Cs, B1, B2, B3, B4, A) end; -decode_binary(<<>>, A) -> - A. +decode_binary(_ModeOffset, _Padding, <<>>, A) -> + A; +decode_binary(ModeOffset, Padding, <<C1:8, Cs/bits>>, A) -> + case b64d(C1, ModeOffset) of + ws -> decode_binary(ModeOffset, Padding, Cs, A); + B1 -> decode_binary(ModeOffset, Padding, Cs, A, B1) + end. -decode_binary(<<C2:8, Cs/bits>>, A, B1) -> - case b64d(C2) of - ws -> decode_binary(Cs, A, B1); - B2 -> decode_binary(Cs, A, B1, B2) +dec_bin(ModeOffset, Padding, Cs, ws, B2, B3, B4, A) -> + dec_bin(ModeOffset, Padding, Cs, B2, B3, B4, A); +dec_bin(ModeOffset, Padding, Cs, B1, ws, B3, B4, A) -> + dec_bin(ModeOffset, Padding, Cs, B1, B3, B4, A); +dec_bin(ModeOffset, Padding, Cs, B1, B2, ws, B4, A) -> + dec_bin(ModeOffset, Padding, Cs, B1, B2, B4, A); +dec_bin(ModeOffset, Padding, Cs, B1, B2, B3, B4, A) -> + case B4 of + ws -> decode_binary(ModeOffset, Padding, Cs, A, B1, B2, B3); + eq when B3 =:= eq -> only_ws_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,(B2 bsr 4):2>>); + eq -> only_ws_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>); + B4 -> decode_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>) end. -decode_binary(<<C3:8, Cs/bits>>, A, B1, B2) -> - case b64d(C3) of - ws -> decode_binary(Cs, A, B1, B2); - B3 -> decode_binary(Cs, A, B1, B2, B3) +dec_bin(ModeOffset, Padding, Cs, ws, B2, B3, A) -> + dec_bin(ModeOffset, Padding, Cs, B2, B3, A); +dec_bin(ModeOffset, Padding, Cs, B1, ws, B3, A) -> + dec_bin(ModeOffset, Padding, Cs, B1, B3, A); +dec_bin(ModeOffset, Padding, Cs, B1, B2, ws, A) -> + dec_bin(ModeOffset, Padding, Cs, B1, B2, A); +dec_bin(ModeOffset, Padding, Cs, B1, B2, B3, A) -> + decode_binary(ModeOffset, Padding, Cs, A, B1, B2, B3). + +dec_bin(ModeOffset, Padding, Cs, ws, B2, A) -> + dec_bin(ModeOffset, Padding, Cs, B2, A); +dec_bin(ModeOffset, Padding, Cs, B1, ws, A) -> + dec_bin(ModeOffset, Padding, Cs, B1, A); +dec_bin(ModeOffset, Padding, Cs, B1, B2, A) -> + decode_binary(ModeOffset, Padding, Cs, A, B1, B2). + +dec_bin(ModeOffset, Padding, Cs, ws, A) -> + decode_binary(ModeOffset, Padding, Cs, A); +dec_bin(ModeOffset, Padding, Cs, B1, A) -> + decode_binary(ModeOffset, Padding,Cs, A, B1). + +decode_binary(ModeOffset, Padding, <<C2:8, Cs/bits>>, A, B1) -> + case b64d(C2, ModeOffset) of + ws -> decode_binary(ModeOffset, Padding, Cs, A, B1); + B2 -> decode_binary(ModeOffset, Padding, Cs, A, B1, B2) end. -decode_binary(<<C4:8, Cs/bits>>, A, B1, B2, B3) -> - case b64d(C4) of - ws -> decode_binary(Cs, A, B1, B2, B3); - eq when B3 =:= eq -> only_ws_binary(Cs, <<A/bits,B1:6,(B2 bsr 4):2>>); - eq -> only_ws_binary(Cs, <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>); - B4 -> decode_binary(Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>) +decode_binary(ModeOffset, Padding, <<C3:8, Cs/bits>>, A, B1, B2) -> + case b64d(C3, ModeOffset) of + ws -> decode_binary(ModeOffset, Padding, Cs, A, B1, B2); + B3 -> decode_binary(ModeOffset, Padding, Cs, A, B1, B2, B3) + end; +decode_binary(ModeOffset, Padding, <<Cs/bits>>, A, B1, B2) -> + case Padding of + true -> missing_padding_error(); + false -> decode_binary(ModeOffset, Padding, Cs, A, B1, B2, eq) + end. + +decode_binary(ModeOffset, Padding, <<C4:8, Cs/bits>>, A, B1, B2, B3) -> + case b64d(C4, ModeOffset) of + ws -> decode_binary(ModeOffset, Padding, Cs, A, B1, B2, B3); + eq when B3 =:= eq -> only_ws_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,(B2 bsr 4):2>>); + eq -> only_ws_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>); + B4 -> decode_binary(ModeOffset, Padding, Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>) + end; +decode_binary(_ModeOffset, Padding, <<>>, A, B1, B2, B3) -> + case Padding of + true -> missing_padding_error(); + false when B3 =:= eq -> <<A/bits,B1:6,(B2 bsr 4):2>>; + false -> <<A/bits,B1:6,B2:6,(B3 bsr 2):4>> end. -only_ws_binary(<<>>, A) -> +only_ws_binary(_ModeOffset, _Padding, <<>>, A) -> A; -only_ws_binary(<<C:8, Cs/bits>>, A) -> - case b64d(C) of - ws -> only_ws_binary(Cs, A) +only_ws_binary(ModeOffset, Padding, <<C:8, Cs/bits>>, A) -> + case b64d(C, ModeOffset) of + ws -> only_ws_binary(ModeOffset, Padding, Cs, A) end. -decode_list_to_string([C1 | Cs]) -> - case b64d(C1) of - ws -> decode_list_to_string(Cs); - B1 -> decode_list_to_string(Cs, B1) +decode_list_to_string(ModeOffset, Padding, [C1 | Cs]) -> + case b64d(C1, ModeOffset) of + ws -> decode_list_to_string(ModeOffset, Padding, Cs); + B1 -> decode_list_to_string(ModeOffset, Padding, Cs, B1) end; -decode_list_to_string([]) -> +decode_list_to_string(_ModeOffset, _Padding, []) -> []. -decode_list_to_string([C2 | Cs], B1) -> - case b64d(C2) of - ws -> decode_list_to_string(Cs, B1); - B2 -> decode_list_to_string(Cs, B1, B2) +decode_list_to_string(ModeOffset, Padding, [C2 | Cs], B1) -> + case b64d(C2, ModeOffset) of + ws -> decode_list_to_string(ModeOffset, Padding, Cs, B1); + B2 -> decode_list_to_string(ModeOffset, Padding, Cs, B1, B2) end. -decode_list_to_string([C3 | Cs], B1, B2) -> - case b64d(C3) of - ws -> decode_list_to_string(Cs, B1, B2); - B3 -> decode_list_to_string(Cs, B1, B2, B3) +decode_list_to_string(ModeOffset, Padding, [C3 | Cs], B1, B2) -> + case b64d(C3, ModeOffset) of + ws -> decode_list_to_string(ModeOffset, Padding, Cs, B1, B2); + B3 -> decode_list_to_string(ModeOffset, Padding, Cs, B1, B2, B3) + end; +decode_list_to_string(ModeOffset, Padding, [], B1, B2) -> + case Padding of + true -> missing_padding_error(); + false -> decode_list_to_string(ModeOffset, Padding, [], B1, B2, eq) end. -decode_list_to_string([C4 | Cs], B1, B2, B3) -> - case b64d(C4) of +decode_list_to_string(ModeOffset, Padding, [C4 | Cs], B1, B2, B3) -> + case b64d(C4, ModeOffset) of ws -> - decode_list_to_string(Cs, B1, B2, B3); + decode_list_to_string(ModeOffset, Padding, Cs, B1, B2, B3); eq when B3 =:= eq -> - only_ws(Cs, binary_to_list(<<B1:6,(B2 bsr 4):2>>)); + only_ws(ModeOffset, Padding, Cs, binary_to_list(<<B1:6,(B2 bsr 4):2>>)); eq -> - only_ws(Cs, binary_to_list(<<B1:6,B2:6,(B3 bsr 2):4>>)); + only_ws(ModeOffset, Padding, Cs, binary_to_list(<<B1:6,B2:6,(B3 bsr 2):4>>)); B4 -> Bits4x6 = (B1 bsl 18) bor (B2 bsl 12) bor (B3 bsl 6) bor B4, Octet1 = Bits4x6 bsr 16, Octet2 = (Bits4x6 bsr 8) band 16#ff, Octet3 = Bits4x6 band 16#ff, - [Octet1, Octet2, Octet3 | decode_list_to_string(Cs)] + [Octet1, Octet2, Octet3 | decode_list_to_string(ModeOffset, Padding, Cs)] + end; +decode_list_to_string(_ModeOffset, Padding, [], B1, B2, B3) -> + case Padding of + true -> missing_padding_error(); + false when B3 =:= eq -> binary_to_list(<<B1:6,(B2 bsr 4):2>>); + false -> binary_to_list(<<B1:6,B2:6,(B3 bsr 2):4>>) end. -only_ws([], A) -> +only_ws(_ModeOffset, _Padding, [], A) -> A; -only_ws([C | Cs], A) -> - case b64d(C) of - ws -> only_ws(Cs, A) +only_ws(ModeOffset, Padding, [C | Cs], A) -> + case b64d(C, ModeOffset) of + ws -> only_ws(ModeOffset, Padding, Cs, A) end. %%%======================================================================== +%%% Error handling functions +%%%======================================================================== + +% always inlined for useful stacktraces when called in tail position +-compile({inline, missing_padding_error/0}). +missing_padding_error() -> + error(missing_padding, none, [{error_info, #{}}]). + +format_error(missing_padding, _) -> + #{general => "data to decode is missing final = padding characters, if this is intended, use the `padding => false` option"}; +format_error(_, _) -> + #{}. + +%%%======================================================================== %%% Internal functions %%%======================================================================== -%% accessors --compile({inline, [{b64d, 1}]}). -%% One-based decode map. -b64d(X) -> - element(X, - {bad,bad,bad,bad,bad,bad,bad,bad,ws,ws,bad,bad,ws,bad,bad, %1-15 +%% accessors + +get_padding(#{padding := Bool}) when is_boolean(Bool) -> Bool; +get_padding(#{}) -> true. + +get_decoding_offset(#{mode := standard}) -> 1; +get_decoding_offset(#{mode := urlsafe}) -> 257; +get_decoding_offset(#{}) -> 1. + +-compile({inline, [{b64d, 2}]}). +b64d(X, Off) -> + element(X + Off, + { + %% standard base64 alphabet (RFC 4648 Section 4) + bad,bad,bad,bad,bad,bad,bad,bad,bad,ws,ws,bad,bad,ws,bad,bad, %0-15 bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, %16-31 ws,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,62,bad,bad,bad,63, %32-47 - 52,53,54,55,56,57,58,59,60,61,bad,bad,bad,eq,bad,bad, %48-63 + 52,53,54,55,56,57,58,59,60,61,bad,bad,bad,eq,bad,bad, %48-61 bad,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14, 15,16,17,18,19,20,21,22,23,24,25,bad,bad,bad,bad,bad, bad,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, @@ -460,13 +660,44 @@ b64d(X) -> bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, + + %% alternative base64url alphabet (RFC 4648 Section 5) + bad,bad,bad,bad,bad,bad,bad,bad,bad,ws,ws,bad,bad,ws,bad,bad, %0-15 + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, %16-31 + ws,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,62,bad,bad, %32-47 + 52,53,54,55,56,57,58,59,60,61,bad,bad,bad,eq,bad,bad, %48-61 + bad,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14, + 15,16,17,18,19,20,21,22,23,24,25,bad,bad,bad,bad,63, + bad,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, + 41,42,43,44,45,46,47,48,49,50,51,bad,bad,bad,bad,bad, + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, + bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad}). --compile({inline, [{b64e, 1}]}). -b64e(X) -> - element(X+1, - {$A, $B, $C, $D, $E, $F, $G, $H, $I, $J, $K, $L, $M, $N, +get_encoding_offset(#{mode := standard}) -> 1; +get_encoding_offset(#{mode := urlsafe}) -> 65; +get_encoding_offset(#{}) -> 1. + +-compile({inline, [{b64e, 2}]}). +b64e(X, Off) -> + element(X + Off, + { + %% standard base64 alphabet (RFC 4648 Section 4) + $A, $B, $C, $D, $E, $F, $G, $H, $I, $J, $K, $L, $M, $N, + $O, $P, $Q, $R, $S, $T, $U, $V, $W, $X, $Y, $Z, + $a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k, $l, $m, $n, + $o, $p, $q, $r, $s, $t, $u, $v, $w, $x, $y, $z, + $0, $1, $2, $3, $4, $5, $6, $7, $8, $9, $+, $/, + + %% alternative base64url alphabet (RFC 4648 Section 5) + $A, $B, $C, $D, $E, $F, $G, $H, $I, $J, $K, $L, $M, $N, $O, $P, $Q, $R, $S, $T, $U, $V, $W, $X, $Y, $Z, $a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k, $l, $m, $n, $o, $p, $q, $r, $s, $t, $u, $v, $w, $x, $y, $z, - $0, $1, $2, $3, $4, $5, $6, $7, $8, $9, $+, $/}). + $0, $1, $2, $3, $4, $5, $6, $7, $8, $9, $-, $_}). |