mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00

* Revert change to `Server#initialize` (new) method signature from #2798 * Test changes for updated Server#intialize
218 lines
5.5 KiB
Ruby
218 lines
5.5 KiB
Ruby
require_relative "helper"
|
|
|
|
# These tests check for invalid request headers and metadata.
|
|
# Content-Length, Transfer-Encoding, and chunked body size
|
|
# values are checked for validity
|
|
#
|
|
# See https://datatracker.ietf.org/doc/html/rfc7230
|
|
#
|
|
# https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2 Content-Length
|
|
# https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.1 Transfer-Encoding
|
|
# https://datatracker.ietf.org/doc/html/rfc7230#section-4.1 chunked body size
|
|
#
|
|
class TestRequestInvalid < Minitest::Test
|
|
# running parallel seems to take longer...
|
|
# parallelize_me! unless JRUBY_HEAD
|
|
|
|
GET_PREFIX = "GET / HTTP/1.1\r\nConnection: close\r\n"
|
|
CHUNKED = "1\r\nH\r\n4\r\nello\r\n5\r\nWorld\r\n0\r\n\r\n"
|
|
|
|
def setup
|
|
@host = '127.0.0.1'
|
|
|
|
@ios = []
|
|
|
|
# this app should never be called, used for debugging
|
|
app = ->(env) {
|
|
body = ''.dup
|
|
env.each do |k,v|
|
|
body << "#{k} = #{v}\n"
|
|
if k == 'rack.input'
|
|
body << "#{v.read}\n"
|
|
end
|
|
end
|
|
[200, {}, [body]]
|
|
}
|
|
|
|
@log_writer = Puma::LogWriter.strings
|
|
@server = Puma::Server.new app, nil, {log_writer: @log_writer}
|
|
@port = (@server.add_tcp_listener @host, 0).addr[1]
|
|
@server.run
|
|
sleep 0.15 if Puma.jruby?
|
|
end
|
|
|
|
def teardown
|
|
@server.stop(true)
|
|
@ios.each { |io| io.close if io && !io.closed? }
|
|
end
|
|
|
|
def send_http_and_read(req)
|
|
send_http(req).read
|
|
end
|
|
|
|
def send_http(req)
|
|
new_connection << req
|
|
end
|
|
|
|
def new_connection
|
|
TCPSocket.new(@host, @port).tap {|sock| @ios << sock}
|
|
end
|
|
|
|
def assert_status(str, status = 400)
|
|
assert str.start_with?("HTTP/1.1 #{status}"), "'#{str[/[^\r]+/]}' should be #{status}"
|
|
end
|
|
|
|
# ──────────────────────────────────── below are invalid Content-Length
|
|
|
|
def test_content_length_multiple
|
|
te = [
|
|
'Content-Length: 5',
|
|
'Content-Length: 5'
|
|
].join "\r\n"
|
|
|
|
data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\nHello\r\n\r\n"
|
|
|
|
assert_status data
|
|
end
|
|
|
|
def test_content_length_bad_characters_1
|
|
te = 'Content-Length: 5.01'
|
|
|
|
data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\nHello\r\n\r\n"
|
|
|
|
assert_status data
|
|
end
|
|
|
|
def test_content_length_bad_characters_2
|
|
te = 'Content-Length: +5'
|
|
|
|
data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\nHello\r\n\r\n"
|
|
|
|
assert_status data
|
|
end
|
|
|
|
def test_content_length_bad_characters_3
|
|
te = 'Content-Length: 5 test'
|
|
|
|
data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\nHello\r\n\r\n"
|
|
|
|
assert_status data
|
|
end
|
|
|
|
# ──────────────────────────────────── below are invalid Transfer-Encoding
|
|
|
|
def test_transfer_encoding_chunked_not_last
|
|
te = [
|
|
'Transfer-Encoding: chunked',
|
|
'Transfer-Encoding: gzip'
|
|
].join "\r\n"
|
|
|
|
data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{CHUNKED}"
|
|
|
|
assert_status data
|
|
end
|
|
|
|
def test_transfer_encoding_chunked_multiple
|
|
te = [
|
|
'Transfer-Encoding: chunked',
|
|
'Transfer-Encoding: gzip',
|
|
'Transfer-Encoding: chunked'
|
|
].join "\r\n"
|
|
|
|
data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{CHUNKED}"
|
|
|
|
assert_status data
|
|
end
|
|
|
|
def test_transfer_encoding_invalid_single
|
|
te = 'Transfer-Encoding: xchunked'
|
|
|
|
data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{CHUNKED}"
|
|
|
|
assert_status data, 501
|
|
end
|
|
|
|
def test_transfer_encoding_invalid_multiple
|
|
te = [
|
|
'Transfer-Encoding: x_gzip',
|
|
'Transfer-Encoding: gzip',
|
|
'Transfer-Encoding: chunked'
|
|
].join "\r\n"
|
|
|
|
data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{CHUNKED}"
|
|
|
|
assert_status data, 501
|
|
end
|
|
|
|
def test_transfer_encoding_single_not_chunked
|
|
te = 'Transfer-Encoding: gzip'
|
|
|
|
data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{CHUNKED}"
|
|
|
|
assert_status data
|
|
end
|
|
|
|
# ──────────────────────────────────── below are invalid chunked size
|
|
|
|
def test_chunked_size_bad_characters_1
|
|
te = 'Transfer-Encoding: chunked'
|
|
chunked ='5.01'
|
|
|
|
data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n1\r\nh\r\n#{chunked}\r\nHello\r\n0\r\n\r\n"
|
|
|
|
assert_status data
|
|
end
|
|
|
|
def test_chunked_size_bad_characters_2
|
|
te = 'Transfer-Encoding: chunked'
|
|
chunked ='+5'
|
|
|
|
data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n1\r\nh\r\n#{chunked}\r\nHello\r\n0\r\n\r\n"
|
|
|
|
assert_status data
|
|
end
|
|
|
|
def test_chunked_size_bad_characters_3
|
|
te = 'Transfer-Encoding: chunked'
|
|
chunked ='5 bad'
|
|
|
|
data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n1\r\nh\r\n#{chunked}\r\nHello\r\n0\r\n\r\n"
|
|
|
|
assert_status data
|
|
end
|
|
|
|
def test_chunked_size_bad_characters_4
|
|
te = 'Transfer-Encoding: chunked'
|
|
chunked ='0xA'
|
|
|
|
data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n1\r\nh\r\n#{chunked}\r\nHelloHello\r\n0\r\n\r\n"
|
|
|
|
assert_status data
|
|
end
|
|
|
|
# size is less than bytesize
|
|
def test_chunked_size_mismatch_1
|
|
te = 'Transfer-Encoding: chunked'
|
|
chunked =
|
|
"5\r\nHello\r\n" \
|
|
"4\r\nWorld\r\n" \
|
|
"0"
|
|
|
|
data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{chunked}\r\n\r\n"
|
|
|
|
assert_status data
|
|
end
|
|
|
|
# size is greater than bytesize
|
|
def test_chunked_size_mismatch_2
|
|
te = 'Transfer-Encoding: chunked'
|
|
chunked =
|
|
"5\r\nHello\r\n" \
|
|
"6\r\nWorld\r\n" \
|
|
"0"
|
|
|
|
data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{chunked}\r\n\r\n"
|
|
|
|
assert_status data
|
|
end
|
|
end
|