diff options
author | Loïc Hoguin <[email protected]> | 2014-12-31 00:07:42 +0100 |
---|---|---|
committer | Loïc Hoguin <[email protected]> | 2014-12-31 00:07:42 +0100 |
commit | 2548432042a97c4e174bb956e542b54203c46d3b (patch) | |
tree | c50bbf46fc75acaf3902d86a16f5dccf62b611b7 | |
parent | 9da51ee5f9392cdc61a134b0266c5a26320923ab (diff) | |
download | cowlib-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.hrl | 14 | ||||
-rw-r--r-- | src/cow_http_hd.erl | 129 |
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. |