forked from soveran/cuba
-
Notifications
You must be signed in to change notification settings - Fork 144
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add invalid_request_body plugin for custom handling of invalid reques…
…t bodies
- Loading branch information
1 parent
1d69b7d
commit 6381bc5
Showing
4 changed files
with
164 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
# frozen-string-literal: true | ||
|
||
# | ||
class Roda | ||
module RodaPlugins | ||
# The invalid_request_body plugin allows for custom handling of invalid request | ||
# bodies. Roda uses Rack for parsing request bodies, so by default, any | ||
# invalid request bodies would result in Rack raising an exception, and the | ||
# exception could change for different reasons the request body is invalid. | ||
# This plugin overrides RodaRequest#POST (which parses parameters from request | ||
# bodies), and if parsing raises an exception, it allows for custom behavior. | ||
# | ||
# If you want to treat an invalid request body as the submission of no parameters, | ||
# you can use the :empty_hash argument when loading the plugin: | ||
# | ||
# plugin :invalid_request_body, :empty_hash | ||
# | ||
# If you want to return a empty 400 (Bad Request) response if an invalid request | ||
# body is submitted, you can use the :empty_400 argument when loading the plugin: | ||
# | ||
# plugin :invalid_request_body, :empty_400 | ||
# | ||
# If you want to raise a Roda::RodaPlugins::InvalidRequestBody::Error exception | ||
# if an invalid request body is submitted (which makes it easier to handle these | ||
# exceptions when using the error_handler plugin), you can use the :raise argument | ||
# when loading the plugin: | ||
# | ||
# plugin :invalid_request_body, :raise | ||
# | ||
# For custom behavior, you can pass a block when loading the plugin. The block | ||
# is called with the exception Rack raised when parsing the body. The block will | ||
# be used to define a method in the application's RodaRequest class. It can either | ||
# return a hash of parameters, or you can raise a different exception, or you | ||
# can halt processing and return a response: | ||
# | ||
# plugin :invalid_request_body do |exception| | ||
# # To treat the exception raised as a submitted parameter | ||
# {body_error: e} | ||
# end | ||
module InvalidRequestBody | ||
# Exception class raised for invalid request bodies. | ||
Error = Class.new(RodaError) | ||
|
||
# Set the action to use (:empty_400, :empty_hash, :raise) for invalid request bodies, | ||
# or use a block for custom behavior. | ||
def self.configure(app, action=nil, &block) | ||
if action | ||
if block | ||
raise RodaError, "cannot provide both block and action when loading invalid_request_body plugin" | ||
end | ||
|
||
method = :"handle_invalid_request_body_#{action}" | ||
unless RequestMethods.private_method_defined?(method) | ||
raise RodaError, "invalid invalid_request_body action provided: #{action}" | ||
end | ||
|
||
app::RodaRequest.send(:alias_method, :handle_invalid_request_body, method) | ||
elsif block | ||
app::RodaRequest.class_eval do | ||
define_method(:handle_invalid_request_body, &block) | ||
alias handle_invalid_request_body handle_invalid_request_body | ||
end | ||
else | ||
raise RodaError, "must provide block or action when loading invalid_request_body plugin" | ||
end | ||
|
||
app::RodaRequest.send(:private, :handle_invalid_request_body) | ||
end | ||
|
||
module RequestMethods | ||
# Handle invalid request bodies as configured if the default behavior | ||
# raises an exception. | ||
def POST | ||
super | ||
rescue => e | ||
handle_invalid_request_body(e) | ||
end | ||
|
||
private | ||
|
||
# Return an empty 400 HTTP response for invalid request bodies. | ||
def handle_invalid_request_body_empty_400(e) | ||
response.status = 400 | ||
headers = response.headers | ||
headers.clear | ||
headers[RodaResponseHeaders::CONTENT_TYPE] = 'text/html' | ||
headers[RodaResponseHeaders::CONTENT_LENGTH] ='0' | ||
throw :halt, response.finish_with_body([]) | ||
end | ||
|
||
# Treat invalid request bodies by using an empty hash as the | ||
# POST params. | ||
def handle_invalid_request_body_empty_hash(e) | ||
{} | ||
end | ||
|
||
# Raise a specific error for all invalid request bodies, | ||
# to allow for easy rescuing using the error_handler plugin. | ||
def handle_invalid_request_body_raise(e) | ||
raise Error, e.message | ||
end | ||
end | ||
end | ||
|
||
register_plugin(:invalid_request_body, InvalidRequestBody) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
require_relative "../spec_helper" | ||
|
||
describe "invalid_request_body plugin" do | ||
def invalid_request_body_app(*args, &block) | ||
app(:bare) do | ||
plugin :invalid_request_body, *args, &block | ||
route{|r| r.POST.inspect} | ||
end | ||
end | ||
content_type = 'multipart/form-data; boundary=foobar' | ||
valid_body = "--foobar\r\nContent-Disposition: form-data; name=\"x\"\r\n\r\ny\r\n--foobar--" | ||
define_method :valid_request_hash do | ||
{"REQUEST_METHOD"=>'POST', 'CONTENT_TYPE'=>content_type, 'CONTENT_LENGTH'=>valid_body.bytesize.to_s, 'rack.input'=>rack_input(valid_body)} | ||
end | ||
define_method :invalid_request_hash do | ||
{"REQUEST_METHOD"=>'POST', 'CONTENT_TYPE'=>content_type, 'CONTENT_LENGTH'=>'100', 'rack.input'=>rack_input} | ||
end | ||
|
||
it "supports :empty_400 plugin argument" do | ||
invalid_request_body_app(:empty_400) | ||
body(valid_request_hash).must_equal '{"x"=>"y"}' | ||
req(invalid_request_hash).must_equal [400, {RodaResponseHeaders::CONTENT_TYPE=>'text/html', RodaResponseHeaders::CONTENT_LENGTH=>'0'}, []] | ||
end | ||
|
||
it "supports :empty_hash plugin argument" do | ||
invalid_request_body_app(:empty_hash) | ||
body(valid_request_hash).must_equal '{"x"=>"y"}' | ||
req(invalid_request_hash).must_equal [200, {RodaResponseHeaders::CONTENT_TYPE=>'text/html', RodaResponseHeaders::CONTENT_LENGTH=>'2'}, ['{}']] | ||
end | ||
|
||
it "supports :raise plugin argument" do | ||
invalid_request_body_app(:raise) | ||
body(valid_request_hash).must_equal '{"x"=>"y"}' | ||
proc{req(invalid_request_hash)}.must_raise Roda::RodaPlugins::InvalidRequestBody::Error | ||
end | ||
|
||
it "supports plugin block argument" do | ||
invalid_request_body_app{|e| {'y'=>"x"}} | ||
body(valid_request_hash).must_equal '{"x"=>"y"}' | ||
body(invalid_request_hash).must_equal '{"y"=>"x"}' | ||
end | ||
|
||
it "raises Error if configuring plugin with invalid plugin argument" do | ||
proc{invalid_request_body_app(:foo)}.must_raise Roda::RodaError | ||
end | ||
|
||
it "raises Error if configuring plugin with block and regular argument" do | ||
proc{invalid_request_body_app(:raise){}}.must_raise Roda::RodaError | ||
end | ||
|
||
it "raises Error if configuring plugin without block or regular argument" do | ||
proc{invalid_request_body_app}.must_raise Roda::RodaError | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters