Browse Source

Fix cowboy_http:cookie_to_iodata/3

No more trying to quote, this is still completely broken everywhere.
Loïc Hoguin 12 years ago
parent
commit
f077c711a8
2 changed files with 47 additions and 22 deletions
  1. 41 22
      src/cowboy_http.erl
  2. 6 0
      src/cowboy_req.erl

+ 41 - 22
src/cowboy_http.erl

@@ -815,9 +815,20 @@ ce_identity(Data) ->
 -spec cookie_to_iodata(iodata(), iodata(), cowboy_req:cookie_opts())
 	-> iodata().
 cookie_to_iodata(Name, Value, Opts) ->
+	case binary:match(Name, [<<$=>>, <<$,>>, <<$;>>,
+			<<$\s>>, <<$\t>>, <<$\r>>, <<$\n>>, <<$\013>>, <<$\014>>]) of
+		nomatch -> ok
+	end,
+	case binary:match(Value, [<<$,>>, <<$;>>,
+			<<$\s>>, <<$\t>>, <<$\r>>, <<$\n>>, <<$\013>>, <<$\014>>]) of
+		nomatch -> ok
+	end,
 	MaxAgeBin = case lists:keyfind(max_age, 1, Opts) of
 		false -> <<>>;
-		{_, MaxAge} when is_integer(MaxAge), MaxAge >= 0 ->
+		{_, 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),
@@ -826,11 +837,11 @@ cookie_to_iodata(Name, Value, Opts) ->
 	end,
 	DomainBin = case lists:keyfind(domain, 1, Opts) of
 		false -> <<>>;
-		{_, Domain} -> [<<"; Domain=">>, quote(Domain)]
+		{_, Domain} -> [<<"; Domain=">>, Domain]
 	end,
 	PathBin = case lists:keyfind(path, 1, Opts) of
 		false -> <<>>;
-		{_, Path} -> [<<"; Path=">>, quote(Path)]
+		{_, Path} -> [<<"; Path=">>, Path]
 	end,
 	SecureBin = case lists:keyfind(secure, 1, Opts) of
 		false -> <<>>;
@@ -840,21 +851,9 @@ cookie_to_iodata(Name, Value, Opts) ->
 		false -> <<>>;
 		{_, true} -> <<"; HttpOnly">>
 	end,
-	[Name, <<"=">>, quote(Value), <<"; Version=1">>,
+	[Name, <<"=">>, Value, <<"; Version=1">>,
 		MaxAgeBin, DomainBin, PathBin, SecureBin, HttpOnlyBin].
 
--spec quote(binary()) -> binary().
-quote(Bin) ->
-	quote(Bin, << $" >>).
-
--spec quote(binary(), binary()) -> binary().
-quote(<<>>, Acc) ->
-	<< Acc/binary, $" >>;
-quote(<< $", Rest/bits >>, Acc) ->
-	quote(Rest, << Acc/binary, $\\, $" >>);
-quote(<< C, Rest/bits >>, Acc) ->
-	quote(Rest, << Acc/binary, C >>).
-
 %% @doc Convert an HTTP version tuple to its binary form.
 -spec version_to_binary(version()) -> binary().
 version_to_binary({1, 1}) -> <<"HTTP/1.1">>;
@@ -1160,14 +1159,14 @@ cookie_to_iodata_test_() ->
 	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; Version=1; "
+				"Domain=acme.com; HttpOnly">>},
 		{<<"Customer">>, <<"WILE_E_COYOTE">>,
 			[{path, <<"/acme">>}],
-			<<"Customer=\"WILE_E_COYOTE\"; Version=1; 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\"">>}
+			<<"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].
@@ -1177,7 +1176,7 @@ cookie_to_iodata_max_age_test() ->
 		binary:split(iolist_to_binary(
 			cookie_to_iodata(N, V, O)), <<";">>, [global])
 	end,
-	[<<"Customer=\"WILE_E_COYOTE\"">>,
+	[<<"Customer=WILE_E_COYOTE">>,
 		<<" Version=1">>,
 		<<" Expires=", _/binary>>,
 		<<" Max-Age=111">>,
@@ -1186,13 +1185,33 @@ cookie_to_iodata_max_age_test() ->
 	case catch F(<<"Customer">>, <<"WILE_E_COYOTE">>, [{max_age, -111}]) of
 		{'EXIT', {{case_clause, {max_age, -111}}, _}} -> ok
 	end,
-	[<<"Customer=\"WILE_E_COYOTE\"">>,
+	[<<"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 = [

+ 6 - 0
src/cowboy_req.erl

@@ -800,6 +800,12 @@ multipart_skip(Req) ->
 %% Response API.
 
 %% @doc Add a cookie header to the response.
+%%
+%% The cookie name cannot contain any of the following characters:
+%%   =,;\s\t\r\n\013\014
+%%
+%% The cookie value cannot contain any of the following characters:
+%%   ,; \t\r\n\013\014
 -spec set_resp_cookie(iodata(), iodata(), cookie_opts(), Req)
 	-> Req when Req::req().
 set_resp_cookie(Name, Value, Opts, Req) ->