diff options
Diffstat (limited to 'tool/test/webrick/test_httpresponse.rb')
-rw-r--r-- | tool/test/webrick/test_httpresponse.rb | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/tool/test/webrick/test_httpresponse.rb b/tool/test/webrick/test_httpresponse.rb new file mode 100644 index 0000000000..89a0f7036e --- /dev/null +++ b/tool/test/webrick/test_httpresponse.rb @@ -0,0 +1,282 @@ +# frozen_string_literal: false +require "webrick" +require "minitest/autorun" +require "stringio" +require "net/http" + +module WEBrick + class TestHTTPResponse < MiniTest::Unit::TestCase + class FakeLogger + attr_reader :messages + + def initialize + @messages = [] + end + + def warn msg + @messages << msg + end + end + + attr_reader :config, :logger, :res + + def setup + super + @logger = FakeLogger.new + @config = Config::HTTP + @config[:Logger] = logger + @res = HTTPResponse.new config + @res.keep_alive = true + end + + def test_prevent_response_splitting_headers_crlf + res['X-header'] = "malicious\r\nCookie: cracked_indicator_for_test" + io = StringIO.new + res.send_response io + io.rewind + res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io)) + assert_equal '500', res.code + refute_match 'cracked_indicator_for_test', io.string + end + + def test_prevent_response_splitting_cookie_headers_crlf + user_input = "malicious\r\nCookie: cracked_indicator_for_test" + res.cookies << WEBrick::Cookie.new('author', user_input) + io = StringIO.new + res.send_response io + io.rewind + res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io)) + assert_equal '500', res.code + refute_match 'cracked_indicator_for_test', io.string + end + + def test_prevent_response_splitting_headers_cr + res['X-header'] = "malicious\rCookie: cracked_indicator_for_test" + io = StringIO.new + res.send_response io + io.rewind + res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io)) + assert_equal '500', res.code + refute_match 'cracked_indicator_for_test', io.string + end + + def test_prevent_response_splitting_cookie_headers_cr + user_input = "malicious\rCookie: cracked_indicator_for_test" + res.cookies << WEBrick::Cookie.new('author', user_input) + io = StringIO.new + res.send_response io + io.rewind + res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io)) + assert_equal '500', res.code + refute_match 'cracked_indicator_for_test', io.string + end + + def test_prevent_response_splitting_headers_lf + res['X-header'] = "malicious\nCookie: cracked_indicator_for_test" + io = StringIO.new + res.send_response io + io.rewind + res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io)) + assert_equal '500', res.code + refute_match 'cracked_indicator_for_test', io.string + end + + def test_prevent_response_splitting_cookie_headers_lf + user_input = "malicious\nCookie: cracked_indicator_for_test" + res.cookies << WEBrick::Cookie.new('author', user_input) + io = StringIO.new + res.send_response io + io.rewind + res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io)) + assert_equal '500', res.code + refute_match 'cracked_indicator_for_test', io.string + end + + def test_set_redirect_response_splitting + url = "malicious\r\nCookie: cracked_indicator_for_test" + assert_raises(URI::InvalidURIError) do + res.set_redirect(WEBrick::HTTPStatus::MultipleChoices, url) + end + end + + def test_set_redirect_html_injection + url = 'http://example.com////?a</a><head></head><body><img src=1></body>' + assert_raises(WEBrick::HTTPStatus::MultipleChoices) do + res.set_redirect(WEBrick::HTTPStatus::MultipleChoices, url) + end + res.status = 300 + io = StringIO.new + res.send_response(io) + io.rewind + res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io)) + assert_equal '300', res.code + refute_match(/<img/, io.string) + end + + def test_304_does_not_log_warning + res.status = 304 + res.setup_header + assert_equal 0, logger.messages.length + end + + def test_204_does_not_log_warning + res.status = 204 + res.setup_header + + assert_equal 0, logger.messages.length + end + + def test_1xx_does_not_log_warnings + res.status = 105 + res.setup_header + + assert_equal 0, logger.messages.length + end + + def test_200_chunked_does_not_set_content_length + res.chunked = false + res["Transfer-Encoding"] = 'chunked' + res.setup_header + assert_nil res.header.fetch('content-length', nil) + end + + def test_send_body_io + IO.pipe {|body_r, body_w| + body_w.write 'hello' + body_w.close + + @res.body = body_r + + IO.pipe {|r, w| + + @res.send_body w + + w.close + + assert_equal 'hello', r.read + } + } + assert_equal 0, logger.messages.length + end + + def test_send_body_string + @res.body = 'hello' + + IO.pipe {|r, w| + @res.send_body w + + w.close + + assert_equal 'hello', r.read + } + assert_equal 0, logger.messages.length + end + + def test_send_body_string_io + @res.body = StringIO.new 'hello' + + IO.pipe {|r, w| + @res.send_body w + + w.close + + assert_equal 'hello', r.read + } + assert_equal 0, logger.messages.length + end + + def test_send_body_io_chunked + @res.chunked = true + + IO.pipe {|body_r, body_w| + + body_w.write 'hello' + body_w.close + + @res.body = body_r + + IO.pipe {|r, w| + @res.send_body w + + w.close + + r.binmode + assert_equal "5\r\nhello\r\n0\r\n\r\n", r.read + } + } + assert_equal 0, logger.messages.length + end + + def test_send_body_string_chunked + @res.chunked = true + + @res.body = 'hello' + + IO.pipe {|r, w| + @res.send_body w + + w.close + + r.binmode + assert_equal "5\r\nhello\r\n0\r\n\r\n", r.read + } + assert_equal 0, logger.messages.length + end + + def test_send_body_string_io_chunked + @res.chunked = true + + @res.body = StringIO.new 'hello' + + IO.pipe {|r, w| + @res.send_body w + + w.close + + r.binmode + assert_equal "5\r\nhello\r\n0\r\n\r\n", r.read + } + assert_equal 0, logger.messages.length + end + + def test_send_body_proc + @res.body = Proc.new { |out| out.write('hello') } + IO.pipe do |r, w| + @res.send_body(w) + w.close + r.binmode + assert_equal 'hello', r.read + end + assert_equal 0, logger.messages.length + end + + def test_send_body_proc_chunked + @res.body = Proc.new { |out| out.write('hello') } + @res.chunked = true + IO.pipe do |r, w| + @res.send_body(w) + w.close + r.binmode + assert_equal "5\r\nhello\r\n0\r\n\r\n", r.read + end + assert_equal 0, logger.messages.length + end + + def test_set_error + status = 400 + message = 'missing attribute' + @res.status = status + error = WEBrick::HTTPStatus[status].new(message) + body = @res.set_error(error) + assert_match(/#{@res.reason_phrase}/, body) + assert_match(/#{message}/, body) + end + + def test_no_extraneous_space + [200, 300, 400, 500].each do |status| + @res.status = status + assert_match(/\S\r\n/, @res.status_line) + end + end + end +end |