cow_base64url.erl 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. %% Copyright (c) 2017-2018, Loïc Hoguin <essen@ninenines.eu>
  2. %%
  3. %% Permission to use, copy, modify, and/or distribute this software for any
  4. %% purpose with or without fee is hereby granted, provided that the above
  5. %% copyright notice and this permission notice appear in all copies.
  6. %%
  7. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. %% This module implements "base64url" following the algorithm
  15. %% found in Appendix C of RFC7515. The option #{padding => false}
  16. %% must be given to reproduce this variant exactly. The default
  17. %% will leave the padding characters.
  18. -module(cow_base64url).
  19. -export([decode/1]).
  20. -export([decode/2]).
  21. -export([encode/1]).
  22. -export([encode/2]).
  23. -ifdef(TEST).
  24. -include_lib("proper/include/proper.hrl").
  25. -endif.
  26. decode(Enc) ->
  27. decode(Enc, #{}).
  28. decode(Enc0, Opts) ->
  29. Enc1 = << << case C of
  30. $- -> $+;
  31. $_ -> $/;
  32. _ -> C
  33. end >> || << C >> <= Enc0 >>,
  34. Enc = case Opts of
  35. #{padding := false} ->
  36. case byte_size(Enc1) rem 4 of
  37. 0 -> Enc1;
  38. 2 -> << Enc1/binary, "==" >>;
  39. 3 -> << Enc1/binary, "=" >>
  40. end;
  41. _ ->
  42. Enc1
  43. end,
  44. base64:decode(Enc).
  45. encode(Dec) ->
  46. encode(Dec, #{}).
  47. encode(Dec, Opts) ->
  48. encode(base64:encode(Dec), Opts, <<>>).
  49. encode(<<$+, R/bits>>, Opts, Acc) -> encode(R, Opts, <<Acc/binary, $->>);
  50. encode(<<$/, R/bits>>, Opts, Acc) -> encode(R, Opts, <<Acc/binary, $_>>);
  51. encode(<<$=, _/bits>>, #{padding := false}, Acc) -> Acc;
  52. encode(<<C, R/bits>>, Opts, Acc) -> encode(R, Opts, <<Acc/binary, C>>);
  53. encode(<<>>, _, Acc) -> Acc.
  54. -ifdef(TEST).
  55. rfc7515_test() ->
  56. Dec = <<3,236,255,224,193>>,
  57. Enc = <<"A-z_4ME">>,
  58. Pad = <<"A-z_4ME=">>,
  59. Dec = decode(<<Enc/binary,$=>>),
  60. Dec = decode(Enc, #{padding => false}),
  61. Pad = encode(Dec),
  62. Enc = encode(Dec, #{padding => false}),
  63. ok.
  64. prop_identity() ->
  65. ?FORALL(B, binary(), B =:= decode(encode(B))).
  66. prop_identity_no_padding() ->
  67. ?FORALL(B, binary(), B =:= decode(encode(B, #{padding => false}), #{padding => false})).
  68. -endif.