diff --git a/CHANGELOG.md b/CHANGELOG.md index d3d7fa370..3e305f5f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) diff --git a/lib/rack/body_proxy.rb b/lib/rack/body_proxy.rb index fbb344b81..729157986 100644 --- a/lib/rack/body_proxy.rb +++ b/lib/rack/body_proxy.rb @@ -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 @@ -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) diff --git a/test/spec_body_proxy.rb b/test/spec_body_proxy.rb index 91fcbd74e..c003713ad 100644 --- a/test/spec_body_proxy.rb +++ b/test/spec_body_proxy.rb @@ -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