diff options
Diffstat (limited to 'src/cowboy_http.erl')
-rw-r--r-- | src/cowboy_http.erl | 307 |
1 files changed, 279 insertions, 28 deletions
diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl index b05611b..9b8a178 100644 --- a/src/cowboy_http.erl +++ b/src/cowboy_http.erl @@ -16,7 +16,9 @@ -module(cowboy_http). %% Parsing. --export([list/2, nonempty_list/2, token/2, token_ci/2]). +-export([list/2, nonempty_list/2, + media_range/2, charset/2, + token/2, token_ci/2, quoted_string/2]). %% Interpretation. -export([connection_to_atom/1]). @@ -44,53 +46,211 @@ list(Data, Fun) -> end. -spec list(binary(), fun(), [binary()]) -> [any()] | {error, badarg}. -list(<<>>, _Fun, Acc) -> - Acc; %% From the RFC: %% <blockquote>Wherever this construct is used, null elements are allowed, %% but do not contribute to the count of elements present. %% That is, "(element), , (element) " is permitted, but counts %% as only two elements. Therefore, where at least one element is required, %% at least one non-null element MUST be present.</blockquote> -list(<< $,, Rest/bits >>, Fun, Acc) -> - list(Rest, Fun, Acc); list(Data, Fun, Acc) -> - Fun(Data, - fun (R, <<>>) -> list_separator(R, - fun (D) -> list(D, Fun, Acc) end); - (R, I) -> list_separator(R, - fun (D) -> list(D, Fun, [I|Acc]) end) + whitespace(Data, + fun (<<>>) -> Acc; + (<< $,, Rest/bits >>) -> list(Rest, Fun, Acc); + (Rest) -> Fun(Rest, + fun (D, I) -> whitespace(D, + fun (<<>>) -> [I|Acc]; + (<< $,, R/bits >>) -> list(R, Fun, [I|Acc]); + (_Any) -> {error, badarg} + end) + end) end). --spec list_separator(binary(), fun()) -> any(). -list_separator(<<>>, Fun) -> - Fun(<<>>); -list_separator(<< $,, Rest/bits >>, Fun) -> - Fun(Rest); -list_separator(<< C, Rest/bits >>, Fun) +%% @doc Parse a media range. +-spec media_range(binary(), fun()) -> any(). +media_range(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">>) -> + qvalue(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 Parse a charset, followed by an optional quality value. +-spec charset(binary(), fun()) -> any(). +charset(Data, Fun) -> + token_ci(Data, + fun (_Rest, <<>>) -> {error, badarg}; + (Rest, Charset) -> + whitespace(Rest, + fun (<< $;, Rest2/bits >>) -> + whitespace(Rest2, + fun (Rest3) -> + qparam(Rest3, + fun (Rest4, Quality) -> + Fun(Rest4, {Charset, Quality}) + end) + end); + (Rest2) -> + Fun(Rest2, {Charset, 1000}) + end) + end). + +%% Parse a quality parameter string (for example q=0.500). +-spec qparam(binary(), fun()) -> any(). +qparam(<< $q, Rest/bits >>, Fun) -> + whitespace(Rest, + fun (<< $=, Rest2/bits >>) -> + whitespace(Rest2, + fun (Rest3) -> + qvalue(Rest3, + fun (Rest4, Quality) -> + Fun(Rest4, Quality) + end) + end); + (_Rest2) -> + {error, badarg} + end). + +%% @doc Skip whitespace. +-spec whitespace(binary(), fun()) -> any(). +whitespace(<< C, Rest/bits >>, Fun) when C =:= $\s; C =:= $\t -> - list_separator(Rest, Fun); -list_separator(_Data, _Fun) -> - {error, badarg}. + whitespace(Rest, Fun); +whitespace(Data, Fun) -> + Fun(Data). %% @doc Parse a case-insensitive token. %% %% Changes all characters to lowercase. -spec token_ci(binary(), fun()) -> any(). token_ci(Data, Fun) -> - token(Data, Fun, ci). + token(Data, Fun, ci, <<>>). %% @doc Parse a token. -spec token(binary(), fun()) -> any(). token(Data, Fun) -> - token(Data, Fun, cs). - --spec token(binary(), fun(), ci | cs) -> any(). -token(<< C, Rest/bits >>, Fun, Case) - when C =:= $\s; C =:= $\t -> - token(Rest, Fun, Case); -token(Data, Fun, Case) -> - token(Data, Fun, Case, <<>>). + token(Data, Fun, cs, <<>>). -spec token(binary(), fun(), ci | cs, binary()) -> any(). token(<<>>, Fun, _Case, Acc) -> @@ -108,6 +268,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 qvalue(binary(), fun()) -> any(). +qvalue(<< $0, $., Rest/bits >>, Fun) -> + qvalue(Rest, Fun, 0, 100); +qvalue(<< $0, Rest/bits >>, Fun) -> + Fun(Rest, 0); +qvalue(<< $1, $., $0, $0, $0, Rest/bits >>, Fun) -> + Fun(Rest, 1000); +qvalue(<< $1, $., $0, $0, Rest/bits >>, Fun) -> + Fun(Rest, 1000); +qvalue(<< $1, $., $0, Rest/bits >>, Fun) -> + Fun(Rest, 1000); +qvalue(<< $1, Rest/bits >>, Fun) -> + Fun(Rest, 1000); +qvalue(_Data, _Fun) -> + {error, badarg}. + +-spec qvalue(binary(), fun(), integer(), 1 | 10 | 100) -> any(). +qvalue(Data, Fun, Q, 0) -> + Fun(Data, Q); +qvalue(<< 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 -> + qvalue(Rest, Fun, Q + (C - $0) * M, M div 10); +qvalue(Data, Fun, Q, _M) -> + Fun(Data, Q). + %% Interpretation. %% @doc Walk through a tokens list and return whether @@ -128,6 +330,17 @@ connection_to_atom([_Any|Tail]) -> -ifdef(TEST). +nonempty_charset_list_test_() -> + %% {Value, Result} + Tests = [ + {<<>>, {error, badarg}}, + {<<"iso-8859-5, unicode-1-1;q=0.8">>, [ + {<<"iso-8859-5">>, 1000}, + {<<"unicode-1-1">>, 800} + ]} + ], + [{V, fun() -> R = nonempty_list(V, fun charset/2) end} || {V, R} <- Tests]. + nonempty_token_list_test_() -> %% {Value, Result} Tests = [ @@ -144,6 +357,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 = [ |