diff --git a/lib/net/http/header.rb b/lib/net/http/header.rb
index 6106e794..a39043e8 100644
--- a/lib/net/http/header.rb
+++ b/lib/net/http/header.rb
@@ -631,11 +631,35 @@ def set_range(r, e = nil)
# res = Net::HTTP.get_response(hostname, '/todos/1')
# res.content_length # => nil
#
+ # The value must consist of digits only.
+ # Multiple 'Content-Length' values are accepted
+ # only when they are all identical;
+ # otherwise Net::HTTPHeaderSyntaxError is raised.
+ # See {RFC 9110 Section 8.6}[https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6].
def content_length
- return nil unless key?('Content-Length')
- len = self['Content-Length'].slice(/\d+/) or
- raise Net::HTTPHeaderSyntaxError, 'wrong Content-Length format'
- len.to_i
+ values = @header['content-length']
+ return nil if values.nil?
+
+ lengths = []
+ values.each do |value|
+ value = value.strip
+ if value.empty?
+ raise Net::HTTPHeaderSyntaxError,
+ "empty Content-Length value"
+ end
+ lengths.concat(value.split(/\s*,\s*/, -1))
+ end
+ if lengths.uniq.size != 1
+ raise Net::HTTPHeaderSyntaxError,
+ "Content-Length has multiple different values: " +
+ values.join(", ")
+ end
+ length = lengths.first
+ unless /\A\d+\z/.match?(length)
+ raise Net::HTTPHeaderSyntaxError,
+ "wrong Content-Length format: #{length}"
+ end
+ length.to_i
end
# Sets the value of field 'Content-Length' to the given numeric;
diff --git a/test/net/http/test_httpheader.rb b/test/net/http/test_httpheader.rb
index f4b78603..3c09f4c1 100644
--- a/test/net/http/test_httpheader.rb
+++ b/test/net/http/test_httpheader.rb
@@ -396,12 +396,17 @@ def test_content_length
try_content_length 500, '500'
try_content_length 10000_0000_0000, '1000000000000'
try_content_length 123, ' 123'
- try_content_length 1, '1 23'
- try_content_length 500, '(OK)500'
- assert_raise(Net::HTTPHeaderSyntaxError, 'here is no digit, but') {
- @c['content-length'] = 'no digit'
- @c.content_length
- }
+
+ # Same values in one Content-Length field are accepted.
+ # See: https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6-13
+ try_content_length 5, '5, 5'
+
+ # Same values in multiple Content-Length fields are accepted.
+ # See: https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6-13
+ @c.delete('content-length')
+ @c.add_field('content-length', '7')
+ @c.add_field('content-length', '7')
+ assert_equal 7, @c.content_length
end
def try_content_length(len, str)
@@ -409,6 +414,37 @@ def try_content_length(len, str)
assert_equal len, @c.content_length
end
+ def test_content_length_invalid
+ try_invalid_content_length ''
+ try_invalid_content_length '1 23'
+ try_invalid_content_length '(OK)500'
+ try_invalid_content_length 'no digit'
+ try_invalid_content_length 'abc5'
+ try_invalid_content_length '5abc'
+ try_invalid_content_length '5, 6'
+
+ @c.delete('content-length')
+ @c.add_field('content-length', '7')
+ @c.add_field('content-length', '8')
+ assert_raise(Net::HTTPHeaderSyntaxError) {
+ @c.content_length
+ }
+
+ @c.delete('content-length')
+ @c.add_field('content-length', '5')
+ @c.add_field('content-length', '')
+ assert_raise(Net::HTTPHeaderSyntaxError) {
+ @c.content_length
+ }
+ end
+
+ def try_invalid_content_length(str)
+ @c['content-length'] = str
+ assert_raise(Net::HTTPHeaderSyntaxError, str) {
+ @c.content_length
+ }
+ end
+
def test_content_length=
@c.content_length = 0
assert_equal 0, @c.content_length
diff --git a/test/net/http/test_httpresponse.rb b/test/net/http/test_httpresponse.rb
index 7e7ae8a8..e6d8f412 100644
--- a/test/net/http/test_httpresponse.rb
+++ b/test/net/http/test_httpresponse.rb
@@ -64,6 +64,64 @@ def test_read_body
Connection: close
Content-Length: 5
+hello
+EOS
+
+ res = Net::HTTPResponse.read_new(io)
+
+ body = nil
+
+ res.reading_body io, true do
+ body = res.read_body
+ end
+
+ assert_equal 'hello', body
+ end
+
+ def test_read_body_invalid_content_length
+ io = dummy_io(<