diff options
Diffstat (limited to 'src/cowboy_http.erl')
-rw-r--r-- | src/cowboy_http.erl | 221 |
1 files changed, 220 insertions, 1 deletions
diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl index 8648b86..5036d63 100644 --- a/src/cowboy_http.erl +++ b/src/cowboy_http.erl @@ -16,7 +16,8 @@ -module(cowboy_http). %% Parsing. --export([list/2, nonempty_list/2, token/2, token_ci/2]). +-export([list/2, nonempty_list/2, + media_range/2, token/2, token_ci/2, quoted_string/2]). %% Interpretation. -export([connection_to_atom/1]). @@ -63,6 +64,144 @@ list(Data, Fun, Acc) -> end) end). +%% @doc Parse a media range. +-spec media_range(binary(), fun()) -> any(). +media_range(Data, Fun) -> + whitespace(Data, + fun (<<>>) -> {error, badarg}; + (Rest) -> media_range_type(Rest, Fun) + end). + +-spec media_range_type(binary(), fun()) -> any(). +media_range_type(Data, Fun) -> + token_ci(Data, + fun (_Rest, <<>>) -> {error, badarg}; + (Rest, Type) -> whitespace(Rest, + fun (<< $/, Rest2/bits >>) -> whitespace(Rest2, + fun (<<>>) -> {error, badarg}; + (Rest3) -> media_range_subtype(Rest3, Fun, Type) + end); + (_Rest2) -> {error, badarg} + end) + end). + +-spec media_range_subtype(binary(), fun(), binary()) -> any(). +media_range_subtype(Data, Fun, Type) -> + token_ci(Data, + fun (_Rest, <<>>) -> {error, badarg}; + (Rest, SubType) -> media_range_params(Rest, Fun, Type, SubType, []) + end). + +-spec media_range_params(binary(), fun(), binary(), binary(), + [{binary(), binary()}]) -> any(). +media_range_params(Data, Fun, Type, SubType, Acc) -> + whitespace(Data, + fun (<< $;, Rest/bits >>) -> + whitespace(Rest, + fun (Rest2) -> + media_range_param_attr(Rest2, Fun, Type, SubType, Acc) + end); + (Rest) -> Fun(Rest, {{Type, SubType, lists:reverse(Acc)}, 1000, []}) + end). + +-spec media_range_param_attr(binary(), fun(), binary(), binary(), + [{binary(), binary()}]) -> any(). +media_range_param_attr(Data, Fun, Type, SubType, Acc) -> + token_ci(Data, + fun (_Rest, <<>>) -> {error, badarg}; + (Rest, Attr) -> + whitespace(Rest, + fun (<< $=, Rest2/bits >>) -> + whitespace(Rest2, + fun (<<>>) -> {error, badarg}; + (Rest3) -> + media_range_param_value(Rest3, Fun, + Type, SubType, Acc, Attr) + end); + (_Rest2) -> + {error, badarg} + end) + end). + +-spec media_range_param_value(binary(), fun(), binary(), binary(), + [{binary(), binary()}], binary()) -> any(). +media_range_param_value(Data, Fun, Type, SubType, Acc, <<"q">>) -> + quality(Data, + fun (Rest, Quality) -> + accept_ext(Rest, Fun, Type, SubType, Acc, Quality, []) + end); +media_range_param_value(Data = << $", _/bits >>, Fun, + Type, SubType, Acc, Attr) -> + quoted_string(Data, + fun (Rest, Value) -> + media_range_params(Rest, Fun, + Type, SubType, [{Attr, Value}|Acc]) + end); +media_range_param_value(Data, Fun, Type, SubType, Acc, Attr) -> + token(Data, + fun (_Rest, <<>>) -> {error, badarg}; + (Rest, Value) -> + media_range_params(Rest, Fun, + Type, SubType, [{Attr, Value}|Acc]) + end). + +-spec accept_ext(binary(), fun(), binary(), binary(), + [{binary(), binary()}], 0..1000, + [{binary(), binary()} | binary()]) -> any(). +accept_ext(Data, Fun, Type, SubType, Params, Quality, Acc) -> + whitespace(Data, + fun (<< $;, Rest/bits >>) -> + whitespace(Rest, + fun (Rest2) -> + accept_ext_attr(Rest2, Fun, + Type, SubType, Params, Quality, Acc) + end); + (Rest) -> + Fun(Rest, {{Type, SubType, lists:reverse(Params)}, + Quality, lists:reverse(Acc)}) + end). + +-spec accept_ext_attr(binary(), fun(), binary(), binary(), + [{binary(), binary()}], 0..1000, + [{binary(), binary()} | binary()]) -> any(). +accept_ext_attr(Data, Fun, Type, SubType, Params, Quality, Acc) -> + token_ci(Data, + fun (_Rest, <<>>) -> {error, badarg}; + (Rest, Attr) -> + whitespace(Rest, + fun (<< $=, Rest2/bits >>) -> + whitespace(Rest2, + fun (<<>>) -> {error, badarg}; + (Rest3) -> + accept_ext_value(Rest3, Fun, + Type, SubType, Params, + Quality, Acc, Attr) + end); + (Rest2) -> + accept_ext(Rest2, Fun, + Type, SubType, Params, + Quality, [Attr|Acc]) + end) + end). + +-spec accept_ext_value(binary(), fun(), binary(), binary(), + [{binary(), binary()}], 0..1000, + [{binary(), binary()} | binary()], binary()) -> any(). +accept_ext_value(Data = << $", _/bits >>, Fun, + Type, SubType, Params, Quality, Acc, Attr) -> + quoted_string(Data, + fun (Rest, Value) -> + accept_ext(Rest, Fun, + Type, SubType, Params, Quality, [{Attr, Value}|Acc]) + end); +accept_ext_value(Data, Fun, Type, SubType, Params, Quality, Acc, Attr) -> + token(Data, + fun (_Rest, <<>>) -> {error, badarg}; + (Rest, Value) -> + accept_ext(Rest, Fun, + Type, SubType, Params, Quality, [{Attr, Value}|Acc]) + end). + %% @doc Skip whitespace. -spec whitespace(binary(), fun()) -> any(). whitespace(<< C, Rest/bits >>, Fun) @@ -99,6 +238,48 @@ token(<< C, Rest/bits >>, Fun, Case = ci, Acc) -> token(<< C, Rest/bits >>, Fun, Case, Acc) -> token(Rest, Fun, Case, << Acc/binary, C >>). +%% @doc Parse a quoted string. +-spec quoted_string(binary(), fun()) -> any(). +quoted_string(<< $", Rest/bits >>, Fun) -> + quoted_string(Rest, Fun, <<>>). + +-spec quoted_string(binary(), fun(), binary()) -> any(). +quoted_string(<<>>, _Fun, _Acc) -> + {error, badarg}; +quoted_string(<< $", Rest/bits >>, Fun, Acc) -> + Fun(Rest, Acc); +quoted_string(<< $\\, C, Rest/bits >>, Fun, Acc) -> + quoted_string(Rest, Fun, << Acc/binary, C >>); +quoted_string(<< C, Rest/bits >>, Fun, Acc) -> + quoted_string(Rest, Fun, << Acc/binary, C >>). + +%% @doc Parse a quality value. +-spec quality(binary(), fun()) -> any(). +quality(<< $0, $., Rest/bits >>, Fun) -> + quality(Rest, Fun, 0, 100); +quality(<< $0, Rest/bits >>, Fun) -> + Fun(Rest, 0); +quality(<< $1, $., $0, $0, $0, Rest/bits >>, Fun) -> + Fun(Rest, 1000); +quality(<< $1, $., $0, $0, Rest/bits >>, Fun) -> + Fun(Rest, 1000); +quality(<< $1, $., $0, Rest/bits >>, Fun) -> + Fun(Rest, 1000); +quality(<< $1, Rest/bits >>, Fun) -> + Fun(Rest, 1000); +quality(_Data, _Fun) -> + {error, badarg}. + +-spec quality(binary(), fun(), integer(), 1 | 10 | 100) -> any(). +quality(Data, Fun, Q, 0) -> + Fun(Data, Q); +quality(<< C, Rest/bits >>, Fun, Q, M) + when C =:= $0; C =:= $1; C =:= $2; C =:= $3; C =:= $4; + C =:= $5; C =:= $6; C =:= $7; C =:= $8; C =:= $9 -> + quality(Rest, Fun, Q + (C - $0) * M, M div 10); +quality(Data, Fun, Q, _M) -> + Fun(Data, Q). + %% Interpretation. %% @doc Walk through a tokens list and return whether @@ -135,6 +316,44 @@ nonempty_token_list_test_() -> ], [{V, fun() -> R = nonempty_list(V, fun token/2) end} || {V, R} <- Tests]. +media_range_list_test_() -> + %% {Tokens, Result} + Tests = [ + {<<"audio/*; q=0.2, audio/basic">>, [ + {{<<"audio">>, <<"*">>, []}, 200, []}, + {{<<"audio">>, <<"basic">>, []}, 1000, []} + ]}, + {<<"text/plain; q=0.5, text/html, " + "text/x-dvi; q=0.8, text/x-c">>, [ + {{<<"text">>, <<"plain">>, []}, 500, []}, + {{<<"text">>, <<"html">>, []}, 1000, []}, + {{<<"text">>, <<"x-dvi">>, []}, 800, []}, + {{<<"text">>, <<"x-c">>, []}, 1000, []} + ]}, + {<<"text/*, text/html, text/html;level=1, */*">>, [ + {{<<"text">>, <<"*">>, []}, 1000, []}, + {{<<"text">>, <<"html">>, []}, 1000, []}, + {{<<"text">>, <<"html">>, [{<<"level">>, <<"1">>}]}, 1000, []}, + {{<<"*">>, <<"*">>, []}, 1000, []} + ]}, + {<<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, " + "text/html;level=2;q=0.4, */*;q=0.5">>, [ + {{<<"text">>, <<"*">>, []}, 300, []}, + {{<<"text">>, <<"html">>, []}, 700, []}, + {{<<"text">>, <<"html">>, [{<<"level">>, <<"1">>}]}, 1000, []}, + {{<<"text">>, <<"html">>, [{<<"level">>, <<"2">>}]}, 400, []}, + {{<<"*">>, <<"*">>, []}, 500, []} + ]}, + {<<"text/html;level=1;quoted=\"hi hi hi\";" + "q=0.123;standalone;complex=gits, text/plain">>, [ + {{<<"text">>, <<"html">>, + [{<<"level">>, <<"1">>}, {<<"quoted">>, <<"hi hi hi">>}]}, 123, + [<<"standalone">>, {<<"complex">>, <<"gits">>}]}, + {{<<"text">>, <<"plain">>, []}, 1000, []} + ]} + ], + [{V, fun() -> R = list(V, fun media_range/2) end} || {V, R} <- Tests]. + connection_to_atom_test_() -> %% {Tokens, Result} Tests = [ |