Browse Source

Properly handle OPTIONS * requests

Support for these was broken during the development
of Cowboy 2.0. It is now fixed and better handled
than it ever was.
Loïc Hoguin 7 years ago
parent
commit
bc39b433bb
5 changed files with 38 additions and 19 deletions
  1. 4 2
      src/cowboy_req.erl
  2. 4 0
      src/cowboy_router.erl
  3. 2 0
      test/handlers/asterisk_h.erl
  4. 6 15
      test/rfc7230_SUITE.erl
  5. 22 2
      test/rfc7231_SUITE.erl

+ 4 - 2
src/cowboy_req.erl

@@ -228,8 +228,10 @@ uri(#{scheme := Scheme0, host := Host0, port := Port0,
 	end,
 	Host = maps:get(host, Opts, Host0),
 	Port = maps:get(port, Opts, Port0),
-	Path = maps:get(path, Opts, Path0),
-	Qs = maps:get(qs, Opts, Qs0),
+	{Path, Qs} = case maps:get(path, Opts, Path0) of
+		<<"*">> -> {<<>>, <<>>};
+		P -> {P, maps:get(qs, Opts, Qs0)}
+	end,
 	Fragment = maps:get(fragment, Opts, undefined),
 	[uri_host(Scheme, Scheme0, Port, Host), uri_path(Path), uri_qs(Qs), uri_fragment(Fragment)].
 

+ 4 - 0
src/cowboy_router.erl

@@ -82,6 +82,8 @@ compile_paths([{PathMatch, Fields, Handler, Opts}|Tail], Acc)
 		Fields, Handler, Opts}|Tail], Acc);
 compile_paths([{'_', Fields, Handler, Opts}|Tail], Acc) ->
 	compile_paths(Tail, [{'_', Fields, Handler, Opts}] ++ Acc);
+compile_paths([{<<"*">>, Fields, Handler, Opts}|Tail], Acc) ->
+	compile_paths(Tail, [{<<"*">>, Fields, Handler, Opts}|Acc]);
 compile_paths([{<< $/, PathMatch/bits >>, Fields, Handler, Opts}|Tail],
 		Acc) ->
 	PathRules = compile_rules(PathMatch, $/, [], [], <<>>),
@@ -252,6 +254,8 @@ match_path([{'_', [], Handler, Opts}|_Tail], HostInfo, _, Bindings) ->
 	{ok, Handler, Opts, Bindings, HostInfo, undefined};
 match_path([{<<"*">>, _, Handler, Opts}|_Tail], HostInfo, <<"*">>, Bindings) ->
 	{ok, Handler, Opts, Bindings, HostInfo, undefined};
+match_path([_|Tail], HostInfo, <<"*">>, Bindings) ->
+	match_path(Tail, HostInfo, <<"*">>, Bindings);
 match_path([{PathMatch, Fields, Handler, Opts}|Tail], HostInfo, Tokens,
 		Bindings) when is_list(Tokens) ->
 	case list_match(Tokens, PathMatch, Bindings) of

+ 2 - 0
test/handlers/asterisk_h.erl

@@ -7,6 +7,8 @@
 init(Req, Opts) ->
 	echo(cowboy_req:header(<<"x-echo">>, Req), Req, Opts).
 
+echo(undefined, Req, Opts) ->
+	{ok, cowboy_req:reply(200, Req), Opts};
 echo(What, Req, Opts) ->
 	F = binary_to_atom(What, latin1),
 	Value = case cowboy_req:F(Req) of

+ 6 - 15
test/rfc7230_SUITE.erl

@@ -41,9 +41,8 @@ init_routes(_) -> [
 		{"/echo/:key[/:arg]", echo_h, []},
 		{"/length/echo/:key", echo_h, []},
 		{"/resp/:key[/:arg]", resp_h, []},
-		{"/send_message", send_message_h, []}
-%% @todo Something is clearly wrong about routing * right now.
-%%		{"*", asterisk_h, []}
+		{"/send_message", send_message_h, []},
+		{"*", asterisk_h, []}
 	]},
 	{"127.0.0.1", [{"/echo/:key", echo_h, []}]},
 	{"example.org", [{"/echo/:key", echo_h, []}]}
@@ -603,20 +602,12 @@ asterisk_form_reject_if_not_options(Config) ->
 		"\r\n"),
 	{error, closed} = raw_recv(Client, 0, 1000).
 
-asterisk_form_empty_path(Config) ->
-	doc("The path is empty when using asterisk-form. (RFC7230 5.5)"),
-	#{code := 200, body := <<>>} = do_raw(Config,
+asterisk_form_empty_path_query(Config) ->
+	doc("The path and query components are empty when using asterisk-form. (RFC7230 5.5)"),
+	#{code := 200, body := <<"http://localhost">>} = do_raw(Config,
 		"OPTIONS * HTTP/1.1\r\n"
 		"Host: localhost\r\n"
-		"X-Echo: path\r\n"
-		"\r\n").
-
-asterisk_form_empty_query(Config) ->
-	doc("The query is empty when using asterisk-form. (RFC7230 5.5)"),
-	#{code := 200, body := <<>>} = do_raw(Config,
-		"OPTIONS * HTTP/1.1\r\n"
-		"Host: localhost\r\n"
-		"X-Echo: query\r\n"
+		"X-Echo: uri\r\n"
 		"\r\n").
 
 %% Invalid request-target.

+ 22 - 2
test/rfc7231_SUITE.erl

@@ -34,6 +34,7 @@ end_per_group(Name, _) ->
 
 init_dispatch(_) ->
 	cowboy_router:compile([{"[...]", [
+		{"*", asterisk_h, []},
 		{"/", hello_h, []},
 		{"/echo/:key", echo_h, []},
 		{"/resp/:key[/:arg]", resp_h, []}
@@ -148,8 +149,27 @@ method_options(Config) ->
 	{ok, <<"OPTIONS">>} = gun:await_body(ConnPid, Ref),
 	ok.
 
-%method_options_asterisk(Config) ->
-%method_options_content_length_0(Config) ->
+method_options_asterisk(Config) ->
+	doc("The OPTIONS method is accepted with an asterisk. (RFC7231 4.3.7)"),
+	ConnPid = gun_open(Config),
+	Ref = gun:options(ConnPid, "*", [
+		{<<"accept-encoding">>, <<"gzip">>},
+		{<<"x-echo">>, <<"method">>}
+	]),
+	{response, nofin, 200, _} = gun:await(ConnPid, Ref),
+	{ok, <<"OPTIONS">>} = gun:await_body(ConnPid, Ref),
+	ok.
+
+method_options_content_length_0(Config) ->
+	doc("The OPTIONS method must set the content-length header "
+		"to 0 when no body is returned. (RFC7231 4.3.7)"),
+	ConnPid = gun_open(Config),
+	Ref = gun:options(ConnPid, "*", [
+		{<<"accept-encoding">>, <<"gzip">>}
+	]),
+	{response, fin, 200, Headers} = gun:await(ConnPid, Ref),
+	{_, <<"0">>} = lists:keyfind(<<"content-length">>, 1, Headers),
+	ok.
 
 method_trace(Config) ->
 	doc("The TRACE method is currently not implemented. (RFC7231 4.3.8)"),