aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2014-12-31 00:07:42 +0100
committerLoïc Hoguin <[email protected]>2014-12-31 00:07:42 +0100
commit2548432042a97c4e174bb956e542b54203c46d3b (patch)
treec50bbf46fc75acaf3902d86a16f5dccf62b611b7
parent9da51ee5f9392cdc61a134b0266c5a26320923ab (diff)
downloadcowlib-2548432042a97c4e174bb956e542b54203c46d3b.tar.gz
cowlib-2548432042a97c4e174bb956e542b54203c46d3b.tar.bz2
cowlib-2548432042a97c4e174bb956e542b54203c46d3b.zip
Add cow_http_hd:parse_authorization/1
Supports Basic, Digest and Bearer schemes only. From RFC7235, RFC2617 and RFC6750.
-rw-r--r--include/cow_inline.hrl14
-rw-r--r--src/cow_http_hd.erl129
2 files changed, 143 insertions, 0 deletions
diff --git a/include/cow_inline.hrl b/include/cow_inline.hrl
index 3abb018..758459e 100644
--- a/include/cow_inline.hrl
+++ b/include/cow_inline.hrl
@@ -60,6 +60,13 @@
C =:= $A orelse C =:= $B orelse C =:= $C orelse
C =:= $D orelse C =:= $E orelse C =:= $F).
+%% IS_LHEX(Character)
+
+-define(IS_LHEX(C),
+ ?IS_DIGIT(C) orelse
+ C =:= $a orelse C =:= $b orelse C =:= $c orelse
+ C =:= $d orelse C =:= $e orelse C =:= $f).
+
%% IS_TOKEN(Character)
-define(IS_TOKEN(C),
@@ -69,6 +76,13 @@
orelse C =:= $^ orelse C =:= $_ orelse C =:= $` orelse C =:= $| orelse C =:= $~
).
+%% IS_TOKEN68(Character)
+
+-define(IS_TOKEN68(C),
+ ?IS_ALPHA(C) orelse ?IS_DIGIT(C) orelse
+ C =:= $- orelse C =:= $. orelse C =:= $_ orelse
+ C =:= $~ orelse C =:= $+ orelse C =:= $/).
+
%% IS_URI_UNRESERVED(Character)
-define(IS_URI_UNRESERVED(C),
diff --git a/src/cow_http_hd.erl b/src/cow_http_hd.erl
index e2bde6d..bfef48e 100644
--- a/src/cow_http_hd.erl
+++ b/src/cow_http_hd.erl
@@ -21,6 +21,7 @@
-export([parse_accept_ranges/1]).
-export([parse_age/1]).
-export([parse_allow/1]).
+-export([parse_authorization/1]).
-export([parse_cache_control/1]).
-export([parse_connection/1]).
-export([parse_content_encoding/1]).
@@ -816,6 +817,134 @@ horse_parse_allow() ->
).
-endif.
+%% @doc Parse the Authorization header.
+%%
+%% We support Basic, Digest and Bearer schemes only.
+%%
+%% In the Digest case we do not validate that the mandatory
+%% fields are present. When parsing auth-params, we do not
+%% accept BWS characters around the "=".
+
+-spec parse_authorization(binary())
+ -> {basic, binary(), binary()}
+ | {bearer, binary()}
+ | {digest, [{binary(), binary()}]}.
+parse_authorization(<<"Basic ", R/bits >>) ->
+ auth_basic(base64:decode(R), <<>>);
+parse_authorization(<<"Bearer ", R/bits >>) when R =/= <<>> ->
+ validate_auth_bearer(R),
+ {bearer, R};
+parse_authorization(<<"Digest ", R/bits >>) ->
+ {digest, nonempty(auth_digest_list(R, []))}.
+
+auth_basic(<< $:, Password/bits >>, UserID) -> {basic, UserID, Password};
+auth_basic(<< C, R/bits >>, UserID) -> auth_basic(R, << UserID/binary, C >>).
+
+validate_auth_bearer(<< C, R/bits >>) when ?IS_TOKEN68(C) -> validate_auth_bearer(R);
+validate_auth_bearer(<< $=, R/bits >>) -> validate_auth_bearer_eq(R);
+validate_auth_bearer(<<>>) -> ok.
+
+validate_auth_bearer_eq(<< $=, R/bits >>) -> validate_auth_bearer_eq(R);
+validate_auth_bearer_eq(<<>>) -> ok.
+
+auth_digest_list(<<>>, Acc) -> lists:reverse(Acc);
+auth_digest_list(<< $\s, R/bits >>, Acc) -> auth_digest_list(R, Acc);
+auth_digest_list(<< $\t, R/bits >>, Acc) -> auth_digest_list(R, Acc);
+auth_digest_list(<< $,, R/bits >>, Acc) -> auth_digest_list(R, Acc);
+auth_digest_list(<< "algorithm=", C, R/bits >>, Acc) when ?IS_TOKEN(C) -> auth_digest_token(R, Acc, <<"algorithm">>, << C >>);
+auth_digest_list(<< "cnonce=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"cnonce">>, <<>>);
+auth_digest_list(<< "qop=", C, R/bits >>, Acc) when ?IS_TOKEN(C) -> auth_digest_token(R, Acc, <<"qop">>, << C >>);
+auth_digest_list(<< "nc=", A, B, C, D, E, F, G, H, R/bits >>, Acc)
+ when ?IS_LHEX(A), ?IS_LHEX(B), ?IS_LHEX(C), ?IS_LHEX(D),
+ ?IS_LHEX(E), ?IS_LHEX(F), ?IS_LHEX(G), ?IS_LHEX(H) ->
+ auth_digest_list_sep(R, [{<<"nc">>, << A, B, C, D, E, F, G, H >>}|Acc]);
+auth_digest_list(<< "nonce=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"nonce">>, <<>>);
+auth_digest_list(<< "opaque=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"opaque">>, <<>>);
+auth_digest_list(<< "realm=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"realm">>, <<>>);
+auth_digest_list(<< "response=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"response">>, <<>>);
+auth_digest_list(<< "uri=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"uri">>, <<>>);
+auth_digest_list(<< "username=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"username">>, <<>>);
+auth_digest_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) ->
+ case C of
+ ?INLINE_LOWERCASE(auth_digest_param, R, Acc, <<>>)
+ end.
+
+auth_digest_param(<< $=, $", R/bits >>, Acc, K) -> auth_digest_quoted(R, Acc, K, <<>>);
+auth_digest_param(<< $=, C, R/bits >>, Acc, K) when ?IS_TOKEN(C) -> auth_digest_token(R, Acc, K, << C >>);
+auth_digest_param(<< C, R/bits >>, Acc, K) when ?IS_TOKEN(C) ->
+ case C of
+ ?INLINE_LOWERCASE(auth_digest_param, R, Acc, K)
+ end.
+
+auth_digest_token(<<>>, Acc, K, V) -> lists:reverse([{K, V}|Acc]);
+auth_digest_token(<< $,, R/bits >>, Acc, K, V) -> auth_digest_list(R, [{K, V}|Acc]);
+auth_digest_token(<< $\s, R/bits >>, Acc, K, V) -> auth_digest_list_sep(R, [{K, V}|Acc]);
+auth_digest_token(<< $\t, R/bits >>, Acc, K, V) -> auth_digest_list_sep(R, [{K, V}|Acc]);
+auth_digest_token(<< C, R/bits >>, Acc, K, V) when ?IS_TOKEN(C) -> auth_digest_token(R, Acc, K, << V/binary, C >>).
+
+auth_digest_quoted(<< $", R/bits >>, Acc, K, V) -> auth_digest_list_sep(R, [{K, V}|Acc]);
+auth_digest_quoted(<< $\\, C, R/bits >>, Acc, K, V) when ?IS_VCHAR_OBS(C) -> auth_digest_quoted(R, Acc, K, << V/binary, C >>);
+auth_digest_quoted(<< C, R/bits >>, Acc, K, V) when ?IS_VCHAR_OBS(C) -> auth_digest_quoted(R, Acc, K, << V/binary, C >>).
+
+auth_digest_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+auth_digest_list_sep(<< $,, R/bits >>, Acc) -> auth_digest_list(R, Acc);
+auth_digest_list_sep(<< $\s, R/bits >>, Acc) -> auth_digest_list_sep(R, Acc);
+auth_digest_list_sep(<< $\t, R/bits >>, Acc) -> auth_digest_list_sep(R, Acc).
+
+-ifdef(TEST).
+parse_authorization_test_() ->
+ Tests = [
+ {<<"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==">>, {basic, <<"Aladdin">>, <<"open sesame">>}},
+ {<<"Bearer mF_9.B5f-4.1JqM">>, {bearer, <<"mF_9.B5f-4.1JqM">>}},
+ {<<"Digest username=\"Mufasa\","
+ "realm=\"[email protected]\","
+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
+ "uri=\"/dir/index.html\","
+ "qop=auth,"
+ "nc=00000001,"
+ "cnonce=\"0a4f113b\","
+ "response=\"6629fae49393a05397450978507c4ef1\","
+ "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"">>,
+ {digest, [
+ {<<"username">>, <<"Mufasa">>},
+ {<<"realm">>, <<"[email protected]">>},
+ {<<"nonce">>, <<"dcd98b7102dd2f0e8b11d0f600bfb0c093">>},
+ {<<"uri">>, <<"/dir/index.html">>},
+ {<<"qop">>, <<"auth">>},
+ {<<"nc">>, <<"00000001">>},
+ {<<"cnonce">>, <<"0a4f113b">>},
+ {<<"response">>, <<"6629fae49393a05397450978507c4ef1">>},
+ {<<"opaque">>, <<"5ccc069c403ebaf9f0171e9517f40e41">>}]}}
+ ],
+ [{V, fun() -> R = parse_authorization(V) end} || {V, R} <- Tests].
+-endif.
+
+-ifdef(PERF).
+horse_parse_authorization_basic() ->
+ horse:repeat(20000,
+ parse_authorization(<<"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==">>)
+ ).
+
+horse_parse_authorization_bearer() ->
+ horse:repeat(20000,
+ parse_authorization(<<"Bearer mF_9.B5f-4.1JqM">>)
+ ).
+
+horse_parse_authorization_digest() ->
+ horse:repeat(20000,
+ parse_authorization(
+ <<"Digest username=\"Mufasa\","
+ "realm=\"[email protected]\","
+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
+ "uri=\"/dir/index.html\","
+ "qop=auth,"
+ "nc=00000001,"
+ "cnonce=\"0a4f113b\","
+ "response=\"6629fae49393a05397450978507c4ef1\","
+ "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"">>)
+ ).
+-endif.
+
%% @doc Parse the Cache-Control header.
%%
%% In the fields list case, we do not support escaping, which shouldn't be needed anyway.