Skip to content

Commit

Permalink
Backport #2062 to 3-0-stable (#2176)
Browse files Browse the repository at this point in the history
* Do not allow BodyProxy to respond to to_str, make to_ary call close

See rack/rack-test#335 for an example
where allowing BodyProxy to respond to to_str (when provided an
invalid rack body) complicated debugging.

Call BodyProxy#close if BodyProxy#to_ary is called, as not doing so
violates SPEC.

* Changelog for #2062

---------

Co-authored-by: Jeremy Evans <code@jeremyevans.net>
  • Loading branch information
Earlopain and jeremyevans committed May 9, 2024
1 parent 4da4e4a commit b9a60d9
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 8 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. For info on

## Unreleased

- Backport #2062 to 3-0-stable: Do not allow `BodyProxy` to respond to `to_str`, make `to_ary` call close . ([#2062](https://github.com/rack/rack/pull/2062), [@jeremyevans](https://github.com/jeremyevans))

## [3.0.10] - 2024-03-21

- Backport #2104 to 3-0-stable: Return empty when parsing a multi-part POST with only one end delimiter. ([#2164](https://github.com/rack/rack/pull/2164), [@JoeDupuis](https://github.com/JoeDupuis))
Expand Down
20 changes: 18 additions & 2 deletions lib/rack/body_proxy.rb
Expand Up @@ -15,7 +15,12 @@ def initialize(body, &block)

# Return whether the wrapped body responds to the method.
def respond_to_missing?(method_name, include_all = false)
super or @body.respond_to?(method_name, include_all)
case method_name
when :to_str
false
else
super or @body.respond_to?(method_name, include_all)
end
end

# If not already closed, close the wrapped body and
Expand All @@ -38,7 +43,18 @@ def closed?

# Delegate missing methods to the wrapped body.
def method_missing(method_name, *args, &block)
@body.__send__(method_name, *args, &block)
case method_name
when :to_str
super
when :to_ary
begin
@body.__send__(method_name, *args, &block)
ensure
close
end
else
@body.__send__(method_name, *args, &block)
end
end
# :nocov:
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
Expand Down
36 changes: 30 additions & 6 deletions test/spec_body_proxy.rb
Expand Up @@ -74,13 +74,37 @@ def body.banana(foo: nil); foo end
proxy.banana(foo: 1).must_equal 1
end

it 'not respond to :to_ary' do
body = Object.new.tap { |o| def o.to_ary() end }
body.respond_to?(:to_ary).must_equal true
it 'respond to :to_ary if body does responds to it, and have to_ary call close' do
proxy_closed = false
proxy = Rack::BodyProxy.new([]) { proxy_closed = true }
proxy.respond_to?(:to_ary).must_equal true
proxy_closed.must_equal false
proxy.to_ary.must_equal []
proxy_closed.must_equal true
end

proxy = Rack::BodyProxy.new(body) { }
x = [proxy]
assert_equal x, x.flatten
it 'not respond to :to_ary if body does not respond to it' do
proxy = Rack::BodyProxy.new([].map) { }
proxy.respond_to?(:to_ary).must_equal false
proc do
proxy.to_ary
end.must_raise NoMethodError
end

it 'not respond to :to_str' do
proxy = Rack::BodyProxy.new("string body") { }
proxy.respond_to?(:to_str).must_equal false
proc do
proxy.to_str
end.must_raise NoMethodError
end

it 'not respond to :to_path if body does not respond to it' do
proxy = Rack::BodyProxy.new("string body") { }
proxy.respond_to?(:to_path).must_equal false
proc do
proxy.to_path
end.must_raise NoMethodError
end

it 'not close more than one time' do
Expand Down

0 comments on commit b9a60d9

Please sign in to comment.