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(<