diff options
Diffstat (limited to 'test/unit')
-rw-r--r-- | test/unit/test_configurator.rb | 8 | ||||
-rw-r--r-- | test/unit/test_http_parser.rb | 446 | ||||
-rw-r--r-- | test/unit/test_http_parser_ng.rb | 463 | ||||
-rw-r--r-- | test/unit/test_http_parser_xftrust.rb | 38 | ||||
-rw-r--r-- | test/unit/test_request.rb | 18 | ||||
-rw-r--r-- | test/unit/test_response.rb | 55 | ||||
-rw-r--r-- | test/unit/test_server.rb | 11 | ||||
-rw-r--r-- | test/unit/test_signals.rb | 2 | ||||
-rw-r--r-- | test/unit/test_stream_input.rb | 204 | ||||
-rw-r--r-- | test/unit/test_tee_input.rb | 117 | ||||
-rw-r--r-- | test/unit/test_upload.rb | 12 |
11 files changed, 1044 insertions, 330 deletions
diff --git a/test/unit/test_configurator.rb b/test/unit/test_configurator.rb index ac1efa8..c19c427 100644 --- a/test/unit/test_configurator.rb +++ b/test/unit/test_configurator.rb @@ -33,6 +33,14 @@ class TestConfigurator < Test::Unit::TestCase assert_equal "0.0.0.0:2007", meth.call('2007') assert_equal "0.0.0.0:2007", meth.call(2007) + %w([::1]:2007 [::]:2007).each do |addr| + assert_equal addr, meth.call(addr.dup) + end + + # for Rainbows! users only + assert_equal "[::]:80", meth.call("[::]") + assert_equal "127.6.6.6:80", meth.call("127.6.6.6") + # the next two aren't portable, consider them unsupported for now # assert_match %r{\A\d+\.\d+\.\d+\.\d+:2007\z}, meth.call('1:2007') # assert_match %r{\A\d+\.\d+\.\d+\.\d+:2007\z}, meth.call('2:2007') diff --git a/test/unit/test_http_parser.rb b/test/unit/test_http_parser.rb index 222c227..dc1aab7 100644 --- a/test/unit/test_http_parser.rb +++ b/test/unit/test_http_parser.rb @@ -14,9 +14,10 @@ class HttpParserTest < Test::Unit::TestCase def test_parse_simple parser = HttpParser.new - req = {} - http = "GET / HTTP/1.1\r\n\r\n" - assert_equal req, parser.headers(req, http) + req = parser.env + http = parser.buf + http << "GET / HTTP/1.1\r\n\r\n" + assert_equal req, parser.parse assert_equal '', http assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL'] @@ -28,17 +29,17 @@ class HttpParserTest < Test::Unit::TestCase assert_equal '', req['QUERY_STRING'] assert parser.keepalive? - parser.reset + parser.clear req.clear - http = "G" - assert_nil parser.headers(req, http) + http << "G" + assert_nil parser.parse assert_equal "G", http assert req.empty? # try parsing again to ensure we were reset correctly - http = "GET /hello-world HTTP/1.1\r\n\r\n" - assert parser.headers(req, http) + http << "ET /hello-world HTTP/1.1\r\n\r\n" + assert parser.parse assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL'] assert_equal '/hello-world', req['REQUEST_PATH'] @@ -53,96 +54,161 @@ class HttpParserTest < Test::Unit::TestCase def test_tab_lws parser = HttpParser.new - req = {} - tmp = "GET / HTTP/1.1\r\nHost:\tfoo.bar\r\n\r\n" - assert_equal req.object_id, parser.headers(req, tmp).object_id + req = parser.env + parser.buf << "GET / HTTP/1.1\r\nHost:\tfoo.bar\r\n\r\n" + assert_equal req.object_id, parser.parse.object_id assert_equal "foo.bar", req['HTTP_HOST'] end def test_connection_close_no_ka parser = HttpParser.new - req = {} - tmp = "GET / HTTP/1.1\r\nConnection: close\r\n\r\n" - assert_equal req.object_id, parser.headers(req, tmp).object_id + req = parser.env + parser.buf << "GET / HTTP/1.1\r\nConnection: close\r\n\r\n" + assert_equal req.object_id, parser.parse.object_id assert_equal "GET", req['REQUEST_METHOD'] assert ! parser.keepalive? end def test_connection_keep_alive_ka parser = HttpParser.new - req = {} - tmp = "HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n" - assert_equal req.object_id, parser.headers(req, tmp).object_id + req = parser.env + parser.buf << "HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n" + assert_equal req.object_id, parser.parse.object_id assert parser.keepalive? end - def test_connection_keep_alive_ka_bad_method + def test_connection_keep_alive_no_body parser = HttpParser.new - req = {} - tmp = "POST / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n" - assert_equal req.object_id, parser.headers(req, tmp).object_id - assert ! parser.keepalive? + req = parser.env + parser.buf << "POST / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n" + assert_equal req.object_id, parser.parse.object_id + assert parser.keepalive? + end + + def test_connection_keep_alive_no_body_empty + parser = HttpParser.new + req = parser.env + parser.buf << "POST / HTTP/1.1\r\n" \ + "Content-Length: 0\r\n" \ + "Connection: keep-alive\r\n\r\n" + assert_equal req.object_id, parser.parse.object_id + assert parser.keepalive? end def test_connection_keep_alive_ka_bad_version parser = HttpParser.new - req = {} - tmp = "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n" - assert_equal req.object_id, parser.headers(req, tmp).object_id + req = parser.env + parser.buf << "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n" + assert_equal req.object_id, parser.parse.object_id assert parser.keepalive? end def test_parse_server_host_default_port parser = HttpParser.new - req = {} - tmp = "GET / HTTP/1.1\r\nHost: foo\r\n\r\n" - assert_equal req, parser.headers(req, tmp) + req = parser.env + parser.buf << "GET / HTTP/1.1\r\nHost: foo\r\n\r\n" + assert_equal req, parser.parse assert_equal 'foo', req['SERVER_NAME'] assert_equal '80', req['SERVER_PORT'] - assert_equal '', tmp + assert_equal '', parser.buf assert parser.keepalive? end def test_parse_server_host_alt_port parser = HttpParser.new - req = {} - tmp = "GET / HTTP/1.1\r\nHost: foo:999\r\n\r\n" - assert_equal req, parser.headers(req, tmp) + req = parser.env + parser.buf << "GET / HTTP/1.1\r\nHost: foo:999\r\n\r\n" + assert_equal req, parser.parse assert_equal 'foo', req['SERVER_NAME'] assert_equal '999', req['SERVER_PORT'] - assert_equal '', tmp + assert_equal '', parser.buf assert parser.keepalive? end def test_parse_server_host_empty_port parser = HttpParser.new - req = {} - tmp = "GET / HTTP/1.1\r\nHost: foo:\r\n\r\n" - assert_equal req, parser.headers(req, tmp) + req = parser.env + parser.buf << "GET / HTTP/1.1\r\nHost: foo:\r\n\r\n" + assert_equal req, parser.parse assert_equal 'foo', req['SERVER_NAME'] assert_equal '80', req['SERVER_PORT'] - assert_equal '', tmp + assert_equal '', parser.buf assert parser.keepalive? end def test_parse_server_host_xfp_https parser = HttpParser.new - req = {} - tmp = "GET / HTTP/1.1\r\nHost: foo:\r\n" \ - "X-Forwarded-Proto: https\r\n\r\n" - assert_equal req, parser.headers(req, tmp) + req = parser.env + parser.buf << "GET / HTTP/1.1\r\nHost: foo:\r\n" \ + "X-Forwarded-Proto: https\r\n\r\n" + assert_equal req, parser.parse assert_equal 'foo', req['SERVER_NAME'] assert_equal '443', req['SERVER_PORT'] - assert_equal '', tmp + assert_equal '', parser.buf assert parser.keepalive? end + def test_parse_xfp_https_chained + parser = HttpParser.new + req = parser.env + parser.buf << "GET / HTTP/1.0\r\n" \ + "X-Forwarded-Proto: https,http\r\n\r\n" + assert_equal req, parser.parse + assert_equal '443', req['SERVER_PORT'], req.inspect + assert_equal 'https', req['rack.url_scheme'], req.inspect + assert_equal '', parser.buf + end + + def test_parse_xfp_https_chained_backwards + parser = HttpParser.new + req = parser.env + parser.buf << "GET / HTTP/1.0\r\n" \ + "X-Forwarded-Proto: http,https\r\n\r\n" + assert_equal req, parser.parse + assert_equal '80', req['SERVER_PORT'], req.inspect + assert_equal 'http', req['rack.url_scheme'], req.inspect + assert_equal '', parser.buf + end + + def test_parse_xfp_gopher_is_ignored + parser = HttpParser.new + req = parser.env + parser.buf << "GET / HTTP/1.0\r\n" \ + "X-Forwarded-Proto: gopher\r\n\r\n" + assert_equal req, parser.parse + assert_equal '80', req['SERVER_PORT'], req.inspect + assert_equal 'http', req['rack.url_scheme'], req.inspect + assert_equal '', parser.buf + end + + def test_parse_x_forwarded_ssl_on + parser = HttpParser.new + req = parser.env + parser.buf << "GET / HTTP/1.0\r\n" \ + "X-Forwarded-Ssl: on\r\n\r\n" + assert_equal req, parser.parse + assert_equal '443', req['SERVER_PORT'], req.inspect + assert_equal 'https', req['rack.url_scheme'], req.inspect + assert_equal '', parser.buf + end + + def test_parse_x_forwarded_ssl_off + parser = HttpParser.new + req = parser.env + parser.buf << "GET / HTTP/1.0\r\nX-Forwarded-Ssl: off\r\n\r\n" + assert_equal req, parser.parse + assert_equal '80', req['SERVER_PORT'], req.inspect + assert_equal 'http', req['rack.url_scheme'], req.inspect + assert_equal '', parser.buf + end + def test_parse_strange_headers parser = HttpParser.new - req = {} + req = parser.env should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n" - assert_equal req, parser.headers(req, should_be_good) - assert_equal '', should_be_good + parser.buf << should_be_good + assert_equal req, parser.parse + assert_equal '', parser.buf assert parser.keepalive? end @@ -152,14 +218,14 @@ class HttpParserTest < Test::Unit::TestCase def test_nasty_pound_header parser = HttpParser.new nasty_pound_header = "GET / HTTP/1.1\r\nX-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgEBBAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n\tRA==\r\n\t-----END CERTIFICATE-----\r\n\r\n" - req = {} - buf = nasty_pound_header.dup + req = parser.env + parser.buf << nasty_pound_header.dup assert nasty_pound_header =~ /(-----BEGIN .*--END CERTIFICATE-----)/m expect = $1.dup expect.gsub!(/\r\n\t/, ' ') - assert_equal req, parser.headers(req, buf) - assert_equal '', buf + assert_equal req, parser.parse + assert_equal '', parser.buf assert_equal expect, req['HTTP_X_SSL_BULLSHIT'] end @@ -170,9 +236,10 @@ class HttpParserTest < Test::Unit::TestCase "\t\r\n" \ " \r\n" \ " ASDF\r\n\r\n" - req = {} - assert_equal req, parser.headers(req, header) - assert_equal '', header + parser.buf << header + req = parser.env + assert_equal req, parser.parse + assert_equal '', parser.buf assert_equal 'ASDF', req['HTTP_X_ASDF'] end @@ -184,9 +251,10 @@ class HttpParserTest < Test::Unit::TestCase "\t\r\n" \ " x\r\n" \ " ASDF\r\n\r\n" - req = {} - assert_equal req, parser.headers(req, header) - assert_equal '', header + req = parser.env + parser.buf << header + assert_equal req, parser.parse + assert_equal '', parser.buf assert_equal 'hi y x ASDF', req['HTTP_X_ASDF'] end @@ -196,8 +264,9 @@ class HttpParserTest < Test::Unit::TestCase "Host: \r\n" \ " YHBT.net\r\n" \ "\r\n" - req = {} - assert_equal req, parser.headers(req, header) + parser.buf << header + req = parser.env + assert_equal req, parser.parse assert_equal 'example.com', req['HTTP_HOST'] end @@ -206,21 +275,22 @@ class HttpParserTest < Test::Unit::TestCase # in case we ever go multithreaded/evented... def test_resumable_continuations nr = 1000 - req = {} header = "GET / HTTP/1.1\r\n" \ "X-ASDF: \r\n" \ " hello\r\n" tmp = [] nr.times { |i| parser = HttpParser.new - assert parser.headers(req, "#{header} #{i}\r\n").nil? + req = parser.env + parser.buf << "#{header} #{i}\r\n" + assert parser.parse.nil? asdf = req['HTTP_X_ASDF'] assert_equal "hello #{i}", asdf tmp << [ parser, asdf ] - req.clear } tmp.each_with_index { |(parser, asdf), i| - assert_equal req, parser.headers(req, "#{header} #{i}\r\n .\r\n\r\n") + parser.buf << " .\r\n\r\n" + assert parser.parse assert_equal "hello #{i} .", asdf } end @@ -231,8 +301,8 @@ class HttpParserTest < Test::Unit::TestCase " y\r\n" \ "Host: hello\r\n" \ "\r\n" - req = {} - assert_raises(HttpParserError) { parser.headers(req, header) } + parser.buf << header + assert_raises(HttpParserError) { parser.parse } end def test_parse_ie6_urls @@ -244,7 +314,7 @@ class HttpParserTest < Test::Unit::TestCase /mal"formed"? ).each do |path| parser = HttpParser.new - req = {} + req = parser.env sorta_safe = %(GET #{path} HTTP/1.1\r\n\r\n) assert_equal req, parser.headers(req, sorta_safe) assert_equal path, req['REQUEST_URI'] @@ -255,13 +325,13 @@ class HttpParserTest < Test::Unit::TestCase def test_parse_error parser = HttpParser.new - req = {} + req = parser.env bad_http = "GET / SsUTF/1.1" assert_raises(HttpParserError) { parser.headers(req, bad_http) } # make sure we can recover - parser.reset + parser.clear req.clear assert_equal req, parser.headers(req, "GET / HTTP/1.0\r\n\r\n") assert ! parser.keepalive? @@ -269,7 +339,7 @@ class HttpParserTest < Test::Unit::TestCase def test_piecemeal parser = HttpParser.new - req = {} + req = parser.env http = "GET" assert_nil parser.headers(req, http) assert_nil parser.headers(req, http) @@ -291,9 +361,10 @@ class HttpParserTest < Test::Unit::TestCase # not common, but underscores do appear in practice def test_absolute_uri_underscores parser = HttpParser.new - req = {} + req = parser.env http = "GET http://under_score.example.com/foo?q=bar HTTP/1.0\r\n\r\n" - assert_equal req, parser.headers(req, http) + parser.buf << http + assert_equal req, parser.parse assert_equal 'http', req['rack.url_scheme'] assert_equal '/foo?q=bar', req['REQUEST_URI'] assert_equal '/foo', req['REQUEST_PATH'] @@ -302,16 +373,17 @@ class HttpParserTest < Test::Unit::TestCase assert_equal 'under_score.example.com', req['HTTP_HOST'] assert_equal 'under_score.example.com', req['SERVER_NAME'] assert_equal '80', req['SERVER_PORT'] - assert_equal "", http + assert_equal "", parser.buf assert ! parser.keepalive? end # some dumb clients add users because they're stupid def test_absolute_uri_w_user parser = HttpParser.new - req = {} + req = parser.env http = "GET http://user%20space@example.com/foo?q=bar HTTP/1.0\r\n\r\n" - assert_equal req, parser.headers(req, http) + parser.buf << http + assert_equal req, parser.parse assert_equal 'http', req['rack.url_scheme'] assert_equal '/foo?q=bar', req['REQUEST_URI'] assert_equal '/foo', req['REQUEST_PATH'] @@ -320,7 +392,7 @@ class HttpParserTest < Test::Unit::TestCase assert_equal 'example.com', req['HTTP_HOST'] assert_equal 'example.com', req['SERVER_NAME'] assert_equal '80', req['SERVER_PORT'] - assert_equal "", http + assert_equal "", parser.buf assert ! parser.keepalive? end @@ -329,7 +401,7 @@ class HttpParserTest < Test::Unit::TestCase def test_absolute_uri_uri_parse "#{URI::REGEXP::PATTERN::UNRESERVED};:&=+$,".split(//).each do |char| parser = HttpParser.new - req = {} + req = parser.env http = "GET http://#{char}@example.com/ HTTP/1.0\r\n\r\n" assert_equal req, parser.headers(req, http) assert_equal 'http', req['rack.url_scheme'] @@ -347,9 +419,9 @@ class HttpParserTest < Test::Unit::TestCase def test_absolute_uri parser = HttpParser.new - req = {} - http = "GET http://example.com/foo?q=bar HTTP/1.0\r\n\r\n" - assert_equal req, parser.headers(req, http) + req = parser.env + parser.buf << "GET http://example.com/foo?q=bar HTTP/1.0\r\n\r\n" + assert_equal req, parser.parse assert_equal 'http', req['rack.url_scheme'] assert_equal '/foo?q=bar', req['REQUEST_URI'] assert_equal '/foo', req['REQUEST_PATH'] @@ -358,17 +430,18 @@ class HttpParserTest < Test::Unit::TestCase assert_equal 'example.com', req['HTTP_HOST'] assert_equal 'example.com', req['SERVER_NAME'] assert_equal '80', req['SERVER_PORT'] - assert_equal "", http + assert_equal "", parser.buf assert ! parser.keepalive? end # X-Forwarded-Proto is not in rfc2616, absolute URIs are, however... def test_absolute_uri_https parser = HttpParser.new - req = {} + req = parser.env http = "GET https://example.com/foo?q=bar HTTP/1.1\r\n" \ "X-Forwarded-Proto: http\r\n\r\n" - assert_equal req, parser.headers(req, http) + parser.buf << http + assert_equal req, parser.parse assert_equal 'https', req['rack.url_scheme'] assert_equal '/foo?q=bar', req['REQUEST_URI'] assert_equal '/foo', req['REQUEST_PATH'] @@ -377,17 +450,17 @@ class HttpParserTest < Test::Unit::TestCase assert_equal 'example.com', req['HTTP_HOST'] assert_equal 'example.com', req['SERVER_NAME'] assert_equal '443', req['SERVER_PORT'] - assert_equal "", http + assert_equal "", parser.buf assert parser.keepalive? end # Host: header should be ignored for absolute URIs def test_absolute_uri_with_port parser = HttpParser.new - req = {} - http = "GET http://example.com:8080/foo?q=bar HTTP/1.2\r\n" \ + req = parser.env + parser.buf << "GET http://example.com:8080/foo?q=bar HTTP/1.2\r\n" \ "Host: bad.example.com\r\n\r\n" - assert_equal req, parser.headers(req, http) + assert_equal req, parser.parse assert_equal 'http', req['rack.url_scheme'] assert_equal '/foo?q=bar', req['REQUEST_URI'] assert_equal '/foo', req['REQUEST_PATH'] @@ -396,16 +469,16 @@ class HttpParserTest < Test::Unit::TestCase assert_equal 'example.com:8080', req['HTTP_HOST'] assert_equal 'example.com', req['SERVER_NAME'] assert_equal '8080', req['SERVER_PORT'] - assert_equal "", http + assert_equal "", parser.buf assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final end def test_absolute_uri_with_empty_port parser = HttpParser.new - req = {} - http = "GET https://example.com:/foo?q=bar HTTP/1.1\r\n" \ + req = parser.env + parser.buf << "GET https://example.com:/foo?q=bar HTTP/1.1\r\n" \ "Host: bad.example.com\r\n\r\n" - assert_equal req, parser.headers(req, http) + assert_equal req, parser.parse assert_equal 'https', req['rack.url_scheme'] assert_equal '/foo?q=bar', req['REQUEST_URI'] assert_equal '/foo', req['REQUEST_PATH'] @@ -414,42 +487,192 @@ class HttpParserTest < Test::Unit::TestCase assert_equal 'example.com:', req['HTTP_HOST'] assert_equal 'example.com', req['SERVER_NAME'] assert_equal '443', req['SERVER_PORT'] + assert_equal "", parser.buf + assert parser.keepalive? # TODO: read HTTP/1.2 when it's final + end + + def test_absolute_ipv6_uri + parser = HttpParser.new + req = parser.env + url = "http://[::1]/foo?q=bar" + http = "GET #{url} HTTP/1.1\r\n" \ + "Host: bad.example.com\r\n\r\n" + assert_equal req, parser.headers(req, http) + assert_equal 'http', req['rack.url_scheme'] + assert_equal '/foo?q=bar', req['REQUEST_URI'] + assert_equal '/foo', req['REQUEST_PATH'] + assert_equal 'q=bar', req['QUERY_STRING'] + + uri = URI.parse(url) + assert_equal "[::1]", uri.host, + "URI.parse changed upstream for #{url}? host=#{uri.host}" + assert_equal "[::1]", req['HTTP_HOST'] + assert_equal "[::1]", req['SERVER_NAME'] + assert_equal '80', req['SERVER_PORT'] assert_equal "", http assert parser.keepalive? # TODO: read HTTP/1.2 when it's final end - def test_put_body_oneshot + def test_absolute_ipv6_uri_alpha parser = HttpParser.new - req = {} - http = "PUT / HTTP/1.0\r\nContent-Length: 5\r\n\r\nabcde" + req = parser.env + url = "http://[::a]/" + http = "GET #{url} HTTP/1.1\r\n" \ + "Host: bad.example.com\r\n\r\n" assert_equal req, parser.headers(req, http) + assert_equal 'http', req['rack.url_scheme'] + + uri = URI.parse(url) + assert_equal "[::a]", uri.host, + "URI.parse changed upstream for #{url}? host=#{uri.host}" + assert_equal "[::a]", req['HTTP_HOST'] + assert_equal "[::a]", req['SERVER_NAME'] + assert_equal '80', req['SERVER_PORT'] + end + + def test_absolute_ipv6_uri_alpha_2 + parser = HttpParser.new + req = parser.env + url = "http://[::B]/" + http = "GET #{url} HTTP/1.1\r\n" \ + "Host: bad.example.com\r\n\r\n" + assert_equal req, parser.headers(req, http) + assert_equal 'http', req['rack.url_scheme'] + + uri = URI.parse(url) + assert_equal "[::B]", uri.host, + "URI.parse changed upstream for #{url}? host=#{uri.host}" + assert_equal "[::B]", req['HTTP_HOST'] + assert_equal "[::B]", req['SERVER_NAME'] + assert_equal '80', req['SERVER_PORT'] + end + + def test_absolute_ipv6_uri_with_empty_port + parser = HttpParser.new + req = parser.env + url = "https://[::1]:/foo?q=bar" + http = "GET #{url} HTTP/1.1\r\n" \ + "Host: bad.example.com\r\n\r\n" + assert_equal req, parser.headers(req, http) + assert_equal 'https', req['rack.url_scheme'] + assert_equal '/foo?q=bar', req['REQUEST_URI'] + assert_equal '/foo', req['REQUEST_PATH'] + assert_equal 'q=bar', req['QUERY_STRING'] + + uri = URI.parse(url) + assert_equal "[::1]", uri.host, + "URI.parse changed upstream for #{url}? host=#{uri.host}" + assert_equal "[::1]:", req['HTTP_HOST'] + assert_equal "[::1]", req['SERVER_NAME'] + assert_equal '443', req['SERVER_PORT'] + assert_equal "", http + assert parser.keepalive? # TODO: read HTTP/1.2 when it's final + end + + def test_absolute_ipv6_uri_with_port + parser = HttpParser.new + req = parser.env + url = "https://[::1]:666/foo?q=bar" + http = "GET #{url} HTTP/1.1\r\n" \ + "Host: bad.example.com\r\n\r\n" + assert_equal req, parser.headers(req, http) + assert_equal 'https', req['rack.url_scheme'] + assert_equal '/foo?q=bar', req['REQUEST_URI'] + assert_equal '/foo', req['REQUEST_PATH'] + assert_equal 'q=bar', req['QUERY_STRING'] + + uri = URI.parse(url) + assert_equal "[::1]", uri.host, + "URI.parse changed upstream for #{url}? host=#{uri.host}" + assert_equal "[::1]:666", req['HTTP_HOST'] + assert_equal "[::1]", req['SERVER_NAME'] + assert_equal '666', req['SERVER_PORT'] + assert_equal "", http + assert parser.keepalive? # TODO: read HTTP/1.2 when it's final + end + + def test_ipv6_host_header + parser = HttpParser.new + req = parser.env + parser.buf << "GET / HTTP/1.1\r\n" \ + "Host: [::1]\r\n\r\n" + assert_equal req, parser.parse + assert_equal "[::1]", req['HTTP_HOST'] + assert_equal "[::1]", req['SERVER_NAME'] + assert_equal '80', req['SERVER_PORT'] + assert_equal "", parser.buf + assert parser.keepalive? # TODO: read HTTP/1.2 when it's final + end + + def test_ipv6_host_header_with_port + parser = HttpParser.new + req = parser.env + parser.buf << "GET / HTTP/1.1\r\n" \ + "Host: [::1]:666\r\n\r\n" + assert_equal req, parser.parse + assert_equal "[::1]", req['SERVER_NAME'] + assert_equal '666', req['SERVER_PORT'] + assert_equal "[::1]:666", req['HTTP_HOST'] + assert_equal "", parser.buf + assert parser.keepalive? # TODO: read HTTP/1.2 when it's final + end + + def test_ipv6_host_header_with_empty_port + parser = HttpParser.new + req = parser.env + parser.buf << "GET / HTTP/1.1\r\nHost: [::1]:\r\n\r\n" + assert_equal req, parser.parse + assert_equal "[::1]", req['SERVER_NAME'] + assert_equal '80', req['SERVER_PORT'] + assert_equal "[::1]:", req['HTTP_HOST'] + assert_equal "", parser.buf + assert parser.keepalive? # TODO: read HTTP/1.2 when it's final + end + + # XXX Highly unlikely..., just make sure we don't segfault or assert on it + def test_broken_ipv6_host_header + parser = HttpParser.new + req = parser.env + parser.buf << "GET / HTTP/1.1\r\nHost: [::1:\r\n\r\n" + assert_equal req, parser.parse + assert_equal "[", req['SERVER_NAME'] + assert_equal ':1:', req['SERVER_PORT'] + assert_equal "[::1:", req['HTTP_HOST'] + assert_equal "", parser.buf + end + + def test_put_body_oneshot + parser = HttpParser.new + req = parser.env + parser.buf << "PUT / HTTP/1.0\r\nContent-Length: 5\r\n\r\nabcde" + assert_equal req, parser.parse assert_equal '/', req['REQUEST_PATH'] assert_equal '/', req['REQUEST_URI'] assert_equal 'PUT', req['REQUEST_METHOD'] assert_equal 'HTTP/1.0', req['HTTP_VERSION'] assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL'] - assert_equal "abcde", http + assert_equal "abcde", parser.buf assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final end def test_put_body_later parser = HttpParser.new - req = {} - http = "PUT /l HTTP/1.0\r\nContent-Length: 5\r\n\r\n" - assert_equal req, parser.headers(req, http) + req = parser.env + parser.buf << "PUT /l HTTP/1.0\r\nContent-Length: 5\r\n\r\n" + assert_equal req, parser.parse assert_equal '/l', req['REQUEST_PATH'] assert_equal '/l', req['REQUEST_URI'] assert_equal 'PUT', req['REQUEST_METHOD'] assert_equal 'HTTP/1.0', req['HTTP_VERSION'] assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL'] - assert_equal "", http + assert_equal "", parser.buf assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final end def test_unknown_methods %w(GETT HEADR XGET XHEAD).each { |m| parser = HttpParser.new - req = {} + req = parser.env s = "#{m} /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n" ok = false assert_nothing_raised do @@ -461,23 +684,24 @@ class HttpParserTest < Test::Unit::TestCase assert_equal 'page=1', req['QUERY_STRING'] assert_equal "", s assert_equal m, req['REQUEST_METHOD'] - assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final + assert parser.keepalive? # TODO: read HTTP/1.2 when it's final } end def test_fragment_in_uri parser = HttpParser.new - req = {} + req = parser.env get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n" + parser.buf << get ok = false assert_nothing_raised do - ok = parser.headers(req, get) + ok = parser.parse end assert ok assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI'] assert_equal 'posts-17408', req['FRAGMENT'] assert_equal 'page=1', req['QUERY_STRING'] - assert_equal '', get + assert_equal '', parser.buf assert parser.keepalive? end @@ -503,8 +727,9 @@ class HttpParserTest < Test::Unit::TestCase 10.times do |c| get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n" assert_raises Unicorn::HttpParserError do - parser.headers({}, get) - parser.reset + parser.buf << get + parser.parse + parser.clear end end @@ -512,25 +737,28 @@ class HttpParserTest < Test::Unit::TestCase 10.times do |c| get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n" assert_raises Unicorn::HttpParserError do - parser.headers({}, get) - parser.reset + parser.buf << get + parser.parse + parser.clear end end # then large headers are rejected too get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n" get << "X-Test: test\r\n" * (80 * 1024) + parser.buf << get assert_raises Unicorn::HttpParserError do - parser.headers({}, get) - parser.reset + parser.parse end + parser.clear # finally just that random garbage gets blocked all the time 10.times do |c| get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n" assert_raises Unicorn::HttpParserError do - parser.headers({}, get) - parser.reset + parser.buf << get + parser.parse + parser.clear end end diff --git a/test/unit/test_http_parser_ng.rb b/test/unit/test_http_parser_ng.rb index cb30f32..e57428c 100644 --- a/test/unit/test_http_parser_ng.rb +++ b/test/unit/test_http_parser_ng.rb @@ -8,57 +8,164 @@ include Unicorn class HttpParserNgTest < Test::Unit::TestCase def setup + HttpParser.keepalive_requests = HttpParser::KEEPALIVE_REQUESTS_DEFAULT @parser = HttpParser.new end + def test_keepalive_requests_default_constant + assert_kind_of Integer, HttpParser::KEEPALIVE_REQUESTS_DEFAULT + assert HttpParser::KEEPALIVE_REQUESTS_DEFAULT >= 0 + end + + def test_keepalive_requests_setting + HttpParser.keepalive_requests = 0 + assert_equal 0, HttpParser.keepalive_requests + HttpParser.keepalive_requests = nil + assert HttpParser.keepalive_requests >= 0xffffffff + HttpParser.keepalive_requests = 1 + assert_equal 1, HttpParser.keepalive_requests + HttpParser.keepalive_requests = 666 + assert_equal 666, HttpParser.keepalive_requests + + assert_raises(TypeError) { HttpParser.keepalive_requests = "666" } + assert_raises(TypeError) { HttpParser.keepalive_requests = [] } + end + + def test_keepalive_requests_with_next? + req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze + expect = { + "SERVER_NAME" => "example.com", + "HTTP_HOST" => "example.com", + "rack.url_scheme" => "http", + "REQUEST_PATH" => "/", + "SERVER_PROTOCOL" => "HTTP/1.1", + "PATH_INFO" => "/", + "HTTP_VERSION" => "HTTP/1.1", + "REQUEST_URI" => "/", + "SERVER_PORT" => "80", + "REQUEST_METHOD" => "GET", + "QUERY_STRING" => "" + }.freeze + HttpParser::KEEPALIVE_REQUESTS_DEFAULT.times do |nr| + @parser.buf << req + assert_equal expect, @parser.parse + assert @parser.next? + end + @parser.buf << req + assert_equal expect, @parser.parse + assert ! @parser.next? + end + + def test_fewer_keepalive_requests_with_next? + HttpParser.keepalive_requests = 5 + @parser = HttpParser.new + req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze + expect = { + "SERVER_NAME" => "example.com", + "HTTP_HOST" => "example.com", + "rack.url_scheme" => "http", + "REQUEST_PATH" => "/", + "SERVER_PROTOCOL" => "HTTP/1.1", + "PATH_INFO" => "/", + "HTTP_VERSION" => "HTTP/1.1", + "REQUEST_URI" => "/", + "SERVER_PORT" => "80", + "REQUEST_METHOD" => "GET", + "QUERY_STRING" => "" + }.freeze + 5.times do |nr| + @parser.buf << req + assert_equal expect, @parser.parse + assert @parser.next? + end + @parser.buf << req + assert_equal expect, @parser.parse + assert ! @parser.next? + end + + def test_default_keepalive_is_off + assert ! @parser.keepalive? + assert ! @parser.next? + assert_nothing_raised do + @parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" + @parser.parse + end + assert @parser.keepalive? + @parser.clear + assert ! @parser.keepalive? + assert ! @parser.next? + end + def test_identity_byte_headers - req = {} + req = @parser.env str = "PUT / HTTP/1.1\r\n" str << "Content-Length: 123\r\n" str << "\r" - hdr = "" + hdr = @parser.buf str.each_byte { |byte| - assert_nil @parser.headers(req, hdr << byte.chr) + hdr << byte.chr + assert_nil @parser.parse } hdr << "\n" - assert_equal req.object_id, @parser.headers(req, hdr).object_id + assert_equal req.object_id, @parser.parse.object_id assert_equal '123', req['CONTENT_LENGTH'] assert_equal 0, hdr.size assert ! @parser.keepalive? assert @parser.headers? assert_equal 123, @parser.content_length + dst = "" + buf = '.' * 123 + @parser.filter_body(dst, buf) + assert_equal '.' * 123, dst + assert_equal "", buf + assert @parser.keepalive? end def test_identity_step_headers - req = {} - str = "PUT / HTTP/1.1\r\n" - assert ! @parser.headers(req, str) + req = @parser.env + str = @parser.buf + str << "PUT / HTTP/1.1\r\n" + assert ! @parser.parse str << "Content-Length: 123\r\n" - assert ! @parser.headers(req, str) + assert ! @parser.parse str << "\r\n" - assert_equal req.object_id, @parser.headers(req, str).object_id + assert_equal req.object_id, @parser.parse.object_id assert_equal '123', req['CONTENT_LENGTH'] assert_equal 0, str.size assert ! @parser.keepalive? assert @parser.headers? + dst = "" + buf = '.' * 123 + @parser.filter_body(dst, buf) + assert_equal '.' * 123, dst + assert_equal "", buf + assert @parser.keepalive? end def test_identity_oneshot_header - req = {} - str = "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n" - assert_equal req.object_id, @parser.headers(req, str).object_id + req = @parser.env + str = @parser.buf + str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n" + assert_equal req.object_id, @parser.parse.object_id assert_equal '123', req['CONTENT_LENGTH'] assert_equal 0, str.size assert ! @parser.keepalive? + assert @parser.headers? + dst = "" + buf = '.' * 123 + @parser.filter_body(dst, buf) + assert_equal '.' * 123, dst + assert_equal "", buf end def test_identity_oneshot_header_with_body body = ('a' * 123).freeze - req = {} - str = "PUT / HTTP/1.1\r\n" \ - "Content-Length: #{body.length}\r\n" \ - "\r\n#{body}" - assert_equal req.object_id, @parser.headers(req, str).object_id + req = @parser.env + str = @parser.buf + str << "PUT / HTTP/1.1\r\n" \ + "Content-Length: #{body.length}\r\n" \ + "\r\n#{body}" + assert_equal req.object_id, @parser.parse.object_id assert_equal '123', req['CONTENT_LENGTH'] assert_equal 123, str.size assert_equal body, str @@ -67,12 +174,13 @@ class HttpParserNgTest < Test::Unit::TestCase assert_equal 0, str.size assert_equal tmp, body assert_equal "", @parser.filter_body(tmp, str) - assert ! @parser.keepalive? + assert @parser.keepalive? end def test_identity_oneshot_header_with_body_partial - str = "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na" - assert_equal Hash, @parser.headers({}, str).class + str = @parser.buf + str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na" + assert_equal Hash, @parser.parse.class assert_equal 1, str.size assert_equal 'a', str tmp = '' @@ -85,12 +193,13 @@ class HttpParserNgTest < Test::Unit::TestCase assert_nil rv assert_equal "", str assert_equal str.object_id, @parser.filter_body(tmp, str).object_id - assert ! @parser.keepalive? + assert @parser.keepalive? end def test_identity_oneshot_header_with_body_slop - str = "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG" - assert_equal Hash, @parser.headers({}, str).class + str = @parser.buf + str << "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG" + assert_equal Hash, @parser.parse.class assert_equal 2, str.size assert_equal 'aG', str tmp = '' @@ -99,92 +208,100 @@ class HttpParserNgTest < Test::Unit::TestCase assert_equal "G", @parser.filter_body(tmp, str) assert_equal 1, tmp.size assert_equal "a", tmp - assert ! @parser.keepalive? + assert @parser.keepalive? end def test_chunked - str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" - req = {} - assert_equal req, @parser.headers(req, str) + str = @parser.buf + req = @parser.env + str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" + assert_equal req, @parser.parse, "msg=#{str}" assert_equal 0, str.size tmp = "" - assert_nil @parser.filter_body(tmp, "6") + assert_nil @parser.filter_body(tmp, str << "6") assert_equal 0, tmp.size - assert_nil @parser.filter_body(tmp, rv = "\r\n") - assert_equal 0, rv.size + assert_nil @parser.filter_body(tmp, str << "\r\n") + assert_equal 0, str.size assert_equal 0, tmp.size tmp = "" - assert_nil @parser.filter_body(tmp, "..") + assert_nil @parser.filter_body(tmp, str << "..") assert_equal "..", tmp - assert_nil @parser.filter_body(tmp, "abcd\r\n0\r\n") + assert_nil @parser.filter_body(tmp, str << "abcd\r\n0\r\n") assert_equal "abcd", tmp - rv = "PUT" - assert_equal rv.object_id, @parser.filter_body(tmp, rv).object_id - assert_equal "PUT", rv + assert_equal str.object_id, @parser.filter_body(tmp, str << "PUT").object_id + assert_equal "PUT", str assert ! @parser.keepalive? + str << "TY: FOO\r\n\r\n" + assert_equal req, @parser.parse + assert_equal "FOO", req["HTTP_PUTTY"] + assert @parser.keepalive? end def test_two_chunks - str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" - req = {} - assert_equal req, @parser.headers(req, str) + str = @parser.buf + str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" + req = @parser.env + assert_equal req, @parser.parse assert_equal 0, str.size tmp = "" - assert_nil @parser.filter_body(tmp, "6") + assert_nil @parser.filter_body(tmp, str << "6") assert_equal 0, tmp.size - assert_nil @parser.filter_body(tmp, rv = "\r\n") - assert_equal "", rv + assert_nil @parser.filter_body(tmp, str << "\r\n") + assert_equal "", str assert_equal 0, tmp.size tmp = "" - assert_nil @parser.filter_body(tmp, "..") + assert_nil @parser.filter_body(tmp, str << "..") assert_equal 2, tmp.size assert_equal "..", tmp - assert_nil @parser.filter_body(tmp, "abcd\r\n1") + assert_nil @parser.filter_body(tmp, str << "abcd\r\n1") assert_equal "abcd", tmp - assert_nil @parser.filter_body(tmp, "\r") + assert_nil @parser.filter_body(tmp, str << "\r") assert_equal "", tmp - assert_nil @parser.filter_body(tmp, "\n") + assert_nil @parser.filter_body(tmp, str << "\n") assert_equal "", tmp - assert_nil @parser.filter_body(tmp, "z") + assert_nil @parser.filter_body(tmp, str << "z") assert_equal "z", tmp - assert_nil @parser.filter_body(tmp, "\r\n") - assert_nil @parser.filter_body(tmp, "0") - assert_nil @parser.filter_body(tmp, "\r") - rv = @parser.filter_body(tmp, buf = "\nGET") + assert_nil @parser.filter_body(tmp, str << "\r\n") + assert_nil @parser.filter_body(tmp, str << "0") + assert_nil @parser.filter_body(tmp, str << "\r") + rv = @parser.filter_body(tmp, str << "\nGET") assert_equal "GET", rv - assert_equal buf.object_id, rv.object_id + assert_equal str.object_id, rv.object_id assert ! @parser.keepalive? end def test_big_chunk - str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \ - "4000\r\nabcd" - req = {} - assert_equal req, @parser.headers(req, str) + str = @parser.buf + str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \ + "4000\r\nabcd" + req = @parser.env + assert_equal req, @parser.parse tmp = '' assert_nil @parser.filter_body(tmp, str) assert_equal '', str - str = ' ' * 16300 + str << ' ' * 16300 assert_nil @parser.filter_body(tmp, str) assert_equal '', str - str = ' ' * 80 + str << ' ' * 80 assert_nil @parser.filter_body(tmp, str) assert_equal '', str assert ! @parser.body_eof? - assert_equal "", @parser.filter_body(tmp, "\r\n0\r\n") + assert_equal "", @parser.filter_body(tmp, str << "\r\n0\r\n") assert_equal "", tmp assert @parser.body_eof? - assert_equal req, @parser.trailers(req, moo = "\r\n") - assert_equal "", moo + str << "\r\n" + assert_equal req, @parser.parse + assert_equal "", str assert @parser.body_eof? - assert ! @parser.keepalive? + assert @parser.keepalive? end def test_two_chunks_oneshot - str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \ - "1\r\na\r\n2\r\n..\r\n0\r\n" - req = {} - assert_equal req, @parser.headers(req, str) + str = @parser.buf + req = @parser.env + str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \ + "1\r\na\r\n2\r\n..\r\n0\r\n" + assert_equal req, @parser.parse tmp = '' assert_nil @parser.filter_body(tmp, str) assert_equal 'a..', tmp @@ -195,31 +312,33 @@ class HttpParserNgTest < Test::Unit::TestCase def test_chunks_bytewise chunked = "10\r\nabcdefghijklmnop\r\n11\r\n0123456789abcdefg\r\n0\r\n" - str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n#{chunked}" - req = {} - assert_equal req, @parser.headers(req, str) - assert_equal chunked, str + str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" + buf = @parser.buf + buf << str + req = @parser.env + assert_equal req, @parser.parse + assert_equal "", buf tmp = '' - buf = '' body = '' - str = str[0..-2] + str = chunked[0..-2] str.each_byte { |byte| assert_nil @parser.filter_body(tmp, buf << byte.chr) body << tmp } assert_equal 'abcdefghijklmnop0123456789abcdefg', body - rv = @parser.filter_body(tmp, buf << "\n") + rv = @parser.filter_body(tmp, buf<< "\n") assert_equal rv.object_id, buf.object_id assert ! @parser.keepalive? end def test_trailers - str = "PUT / HTTP/1.1\r\n" \ - "Trailer: Content-MD5\r\n" \ - "transfer-Encoding: chunked\r\n\r\n" \ - "1\r\na\r\n2\r\n..\r\n0\r\n" - req = {} - assert_equal req, @parser.headers(req, str) + req = @parser.env + str = @parser.buf + str << "PUT / HTTP/1.1\r\n" \ + "Trailer: Content-MD5\r\n" \ + "transfer-Encoding: chunked\r\n\r\n" \ + "1\r\na\r\n2\r\n..\r\n0\r\n" + assert_equal req, @parser.parse assert_equal 'Content-MD5', req['HTTP_TRAILER'] assert_nil req['HTTP_CONTENT_MD5'] tmp = '' @@ -234,19 +353,22 @@ class HttpParserNgTest < Test::Unit::TestCase assert_nil @parser.trailers(req, str) assert_equal md5_b64, req['HTTP_CONTENT_MD5'] assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str - assert_nil @parser.trailers(req, str << "\r") - assert_equal req, @parser.trailers(req, str << "\nGET / ") + str << "\r" + assert_nil @parser.parse + str << "\nGET / " + assert_equal req, @parser.parse assert_equal "GET / ", str - assert ! @parser.keepalive? + assert @parser.keepalive? end def test_trailers_slowly - str = "PUT / HTTP/1.1\r\n" \ - "Trailer: Content-MD5\r\n" \ - "transfer-Encoding: chunked\r\n\r\n" \ - "1\r\na\r\n2\r\n..\r\n0\r\n" - req = {} - assert_equal req, @parser.headers(req, str) + str = @parser.buf + str << "PUT / HTTP/1.1\r\n" \ + "Trailer: Content-MD5\r\n" \ + "transfer-Encoding: chunked\r\n\r\n" \ + "1\r\na\r\n2\r\n..\r\n0\r\n" + req = @parser.env + assert_equal req, @parser.parse assert_equal 'Content-MD5', req['HTTP_TRAILER'] assert_nil req['HTTP_CONTENT_MD5'] tmp = '' @@ -264,16 +386,19 @@ class HttpParserNgTest < Test::Unit::TestCase } assert_equal md5_b64, req['HTTP_CONTENT_MD5'] assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str - assert_nil @parser.trailers(req, str << "\r") - assert_equal req, @parser.trailers(req, str << "\n") + str << "\r" + assert_nil @parser.parse + str << "\n" + assert_equal req, @parser.parse end def test_max_chunk - str = "PUT / HTTP/1.1\r\n" \ - "transfer-Encoding: chunked\r\n\r\n" \ - "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n" - req = {} - assert_equal req, @parser.headers(req, str) + str = @parser.buf + str << "PUT / HTTP/1.1\r\n" \ + "transfer-Encoding: chunked\r\n\r\n" \ + "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n" + req = @parser.env + assert_equal req, @parser.parse assert_nil @parser.content_length assert_nothing_raised { @parser.filter_body('', str) } assert ! @parser.keepalive? @@ -281,64 +406,61 @@ class HttpParserNgTest < Test::Unit::TestCase def test_max_body n = HttpParser::LENGTH_MAX - str = "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n" - req = {} - assert_nothing_raised { @parser.headers(req, str) } + @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n" + req = @parser.env + assert_nothing_raised { @parser.headers(req, @parser.buf) } assert_equal n, req['CONTENT_LENGTH'].to_i assert ! @parser.keepalive? end def test_overflow_chunk n = HttpParser::CHUNK_MAX + 1 - str = "PUT / HTTP/1.1\r\n" \ - "transfer-Encoding: chunked\r\n\r\n" \ - "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n" - req = {} - assert_equal req, @parser.headers(req, str) + str = @parser.buf + req = @parser.env + str << "PUT / HTTP/1.1\r\n" \ + "transfer-Encoding: chunked\r\n\r\n" \ + "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n" + assert_equal req, @parser.parse assert_nil @parser.content_length assert_raise(HttpParserError) { @parser.filter_body('', str) } - assert ! @parser.keepalive? end def test_overflow_content_length n = HttpParser::LENGTH_MAX + 1 - str = "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n" - assert_raise(HttpParserError) { @parser.headers({}, str) } - assert ! @parser.keepalive? + @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n" + assert_raise(HttpParserError) { @parser.parse } end def test_bad_chunk - str = "PUT / HTTP/1.1\r\n" \ - "transfer-Encoding: chunked\r\n\r\n" \ - "#zzz\r\na\r\n2\r\n..\r\n0\r\n" - req = {} - assert_equal req, @parser.headers(req, str) + @parser.buf << "PUT / HTTP/1.1\r\n" \ + "transfer-Encoding: chunked\r\n\r\n" \ + "#zzz\r\na\r\n2\r\n..\r\n0\r\n" + req = @parser.env + assert_equal req, @parser.parse assert_nil @parser.content_length - assert_raise(HttpParserError) { @parser.filter_body('', str) } - assert ! @parser.keepalive? + assert_raise(HttpParserError) { @parser.filter_body("", @parser.buf) } end def test_bad_content_length - str = "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n" - assert_raise(HttpParserError) { @parser.headers({}, str) } - assert ! @parser.keepalive? + @parser.buf << "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n" + assert_raise(HttpParserError) { @parser.parse } end def test_bad_trailers - str = "PUT / HTTP/1.1\r\n" \ - "Trailer: Transfer-Encoding\r\n" \ - "transfer-Encoding: chunked\r\n\r\n" \ - "1\r\na\r\n2\r\n..\r\n0\r\n" - req = {} - assert_equal req, @parser.headers(req, str) + str = @parser.buf + req = @parser.env + str << "PUT / HTTP/1.1\r\n" \ + "Trailer: Transfer-Encoding\r\n" \ + "transfer-Encoding: chunked\r\n\r\n" \ + "1\r\na\r\n2\r\n..\r\n0\r\n" + assert_equal req, @parser.parse assert_equal 'Transfer-Encoding', req['HTTP_TRAILER'] tmp = '' assert_nil @parser.filter_body(tmp, str) assert_equal 'a..', tmp assert_equal '', str str << "Transfer-Encoding: identity\r\n\r\n" - assert_raise(HttpParserError) { @parser.trailers(req, str) } - assert ! @parser.keepalive? + assert_raise(HttpParserError) { @parser.parse } end def test_repeat_headers @@ -347,18 +469,19 @@ class HttpParserNgTest < Test::Unit::TestCase "Trailer: Content-SHA1\r\n" \ "transfer-Encoding: chunked\r\n\r\n" \ "1\r\na\r\n2\r\n..\r\n0\r\n" - req = {} - assert_equal req, @parser.headers(req, str) + req = @parser.env + @parser.buf << str + assert_equal req, @parser.parse assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER'] assert ! @parser.keepalive? end def test_parse_simple_request parser = HttpParser.new - req = {} - http = "GET /read-rfc1945-if-you-dont-believe-me\r\n" - assert_equal req, parser.headers(req, http) - assert_equal '', http + req = parser.env + parser.buf << "GET /read-rfc1945-if-you-dont-believe-me\r\n" + assert_equal req, parser.parse + assert_equal '', parser.buf expect = { "SERVER_NAME"=>"localhost", "rack.url_scheme"=>"http", @@ -388,7 +511,8 @@ class HttpParserNgTest < Test::Unit::TestCase "*" => { qs => "", pi => "" }, }.each do |uri,expect| assert_equal req, @parser.headers(req.clear, str % [ uri ]) - @parser.reset + req = req.dup + @parser.clear assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch" assert_equal expect[qs], req[qs], "#{qs} mismatch" assert_equal expect[pi], req[pi], "#{pi} mismatch" @@ -412,7 +536,8 @@ class HttpParserNgTest < Test::Unit::TestCase "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" }, }.each do |uri,expect| assert_equal req, @parser.headers(req.clear, str % [ uri ]) - @parser.reset + req = req.dup + @parser.clear assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch" assert_equal "example.com", req["HTTP_HOST"], "Host: mismatch" assert_equal expect[qs], req[qs], "#{qs} mismatch" @@ -440,11 +565,22 @@ class HttpParserNgTest < Test::Unit::TestCase end end + def test_backtrace_is_empty + begin + @parser.headers({}, "AAADFSFDSFD\r\n\r\n") + assert false, "should never get here line:#{__LINE__}" + rescue HttpParserError => e + assert_equal [], e.backtrace + return + end + assert false, "should never get here line:#{__LINE__}" + end + def test_ignore_version_header - http = "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n" - req = {} - assert_equal req, @parser.headers(req, http) - assert_equal '', http + @parser.buf << "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n" + req = @parser.env + assert_equal req, @parser.parse + assert_equal '', @parser.buf expect = { "SERVER_NAME" => "localhost", "rack.url_scheme" => "http", @@ -460,4 +596,59 @@ class HttpParserNgTest < Test::Unit::TestCase assert_equal expect, req end + def test_pipelined_requests + host = "example.com" + expect = { + "HTTP_HOST" => host, + "SERVER_NAME" => host, + "REQUEST_PATH" => "/", + "rack.url_scheme" => "http", + "SERVER_PROTOCOL" => "HTTP/1.1", + "PATH_INFO" => "/", + "HTTP_VERSION" => "HTTP/1.1", + "REQUEST_URI" => "/", + "SERVER_PORT" => "80", + "REQUEST_METHOD" => "GET", + "QUERY_STRING" => "" + } + req1 = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" + req2 = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n" + @parser.buf << (req1 + req2) + env1 = @parser.parse.dup + assert_equal expect, env1 + assert_equal req2, @parser.buf + assert ! @parser.env.empty? + assert @parser.next? + assert @parser.keepalive? + assert @parser.headers? + assert_equal expect, @parser.env + env2 = @parser.parse.dup + host.replace "www.example.com" + assert_equal "www.example.com", expect["HTTP_HOST"] + assert_equal "www.example.com", expect["SERVER_NAME"] + assert_equal expect, env2 + assert_equal "", @parser.buf + end + + def test_keepalive_requests_disabled + req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze + expect = { + "SERVER_NAME" => "example.com", + "HTTP_HOST" => "example.com", + "rack.url_scheme" => "http", + "REQUEST_PATH" => "/", + "SERVER_PROTOCOL" => "HTTP/1.1", + "PATH_INFO" => "/", + "HTTP_VERSION" => "HTTP/1.1", + "REQUEST_URI" => "/", + "SERVER_PORT" => "80", + "REQUEST_METHOD" => "GET", + "QUERY_STRING" => "" + }.freeze + HttpParser.keepalive_requests = 0 + @parser = HttpParser.new + @parser.buf << req + assert_equal expect, @parser.parse + assert ! @parser.next? + end end diff --git a/test/unit/test_http_parser_xftrust.rb b/test/unit/test_http_parser_xftrust.rb new file mode 100644 index 0000000..db8cfa9 --- /dev/null +++ b/test/unit/test_http_parser_xftrust.rb @@ -0,0 +1,38 @@ +# -*- encoding: binary -*- +require 'test/test_helper' + +include Unicorn + +class HttpParserXFTrustTest < Test::Unit::TestCase + def setup + assert HttpParser.trust_x_forwarded? + end + + def test_xf_trust_false_xfp + HttpParser.trust_x_forwarded = false + parser = HttpParser.new + parser.buf << "GET / HTTP/1.1\r\nHost: foo:\r\n" \ + "X-Forwarded-Proto: https\r\n\r\n" + env = parser.parse + assert_kind_of Hash, env + assert_equal 'foo', env['SERVER_NAME'] + assert_equal '80', env['SERVER_PORT'] + assert_equal 'http', env['rack.url_scheme'] + end + + def test_xf_trust_false_xfs + HttpParser.trust_x_forwarded = false + parser = HttpParser.new + parser.buf << "GET / HTTP/1.1\r\nHost: foo:\r\n" \ + "X-Forwarded-SSL: on\r\n\r\n" + env = parser.parse + assert_kind_of Hash, env + assert_equal 'foo', env['SERVER_NAME'] + assert_equal '80', env['SERVER_PORT'] + assert_equal 'http', env['rack.url_scheme'] + end + + def teardown + HttpParser.trust_x_forwarded = true + end +end diff --git a/test/unit/test_request.rb b/test/unit/test_request.rb index 1896300..bd452a5 100644 --- a/test/unit/test_request.rb +++ b/test/unit/test_request.rb @@ -11,7 +11,11 @@ class RequestTest < Test::Unit::TestCase class MockRequest < StringIO alias_method :readpartial, :sysread + alias_method :kgio_read!, :sysread alias_method :read_nonblock, :sysread + def kgio_addr + '127.0.0.1' + end end def setup @@ -117,7 +121,7 @@ class RequestTest < Test::Unit::TestCase def test_no_content_stringio client = MockRequest.new("GET / HTTP/1.1\r\nHost: foo\r\n\r\n") - res = env = nil + env = nil assert_nothing_raised { env = @request.read(client) } assert_equal StringIO, env['rack.input'].class end @@ -126,7 +130,7 @@ class RequestTest < Test::Unit::TestCase client = MockRequest.new("PUT / HTTP/1.1\r\n" \ "Content-Length: 0\r\n" \ "Host: foo\r\n\r\n") - res = env = nil + env = nil assert_nothing_raised { env = @request.read(client) } assert_equal StringIO, env['rack.input'].class end @@ -135,7 +139,7 @@ class RequestTest < Test::Unit::TestCase client = MockRequest.new("PUT / HTTP/1.1\r\n" \ "Content-Length: 1\r\n" \ "Host: foo\r\n\r\n") - res = env = nil + env = nil assert_nothing_raised { env = @request.read(client) } assert_equal Unicorn::TeeInput, env['rack.input'].class end @@ -159,6 +163,14 @@ class RequestTest < Test::Unit::TestCase buf = (' ' * bs).freeze length = bs * count client = Tempfile.new('big_put') + def client.kgio_addr; '127.0.0.1'; end + def client.kgio_read(*args) + readpartial(*args) + rescue EOFError + end + def client.kgio_read!(*args) + readpartial(*args) + end client.syswrite( "PUT / HTTP/1.1\r\n" \ "Host: foo\r\n" \ diff --git a/test/unit/test_response.rb b/test/unit/test_response.rb index b3bc3a2..fb6edef 100644 --- a/test/unit/test_response.rb +++ b/test/unit/test_response.rb @@ -7,41 +7,42 @@ # for more information. require 'test/test_helper' +require 'time' include Unicorn class ResponseTest < Test::Unit::TestCase - + include Unicorn::HttpResponse + + def test_httpdate + before = Time.now.to_i + str = httpdate + assert_kind_of(String, str) + middle = Time.parse(str).to_i + after = Time.now.to_i + assert before <= middle + assert middle <= after + end + def test_response_headers out = StringIO.new - HttpResponse.write(out,[200, {"X-Whatever" => "stuff"}, ["cool"]]) + http_response_write(out, 200, {"X-Whatever" => "stuff"}, ["cool"]) assert ! out.closed? + assert out.length > 0, "output didn't have data" end def test_response_string_status out = StringIO.new - HttpResponse.write(out,['200', {}, []]) + http_response_write(out,'200', {}, []) assert ! out.closed? assert out.length > 0, "output didn't have data" assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/).size end - def test_response_OFS_set - old_ofs = $, - $, = "\f\v" - out = StringIO.new - HttpResponse.write(out,[200, {"X-k" => "cd","X-y" => "z"}, ["cool"]]) - assert ! out.closed? - resp = out.string - assert ! resp.include?("\f\v"), "output didn't use $, ($OFS)" - ensure - $, = old_ofs - end - def test_response_200 io = StringIO.new - HttpResponse.write(io, [200, {}, []]) + http_response_write(io, 200, {}, []) assert ! io.closed? assert io.length > 0, "output didn't have data" end @@ -49,7 +50,7 @@ class ResponseTest < Test::Unit::TestCase def test_response_with_default_reason code = 400 io = StringIO.new - HttpResponse.write(io, [code, {}, []]) + http_response_write(io, code, {}, []) assert ! io.closed? lines = io.string.split(/\r\n/) assert_match(/.* Bad Request$/, lines.first, @@ -58,7 +59,7 @@ class ResponseTest < Test::Unit::TestCase def test_rack_multivalue_headers out = StringIO.new - HttpResponse.write(out,[200, {"X-Whatever" => "stuff\nbleh"}, []]) + http_response_write(out,200, {"X-Whatever" => "stuff\nbleh"}, []) assert ! out.closed? assert_match(/^X-Whatever: stuff\r\nX-Whatever: bleh\r\n/, out.string) end @@ -67,21 +68,9 @@ class ResponseTest < Test::Unit::TestCase # some broken clients still rely on it def test_status_header_added out = StringIO.new - HttpResponse.write(out,[200, {"X-Whatever" => "stuff"}, []]) - assert ! out.closed? - assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/i).size - end - - # we always favor the code returned by the application, since "Status" - # in the header hash is not allowed by Rack (but not every app is - # fully Rack-compliant). - def test_status_header_ignores_app_hash - out = StringIO.new - header_hash = {"X-Whatever" => "stuff", 'StaTus' => "666" } - HttpResponse.write(out,[200, header_hash, []]) + http_response_write(out,200, {"X-Whatever" => "stuff"}, []) assert ! out.closed? assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/i).size - assert_equal 1, out.string.split(/\r\n/).grep(/^Status:/i).size end def test_body_closed @@ -89,7 +78,7 @@ class ResponseTest < Test::Unit::TestCase body = StringIO.new(expect_body) body.rewind out = StringIO.new - HttpResponse.write(out,[200, {}, body]) + http_response_write(out,200, {}, body) assert ! out.closed? assert body.closed? assert_match(expect_body, out.string.split(/\r\n/).last) @@ -97,7 +86,7 @@ class ResponseTest < Test::Unit::TestCase def test_unknown_status_pass_through out = StringIO.new - HttpResponse.write(out,["666 I AM THE BEAST", {}, [] ]) + http_response_write(out,"666 I AM THE BEAST", {}, [] ) assert ! out.closed? headers = out.string.split(/\r\n\r\n/).first.split(/\r\n/) assert %r{\AHTTP/\d\.\d 666 I AM THE BEAST\z}.match(headers[0]) diff --git a/test/unit/test_server.rb b/test/unit/test_server.rb index 41d3e02..88d7aba 100644 --- a/test/unit/test_server.rb +++ b/test/unit/test_server.rb @@ -10,7 +10,7 @@ require 'test/test_helper' include Unicorn -class TestHandler +class TestHandler def call(env) while env['rack.input'].read(4096) @@ -19,7 +19,7 @@ class TestHandler rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e $stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n") raise e - end + end end @@ -169,7 +169,6 @@ class WebServerTest < Test::Unit::TestCase def test_client_malformed_body sock = nil - buf = nil bs = 15653984 assert_nothing_raised do sock = TCPSocket.new('127.0.0.1', @port) @@ -271,7 +270,7 @@ class WebServerTest < Test::Unit::TestCase def test_file_streamed_request body = "a" * (Unicorn::Const::MAX_BODY * 2) long = "PUT /test HTTP/1.1\r\nContent-length: #{body.length}\r\n\r\n" + body - do_test(long, Unicorn::Const::CHUNK_SIZE * 2 -400) + do_test(long, Unicorn::Const::CHUNK_SIZE * 2 - 400) end def test_file_streamed_request_bad_body @@ -279,13 +278,11 @@ class WebServerTest < Test::Unit::TestCase long = "GET /test HTTP/1.1\r\nContent-ength: #{body.length}\r\n\r\n" + body assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL, Errno::EBADF) { - do_test(long, Unicorn::Const::CHUNK_SIZE * 2 -400) + do_test(long, Unicorn::Const::CHUNK_SIZE * 2 - 400) } end def test_listener_names assert_equal [ "127.0.0.1:#@port" ], Unicorn.listener_names end - end - diff --git a/test/unit/test_signals.rb b/test/unit/test_signals.rb index 7c78b44..71cf8f4 100644 --- a/test/unit/test_signals.rb +++ b/test/unit/test_signals.rb @@ -166,7 +166,7 @@ class SignalsTest < Test::Unit::TestCase expect = @bs * @count assert_equal(expect, got, "expect=#{expect} got=#{got}") assert_nothing_raised { sock.close } - end unless ENV['RBX_SKIP'] + end def test_request_read app = lambda { |env| diff --git a/test/unit/test_stream_input.rb b/test/unit/test_stream_input.rb new file mode 100644 index 0000000..f59157a --- /dev/null +++ b/test/unit/test_stream_input.rb @@ -0,0 +1,204 @@ +# -*- encoding: binary -*- + +require 'test/unit' +require 'digest/sha1' +require 'unicorn' + +class TestStreamInput < Test::Unit::TestCase + def setup + @rs = $/ + @env = {} + @rd, @wr = Kgio::UNIXSocket.pair + @rd.sync = @wr.sync = true + @start_pid = $$ + end + + def teardown + return if $$ != @start_pid + $/ = @rs + @rd.close rescue nil + @wr.close rescue nil + Process.waitall + end + + def test_read_negative + r = init_request('hello') + si = Unicorn::StreamInput.new(@rd, r) + assert_raises(ArgumentError) { si.read(-1) } + assert_equal 'hello', si.read + end + + def test_read_small + r = init_request('hello') + si = Unicorn::StreamInput.new(@rd, r) + assert_equal 'hello', si.read + assert_equal '', si.read + assert_nil si.read(5) + assert_nil si.gets + end + + def test_gets_oneliner + r = init_request('hello') + si = Unicorn::StreamInput.new(@rd, r) + assert_equal 'hello', si.gets + assert_nil si.gets + end + + def test_gets_multiline + r = init_request("a\nb\n\n") + si = Unicorn::StreamInput.new(@rd, r) + assert_equal "a\n", si.gets + assert_equal "b\n", si.gets + assert_equal "\n", si.gets + assert_nil si.gets + end + + def test_gets_empty_rs + $/ = nil + r = init_request("a\nb\n\n") + si = Unicorn::StreamInput.new(@rd, r) + assert_equal "a\nb\n\n", si.gets + assert_nil si.gets + end + + def test_read_with_equal_len + r = init_request("abcde") + si = Unicorn::StreamInput.new(@rd, r) + assert_equal "abcde", si.read(5) + assert_nil si.read(5) + end + + def test_big_body_multi + r = init_request('.', Unicorn::Const::MAX_BODY + 1) + si = Unicorn::StreamInput.new(@rd, r) + assert_equal Unicorn::Const::MAX_BODY, @parser.content_length + assert ! @parser.body_eof? + nr = Unicorn::Const::MAX_BODY / 4 + pid = fork { + @rd.close + nr.times { @wr.write('....') } + @wr.close + } + @wr.close + assert_equal '.', si.read(1) + nr.times { |x| + assert_equal '....', si.read(4), "nr=#{x}" + } + assert_nil si.read(1) + status = nil + assert_nothing_raised { pid, status = Process.waitpid2(pid) } + assert status.success? + end + + def test_gets_long + r = init_request("hello", 5 + (4096 * 4 * 3) + "#$/foo#$/".size) + si = Unicorn::StreamInput.new(@rd, r) + status = line = nil + pid = fork { + @rd.close + 3.times { @wr.write("ffff" * 4096) } + @wr.write "#$/foo#$/" + @wr.close + } + @wr.close + assert_nothing_raised { line = si.gets } + assert_equal(4096 * 4 * 3 + 5 + $/.size, line.size) + assert_equal("hello" << ("ffff" * 4096 * 3) << "#$/", line) + assert_nothing_raised { line = si.gets } + assert_equal "foo#$/", line + assert_nil si.gets + assert_nothing_raised { pid, status = Process.waitpid2(pid) } + assert status.success? + end + + def test_read_with_buffer + r = init_request('hello') + si = Unicorn::StreamInput.new(@rd, r) + buf = '' + rv = si.read(4, buf) + assert_equal 'hell', rv + assert_equal 'hell', buf + assert_equal rv.object_id, buf.object_id + assert_equal 'o', si.read + assert_equal nil, si.read(5, buf) + end + + def test_read_with_buffer_clobbers + r = init_request('hello') + si = Unicorn::StreamInput.new(@rd, r) + buf = 'foo' + assert_equal 'hello', si.read(nil, buf) + assert_equal 'hello', buf + assert_equal '', si.read(nil, buf) + assert_equal '', buf + buf = 'asdf' + assert_nil si.read(5, buf) + assert_equal '', buf + end + + def test_read_zero + r = init_request('hello') + si = Unicorn::StreamInput.new(@rd, r) + assert_equal '', si.read(0) + buf = 'asdf' + rv = si.read(0, buf) + assert_equal rv.object_id, buf.object_id + assert_equal '', buf + assert_equal 'hello', si.read + assert_nil si.read(5) + assert_equal '', si.read(0) + buf = 'hello' + rv = si.read(0, buf) + assert_equal rv.object_id, buf.object_id + assert_equal '', rv + end + + def test_gets_read_mix + r = init_request("hello\nasdfasdf") + si = Unicorn::StreamInput.new(@rd, r) + assert_equal "hello\n", si.gets + assert_equal "asdfasdf", si.read(9) + assert_nil si.read(9) + end + + def test_gets_read_mix_chunked + r = @parser = Unicorn::HttpParser.new + body = "6\r\nhello" + @buf = "POST / HTTP/1.1\r\n" \ + "Host: localhost\r\n" \ + "Transfer-Encoding: chunked\r\n" \ + "\r\n#{body}" + assert_equal @env, @parser.headers(@env, @buf) + assert_equal body, @buf + si = Unicorn::StreamInput.new(@rd, r) + @wr.syswrite "\n\r\n" + assert_equal "hello\n", si.gets + @wr.syswrite "8\r\nasdfasdf\r\n" + assert_equal"asdfasdf", si.read(9) + si.read(9) + @wr.syswrite "0\r\n\r\n" + assert_nil si.read(9) + end + + def test_gets_read_mix_big + r = init_request("hello\n#{'.' * 65536}") + si = Unicorn::StreamInput.new(@rd, r) + assert_equal "hello\n", si.gets + assert_equal '.' * 16384, si.read(16384) + assert_equal '.' * 16383, si.read(16383) + assert_equal '.' * 16384, si.read(16384) + assert_equal '.' * 16385, si.read(16385) + assert_nil si.gets + end + + def init_request(body, size = nil) + @parser = Unicorn::HttpParser.new + body = body.to_s.freeze + @buf = "POST / HTTP/1.1\r\n" \ + "Host: localhost\r\n" \ + "Content-Length: #{size || body.size}\r\n" \ + "\r\n#{body}" + assert_equal @env, @parser.headers(@env, @buf) + assert_equal body, @buf + @parser + end +end diff --git a/test/unit/test_tee_input.rb b/test/unit/test_tee_input.rb index a127882..96eb268 100644 --- a/test/unit/test_tee_input.rb +++ b/test/unit/test_tee_input.rb @@ -4,12 +4,15 @@ require 'test/unit' require 'digest/sha1' require 'unicorn' +class TeeInput < Unicorn::TeeInput + attr_accessor :tmp, :len +end + class TestTeeInput < Test::Unit::TestCase def setup @rs = $/ - @env = {} - @rd, @wr = IO.pipe + @rd, @wr = Kgio::UNIXSocket.pair @rd.sync = @wr.sync = true @start_pid = $$ end @@ -27,8 +30,8 @@ class TestTeeInput < Test::Unit::TestCase end def test_gets_long - init_parser("hello", 5 + (4096 * 4 * 3) + "#$/foo#$/".size) - ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf) + r = init_request("hello", 5 + (4096 * 4 * 3) + "#$/foo#$/".size) + ti = TeeInput.new(@rd, r) status = line = nil pid = fork { @rd.close @@ -48,8 +51,8 @@ class TestTeeInput < Test::Unit::TestCase end def test_gets_short - init_parser("hello", 5 + "#$/foo".size) - ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf) + r = init_request("hello", 5 + "#$/foo".size) + ti = TeeInput.new(@rd, r) status = line = nil pid = fork { @rd.close @@ -67,8 +70,8 @@ class TestTeeInput < Test::Unit::TestCase end def test_small_body - init_parser('hello') - ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf) + r = init_request('hello') + ti = TeeInput.new(@rd, r) assert_equal 0, @parser.content_length assert @parser.body_eof? assert_equal StringIO, ti.tmp.class @@ -77,11 +80,12 @@ class TestTeeInput < Test::Unit::TestCase assert_equal 'hello', ti.read assert_equal '', ti.read assert_nil ti.read(4096) + assert_equal 5, ti.size end def test_read_with_buffer - init_parser('hello') - ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf) + r = init_request('hello') + ti = TeeInput.new(@rd, r) buf = '' rv = ti.read(4, buf) assert_equal 'hell', rv @@ -95,8 +99,8 @@ class TestTeeInput < Test::Unit::TestCase end def test_big_body - init_parser('.' * Unicorn::Const::MAX_BODY << 'a') - ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf) + r = init_request('.' * Unicorn::Const::MAX_BODY << 'a') + ti = TeeInput.new(@rd, r) assert_equal 0, @parser.content_length assert @parser.body_eof? assert_kind_of File, ti.tmp @@ -106,9 +110,9 @@ class TestTeeInput < Test::Unit::TestCase def test_read_in_full_if_content_length a, b = 300, 3 - init_parser('.' * b, 300) + r = init_request('.' * b, 300) assert_equal 300, @parser.content_length - ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf) + ti = TeeInput.new(@rd, r) pid = fork { @wr.write('.' * 197) sleep 1 # still a *potential* race here that would make the test moot... @@ -121,13 +125,12 @@ class TestTeeInput < Test::Unit::TestCase end def test_big_body_multi - init_parser('.', Unicorn::Const::MAX_BODY + 1) - ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf) + r = init_request('.', Unicorn::Const::MAX_BODY + 1) + ti = TeeInput.new(@rd, r) assert_equal Unicorn::Const::MAX_BODY, @parser.content_length assert ! @parser.body_eof? assert_kind_of File, ti.tmp assert_equal 0, ti.tmp.pos - assert_equal 1, ti.tmp.size assert_equal Unicorn::Const::MAX_BODY + 1, ti.size nr = Unicorn::Const::MAX_BODY / 4 pid = fork { @@ -138,8 +141,8 @@ class TestTeeInput < Test::Unit::TestCase @wr.close assert_equal '.', ti.read(1) assert_equal Unicorn::Const::MAX_BODY + 1, ti.size - nr.times { - assert_equal '....', ti.read(4) + nr.times { |x| + assert_equal '....', ti.read(4), "nr=#{x}" assert_equal Unicorn::Const::MAX_BODY + 1, ti.size } assert_nil ti.read(1) @@ -150,12 +153,12 @@ class TestTeeInput < Test::Unit::TestCase def test_chunked @parser = Unicorn::HttpParser.new - @buf = "POST / HTTP/1.1\r\n" \ - "Host: localhost\r\n" \ - "Transfer-Encoding: chunked\r\n" \ - "\r\n" - assert_equal @env, @parser.headers(@env, @buf) - assert_equal "", @buf + @parser.buf << "POST / HTTP/1.1\r\n" \ + "Host: localhost\r\n" \ + "Transfer-Encoding: chunked\r\n" \ + "\r\n" + assert @parser.parse + assert_equal "", @parser.buf pid = fork { @rd.close @@ -163,7 +166,7 @@ class TestTeeInput < Test::Unit::TestCase @wr.write("0\r\n\r\n") } @wr.close - ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf) + ti = TeeInput.new(@rd, @parser) assert_nil @parser.content_length assert_nil ti.len assert ! @parser.body_eof? @@ -185,12 +188,13 @@ class TestTeeInput < Test::Unit::TestCase def test_chunked_ping_pong @parser = Unicorn::HttpParser.new - @buf = "POST / HTTP/1.1\r\n" \ + buf = @parser.buf + buf << "POST / HTTP/1.1\r\n" \ "Host: localhost\r\n" \ "Transfer-Encoding: chunked\r\n" \ "\r\n" - assert_equal @env, @parser.headers(@env, @buf) - assert_equal "", @buf + assert @parser.parse + assert_equal "", buf chunks = %w(aa bbb cccc dddd eeee) rd, wr = IO.pipe @@ -201,7 +205,7 @@ class TestTeeInput < Test::Unit::TestCase end @wr.write("0\r\n\r\n") } - ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf) + ti = TeeInput.new(@rd, @parser) assert_nil @parser.content_length assert_nil ti.len assert ! @parser.body_eof? @@ -215,13 +219,14 @@ class TestTeeInput < Test::Unit::TestCase def test_chunked_with_trailer @parser = Unicorn::HttpParser.new - @buf = "POST / HTTP/1.1\r\n" \ + buf = @parser.buf + buf << "POST / HTTP/1.1\r\n" \ "Host: localhost\r\n" \ "Trailer: Hello\r\n" \ "Transfer-Encoding: chunked\r\n" \ "\r\n" - assert_equal @env, @parser.headers(@env, @buf) - assert_equal "", @buf + assert @parser.parse + assert_equal "", buf pid = fork { @rd.close @@ -230,28 +235,62 @@ class TestTeeInput < Test::Unit::TestCase @wr.write("Hello: World\r\n\r\n") } @wr.close - ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf) + ti = TeeInput.new(@rd, @parser) assert_nil @parser.content_length assert_nil ti.len assert ! @parser.body_eof? assert_equal 25, ti.size - assert_equal "World", @env['HTTP_HELLO'] + assert_equal "World", @parser.env['HTTP_HELLO'] status = nil assert_nothing_raised { pid, status = Process.waitpid2(pid) } assert status.success? end + def test_chunked_and_size_slow + @parser = Unicorn::HttpParser.new + buf = @parser.buf + buf << "POST / HTTP/1.1\r\n" \ + "Host: localhost\r\n" \ + "Trailer: Hello\r\n" \ + "Transfer-Encoding: chunked\r\n" \ + "\r\n" + assert @parser.parse + assert_equal "", buf + + @wr.write("9\r\nabcde") + ti = TeeInput.new(@rd, @parser) + assert_nil @parser.content_length + assert_equal "abcde", ti.read(9) + assert ! @parser.body_eof? + @wr.write("fghi\r\n0\r\nHello: World\r\n\r\n") + assert_equal 9, ti.size + assert_equal "fghi", ti.read(9) + assert_equal nil, ti.read(9) + assert_equal "World", @parser.env['HTTP_HELLO'] + end + + def test_gets_read_mix + r = init_request("hello\nasdfasdf") + ti = Unicorn::TeeInput.new(@rd, r) + assert_equal "hello\n", ti.gets + assert_equal "asdfasdf", ti.read(9) + assert_nil ti.read(9) + end + private - def init_parser(body, size = nil) + def init_request(body, size = nil) @parser = Unicorn::HttpParser.new body = body.to_s.freeze - @buf = "POST / HTTP/1.1\r\n" \ + buf = @parser.buf + buf << "POST / HTTP/1.1\r\n" \ "Host: localhost\r\n" \ "Content-Length: #{size || body.size}\r\n" \ "\r\n#{body}" - assert_equal @env, @parser.headers(@env, @buf) - assert_equal body, @buf + assert @parser.parse + assert_equal body, buf + @buf = buf + @parser end end diff --git a/test/unit/test_upload.rb b/test/unit/test_upload.rb index dc0eb40..e2c103a 100644 --- a/test/unit/test_upload.rb +++ b/test/unit/test_upload.rb @@ -145,8 +145,14 @@ class UploadTest < Test::Unit::TestCase end def test_put_excessive_overwrite_closed + tmp = Tempfile.new('overwrite_check') + tmp.sync = true start_server(lambda { |env| - while env['rack.input'].read(65536); end + nr = 0 + while buf = env['rack.input'].read(65536) + nr += buf.size + end + tmp.write(nr.to_s) [ 200, @hdr, [] ] }) sock = TCPSocket.new(@addr, @port) @@ -157,7 +163,9 @@ class UploadTest < Test::Unit::TestCase assert_raise(Errno::ECONNRESET, Errno::EPIPE) do ::Unicorn::Const::CHUNK_SIZE.times { sock.syswrite(buf) } end - assert_equal "HTTP/1.1 200 OK\r\n", sock.gets + assert_nothing_raised { sock.gets } + tmp.rewind + assert_equal length, tmp.read.to_i end # Despite reading numerous articles and inspecting the 1.9.1-p0 C |