aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2015-01-02 14:55:20 +0100
committerLoïc Hoguin <[email protected]>2015-01-02 14:55:20 +0100
commit5b969398406b43dfa27a137a0edf91deb6bf3a11 (patch)
tree9e8267855bf5750b779b6dab2bd8f1e8ecbd09d3
parent7871cc554bb970bc91bad1e2744a3ea5218c3c6c (diff)
downloadcowlib-5b969398406b43dfa27a137a0edf91deb6bf3a11.tar.gz
cowlib-5b969398406b43dfa27a137a0edf91deb6bf3a11.tar.bz2
cowlib-5b969398406b43dfa27a137a0edf91deb6bf3a11.zip
Add cow_http_hd:parse_www_authenticate/1
From RFC7235, RFC2617 and RFC6750. Features an attempt at simplifying the code using ?IS_WS macro.
-rw-r--r--src/cow_http_hd.erl169
1 files changed, 169 insertions, 0 deletions
diff --git a/src/cow_http_hd.erl b/src/cow_http_hd.erl
index 67eabc5..bbdb0e1 100644
--- a/src/cow_http_hd.erl
+++ b/src/cow_http_hd.erl
@@ -57,6 +57,7 @@
-export([parse_transfer_encoding/1]).
-export([parse_upgrade/1]).
-export([parse_vary/1]).
+-export([parse_www_authenticate/1]).
-export([parse_x_forwarded_for/1]).
-type etag() :: {weak | strong, binary()}.
@@ -3077,6 +3078,174 @@ parse_vary_error_test_() ->
[{V, fun() -> {'EXIT', _} = (catch parse_vary(V)) end} || V <- Tests].
-endif.
+%% @doc Parse the WWW-Authenticate header.
+%%
+%% Unknown schemes are represented as the lowercase binary
+%% instead of an atom. Unlike with parse_authorization/1,
+%% we do not crash on unknown schemes.
+%%
+%% When parsing auth-params, we do not accept BWS characters around the "=".
+
+-spec parse_www_authenticate(binary()) -> [{basic, binary()}
+ | {bearer | digest | binary(), [{binary(), binary()}]}].
+parse_www_authenticate(Authenticate) ->
+ nonempty(www_auth_list(Authenticate, [])).
+
+-define(IS_WS(C), C =:= $\s orelse C =:= $\t).
+-define(IS_WS_COMMA(C), ?IS_WS(C) orelse C =:= $,).
+
+www_auth_list(<<>>, Acc) -> lists:reverse(Acc);
+www_auth_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> www_auth_list(R, Acc);
+www_auth_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) ->
+ case C of
+ ?INLINE_LOWERCASE(www_auth_scheme, R, Acc, <<>>)
+ end.
+
+www_auth_basic_before_realm(<< C, R/bits >>, Acc) when ?IS_WS(C) -> www_auth_basic_before_realm(R, Acc);
+www_auth_basic_before_realm(<< "realm=\"", R/bits >>, Acc) -> www_auth_basic(R, Acc, <<>>).
+
+www_auth_basic(<< $", R/bits >>, Acc, Realm) -> www_auth_list_sep(R, [{basic, Realm}|Acc]);
+www_auth_basic(<< $\\, C, R/bits >>, Acc, Realm) when ?IS_VCHAR_OBS(C) -> www_auth_basic(R, Acc, << Realm/binary, C >>);
+www_auth_basic(<< C, R/bits >>, Acc, Realm) when ?IS_VCHAR_OBS(C) -> www_auth_basic(R, Acc, << Realm/binary, C >>).
+
+www_auth_scheme(<< C, R/bits >>, Acc, Scheme) when ?IS_WS(C) ->
+ case Scheme of
+ <<"basic">> -> www_auth_basic_before_realm(R, Acc);
+ <<"bearer">> -> www_auth_params_list(R, Acc, bearer, []);
+ <<"digest">> -> www_auth_params_list(R, Acc, digest, []);
+ _ -> www_auth_params_list(R, Acc, Scheme, [])
+ end;
+www_auth_scheme(<< C, R/bits >>, Acc, Scheme) when ?IS_TOKEN(C) ->
+ case C of
+ ?INLINE_LOWERCASE(www_auth_scheme, R, Acc, Scheme)
+ end.
+
+www_auth_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+www_auth_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> www_auth_list_sep(R, Acc);
+www_auth_list_sep(<< $,, R/bits >>, Acc) -> www_auth_list(R, Acc).
+
+www_auth_params_list(<<>>, Acc, Scheme, Params) ->
+ lists:reverse([{Scheme, lists:reverse(nonempty(Params))}|Acc]);
+www_auth_params_list(<< C, R/bits >>, Acc, Scheme, Params) when ?IS_WS_COMMA(C) ->
+ www_auth_params_list(R, Acc, Scheme, Params);
+www_auth_params_list(<< "algorithm=", C, R/bits >>, Acc, Scheme, Params) when ?IS_TOKEN(C) ->
+ www_auth_token(R, Acc, Scheme, Params, <<"algorithm">>, << C >>);
+www_auth_params_list(<< "domain=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"domain">>, <<>>);
+www_auth_params_list(<< "error=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"error">>, <<>>);
+www_auth_params_list(<< "error_description=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"error_description">>, <<>>);
+www_auth_params_list(<< "error_uri=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"error_uri">>, <<>>);
+www_auth_params_list(<< "nonce=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"nonce">>, <<>>);
+www_auth_params_list(<< "opaque=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"opaque">>, <<>>);
+www_auth_params_list(<< "qop=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"qop">>, <<>>);
+www_auth_params_list(<< "realm=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"realm">>, <<>>);
+www_auth_params_list(<< "scope=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"scope">>, <<>>);
+www_auth_params_list(<< "stale=false", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_params_list_sep(R, Acc, Scheme, [{<<"stale">>, <<"false">>}|Params]);
+www_auth_params_list(<< "stale=true", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_params_list_sep(R, Acc, Scheme, [{<<"stale">>, <<"true">>}|Params]);
+www_auth_params_list(<< C, R/bits >>, Acc, Scheme, Params) when ?IS_TOKEN(C) ->
+ case C of
+ ?INLINE_LOWERCASE(www_auth_param, R, Acc, Scheme, Params, <<>>)
+ end.
+
+www_auth_param(<< $=, $", R/bits >>, Acc, Scheme, Params, K) ->
+ www_auth_quoted(R, Acc, Scheme, Params, K, <<>>);
+www_auth_param(<< $=, C, R/bits >>, Acc, Scheme, Params, K) when ?IS_TOKEN(C) ->
+ www_auth_token(R, Acc, Scheme, Params, K, << C >>);
+www_auth_param(<< C, R/bits >>, Acc, Scheme, Params, K) when ?IS_TOKEN(C) ->
+ case C of
+ ?INLINE_LOWERCASE(www_auth_param, R, Acc, Scheme, Params, K)
+ end;
+www_auth_param(R, Acc, Scheme, Params, NewScheme) ->
+ www_auth_scheme(R, [{Scheme, lists:reverse(Params)}|Acc], NewScheme).
+
+www_auth_token(<< C, R/bits >>, Acc, Scheme, Params, K, V) when ?IS_TOKEN(C) ->
+ www_auth_token(R, Acc, Scheme, Params, K, << V/binary, C >>);
+www_auth_token(R, Acc, Scheme, Params, K, V) ->
+ www_auth_params_list_sep(R, Acc, Scheme, [{K, V}|Params]).
+
+www_auth_quoted(<< $", R/bits >>, Acc, Scheme, Params, K, V) ->
+ www_auth_params_list_sep(R, Acc, Scheme, [{K, V}|Params]);
+www_auth_quoted(<< $\\, C, R/bits >>, Acc, Scheme, Params, K, V) when ?IS_VCHAR_OBS(C) ->
+ www_auth_quoted(R, Acc, Scheme, Params, K, << V/binary, C >>);
+www_auth_quoted(<< C, R/bits >>, Acc, Scheme, Params, K, V) when ?IS_VCHAR_OBS(C) ->
+ www_auth_quoted(R, Acc, Scheme, Params, K, << V/binary, C >>).
+
+www_auth_params_list_sep(<<>>, Acc, Scheme, Params) ->
+ lists:reverse([{Scheme, lists:reverse(Params)}|Acc]);
+www_auth_params_list_sep(<< C, R/bits >>, Acc, Scheme, Params) when ?IS_WS(C) ->
+ www_auth_params_list_sep(R, Acc, Scheme, Params);
+www_auth_params_list_sep(<< $,, R/bits >>, Acc, Scheme, Params) ->
+ www_auth_params_list_after_sep(R, Acc, Scheme, Params).
+
+www_auth_params_list_after_sep(<<>>, Acc, Scheme, Params) ->
+ lists:reverse([{Scheme, lists:reverse(Params)}|Acc]);
+www_auth_params_list_after_sep(<< C, R/bits >>, Acc, Scheme, Params) when ?IS_WS_COMMA(C) ->
+ www_auth_params_list_after_sep(R, Acc, Scheme, Params);
+www_auth_params_list_after_sep(R, Acc, Scheme, Params) ->
+ www_auth_params_list(R, Acc, Scheme, Params).
+
+-ifdef(TEST).
+parse_www_authenticate_test_() ->
+ Tests = [
+ {<<"Newauth realm=\"apps\", type=1, title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"">>,
+ [{<<"newauth">>, [
+ {<<"realm">>, <<"apps">>},
+ {<<"type">>, <<"1">>},
+ {<<"title">>, <<"Login to \"apps\"">>}]},
+ {basic, <<"simple">>}]},
+ %% Same test, different order.
+ {<<"Basic realm=\"simple\", Newauth realm=\"apps\", type=1, title=\"Login to \\\"apps\\\"\"">>,
+ [{basic, <<"simple">>},
+ {<<"newauth">>, [
+ {<<"realm">>, <<"apps">>},
+ {<<"type">>, <<"1">>},
+ {<<"title">>, <<"Login to \"apps\"">>}]}]},
+ {<<"Bearer realm=\"example\"">>,
+ [{bearer, [{<<"realm">>, <<"example">>}]}]},
+ {<<"Bearer realm=\"example\", error=\"invalid_token\", error_description=\"The access token expired\"">>,
+ [{bearer, [
+ {<<"realm">>, <<"example">>},
+ {<<"error">>, <<"invalid_token">>},
+ {<<"error_description">>, <<"The access token expired">>}
+ ]}]},
+ {<<"Basic realm=\"WallyWorld\"">>,
+ [{basic, <<"WallyWorld">>}]},
+ {<<"Digest realm=\"[email protected]\", qop=\"auth,auth-int\", "
+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
+ "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"">>,
+ [{digest, [
+ {<<"realm">>, <<"[email protected]">>},
+ {<<"qop">>, <<"auth,auth-int">>},
+ {<<"nonce">>, <<"dcd98b7102dd2f0e8b11d0f600bfb0c093">>},
+ {<<"opaque">>, <<"5ccc069c403ebaf9f0171e9517f40e41">>}
+ ]}]}
+ ],
+ [{V, fun() -> R = parse_www_authenticate(V) end} || {V, R} <- Tests].
+
+parse_www_authenticate_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_www_authenticate(V)) end} || V <- Tests].
+-endif.
+
+-ifdef(PERF).
+horse_parse_www_authenticate() ->
+ horse:repeat(200000,
+ parse_www_authenticate(<<"Newauth realm=\"apps\", type=1, title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"">>)
+ ).
+-endif.
+
%% @doc Parse the X-Forwarded-For header.
%%
%% This header has no specification but *looks like* it is