From 6b14cd64fcfd607d7a9a4e24e95c49cd3643cb8b Mon Sep 17 00:00:00 2001 From: geeksilva97 Date: Sat, 3 Feb 2024 18:49:37 -0300 Subject: [PATCH 01/10] return allow header --- src/cowboy_rest.erl | 8 ++++++-- test/rest_handler_SUITE.erl | 12 ++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index 003e5f9da..b5dc1196b 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -328,7 +328,9 @@ uri_too_long(Req, State) -> allowed_methods(Req, State=#state{method=Method}) -> case call(Req, State, allowed_methods) of no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">> -> - next(Req, State, fun malformed_request/2); + Allow = <<"HEAD, GET, OPTIONS">>, + Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req), + next(Req2, State, fun malformed_request/2); no_call when Method =:= <<"OPTIONS">> -> next(Req, State#state{allowed_methods= [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]}, @@ -343,7 +345,9 @@ allowed_methods(Req, State=#state{method=Method}) -> {List, Req2, State2} -> case lists:member(Method, List) of true when Method =:= <<"OPTIONS">> -> - next(Req2, State2#state{allowed_methods=List}, + << ", ", Allow/binary >> = << << ", ", M/binary >> || M <- List >>, + Req3 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req2), + next(Req3, State2#state{allowed_methods=List}, fun malformed_request/2); true -> next(Req2, State2, fun malformed_request/2); diff --git a/test/rest_handler_SUITE.erl b/test/rest_handler_SUITE.erl index 510098f1e..d43b8f16e 100644 --- a/test/rest_handler_SUITE.erl +++ b/test/rest_handler_SUITE.erl @@ -778,6 +778,17 @@ last_modified_missing(Config) -> false = lists:keyfind(<<"last-modified">>, 1, Headers), ok. +head_call(Config) -> + doc("A successful HEAD request to a simple handler results in " + "a 200 OK response with the allow header set. (RFC7231 4.3.7)"), + ConnPid = gun_open(Config), + Ref = gun:head(ConnPid, "/", [ + {<<"accept-encoding">>, <<"gzip">>} + ]), + {response, fin, 200, Headers} = gun:await(ConnPid, Ref), + {_, <<"HEAD, GET, OPTIONS">>} = lists:keyfind(<<"allow">>, 1, Headers), + ok. + options_missing(Config) -> doc("A successful OPTIONS request to a simple handler results in " "a 200 OK response with the allow header set. (RFC7231 4.3.7)"), @@ -799,6 +810,7 @@ provide_callback(Config) -> ]), {response, nofin, 200, Headers} = gun:await(ConnPid, Ref), {_, <<"text/plain">>} = lists:keyfind(<<"content-type">>, 1, Headers), + {_, <<"HEAD, GET, OPTIONS">>} = lists:keyfind(<<"allow">>, 1, Headers), {ok, <<"This is REST!">>} = gun:await_body(ConnPid, Ref), ok. From 3df6822ade362d37fe2f6dd146ce5e4f7602e9dd Mon Sep 17 00:00:00 2001 From: geeksilva97 Date: Sun, 4 Feb 2024 10:22:11 -0300 Subject: [PATCH 02/10] add function to compute allow header content from list --- src/cowboy_rest.erl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index b5dc1196b..8efcfc5b2 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -324,16 +324,20 @@ known_methods(Req, State=#state{method=Method}) -> uri_too_long(Req, State) -> expect(Req, State, uri_too_long, false, fun allowed_methods/2, 414). +stringify_allowed_methods(MethodList) when is_list(MethodList) -> + << ", ", Allow/binary >> = << << ", ", M/binary >> || M <- MethodList >>, + Allow. + %% allowed_methods/2 should return a list of binary methods. allowed_methods(Req, State=#state{method=Method}) -> + DefaultAllowedMethods = [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>], case call(Req, State, allowed_methods) of no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">> -> - Allow = <<"HEAD, GET, OPTIONS">>, + Allow = stringify_allowed_methods(DefaultAllowedMethods), Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req), next(Req2, State, fun malformed_request/2); no_call when Method =:= <<"OPTIONS">> -> - next(Req, State#state{allowed_methods= - [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]}, + next(Req, State#state{allowed_methods=DefaultAllowedMethods}, fun malformed_request/2); no_call -> method_not_allowed(Req, State, From 1288e68f31d6a217567b4789aa148f3cb2c83451 Mon Sep 17 00:00:00 2001 From: geeksilva97 Date: Sun, 4 Feb 2024 10:58:40 -0300 Subject: [PATCH 03/10] add allowd_methods tests --- src/cowboy_rest.erl | 9 +++++---- test/rest_handler_SUITE.erl | 7 +++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index 8efcfc5b2..b50b1baa9 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -340,8 +340,7 @@ allowed_methods(Req, State=#state{method=Method}) -> next(Req, State#state{allowed_methods=DefaultAllowedMethods}, fun malformed_request/2); no_call -> - method_not_allowed(Req, State, - [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]); + method_not_allowed(Req, State, DefaultAllowedMethods); {stop, Req2, State2} -> terminate(Req2, State2); {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> @@ -349,12 +348,14 @@ allowed_methods(Req, State=#state{method=Method}) -> {List, Req2, State2} -> case lists:member(Method, List) of true when Method =:= <<"OPTIONS">> -> - << ", ", Allow/binary >> = << << ", ", M/binary >> || M <- List >>, + Allow = stringify_allowed_methods(List), Req3 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req2), next(Req3, State2#state{allowed_methods=List}, fun malformed_request/2); true -> - next(Req2, State2, fun malformed_request/2); + Allow = stringify_allowed_methods(List), + Req3 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req2), + next(Req3, State2, fun malformed_request/2); false -> method_not_allowed(Req2, State2, List) end diff --git a/test/rest_handler_SUITE.erl b/test/rest_handler_SUITE.erl index d43b8f16e..d8998e9a1 100644 --- a/test/rest_handler_SUITE.erl +++ b/test/rest_handler_SUITE.erl @@ -472,7 +472,8 @@ delete_resource_missing(Config) -> Ref = gun:delete(ConnPid, "/delete_resource?missing", [ {<<"accept-encoding">>, <<"gzip">>} ]), - {response, _, 500, _} = gun:await(ConnPid, Ref), + {response, _, 500, Headers} = gun:await(ConnPid, Ref), + {_, <<"DELETE">>} = lists:keyfind(<<"allow">>, 1, Headers), ok. create_resource_created(Config) -> @@ -483,7 +484,8 @@ create_resource_created(Config) -> Ref = gun:post(ConnPid, "/create_resource?created", [ {<<"content-type">>, <<"application/text">>} ], <<"hello">>, #{}), - {response, _, 201, _} = gun:await(ConnPid, Ref), + {response, _, 201, Headers} = gun:await(ConnPid, Ref), + {_, <<"POST">>} = lists:keyfind(<<"allow">>, 1, Headers), ok. create_resource_see_other(Config) -> @@ -496,6 +498,7 @@ create_resource_see_other(Config) -> ], <<"hello">>, #{}), {response, _, 303, RespHeaders} = gun:await(ConnPid, Ref), {_, _} = lists:keyfind(<<"location">>, 1, RespHeaders), + {_, <<"POST">>} = lists:keyfind(<<"allow">>, 1, RespHeaders), ok. error_on_malformed_accept(Config) -> From ec6fc7d9646995b6c06cbeb0f2c5c8c6e5f76a3f Mon Sep 17 00:00:00 2001 From: geeksilva97 Date: Sun, 4 Feb 2024 11:01:53 -0300 Subject: [PATCH 04/10] removed unused case from allowed_methods callback --- src/cowboy_rest.erl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index b50b1baa9..f4481b000 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -347,11 +347,6 @@ allowed_methods(Req, State=#state{method=Method}) -> switch_handler(Switch, Req2, State2); {List, Req2, State2} -> case lists:member(Method, List) of - true when Method =:= <<"OPTIONS">> -> - Allow = stringify_allowed_methods(List), - Req3 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req2), - next(Req3, State2#state{allowed_methods=List}, - fun malformed_request/2); true -> Allow = stringify_allowed_methods(List), Req3 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req2), From c37a6fba75d2d170ede1b2a78dd72d49efc5b2e0 Mon Sep 17 00:00:00 2001 From: geeksilva97 Date: Sun, 4 Feb 2024 11:04:19 -0300 Subject: [PATCH 05/10] minor refactor --- src/cowboy_rest.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index f4481b000..0363ff531 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -423,8 +423,7 @@ options(Req, State=#state{allowed_methods=Methods, method= <<"OPTIONS">>}) -> Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req), respond(Req2, State, 200); no_call -> - << ", ", Allow/binary >> - = << << ", ", M/binary >> || M <- Methods >>, + Allow = stringify_allowed_methods(Methods), Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req), respond(Req2, State, 200); {stop, Req2, State2} -> From c804ca4901be30087f5450f290bbe7b7dfa8a102 Mon Sep 17 00:00:00 2001 From: geeksilva97 Date: Tue, 6 Feb 2024 10:15:26 -0300 Subject: [PATCH 06/10] remove header setting from options/2 --- src/cowboy_rest.erl | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index 0363ff531..e9e49811a 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -337,8 +337,9 @@ allowed_methods(Req, State=#state{method=Method}) -> Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req), next(Req2, State, fun malformed_request/2); no_call when Method =:= <<"OPTIONS">> -> - next(Req, State#state{allowed_methods=DefaultAllowedMethods}, - fun malformed_request/2); + Allow = stringify_allowed_methods(DefaultAllowedMethods), + Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req), + next(Req2, State, fun malformed_request/2); no_call -> method_not_allowed(Req, State, DefaultAllowedMethods); {stop, Req2, State2} -> @@ -417,15 +418,10 @@ valid_entity_length(Req, State) -> %% If you need to add additional headers to the response at this point, %% you should do it directly in the options/2 call using set_resp_headers. -options(Req, State=#state{allowed_methods=Methods, method= <<"OPTIONS">>}) -> +options(Req, State=#state{method= <<"OPTIONS">>}) -> case call(Req, State, options) of - no_call when Methods =:= [] -> - Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req), - respond(Req2, State, 200); no_call -> - Allow = stringify_allowed_methods(Methods), - Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req), - respond(Req2, State, 200); + respond(Req, State, 200); {stop, Req2, State2} -> terminate(Req2, State2); {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> From eded22d1aa18f3d20d6d84b753ae0134f642665a Mon Sep 17 00:00:00 2001 From: geeksilva97 Date: Tue, 6 Feb 2024 10:16:56 -0300 Subject: [PATCH 07/10] unify case clauses --- src/cowboy_rest.erl | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index e9e49811a..b9f091f90 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -332,11 +332,7 @@ stringify_allowed_methods(MethodList) when is_list(MethodList) -> allowed_methods(Req, State=#state{method=Method}) -> DefaultAllowedMethods = [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>], case call(Req, State, allowed_methods) of - no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">> -> - Allow = stringify_allowed_methods(DefaultAllowedMethods), - Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req), - next(Req2, State, fun malformed_request/2); - no_call when Method =:= <<"OPTIONS">> -> + no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">>; Method =:= <<"OPTIONS">> -> Allow = stringify_allowed_methods(DefaultAllowedMethods), Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req), next(Req2, State, fun malformed_request/2); From 3bc7b08e3d63909a05065f996532e974c4131598 Mon Sep 17 00:00:00 2001 From: geeksilva97 Date: Tue, 6 Feb 2024 10:21:17 -0300 Subject: [PATCH 08/10] handle empty list when parsing allowed methods --- src/cowboy_rest.erl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index b9f091f90..a5056c84e 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -325,8 +325,12 @@ uri_too_long(Req, State) -> expect(Req, State, uri_too_long, false, fun allowed_methods/2, 414). stringify_allowed_methods(MethodList) when is_list(MethodList) -> - << ", ", Allow/binary >> = << << ", ", M/binary >> || M <- MethodList >>, - Allow. + case MethodList of + [] -> <<>>; + _ -> + << ", ", Allow/binary >> = << << ", ", M/binary >> || M <- MethodList >>, + Allow + end. %% allowed_methods/2 should return a list of binary methods. allowed_methods(Req, State=#state{method=Method}) -> From 4100228b6da369f9c88b52b72053ad588ade798e Mon Sep 17 00:00:00 2001 From: geeksilva97 Date: Tue, 6 Feb 2024 10:37:33 -0300 Subject: [PATCH 09/10] remove header setting from method_not_allowed --- src/cowboy_rest.erl | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index a5056c84e..60a685503 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -341,29 +341,26 @@ allowed_methods(Req, State=#state{method=Method}) -> Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req), next(Req2, State, fun malformed_request/2); no_call -> - method_not_allowed(Req, State, DefaultAllowedMethods); + Allow = stringify_allowed_methods(DefaultAllowedMethods), + Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req), + method_not_allowed(Req2, State); {stop, Req2, State2} -> terminate(Req2, State2); {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> switch_handler(Switch, Req2, State2); {List, Req2, State2} -> + Allow = stringify_allowed_methods(List), + Req3 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req2), case lists:member(Method, List) of true -> - Allow = stringify_allowed_methods(List), - Req3 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req2), next(Req3, State2, fun malformed_request/2); false -> - method_not_allowed(Req2, State2, List) + method_not_allowed(Req3, State2) end end. -method_not_allowed(Req, State, []) -> - Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req), - respond(Req2, State, 405); -method_not_allowed(Req, State, Methods) -> - << ", ", Allow/binary >> = << << ", ", M/binary >> || M <- Methods >>, - Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req), - respond(Req2, State, 405). +method_not_allowed(Req, State) -> + respond(Req, State, 405). malformed_request(Req, State) -> expect(Req, State, malformed_request, false, fun is_authorized/2, 400). From 14096932616623b8b57f01b32c25fb741ad934d2 Mon Sep 17 00:00:00 2001 From: geeksilva97 Date: Mon, 26 Feb 2024 15:13:30 -0300 Subject: [PATCH 10/10] remove unused entry from record --- src/cowboy_rest.erl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index 60a685503..34aa24f08 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -246,9 +246,6 @@ handler :: atom(), handler_state :: any(), - %% Allowed methods. Only used for OPTIONS requests. - allowed_methods :: [binary()] | undefined, - %% Media type. content_types_p = [] :: [{binary() | {binary(), binary(), [{binary(), binary()}] | '*'},