From 4318d742241a3589aa39ffa2b6a909d36b8aab57 Mon Sep 17 00:00:00 2001 From: Morgan Aubert Date: Wed, 25 Sep 2024 08:37:07 -1000 Subject: [PATCH] Make it possible to configure how to handle unsupported HTTP methods in handlers --- docs/docs/development/reference/settings.md | 8 +++++++ spec/marten/conf/global_settings_spec.cr | 23 +++++++++++++++++++ spec/marten/handlers/base_spec.cr | 22 +++++++++++++++++- src/marten/conf/global_settings.cr | 12 ++++++++++ .../http_method_not_allowed_strategy.cr | 10 ++++++++ src/marten/handlers/base.cr | 6 ++++- 6 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 src/marten/conf/global_settings/http_method_not_allowed_strategy.cr diff --git a/docs/docs/development/reference/settings.md b/docs/docs/development/reference/settings.md index 7ca169d89..d1862bbdc 100644 --- a/docs/docs/development/reference/settings.md +++ b/docs/docs/development/reference/settings.md @@ -169,6 +169,14 @@ This setting allows you to configure whether an HTTP permanent redirect (301) sh * `:add` - If the incoming URL does not end with a slash and does not match any routes, a redirect is issued to the same URL with a trailing slash appended. * `:remove` - If the incoming URL ends with a slash and does not match any routes, a redirect is issued to the same URL with the trailing slash removed. +### `unsupported_http_method_strategy` + +Default: `:deny` + +The strategy to use when an unsupported HTTP method is encountered. + +This setting allows you to configure the strategy to use when a handler processes an unsupported HTTP method. The default strategy is `:deny`, which means that the application will return a 405 Method Not Allowed response when an unsupported HTTP method is encountered. The other available strategy is `:hide`, which will results in 404 Not Found responses to be returned instead. + ### `use_x_forwarded_host` Default: `false` diff --git a/spec/marten/conf/global_settings_spec.cr b/spec/marten/conf/global_settings_spec.cr index e3450919a..d7d9f3be0 100644 --- a/spec/marten/conf/global_settings_spec.cr +++ b/spec/marten/conf/global_settings_spec.cr @@ -601,6 +601,29 @@ describe Marten::Conf::GlobalSettings do end end + describe "#unsupported_http_method_strategy" do + it "returns :deny by default" do + global_settings = Marten::Conf::GlobalSettings.new + global_settings.unsupported_http_method_strategy.deny?.should be_true + end + + it "returns the configured unsupported HTTP method strategy if explicitly set" do + global_settings = Marten::Conf::GlobalSettings.new + global_settings.unsupported_http_method_strategy = :hide + + global_settings.unsupported_http_method_strategy.hide?.should be_true + end + end + + describe "#unsupported_http_method_strategy=" do + it "allows to configure the unsupported HTTP method strategy" do + global_settings = Marten::Conf::GlobalSettings.new + global_settings.unsupported_http_method_strategy = :hide + + global_settings.unsupported_http_method_strategy.hide?.should be_true + end + end + describe "#use_x_forwarded_host" do it "returns false by default" do global_settings = Marten::Conf::GlobalSettings.new diff --git a/spec/marten/handlers/base_spec.cr b/spec/marten/handlers/base_spec.cr index 22628a2c7..acec60c19 100644 --- a/spec/marten/handlers/base_spec.cr +++ b/spec/marten/handlers/base_spec.cr @@ -404,7 +404,7 @@ describe Marten::Handlers::Base do response.content.should eq "TRACE processed" end - it "returns an HTTP Not Allowed responses for if the method is not handled by the handler" do + it "returns an HTTP Not Allowed response if the method is not handled by the handler" do request = Marten::HTTP::Request.new( ::HTTP::Request.new( method: "PATCH", @@ -416,6 +416,26 @@ describe Marten::Handlers::Base do response = handler.dispatch response.status.should eq 405 end + + it "returns an HTTP Not Found response if the method is not handled and the corresponding setting is set to hide" do + with_overridden_setting( + "unsupported_http_method_strategy", + Marten::Conf::GlobalSettings::UnsupportedHttpMethodStrategy::HIDE + ) do + request = Marten::HTTP::Request.new( + ::HTTP::Request.new( + method: "PATCH", + resource: "", + headers: HTTP::Headers{"Host" => "example.com"} + ) + ) + handler = Marten::Handlers::BaseSpec::Test1Handler.new(request) + + expect_raises(Marten::HTTP::Errors::NotFound) do + handler.dispatch + end + end + end end describe "#head" do diff --git a/src/marten/conf/global_settings.cr b/src/marten/conf/global_settings.cr index a556b183c..70eaa4f9e 100644 --- a/src/marten/conf/global_settings.cr +++ b/src/marten/conf/global_settings.cr @@ -14,6 +14,7 @@ module Marten @root_path : String? @target_env : String? @trailing_slash : TrailingSlash + @unsupported_http_method_strategy : UnsupportedHttpMethodStrategy # Returns the explicit list of allowed hosts for the application. getter allowed_hosts @@ -84,6 +85,9 @@ module Marten # Returns the trailing slash strategy. getter trailing_slash + # Returns the strategy to use when an unsupported HTTP method is encountered. + getter unsupported_http_method_strategy + # Returns a boolean indicating whether the X-Forwarded-Host header is used to look for the host. getter use_x_forwarded_host @@ -172,6 +176,13 @@ module Marten # from URLs if they can't be found. setter trailing_slash + # Allows to set the strategy to use when an unsupported HTTP method is encountered. + # + # The default strategy is `:deny`, which means that the application will return a 405 Method Not Allowed response + # when an unsupported HTTP method is encountered. The other available strategy is `:hide`, which will results in + # 404 Not Found responses being returned instead. + setter unsupported_http_method_strategy + # Allows to set whether the X-Forwarded-Host header is used to look for the host. setter use_x_forwarded_host @@ -215,6 +226,7 @@ module Marten @secret_key = "" @time_zone = Time::Location.load("UTC") @trailing_slash = :do_nothing + @unsupported_http_method_strategy = :deny @use_x_forwarded_host = false @use_x_forwarded_port = false @use_x_forwarded_proto = false diff --git a/src/marten/conf/global_settings/http_method_not_allowed_strategy.cr b/src/marten/conf/global_settings/http_method_not_allowed_strategy.cr new file mode 100644 index 000000000..21f5ad214 --- /dev/null +++ b/src/marten/conf/global_settings/http_method_not_allowed_strategy.cr @@ -0,0 +1,10 @@ +module Marten + module Conf + class GlobalSettings + enum UnsupportedHttpMethodStrategy + DENY # Method Not Allowed (405) + HIDE # Not Found (404) + end + end + end +end diff --git a/src/marten/handlers/base.cr b/src/marten/handlers/base.cr index 7e504bcc5..98da6c0ec 100644 --- a/src/marten/handlers/base.cr +++ b/src/marten/handlers/base.cr @@ -310,7 +310,11 @@ module Marten end private def handle_http_method_not_allowed - HTTP::Response::MethodNotAllowed.new(self.class.http_method_names) + if Marten.settings.unsupported_http_method_strategy.deny? + HTTP::Response::MethodNotAllowed.new(self.class.http_method_names) + else + raise Marten::HTTP::Errors::NotFound.new("Method not allowed") + end end end end