From 6809685620d5c0352206cf1b59a46aae32757cb8 Mon Sep 17 00:00:00 2001 From: Russ Smith Date: Sun, 6 Oct 2024 09:23:19 -0700 Subject: [PATCH 1/6] Adding a MaximumRequestSizeHandler * Configurable to be on/off. Off by default. * Configurable to set the maximum request size. Default is 1MB. Ref #1143 --- .../maximum_request_size_handler_spec.cr | 51 +++++++++++++++++++ spec/spec_helper.cr | 4 ++ src/lucky/maximum_request_size_handler.cr | 40 +++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 spec/lucky/maximum_request_size_handler_spec.cr create mode 100644 src/lucky/maximum_request_size_handler.cr diff --git a/spec/lucky/maximum_request_size_handler_spec.cr b/spec/lucky/maximum_request_size_handler_spec.cr new file mode 100644 index 000000000..a551cd210 --- /dev/null +++ b/spec/lucky/maximum_request_size_handler_spec.cr @@ -0,0 +1,51 @@ +require "../spec_helper" +require "http/server" + +include ContextHelper + +describe Lucky::MaximumRequestSizeHandler do + context "when the handler is disabled" do + it "simply serves the request" do + context = build_small_request_context("/path") + Lucky::MaximumRequestSizeHandler.temp_config(enabled: false) do + run_request_size_handler(context) + end + context.response.status.should eq(HTTP::Status::OK) + end + end + + context "when the handler is enabled" do + it "with a small request, serve the request" do + context = build_small_request_context("/path") + Lucky::MaximumRequestSizeHandler.temp_config(enabled: true) do + run_request_size_handler(context) + end + context.response.status.should eq(HTTP::Status::OK) + end + + it "with a large request, deny the request" do + context = build_large_request_context("/path") + Lucky::MaximumRequestSizeHandler.temp_config(enabled: true) do + run_request_size_handler(context) + end + context.response.status.should eq(HTTP::Status::PAYLOAD_TOO_LARGE) + end + end +end + +private def run_request_size_handler(context) + handler = Lucky::MaximumRequestSizeHandler.new + handler.next = ->(_ctx : HTTP::Server::Context) {} + handler.call(context) +end + +private def build_small_request_context(path : String) : HTTP::Server::Context + build_context(path: path) +end + +private def build_large_request_context(path : String) : HTTP::Server::Context + build_context(path: path).tap do |context| + context.request.headers["Content-Length"] = "1000000" + context.request.body = "a" * 1000000 + end +end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index df67856c0..1ac943359 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -48,4 +48,8 @@ Lucky::ForceSSLHandler.configure do |settings| settings.enabled = true end +Lucky::MaximumRequestSizeHandler.configure do |settings| + settings.enabled = false +end + Habitat.raise_if_missing_settings! diff --git a/src/lucky/maximum_request_size_handler.cr b/src/lucky/maximum_request_size_handler.cr new file mode 100644 index 000000000..7c0aaff3f --- /dev/null +++ b/src/lucky/maximum_request_size_handler.cr @@ -0,0 +1,40 @@ +class Lucky::MaximumRequestSizeHandler + include HTTP::Handler + + Habitat.create do + setting enabled : Bool = false + setting max_size : Int32 = 1_048_576 # 1MB + end + + def call(context) + return call_next(context) unless settings.enabled + + body_size = 0 + body = IO::Memory.new + + begin + buffer = Bytes.new(8192) # 8KB buffer + while (read_bytes = context.request.body.try(&.read(buffer))) + body_size += read_bytes + body.write(buffer[0, read_bytes]) + + if body_size > settings.max_size + context.response.status = HTTP::Status::PAYLOAD_TOO_LARGE + context.response.print("Request entity too large") + return context + end + + break if read_bytes < buffer.size # End of body + end + rescue IO::Error + context.response.status = HTTP::Status::BAD_REQUEST + context.response.print("Error reading request body") + return context + end + + # Reset the request body for downstream handlers + context.request.body = IO::Memory.new(body.to_s) + + call_next(context) + end +end From 82b768a86dde2d493fb4125935e08981a6d2ee8d Mon Sep 17 00:00:00 2001 From: Russ Smith Date: Tue, 8 Oct 2024 15:37:52 -0700 Subject: [PATCH 2/6] Setting the spec max_size to something very small to fix the spec. --- spec/lucky/maximum_request_size_handler_spec.cr | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/lucky/maximum_request_size_handler_spec.cr b/spec/lucky/maximum_request_size_handler_spec.cr index a551cd210..17f81f8d4 100644 --- a/spec/lucky/maximum_request_size_handler_spec.cr +++ b/spec/lucky/maximum_request_size_handler_spec.cr @@ -25,7 +25,10 @@ describe Lucky::MaximumRequestSizeHandler do it "with a large request, deny the request" do context = build_large_request_context("/path") - Lucky::MaximumRequestSizeHandler.temp_config(enabled: true) do + Lucky::MaximumRequestSizeHandler.temp_config( + enabled: true, + max_size: 10, + ) do run_request_size_handler(context) end context.response.status.should eq(HTTP::Status::PAYLOAD_TOO_LARGE) From 2c425737c2a75ed18076a0290149700ba851980f Mon Sep 17 00:00:00 2001 From: Russ Smith Date: Tue, 8 Oct 2024 15:38:13 -0700 Subject: [PATCH 3/6] Making max_size an Int64 --- src/lucky/maximum_request_size_handler.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lucky/maximum_request_size_handler.cr b/src/lucky/maximum_request_size_handler.cr index 7c0aaff3f..d2804f83a 100644 --- a/src/lucky/maximum_request_size_handler.cr +++ b/src/lucky/maximum_request_size_handler.cr @@ -3,7 +3,7 @@ class Lucky::MaximumRequestSizeHandler Habitat.create do setting enabled : Bool = false - setting max_size : Int32 = 1_048_576 # 1MB + setting max_size : Int64 = 1_048_576_i64 # 1MB end def call(context) From 9a9934af8510f2a0b80bfc4ce021c8950fe0388d Mon Sep 17 00:00:00 2001 From: Russ Smith Date: Tue, 8 Oct 2024 15:41:49 -0700 Subject: [PATCH 4/6] Removing the redundant settings in spec helper. --- spec/spec_helper.cr | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 1ac943359..df67856c0 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -48,8 +48,4 @@ Lucky::ForceSSLHandler.configure do |settings| settings.enabled = true end -Lucky::MaximumRequestSizeHandler.configure do |settings| - settings.enabled = false -end - Habitat.raise_if_missing_settings! From 52b2000e2fc69ee6c126bc05c91c0e42ac90c6aa Mon Sep 17 00:00:00 2001 From: Russ Smith Date: Tue, 8 Oct 2024 15:45:20 -0700 Subject: [PATCH 5/6] Linting. I don't have ameba on my mac... --- src/lucky/maximum_request_size_handler.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lucky/maximum_request_size_handler.cr b/src/lucky/maximum_request_size_handler.cr index d2804f83a..0f9fbd1ce 100644 --- a/src/lucky/maximum_request_size_handler.cr +++ b/src/lucky/maximum_request_size_handler.cr @@ -14,7 +14,7 @@ class Lucky::MaximumRequestSizeHandler begin buffer = Bytes.new(8192) # 8KB buffer - while (read_bytes = context.request.body.try(&.read(buffer))) + while read_bytes = context.request.body.try(&.read(buffer)) body_size += read_bytes body.write(buffer[0, read_bytes]) From 786a3031dde746fa64b44ed0566795ca7f10e2ee Mon Sep 17 00:00:00 2001 From: Russ Smith Date: Tue, 8 Oct 2024 16:08:55 -0700 Subject: [PATCH 6/6] Adding docs to the head of the handler file. --- src/lucky/maximum_request_size_handler.cr | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lucky/maximum_request_size_handler.cr b/src/lucky/maximum_request_size_handler.cr index 0f9fbd1ce..6a14a17f1 100644 --- a/src/lucky/maximum_request_size_handler.cr +++ b/src/lucky/maximum_request_size_handler.cr @@ -1,3 +1,15 @@ +# Allows a maximum request size to be set for incoming requests. +# +# Configure the max_size to the maximum size in bytes that you +# want to allow. +# +# ``` +# Lucky::MaximumRequestSizeHandler.configure do |settings| +# settings.enabled = true +# settings.max_size = 1_048_576 # 1MB +# end +# ``` + class Lucky::MaximumRequestSizeHandler include HTTP::Handler