aboutsummaryrefslogtreecommitdiff
path: root/server/_build/default/lib/base64url/src/base64url.erl
blob: fa38269f7fdf099d6107ba311d4bbd554e207941 (plain)
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
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
%%
%% @doc URL safe base64-compatible codec.
%%
%% Based heavily on the code extracted from:
%%   https://github.com/basho/riak_control/blob/master/src/base64url.erl and
%%   https://github.com/mochi/mochiweb/blob/master/src/mochiweb_base64url.erl.
%%

-module(base64url).
-author('Vladimir Dronnikov <dronnikov@gmail.com>').

-export([
    decode/1,
    encode/1,
    encode_mime/1
  ]).

-spec encode(
    binary() | iolist()
  ) -> binary().

encode(Bin) when is_binary(Bin) ->
  << << (urlencode_digit(D)) >> || <<D>> <= base64:encode(Bin), D =/= $= >>;
encode(L) when is_list(L) ->
  encode(iolist_to_binary(L)).

-spec encode_mime(
    binary() | iolist()
  ) -> binary().
encode_mime(Bin) when is_binary(Bin) ->
    << << (urlencode_digit(D)) >> || <<D>> <= base64:encode(Bin) >>;
encode_mime(L) when is_list(L) ->
    encode_mime(iolist_to_binary(L)).

-spec decode(
    binary() | iolist()
  ) -> binary().

decode(Bin) when is_binary(Bin) ->
  Bin2 = case byte_size(Bin) rem 4 of
    % 1 -> << Bin/binary, "===" >>;
    2 -> << Bin/binary, "==" >>;
    3 -> << Bin/binary, "=" >>;
    _ -> Bin
  end,
  base64:decode(<< << (urldecode_digit(D)) >> || <<D>> <= Bin2 >>);
decode(L) when is_list(L) ->
  decode(iolist_to_binary(L)).

urlencode_digit($/) -> $_;
urlencode_digit($+) -> $-;
urlencode_digit(D)  -> D.

urldecode_digit($_) -> $/;
urldecode_digit($-) -> $+;
urldecode_digit(D)  -> D.

-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").

aim_test() ->
  % vanilla base64 produce URL unsafe output
  ?assertNotEqual(
      binary:match(base64:encode([255,127,254,252]), [<<"=">>, <<"/">>, <<"+">>]),
      nomatch),
  % this codec produce URL safe output
  ?assertEqual(
      binary:match(encode([255,127,254,252]), [<<"=">>, <<"/">>, <<"+">>]),
      nomatch),
  % the mime codec produces URL unsafe output, but only because of padding
  ?assertEqual(
      binary:match(encode_mime([255,127,254,252]), [<<"/">>, <<"+">>]),
      nomatch),
  ?assertNotEqual(
      binary:match(encode_mime([255,127,254,252]), [<<"=">>]),
      nomatch).

codec_test() ->
  % codec is lossless with or without padding
  ?assertEqual(decode(encode(<<"foo">>)), <<"foo">>),
  ?assertEqual(decode(encode(<<"foo1">>)), <<"foo1">>),
  ?assertEqual(decode(encode(<<"foo12">>)), <<"foo12">>),
  ?assertEqual(decode(encode(<<"foo123">>)), <<"foo123">>),
  ?assertEqual(decode(encode_mime(<<"foo">>)), <<"foo">>),
  ?assertEqual(decode(encode_mime(<<"foo1">>)), <<"foo1">>),
  ?assertEqual(decode(encode_mime(<<"foo12">>)), <<"foo12">>),
  ?assertEqual(decode(encode_mime(<<"foo123">>)), <<"foo123">>).

iolist_test() ->
  % codec supports iolists
  ?assertEqual(decode(encode("foo")), <<"foo">>),
  ?assertEqual(decode(encode(["fo", "o1"])), <<"foo1">>),
  ?assertEqual(decode(encode([255,127,254,252])), <<255,127,254,252>>),
  ?assertEqual(decode(encode_mime("foo")), <<"foo">>),
  ?assertEqual(decode(encode_mime(["fo", "o1"])), <<"foo1">>),
  ?assertEqual(decode(encode_mime([255,127,254,252])), <<255,127,254,252>>).

-endif.