From faf64524c6758ae1e27404d2ae1383a23538c538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Wed, 23 Oct 2013 11:21:31 +0200 Subject: Move cookie parsing/building code to cowlib The code for parsing has also been rewritten to be more efficient and to be able to handle cookie values with space inside them properly. Update cowlib to 0.2.0. --- Makefile | 2 +- rebar.config | 2 +- src/cowboy_clock.erl | 20 ----- src/cowboy_http.erl | 211 --------------------------------------------------- src/cowboy_req.erl | 14 +--- 5 files changed, 6 insertions(+), 243 deletions(-) diff --git a/Makefile b/Makefile index e9c2c53..8a5d521 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ PLT_APPS = crypto public_key ssl # Dependencies. DEPS = cowlib ranch -dep_cowlib = pkg://cowlib 0.1.0 +dep_cowlib = pkg://cowlib 0.2.0 dep_ranch = pkg://ranch 0.8.5 TEST_DEPS = ct_helper gun diff --git a/rebar.config b/rebar.config index d31efe7..91179db 100644 --- a/rebar.config +++ b/rebar.config @@ -1,4 +1,4 @@ {deps, [ - {cowlib, ".*", {git, "git://github.com/extend/cowlib.git", "0.1.0"}}, + {cowlib, ".*", {git, "git://github.com/extend/cowlib.git", "0.2.0"}}, {ranch, ".*", {git, "git://github.com/extend/ranch.git", "0.8.5"}} ]}. diff --git a/src/cowboy_clock.erl b/src/cowboy_clock.erl index f21616c..6fb522b 100644 --- a/src/cowboy_clock.erl +++ b/src/cowboy_clock.erl @@ -26,7 +26,6 @@ -export([stop/0]). -export([rfc1123/0]). -export([rfc1123/1]). --export([rfc2109/1]). %% gen_server. -export([init/1]). @@ -67,18 +66,6 @@ rfc1123() -> rfc1123(DateTime) -> update_rfc1123(<<>>, undefined, DateTime). -%% @doc Return the given date and time formatted according to RFC-2109. -%% -%% This format is used in the set-cookie header sent with -%% HTTP responses. --spec rfc2109(calendar:datetime()) -> binary(). -rfc2109({Date = {Y, Mo, D}, {H, Mi, S}}) -> - Wday = calendar:day_of_the_week(Date), - << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, "-", - (month(Mo))/binary, "-", (list_to_binary(integer_to_list(Y)))/binary, - " ", (pad_int(H))/binary, $:, (pad_int(Mi))/binary, - $:, (pad_int(S))/binary, " GMT" >>. - %% gen_server. %% @private @@ -188,13 +175,6 @@ month(12) -> <<"Dec">>. -ifdef(TEST). -rfc2109_test_() -> - Tests = [ - {<<"Sat, 14-May-2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}}}, - {<<"Sun, 01-Jan-2012 00:00:00 GMT">>, {{2012, 1, 1}, { 0, 0, 0}}} - ], - [{R, fun() -> R = rfc2109(D) end} || {R, D} <- Tests]. - update_rfc1123_test_() -> Tests = [ {<<"Sat, 14 May 2011 14:25:33 GMT">>, undefined, diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl index f2defdc..9f06522 100644 --- a/src/cowboy_http.erl +++ b/src/cowboy_http.erl @@ -19,7 +19,6 @@ %% Parsing. -export([list/2]). -export([nonempty_list/2]). --export([cookie_list/1]). -export([content_type/1]). -export([media_range/2]). -export([conneg/2]). @@ -46,7 +45,6 @@ -export([ce_identity/1]). %% Interpretation. --export([cookie_to_iodata/3]). -export([urldecode/1]). -export([urldecode/2]). -export([urlencode/1]). @@ -92,76 +90,6 @@ list(Data, Fun, Acc) -> end) end). -%% @doc Parse a list of cookies. -%% -%% We need a special function for this because we need to support both -%% $; and $, as separators as per RFC2109. --spec cookie_list(binary()) -> [{binary(), binary()}] | {error, badarg}. -cookie_list(Data) -> - case cookie_list(Data, []) of - {error, badarg} -> {error, badarg}; - [] -> {error, badarg}; - L -> lists:reverse(L) - end. - --spec cookie_list(binary(), Acc) -> Acc | {error, badarg} - when Acc::[{binary(), binary()}]. -cookie_list(Data, Acc) -> - whitespace(Data, - fun (<<>>) -> Acc; - (<< $,, Rest/binary >>) -> cookie_list(Rest, Acc); - (<< $;, Rest/binary >>) -> cookie_list(Rest, Acc); - (Rest) -> cookie(Rest, - fun (Rest2, << $$, _/binary >>, _) -> - cookie_list(Rest2, Acc); - (Rest2, Name, Value) -> - cookie_list(Rest2, [{Name, Value}|Acc]) - end) - end). - --spec cookie(binary(), fun()) -> any(). -cookie(Data, Fun) -> - whitespace(Data, - fun (Rest) -> - cookie_name(Rest, - fun (_Rest2, <<>>) -> {error, badarg}; - (<< $=, Rest2/binary >>, Name) -> - cookie_value(Rest2, - fun (Rest3, Value) -> - Fun(Rest3, Name, Value) - end); - (_Rest2, _Attr) -> {error, badarg} - end) - end). - --spec cookie_name(binary(), fun()) -> any(). -cookie_name(Data, Fun) -> - cookie_name(Data, Fun, <<>>). - --spec cookie_name(binary(), fun(), binary()) -> any(). -cookie_name(<<>>, Fun, Acc) -> - Fun(<<>>, Acc); -cookie_name(Data = << C, _Rest/binary >>, Fun, Acc) - when C =:= $=; C =:= $,; C =:= $;; C =:= $\s; C =:= $\t; - C =:= $\r; C =:= $\n; C =:= $\013; C =:= $\014 -> - Fun(Data, Acc); -cookie_name(<< C, Rest/binary >>, Fun, Acc) -> - cookie_name(Rest, Fun, << Acc/binary, C >>). - --spec cookie_value(binary(), fun()) -> any(). -cookie_value(Data, Fun) -> - cookie_value(Data, Fun, <<>>). - --spec cookie_value(binary(), fun(), binary()) -> any(). -cookie_value(<<>>, Fun, Acc) -> - Fun(<<>>, Acc); -cookie_value(Data = << C, _Rest/binary >>, Fun, Acc) - when C =:= $,; C =:= $;; C =:= $\s; C =:= $\t; - C =:= $\r; C =:= $\n; C =:= $\013; C =:= $\014 -> - Fun(Data, Acc); -cookie_value(<< C, Rest/binary >>, Fun, Acc) -> - cookie_value(Rest, Fun, << Acc/binary, C >>). - %% @doc Parse a content type. %% %% We lowercase the charset header as we know it's case insensitive. @@ -1025,55 +953,6 @@ ce_identity(Data) -> %% Interpretation. -%% @doc Convert a cookie name, value and options to its iodata form. -%% @end -%% -%% Initially from Mochiweb: -%% * Copyright 2007 Mochi Media, Inc. -%% Initial binary implementation: -%% * Copyright 2011 Thomas Burdick --spec cookie_to_iodata(iodata(), iodata(), cowboy_req:cookie_opts()) - -> iodata(). -cookie_to_iodata(Name, Value, Opts) -> - case binary:match(iolist_to_binary(Name), [<<$=>>, <<$,>>, <<$;>>, - <<$\s>>, <<$\t>>, <<$\r>>, <<$\n>>, <<$\013>>, <<$\014>>]) of - nomatch -> ok - end, - case binary:match(iolist_to_binary(Value), [<<$,>>, <<$;>>, - <<$\s>>, <<$\t>>, <<$\r>>, <<$\n>>, <<$\013>>, <<$\014>>]) of - nomatch -> ok - end, - MaxAgeBin = case lists:keyfind(max_age, 1, Opts) of - false -> <<>>; - {_, 0} -> - %% MSIE requires an Expires date in the past to delete a cookie. - <<"; Expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0">>; - {_, MaxAge} when is_integer(MaxAge), MaxAge > 0 -> - UTC = calendar:universal_time(), - Secs = calendar:datetime_to_gregorian_seconds(UTC), - Expires = calendar:gregorian_seconds_to_datetime(Secs + MaxAge), - [<<"; Expires=">>, cowboy_clock:rfc2109(Expires), - <<"; Max-Age=">>, integer_to_list(MaxAge)] - end, - DomainBin = case lists:keyfind(domain, 1, Opts) of - false -> <<>>; - {_, Domain} -> [<<"; Domain=">>, Domain] - end, - PathBin = case lists:keyfind(path, 1, Opts) of - false -> <<>>; - {_, Path} -> [<<"; Path=">>, Path] - end, - SecureBin = case lists:keyfind(secure, 1, Opts) of - false -> <<>>; - {_, true} -> <<"; Secure">> - end, - HttpOnlyBin = case lists:keyfind(http_only, 1, Opts) of - false -> <<>>; - {_, true} -> <<"; HttpOnly">> - end, - [Name, <<"=">>, Value, <<"; Version=1">>, - MaxAgeBin, DomainBin, PathBin, SecureBin, HttpOnlyBin]. - %% @doc Decode a URL encoded binary. %% @equiv urldecode(Bin, crash) -spec urldecode(binary()) -> binary(). @@ -1224,38 +1103,6 @@ nonempty_token_list_test_() -> ], [{V, fun() -> R = nonempty_list(V, fun token/2) end} || {V, R} <- Tests]. -cookie_list_test_() -> - %% {Value, Result}. - Tests = [ - {<<"name=value; name2=value2">>, [ - {<<"name">>, <<"value">>}, - {<<"name2">>, <<"value2">>} - ]}, - {<<"$Version=1; Customer=WILE_E_COYOTE; $Path=/acme">>, [ - {<<"Customer">>, <<"WILE_E_COYOTE">>} - ]}, - {<<"$Version=1; Customer=WILE_E_COYOTE; $Path=/acme; " - "Part_Number=Rocket_Launcher_0001; $Path=/acme; " - "Shipping=FedEx; $Path=/acme">>, [ - {<<"Customer">>, <<"WILE_E_COYOTE">>}, - {<<"Part_Number">>, <<"Rocket_Launcher_0001">>}, - {<<"Shipping">>, <<"FedEx">>} - ]}, - %% Potential edge cases (initially from Mochiweb). - {<<"foo=\\x">>, [{<<"foo">>, <<"\\x">>}]}, - {<<"=">>, {error, badarg}}, - {<<" foo ; bar ">>, {error, badarg}}, - {<<"foo=;bar=">>, [{<<"foo">>, <<>>}, {<<"bar">>, <<>>}]}, - {<<"foo=\\\";;bar ">>, {error, badarg}}, - {<<"foo=\\\";;bar=good ">>, - [{<<"foo">>, <<"\\\"">>}, {<<"bar">>, <<"good">>}]}, - {<<"foo=\"\\\";bar">>, {error, badarg}}, - {<<"">>, {error, badarg}}, - {<<"foo=bar , baz=wibble ">>, - [{<<"foo">>, <<"bar">>}, {<<"baz">>, <<"wibble">>}]} - ], - [{V, fun() -> R = cookie_list(V) end} || {V, R} <- Tests]. - media_range_list_test_() -> %% {Tokens, Result} Tests = [ @@ -1380,64 +1227,6 @@ digits_test_() -> ], [{V, fun() -> R = digits(V) end} || {V, R} <- Tests]. -cookie_to_iodata_test_() -> - %% {Name, Value, Opts, Result} - Tests = [ - {<<"Customer">>, <<"WILE_E_COYOTE">>, - [{http_only, true}, {domain, <<"acme.com">>}], - <<"Customer=WILE_E_COYOTE; Version=1; " - "Domain=acme.com; HttpOnly">>}, - {<<"Customer">>, <<"WILE_E_COYOTE">>, - [{path, <<"/acme">>}], - <<"Customer=WILE_E_COYOTE; Version=1; Path=/acme">>}, - {<<"Customer">>, <<"WILE_E_COYOTE">>, - [{path, <<"/acme">>}, {badoption, <<"negatory">>}], - <<"Customer=WILE_E_COYOTE; Version=1; Path=/acme">>} - ], - [{R, fun() -> R = iolist_to_binary(cookie_to_iodata(N, V, O)) end} - || {N, V, O, R} <- Tests]. - -cookie_to_iodata_max_age_test() -> - F = fun(N, V, O) -> - binary:split(iolist_to_binary( - cookie_to_iodata(N, V, O)), <<";">>, [global]) - end, - [<<"Customer=WILE_E_COYOTE">>, - <<" Version=1">>, - <<" Expires=", _/binary>>, - <<" Max-Age=111">>, - <<" Secure">>] = F(<<"Customer">>, <<"WILE_E_COYOTE">>, - [{max_age, 111}, {secure, true}]), - case catch F(<<"Customer">>, <<"WILE_E_COYOTE">>, [{max_age, -111}]) of - {'EXIT', {{case_clause, {max_age, -111}}, _}} -> ok - end, - [<<"Customer=WILE_E_COYOTE">>, - <<" Version=1">>, - <<" Expires=", _/binary>>, - <<" Max-Age=86417">>] = F(<<"Customer">>, <<"WILE_E_COYOTE">>, - [{max_age, 86417}]), - ok. - -cookie_to_iodata_failures_test_() -> - F = fun(N, V) -> - try cookie_to_iodata(N, V, []) of - _ -> - false - catch _:_ -> - true - end - end, - Tests = [ - {<<"Na=me">>, <<"Value">>}, - {<<"Name;">>, <<"Value">>}, - {<<"\r\name">>, <<"Value">>}, - {<<"Name">>, <<"Value;">>}, - {<<"Name">>, <<"\value">>} - ], - [{iolist_to_binary(io_lib:format("{~p, ~p} failure", [N, V])), - fun() -> true = F(N, V) end} - || {N, V} <- Tests]. - x_www_form_urlencoded_test_() -> %% {Qs, Result} Tests = [ diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl index 32ff7b0..34302c4 100644 --- a/src/cowboy_req.erl +++ b/src/cowboy_req.erl @@ -115,10 +115,7 @@ -export([lock/1]). -export([to_list/1]). --type cookie_option() :: {max_age, non_neg_integer()} - | {domain, binary()} | {path, binary()} - | {secure, boolean()} | {http_only, boolean()}. --type cookie_opts() :: [cookie_option()]. +-type cookie_opts() :: cow_cookie:cookie_opts(). -export_type([cookie_opts/0]). -type content_decode_fun() :: fun((binary()) @@ -430,7 +427,7 @@ parse_header(Name = <<"content-length">>, Req, Default) -> parse_header(Name = <<"content-type">>, Req, Default) -> parse_header(Name, Req, Default, fun cowboy_http:content_type/1); parse_header(Name = <<"cookie">>, Req, Default) -> - parse_header(Name, Req, Default, fun cowboy_http:cookie_list/1); + parse_header(Name, Req, Default, fun cow_cookie:parse_cookie/1); parse_header(Name = <<"expect">>, Req, Default) -> parse_header(Name, Req, Default, fun (Value) -> @@ -495,10 +492,7 @@ cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) -> {ok, undefined, Req2} -> {Default, Req2#http_req{cookies=[]}}; {ok, Cookies, Req2} -> - cookie(Name, Req2#http_req{cookies=Cookies}, Default); - %% Flash player incorrectly sends an empty Cookie header. - {error, badarg} -> - {Default, Req#http_req{cookies=[]}} + cookie(Name, Req2#http_req{cookies=Cookies}, Default) end; cookie(Name, Req, Default) -> case lists:keyfind(Name, 1, Req#http_req.cookies) of @@ -854,7 +848,7 @@ multipart_skip(Req) -> -spec set_resp_cookie(iodata(), iodata(), cookie_opts(), Req) -> Req when Req::req(). set_resp_cookie(Name, Value, Opts, Req) -> - Cookie = cowboy_http:cookie_to_iodata(Name, Value, Opts), + Cookie = cow_cookie:setcookie(Name, Value, Opts), set_resp_header(<<"set-cookie">>, Cookie, Req). %% @doc Add a header to the response. -- cgit v1.2.3