Skip to content

Commit c8b977f

Browse files
mattbrictsonioquatix
authored andcommitted
Fix content-length calcuation in Rack:Response#write (#2150)
When `Rack::Response` is initialized with an Array, it incorrectly increments its internal `@length` value and emitted content-length header on every subsequent write. The more times `write` is called, the more the error accumulates. This commit fixes the accumulation bug, and fixes/adds specs to properly test the scenario where `write` is used multiple times.
1 parent 8d1bf99 commit c8b977f

File tree

3 files changed

+37
-10
lines changed

3 files changed

+37
-10
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes to this project will be documented in this file. For info on how to format all future additions to this file please reference [Keep A Changelog](https://keepachangelog.com/en/1.0.0/).
44

5+
## Unreleased
6+
7+
### Changed
8+
9+
- Fix incorrect content-length header that was emitted when `Rack::Response#write` was used in some situations. ([#2150](https://github.com/rack/rack/pull/2150), [@mattbrictson])
10+
511
## [3.0.8] - 2023-06-14
612

713
- Fix some unused variable verbose warnings. ([#2084](https://github.com/rack/rack/pull/2084), [@jeremyevans], [@skipkayhil](https://github.com/skipkayhil))

lib/rack/response.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,8 @@ def buffered_body!
328328
@body.each do |part|
329329
@length += part.to_s.bytesize
330330
end
331+
332+
@buffered = true
331333
elsif @body.respond_to?(:each)
332334
# Turn the user supplied body into a buffered array:
333335
body = @body

test/spec_response.rb

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ def object_with_each.each
412412
status.must_equal 404
413413
end
414414

415-
it "correctly updates content-type when writing when not initialized with body" do
415+
it "correctly updates content-length when writing when initialized without body" do
416416
r = Rack::Response.new
417417
r.write('foo')
418418
r.write('bar')
@@ -423,20 +423,39 @@ def object_with_each.each
423423
header['content-length'].must_equal '9'
424424
end
425425

426-
it "correctly updates content-type when writing when initialized with body" do
426+
it "correctly updates content-length when writing when initialized with Array body" do
427+
r = Rack::Response.new(["foo"])
428+
r.write('bar')
429+
r.write('baz')
430+
_, header, body = r.finish
431+
str = "".dup; body.each { |part| str << part }
432+
str.must_equal "foobarbaz"
433+
header['content-length'].must_equal '9'
434+
end
435+
436+
it "correctly updates content-length when writing when initialized with String body" do
437+
r = Rack::Response.new("foo")
438+
r.write('bar')
439+
r.write('baz')
440+
_, header, body = r.finish
441+
str = "".dup; body.each { |part| str << part }
442+
str.must_equal "foobarbaz"
443+
header['content-length'].must_equal '9'
444+
end
445+
446+
it "correctly updates content-length when writing when initialized with object body that responds to #each" do
427447
obj = Object.new
428448
def obj.each
429449
yield 'foo'
430450
yield 'bar'
431451
end
432-
["foobar", ["foo", "bar"], obj].each do
433-
r = Rack::Response.new(["foo", "bar"])
434-
r.write('baz')
435-
_, header, body = r.finish
436-
str = "".dup; body.each { |part| str << part }
437-
str.must_equal "foobarbaz"
438-
header['content-length'].must_equal '9'
439-
end
452+
r = Rack::Response.new(obj)
453+
r.write('baz')
454+
r.write('baz')
455+
_, header, body = r.finish
456+
str = "".dup; body.each { |part| str << part }
457+
str.must_equal "foobarbazbaz"
458+
header['content-length'].must_equal '12'
440459
end
441460

442461
it "doesn't return invalid responses" do

0 commit comments

Comments
 (0)