Skip to content

Commit c117bff

Browse files
darren987469dblock
authored andcommitted
Validate response from the exception handler. Closes ruby-grape#1757.
1 parent 292976d commit c117bff

File tree

10 files changed

+64
-6
lines changed

10 files changed

+64
-6
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#### Fixes
88

99
* Your contribution here.
10+
* [#1776](https://github.com/ruby-grape/grape/pull/1776): Validate response returned by the exception handler - [@darren987469](https://github.com/darren987469).
1011

1112
### 1.1.0 (8/4/2018)
1213

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -2183,7 +2183,7 @@ You can also rescue all exceptions with a code block and handle the Rack respons
21832183
```ruby
21842184
class Twitter::API < Grape::API
21852185
rescue_from :all do |e|
2186-
Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' }).finish
2186+
Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' })
21872187
end
21882188
end
21892189
```
@@ -2254,9 +2254,9 @@ class Twitter::API < Grape::API
22542254
end
22552255
```
22562256

2257-
The `rescue_from` block must return a `Rack::Response` object, call `error!` or re-raise an exception.
2257+
The `rescue_from` handler must return a `Rack::Response` object, call `error!`, or raise an exception (either the original exception or another custom one). The exception raised in `rescue_from` will be handled outside Grape. For example, if you mount Grape in Rails, the exception will be handle by [Rails Action Controller](https://guides.rubyonrails.org/action_controller_overview.html#rescue).
22582258

2259-
The `with` keyword is available as `rescue_from` options, it can be passed method name or Proc object.
2259+
Alternately, use the `with` option in `rescue_from` to specify a method or a `proc`.
22602260

22612261
```ruby
22622262
class Twitter::API < Grape::API

UPGRADING.md

+19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,25 @@
11
Upgrading Grape
22
===============
33

4+
### Upgrading to >= 1.1.1
5+
6+
#### Changes in rescue_from returned object
7+
8+
Grape will now check the object returned from `rescue_from` and ensure that it is a `Rack::Response`. That makes sure response is valid and avoids exposing service information. Change any code that invoked `Rack::Response.new(...).finish` in a custom `rescue_from` block to `Rack::Response.new(...)` to comply with the validation.
9+
10+
```ruby
11+
class Twitter::API < Grape::API
12+
rescue_from :all do |e|
13+
# version prior to 1.1.1
14+
Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' }).finish
15+
# 1.1.1 version
16+
Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' })
17+
end
18+
end
19+
```
20+
21+
See [#1757](https://github.com/ruby-grape/grape/pull/1757) and [#1776](https://github.com/ruby-grape/grape/pull/1776) for more information.
22+
423
### Upgrading to >= 1.1.0
524

625
#### Changes in HTTP Response Code for Unsupported Content Type

lib/grape.rb

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ module Exceptions
7575
autoload :InvalidAcceptHeader
7676
autoload :InvalidVersionHeader
7777
autoload :MethodNotAllowed
78+
autoload :InvalidResponse
7879
end
7980

8081
module Extensions
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module Grape
2+
module Exceptions
3+
class InvalidResponse < Base
4+
def initialize
5+
super(message: compose_message(:invalid_response))
6+
end
7+
end
8+
end
9+
end

lib/grape/locale/en.yml

+1
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,5 @@ en:
4949
invalid_version_header:
5050
problem: 'Invalid version header'
5151
resolution: '%{message}'
52+
invalid_response: 'Invalid response'
5253

lib/grape/middleware/error.rb

+8-2
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def rack_response(message, status = options[:default_status], headers = { Grape:
7373
if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
7474
message = ERB::Util.html_escape(message)
7575
end
76-
Rack::Response.new([message], status, headers).finish
76+
Rack::Response.new([message], status, headers)
7777
end
7878

7979
def format_message(message, backtrace, original_exception = nil)
@@ -127,7 +127,13 @@ def run_rescue_handler(handler, error)
127127
handler = public_method(handler)
128128
end
129129

130-
handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler)
130+
response = handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler)
131+
132+
if response.is_a?(Rack::Response)
133+
response
134+
else
135+
run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new)
136+
end
131137
end
132138
end
133139
end

spec/grape/api_spec.rb

+10
Original file line numberDiff line numberDiff line change
@@ -1723,6 +1723,16 @@ class CustomError < Grape::Exceptions::Base; end
17231723
expect(last_response.status).to eql 500
17241724
expect(last_response.body).to eq('Formatter Error')
17251725
end
1726+
1727+
it 'uses default_rescue_handler to handle invalid response from rescue_from' do
1728+
subject.rescue_from(:all) { 'error' }
1729+
subject.get('/') { raise }
1730+
1731+
expect_any_instance_of(Grape::Middleware::Error).to receive(:default_rescue_handler).and_call_original
1732+
get '/'
1733+
expect(last_response.status).to eql 500
1734+
expect(last_response.body).to eql 'Invalid response'
1735+
end
17261736
end
17271737

17281738
describe '.rescue_from klass, block' do
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
require 'spec_helper'
2+
3+
describe Grape::Exceptions::InvalidResponse do
4+
describe '#message' do
5+
let(:error) { described_class.new }
6+
7+
it 'contains the problem in the message' do
8+
expect(error.message).to include('Invalid response')
9+
end
10+
end
11+
end

spec/grape/middleware/exception_spec.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def app
128128
subject do
129129
Rack::Builder.app do
130130
use Spec::Support::EndpointFaker
131-
use Grape::Middleware::Error, rescue_handlers: { NotImplementedError => -> { [200, {}, 'rescued'] } }
131+
use Grape::Middleware::Error, rescue_handlers: { NotImplementedError => -> { Rack::Response.new('rescued', 200, {}) } }
132132
run ExceptionSpec::OtherExceptionApp
133133
end
134134
end

0 commit comments

Comments
 (0)