Permalink
Browse files

add a base64url codec and use it in mochiweb_session

  • Loading branch information...
1 parent f3f133b commit 167f517416c3c48fa751af00188a42a4ca0f60e0 @etrepum etrepum committed Mar 15, 2013
Showing with 115 additions and 2 deletions.
  1. +2 −0 CHANGES.md
  2. +83 −0 src/mochiweb_base64url.erl
  3. +3 −2 src/mochiweb_session.erl
  4. +27 −0 test/mochiweb_base64url_tests.erl
View
@@ -5,6 +5,8 @@ Version 2.5.0 released 2013-03-04
* New mochiweb_session module for managing session cookies.
NOTE: this module is only supported on R15B02 and later!
https://github.com/mochi/mochiweb/pull/94
+* New mochiweb_base64url module for base64url encoding
+ (URL and Filename safe alphabet, see RFC 4648).
Version 2.4.2 released 2013-02-05
View
@@ -0,0 +1,83 @@
+-module(mochiweb_base64url).
+-export([encode/1, decode/1]).
+%% @doc URL and filename safe base64 variant with no padding,
+%% also known as "base64url" per RFC 4648.
+%%
+%% This differs from base64 in the following ways:
+%% '-' is used in place of '+' (62),
+%% '_' is used in place of '/' (63),
+%% padding is implicit rather than explicit ('=').
+
+-spec encode(iolist()) -> binary().
+encode(B) when is_binary(B) ->
+ encode_binary(B);
+encode(L) when is_list(L) ->
+ encode_binary(iolist_to_binary(L)).
+
+-spec decode(iolist()) -> binary().
+decode(B) when is_binary(B) ->
+ decode_binary(B);
+decode(L) when is_list(L) ->
+ decode_binary(iolist_to_binary(L)).
+
+%% Implementation, derived from stdlib base64.erl
+
+%% One-based decode map.
+-define(DECODE_MAP,
+ {bad,bad,bad,bad,bad,bad,bad,bad,ws,ws,bad,bad,ws,bad,bad, %1-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,bad,bad,bad, %48-63
+ bad,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14, %64-79
+ 15,16,17,18,19,20,21,22,23,24,25,bad,bad,bad,bad,63, %80-95
+ bad,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, %96-111
+ 41,42,43,44,45,46,47,48,49,50,51,bad,bad,bad,bad,bad, %112-127
+ 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}).
+
+encode_binary(Bin) ->
+ Split = 3*(byte_size(Bin) div 3),
+ <<Main0:Split/binary,Rest/binary>> = Bin,
+ Main = << <<(b64e(C)):8>> || <<C:6>> <= Main0 >>,
+ case Rest of
+ <<A:6,B:6,C:4>> ->
+ <<Main/binary,(b64e(A)):8,(b64e(B)):8,(b64e(C bsl 2)):8>>;
+ <<A:6,B:2>> ->
+ <<Main/binary,(b64e(A)):8,(b64e(B bsl 4)):8>>;
+ <<>> ->
+ Main
+ end.
+
+decode_binary(Bin) ->
+ Main = << <<(b64d(C)):6>> || <<C>> <= Bin,
+ (C =/= $\t andalso C =/= $\s andalso
+ C =/= $\r andalso C =/= $\n) >>,
+ case bit_size(Main) rem 8 of
+ 0 ->
+ Main;
+ N ->
+ Split = byte_size(Main) - 1,
+ <<Result:Split/bytes, _:N>> = Main,
+ Result
+ end.
+
+%% accessors
+
+b64e(X) ->
+ element(X+1,
+ {$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, $-, $_}).
+
+b64d(X) ->
+ b64d_ok(element(X, ?DECODE_MAP)).
+
+b64d_ok(I) when is_integer(I) -> I.
View
@@ -33,7 +33,8 @@ generate_session_data(ExpirationTime, Data, FSessionKey, ServerKey)
Key = gen_key(ExpTime, ServerKey),
Hmac = gen_hmac(ExpTime, BData, FSessionKey(ExpTime), Key),
EData = encrypt_data(BData, Key),
- base64:encode(<<ExpirationTime:32/integer, Hmac/binary, EData/binary>>).
+ mochiweb_base64url:encode(
+ <<ExpirationTime:32/integer, Hmac/binary, EData/binary>>).
%% @doc Convenience wrapper for generate_session_data that returns a
%% mochiweb cookie with "id" as the key, a max_age of 20000 seconds,
@@ -64,7 +65,7 @@ generate_session_cookie(ExpirationTime, Data, FSessionKey, ServerKey)
check_session_cookie(ECookie, ExpirationTime, FSessionKey, ServerKey)
when is_binary(ECookie), is_integer(ExpirationTime),
is_function(FSessionKey) ->
- case base64:decode(ECookie) of
+ case mochiweb_base64url:decode(ECookie) of
<<ExpirationTime1:32/integer, BHmac:20/binary, EData/binary>> ->
ETString = integer_to_list(ExpirationTime1),
Key = gen_key(ETString, ServerKey),
@@ -0,0 +1,27 @@
+-module(mochiweb_base64url_tests).
+-include_lib("eunit/include/eunit.hrl").
+
+id(X) ->
+ ?assertEqual(
+ X,
+ mochiweb_base64url:decode(mochiweb_base64url:encode(X))),
+ ?assertEqual(
+ X,
+ mochiweb_base64url:decode(
+ binary_to_list(mochiweb_base64url:encode(binary_to_list(X))))).
+
+random_binary(Short,Long) ->
+ << <<(random:uniform(256) - 1)>>
+ || _ <- lists:seq(1, Short + random:uniform(1 + Long - Short) - 1) >>.
+
+empty_test() ->
+ id(<<>>).
+
+onechar_test() ->
+ [id(<<C>>) || C <- lists:seq(0,255)],
+ ok.
+
+nchar_test() ->
+ %% 1000 tests of 2-6 char strings
+ [id(B) || _ <- lists:seq(1,1000), B <- [random_binary(2, 6)]],
+ ok.

0 comments on commit 167f517

Please sign in to comment.