about summary refs log tree commit homepage
path: root/test/test_request_parser.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/test_request_parser.rb')
-rw-r--r--test/test_request_parser.rb862
1 files changed, 862 insertions, 0 deletions
diff --git a/test/test_request_parser.rb b/test/test_request_parser.rb
new file mode 100644
index 0000000..5e97a0e
--- /dev/null
+++ b/test/test_request_parser.rb
@@ -0,0 +1,862 @@
+# -*- encoding: binary -*-
+## Copyright (c) 2005 Zed A. Shaw
+# You can redistribute it and/or modify it under the same terms as Ruby 1.8
+# or GPL-2.0+ <https://www.gnu.org/licenses/gpl-2.0.txt>
+#
+# Additional work donated by contributors.  See git history of
+# unicorn for more information: git clone https://bogomips.org/unicorn.git
+
+require 'test/unit'
+require 'digest'
+require 'kcar'
+require 'uri'
+
+class TestRequestParser < Test::Unit::TestCase
+  def setup
+    @hp = Kcar::Parser.new
+    @env = {}
+  end
+
+  def test_parse_oneshot_simple
+    buf = "GET / HTTP/1.1\r\n\r\n"
+    http = 'http'.freeze
+    @env['rack.url_scheme'] = http
+    env = @hp.request(@env, buf.dup)
+    assert_same env, @env
+    exp = {
+      'SERVER_PROTOCOL' => 'HTTP/1.1',
+      'HTTP_VERSION' => 'HTTP/1.1',
+      'REQUEST_PATH' => '/',
+      'PATH_INFO' => '/',
+      'REQUEST_URI' => '/',
+      'REQUEST_METHOD' => 'GET',
+      'QUERY_STRING' => '',
+      'rack.url_scheme' => 'http',
+    }
+    assert_equal exp, env
+    assert_same env['HTTP_VERSION'], env['SERVER_PROTOCOL']
+    assert_predicate env['HTTP_VERSION'], :frozen?
+    assert_predicate env['REQUEST_METHOD'], :frozen?
+    assert_same http, env['rack.url_scheme']
+    assert_predicate @hp, :keepalive?
+
+    @hp.reset
+
+    buf = "G"
+    assert_nil @hp.request(@env, buf)
+    # try parsing again to ensure we were reset correctly
+    buf << "ET /hello-world HTTP/1.1\r\n\r\n"
+    assert_same env, @hp.request(env, buf)
+
+    assert_equal 'HTTP/1.1', env['SERVER_PROTOCOL']
+    assert_equal '/hello-world', env['REQUEST_PATH']
+    assert_equal 'HTTP/1.1', env['HTTP_VERSION']
+    assert_equal '/hello-world', env['REQUEST_URI']
+    assert_equal 'GET', env['REQUEST_METHOD']
+    assert_equal '', env['QUERY_STRING']
+    assert @hp.keepalive?
+  end
+
+  def test_tab_lws
+    @hp.request(@env, "GET / HTTP/1.1\r\nHost:\tfoo.bar\r\n\r\n")
+    assert_equal "foo.bar", @env['HTTP_HOST']
+  end
+
+  def test_connection_close_no_ka
+    @hp.request(@env = {}, "GET / HTTP/1.1\r\nConnection: close\r\n\r\n")
+    assert_equal 'GET', @env['REQUEST_METHOD']
+    assert ! @hp.keepalive?
+  end
+
+  def test_connection_keep_alive_ka
+    @hp.request(@env, "HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n")
+    assert @hp.keepalive?
+  end
+
+  def test_connection_keep_alive_no_body
+    r = @hp.request(@env, "POST / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n")
+    assert_same @env, r
+    assert @hp.keepalive?
+  end
+
+  def test_connection_keep_alive_no_body_empty
+    buf = "POST / HTTP/1.1\r\n" \
+          "Content-Length: 0\r\n" \
+          "Connection: keep-alive\r\n\r\n"
+    assert_same @env, @hp.request(@env, buf)
+    assert @hp.keepalive?
+  end
+
+  def test_connection_keep_alive_ka_bad_version
+    @hp.request(@env, "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n")
+    assert @hp.keepalive?
+  end
+
+  def test_parse_server_host_default_port
+    buf = "GET / HTTP/1.1\r\nHost: foo\r\n\r\n"
+    assert_same @env, @hp.request(@env, buf)
+    assert_equal 'foo', @env['SERVER_NAME']
+    assert_equal '80', @env['SERVER_PORT']
+    assert_equal '', buf
+    assert @hp.keepalive?
+  end
+
+  def test_parse_server_host_alt_port
+    buf = "GET / HTTP/1.1\r\nHost: foo:999\r\n\r\n"
+    @hp.request(@env, buf)
+    assert_equal 'foo', @env['SERVER_NAME']
+    assert_equal '999', @env['SERVER_PORT']
+    assert_equal '', buf
+    assert @hp.keepalive?
+  end
+
+  def test_parse_server_host_empty_port
+    @hp.request(@env, "GET / HTTP/1.1\r\nHost: foo:\r\n\r\n")
+    assert_equal 'foo', @env['SERVER_NAME']
+    assert_equal '80', @env['SERVER_PORT']
+    assert @hp.keepalive?
+  end
+
+  def test_parse_host_cont
+    @hp.request(@env, "GET / HTTP/1.1\r\nHost:\r\n foo\r\n\r\n")
+    assert_equal 'foo', @env['SERVER_NAME']
+    assert_equal '80', @env['SERVER_PORT']
+    assert @hp.keepalive?
+  end
+
+  def test_preserve_existing_server_vars
+    @env = {
+      'SERVER_NAME' => 'example.com',
+      'SERVER_PORT' => '1234',
+      'rack.url_scheme' => 'https'
+    }
+    @hp.request(@env, "GET / HTTP/1.0\r\n\r\n")
+    assert_equal 'example.com', @env['SERVER_NAME']
+    assert_equal '1234', @env['SERVER_PORT']
+    assert_equal 'https', @env['rack.url_scheme']
+  end
+
+  def test_parse_strange_headers
+    should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
+    req = @hp.request(@env, should_be_good)
+    assert_same req, @env
+    assert_equal '', should_be_good
+    assert_predicate @hp, :keepalive?
+    assert_equal '++++++++++', @env['HTTP_AAAAAAAAAAAAA']
+  end
+
+  # legacy test case from Mongrel
+  # I still consider Pound irrelevant, unfortunately stupid clients that
+  # send extremely big headers do exist and they've managed to find us...
+  def test_nasty_pound_header
+    nasty_pound_header = "GET / HTTP/1.1\r\n" \
+"X-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"
+    ok = (/(-----BEGIN .*--END CERTIFICATE-----)/m =~ nasty_pound_header)
+    expect = $1.dup
+    assert ok, 'end certificate matched'
+    expect.gsub!(/\r\n\t/, ' ')
+    req = @hp.request(@env, nasty_pound_header.dup)
+    assert_equal expect, req['HTTP_X_SSL_BULLSHIT']
+  end
+
+  def test_multiline_header_0d0a
+    req = @hp.request(@env, "GET / HTTP/1.0\r\n" \
+      "X-Multiline-Header: foo bar\r\n\tcha cha\r\n\tzha zha\r\n\r\n")
+    assert_same req, @env
+    assert_equal 'foo bar cha cha zha zha', req['HTTP_X_MULTILINE_HEADER']
+  end
+
+  def test_multiline_header_0a
+    req = @hp.request(@env, "GET / HTTP/1.0\n" \
+      "X-Multiline-Header: foo bar\n\tcha cha\n\tzha zha\n\n")
+    assert_same req, @env
+    assert_equal 'foo bar cha cha zha zha', req['HTTP_X_MULTILINE_HEADER']
+  end
+
+  def test_continuation_eats_leading_spaces
+    header = "GET / HTTP/1.1\r\n" \
+             "X-ASDF:      \r\n" \
+             "\t\r\n" \
+             "    \r\n" \
+             "  ASDF\r\n\r\n"
+    req = @hp.request(@env, header)
+    assert_same req, @env
+    assert_equal '', header
+    assert_equal 'ASDF', req['HTTP_X_ASDF']
+  end
+
+  def test_continuation_eats_scattered_leading_spaces
+    header = "GET / HTTP/1.1\r\n" \
+             "X-ASDF:   hi\r\n" \
+             "    y\r\n" \
+             "\t\r\n" \
+             "       x\r\n" \
+             "  ASDF\r\n\r\n"
+    req = @hp.request(@env, header)
+    assert_same req, @env
+    assert_equal '', header
+    assert_equal 'hi y x ASDF', req['HTTP_X_ASDF']
+  end
+
+  def test_continuation_eats_trailing_spaces
+    header = "GET / HTTP/1.1\r\n" \
+             "X-ASDF:      \r\n" \
+             "\t\r\n" \
+             "  b  \r\n" \
+             "  ASDF\r\n\r\nZ"
+    req = @hp.request(@env, header)
+    assert_same req, @env
+    assert_equal 'Z', header
+    assert_equal 'b ASDF', req['HTTP_X_ASDF']
+  end
+
+  def test_continuation_with_absolute_uri_and_ignored_host_header
+    header = "GET http://example.com/ HTTP/1.1\r\n" \
+             "Host: \r\n" \
+             "    example.org\r\n" \
+             "\r\n"
+    req = @hp.request(@env, header)
+    assert_same req, @env
+    assert_equal 'example.com', req['HTTP_HOST']
+    assert_equal 'http', req['rack.url_scheme']
+    assert_equal '/', req['PATH_INFO']
+    assert_equal 'example.com', req['SERVER_NAME']
+    assert_equal '80', req['SERVER_PORT']
+  end
+
+  # this may seem to be testing more of an implementation detail, but
+  # it also helps ensure we're safe in the presence of multiple parsers
+  # in case we ever go multithreaded/evented...
+  def test_resumable_continuations
+    nr = 1000
+    header = "GET / HTTP/1.1\r\n" \
+             "X-ASDF:      \r\n" \
+             "  hello\r\n"
+    tmp = []
+    nr.times { |i|
+      hp = Kcar::Parser.new
+      env = {}
+      assert_nil hp.request(env, buf = "#{header} #{i}\r\n")
+      asdf = env['HTTP_X_ASDF']
+      assert_equal "hello #{i}", asdf
+      tmp << [ hp, asdf, env, buf ]
+    }
+    tmp.each_with_index { |(hp, asdf, env, buf), i|
+      buf << " .\r\n\r\n"
+      assert_same env, hp.request(env, buf)
+      assert_equal "hello #{i} .", asdf
+    }
+  end
+
+  def test_invalid_continuation
+    header = "GET / HTTP/1.1\r\n" \
+             "    y\r\n" \
+             "Host: hello\r\n" \
+             "\r\n"
+    buf = header.dup
+    assert_raises(Kcar::ParserError) do
+      @hp.request(@env, buf)
+    end
+    assert_equal header, buf, 'no modification on invalid'
+  end
+
+  def test_parse_ie6_urls
+    %w(/some/random/path"
+       /some/random/path>
+       /some/random/path<
+       /we/love/you/ie6?q=<"">
+       /url?<="&>="
+       /mal"formed"?
+    ).each do |path|
+      sorta_safe = %(GET #{path} HTTP/1.1\r\n\r\n)
+      assert_same @env, @hp.request(@env, sorta_safe)
+      assert_equal path, @env['REQUEST_URI']
+      assert_equal '', sorta_safe
+      assert @hp.keepalive?
+      @hp.reset
+    end
+  end
+
+  def test_parse_error
+    bad_http = "GET / SsUTF/1.1"
+    assert_raises(Kcar::ParserError) { @hp.request(@env, bad_http) }
+
+    # make sure we can recover
+    @env.clear
+    @hp.reset
+    assert_equal @env, @hp.request(@env, "GET / HTTP/1.0\r\n\r\n")
+    assert ! @hp.keepalive?
+  end
+
+  def test_piecemeal
+    http = "GET"
+    req = @env
+    assert_nil @hp.request(@env, http)
+    assert_nil @hp.request(@env, http)
+    assert_nil @hp.request(@env, http << " / HTTP/1.0")
+    assert_equal '/', req['REQUEST_PATH']
+    assert_equal '/', req['REQUEST_URI']
+    assert_equal 'GET', req['REQUEST_METHOD']
+    assert_nil @hp.request(req, http << "\r\n")
+    assert_equal 'HTTP/1.0', req['HTTP_VERSION']
+    assert_nil @hp.request(req, http << "\r")
+    assert_same req, @hp.request(req, http << "\n")
+    assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
+    assert_nil req['FRAGMENT']
+    assert_equal '', req['QUERY_STRING']
+    assert_equal "", http
+    assert ! @hp.keepalive?
+  end
+
+  # not common, but underscores do appear in practice
+  def test_absolute_uri_underscores
+    http = "GET https://under_score.example.com/foo?q=bar HTTP/1.0\r\n\r\n"
+    req = @hp.request(@env, http)
+    assert_same req, @env
+    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']
+    assert_equal 'under_score.example.com', req['HTTP_HOST']
+    assert_equal 'under_score.example.com', req['SERVER_NAME']
+    assert_equal '443', req['SERVER_PORT']
+    assert_equal "", http
+    assert ! @hp.keepalive?
+  end
+
+  # some dumb clients add users because they're stupid
+  def test_absolute_uri_w_user
+    http = "GET http://user%20space@example.com/foo?q=bar HTTP/1.0\r\n\r\n"
+    req = @hp.request(@env, http)
+    assert_same req, @env
+    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']
+    assert_equal 'example.com', req['HTTP_HOST']
+    assert_equal 'example.com', req['SERVER_NAME']
+    assert_equal '80', req['SERVER_PORT']
+    assert_equal "", http
+    assert ! @hp.keepalive?
+  end
+
+  # since Mongrel supported anything URI.parse supported, we're stuck
+  # supporting everything URI.parse supports
+  def test_absolute_uri_uri_parse
+    require 'uri'
+    "#{URI::REGEXP::PATTERN::UNRESERVED};:&=+$,".split(//).each do |char|
+      http = "GET http://#{char}@example.com/ HTTP/1.0\r\n\r\n"
+      req = @hp.request(@env, http)
+      assert_equal 'http', req['rack.url_scheme']
+      assert_equal '/', req['REQUEST_URI']
+      assert_equal '/', req['REQUEST_PATH']
+      assert_equal '', req['QUERY_STRING']
+
+      assert_equal 'example.com', req['HTTP_HOST']
+      assert_equal 'example.com', req['SERVER_NAME']
+      assert_equal '80', req['SERVER_PORT']
+      assert_equal "", http
+      assert ! @hp.keepalive?
+      @hp.reset
+    end
+  end
+
+  def test_absolute_uri
+    req = @hp.request(@env, "GET http://example.com/foo?q=bar HTTP/1.0\r\n\r\n")
+    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']
+    assert_equal '80', req['SERVER_PORT']
+    assert_equal 'example.com', req['HTTP_HOST']
+    assert_equal 'example.com', req['SERVER_NAME']
+    assert ! @hp.keepalive?
+  end
+
+  def test_absolute_uri_https
+    http = "GET https://example.com/foo?q=bar HTTP/1.1\r\n" \
+           "X-Foo: bar\n\r\n"
+    req = @hp.request(@env, 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']
+    assert_equal 'example.com', req['HTTP_HOST']
+    assert_equal 'example.com', req['SERVER_NAME']
+    assert_equal '443', req['SERVER_PORT']
+    assert_equal "", http
+    assert @hp.keepalive?
+  end
+
+  # Host: header should be ignored for absolute URIs
+  def test_absolute_uri_with_port
+    req = @hp.request(@env,"GET http://example.com:8080/foo?q=bar HTTP/1.1\r\n" \
+           "Host: bad.example.com\r\n\r\n")
+    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']
+    assert_equal 'example.com:8080', req['HTTP_HOST']
+    assert_equal 'example.com', req['SERVER_NAME']
+    assert_equal '8080', req['SERVER_PORT']
+    assert @hp.keepalive?
+  end
+
+  def test_absolute_uri_with_empty_port
+    req = @hp.request(@env, "GET https://example.com:/foo?q=bar HTTP/1.1\r\n" \
+           "Host: bad.example.com\r\n\r\n")
+    assert_same req, @env
+    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']
+    assert_equal 'example.com:', req['HTTP_HOST']
+    assert_equal 'example.com', req['SERVER_NAME']
+    assert_equal '443', req['SERVER_PORT']
+    assert @hp.keepalive?
+  end
+
+  def test_absolute_ipv6_uri
+    url = "http://[::1]/foo?q=bar"
+    http = "GET #{url} HTTP/1.1\r\n" \
+           "Host: bad.example.com\r\n\r\n"
+    req = @hp.request(@env, http)
+    assert_same req, @env
+    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
+  end
+
+  def test_absolute_ipv6_uri_alpha
+    url = "http://[::a]/"
+    http = "GET #{url} HTTP/1.1\r\n" \
+           "Host: bad.example.com\r\n\r\n"
+    req = @hp.request(@env, 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
+    url = "http://[::B]/"
+    http = "GET #{url} HTTP/1.1\r\n" \
+           "Host: bad.example.com\r\n\r\n"
+    req = @hp.request(@env, 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
+    url = "https://[::1]:/foo?q=bar"
+    http = "GET #{url} HTTP/1.1\r\n" \
+           "Host: bad.example.com\r\n\r\n"
+    req = @hp.request(@env, 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
+  end
+
+  def test_absolute_ipv6_uri_with_port
+    url = "https://[::1]:666/foo?q=bar"
+    http = "GET #{url} HTTP/1.1\r\n" \
+           "Host: bad.example.com\r\n\r\n"
+    req = @hp.request(@env, 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
+  end
+
+  def test_ipv6_host_header
+    buf = "GET / HTTP/1.1\r\n" \
+          "Host: [::1]\r\n\r\n"
+    req = @hp.request(@env, buf)
+    assert_equal "[::1]", req['HTTP_HOST']
+    assert_equal "[::1]", req['SERVER_NAME']
+    assert_equal '80', req['SERVER_PORT']
+  end
+
+  def test_ipv6_host_header_with_port
+    req = @hp.request(@env, "GET / HTTP/1.1\r\n" \
+                  "Host: [::1]:666\r\n\r\n")
+    assert_equal "[::1]", req['SERVER_NAME']
+    assert_equal '666', req['SERVER_PORT']
+    assert_equal "[::1]:666", req['HTTP_HOST']
+  end
+
+  def test_ipv6_host_header_with_empty_port
+    req = @hp.request(@env, "GET / HTTP/1.1\r\nHost: [::1]:\r\n\r\n")
+    assert_equal "[::1]", req['SERVER_NAME']
+    assert_equal '80', req['SERVER_PORT']
+    assert_equal "[::1]:", req['HTTP_HOST']
+  end
+
+  # XXX Highly unlikely..., just make sure we don't segfault or assert on it
+  def test_broken_ipv6_host_header
+    req = @hp.request(@env, "GET / HTTP/1.1\r\nHost: [::1:\r\n\r\n")
+    assert_equal "[", req['SERVER_NAME']
+    assert_equal ':1:', req['SERVER_PORT']
+    assert_equal "[::1:", req['HTTP_HOST']
+  end
+
+  def test_put_body_oneshot
+    buf = "PUT / HTTP/1.0\r\nContent-Length: 5\r\n\r\nabcde"
+    req = @hp.request(@env, buf)
+    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", buf
+  end
+
+  def test_put_body_later
+    buf = "PUT /l HTTP/1.0\r\nContent-Length: 5\r\n\r\n"
+    req = @hp.request(@env, buf)
+    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 "", buf
+  end
+
+  def test_unknown_methods
+    %w(GETT HEADR XGET XHEAD).each { |m|
+      s = "#{m} /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
+      req = @hp.request(@env, s)
+      assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
+      assert_nil req['FRAGMENT']
+      assert_equal 'page=1', req['QUERY_STRING']
+      assert_equal "", s
+      assert_equal m, req['REQUEST_METHOD']
+      @hp.reset
+    }
+  end
+
+  def test_fragment_in_uri
+    get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
+    req = @hp.request(@env, get)
+    assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
+    assert_nil req['FRAGMENT']
+    assert_equal 'page=1', req['QUERY_STRING']
+    assert_equal '', get
+  end
+
+  # lame random garbage maker
+  def rand_data(min, max, readable=true)
+    count = min + ((rand(max)+1) *10).to_i
+    res = count.to_s + "/"
+
+    if readable
+      res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
+    else
+      res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
+    end
+
+    return res
+  end
+
+  def test_horrible_queries
+    # then that large header names are caught
+    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(Kcar::ParserError, Kcar::RequestURITooLongError) do
+        @hp.request(@env, get)
+        @hp.clear
+      end
+    end
+
+    # then that large mangled field values are caught
+    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(Kcar::ParserError,Kcar::RequestURITooLongError) do
+        @hp.request(@env, get)
+        @hp.clear
+      end
+    end
+
+    # then large headers are rejected too FIXME not supported, yet
+    if false
+      get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
+      get << "X-Test: test\r\n" * (80 * 1024)
+      @hp.reset
+      assert_raises(Kcar::ParserError,Kcar::RequestURITooLongError) do
+        @hp.request(@env, get)
+      end
+    end
+
+    # 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"
+      @hp.reset
+      assert_raises(Kcar::ParserError,Kcar::RequestURITooLongError) do
+        @hp.request(@env, get)
+      end
+    end
+  end
+
+  def test_leading_tab
+    get = "GET / HTTP/1.1\r\nHost:\texample.com\r\n\r\n"
+    req = @hp.request(@env, get)
+    assert_equal 'example.com', req['HTTP_HOST']
+  end
+
+  def test_trailing_whitespace
+    get = "GET / HTTP/1.1\r\nHost: example.com \r\n\r\n"
+    req = @hp.request(@env, get)
+    assert_equal 'example.com', req['HTTP_HOST']
+  end
+
+  def test_trailing_tab
+    get = "GET / HTTP/1.1\r\nHost: example.com\t\r\n\r\n"
+    req = @hp.request(@env, get)
+    assert_equal 'example.com', req['HTTP_HOST']
+  end
+
+  def test_trailing_multiple_linear_whitespace
+    get = "GET / HTTP/1.1\r\nHost: example.com\t \t \t\r\n\r\n"
+    req = @hp.request(@env, get)
+    assert_equal 'example.com', req['HTTP_HOST']
+  end
+
+  def test_embedded_linear_whitespace_ok
+    get = "GET / HTTP/1.1\r\nX-Space: hello\t world\t \r\n\r\n"
+    req = @hp.request(@env, get)
+    assert_equal "hello\t world", req["HTTP_X_SPACE"]
+  end
+
+  def test_null_byte_header
+    get = "GET / HTTP/1.1\r\nHost: \0\r\n\r\n"
+    assert_raises(Kcar::ParserError) { @hp.request(@env, get) }
+  end
+
+  def test_null_byte_in_middle
+    get = "GET / HTTP/1.1\r\nHost: hello\0world\r\n\r\n"
+    assert_raises(Kcar::ParserError) { @hp.request(@env, get) }
+  end
+
+  def test_null_byte_at_end
+    get = "GET / HTTP/1.1\r\nHost: hello\0\r\n\r\n"
+    assert_raises(Kcar::ParserError) { @hp.request(@env, get) }
+  end
+
+  def test_empty_header
+    get = "GET / HTTP/1.1\r\nHost:  \r\n\r\n"
+    req = @hp.request(@env, get)
+    assert_equal '', req['HTTP_HOST']
+  end
+
+  def test_connection_TE
+    req = @hp.request(@env, "GET / HTTP/1.1\r\nHost: example.com\r\n" \
+                            "Connection: TE\r\n" \
+                            "TE: trailers\r\n\r\n")
+    assert_predicate @hp, :keepalive?
+    assert_equal 'TE', req['HTTP_CONNECTION']
+  end
+
+  def test_repeat_headers
+    str = "PUT / HTTP/1.1\r\n" \
+          "Trailer: Content-MD5\r\n" \
+          "Trailer: Content-SHA1\r\n" \
+          "transfer-Encoding: chunked\r\n\r\n" \
+          "1\r\na\r\n2\r\n..\r\n0\r\n"
+    req = @hp.request(@env, str)
+    assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER']
+    assert_equal "1\r\na\r\n2\r\n..\r\n0\r\n", str
+    assert @hp.keepalive?
+  end
+
+  def test_http_09
+    buf = "GET /read-rfc1945-if-you-dont-believe-me\r\n"
+    req = @hp.request(@env, buf)
+    assert_equal '', buf
+    expect = {
+      "REQUEST_PATH" => "/read-rfc1945-if-you-dont-believe-me",
+      "PATH_INFO" => "/read-rfc1945-if-you-dont-believe-me",
+      "REQUEST_URI" => "/read-rfc1945-if-you-dont-believe-me",
+      "SERVER_PROTOCOL" => "HTTP/0.9",
+      "HTTP_VERSION" => "HTTP/0.9",
+      "REQUEST_METHOD" => "GET",
+      "QUERY_STRING" => ""
+    }
+    assert_equal expect, req
+  end
+
+  def test_path_info_semicolon
+    qs = "QUERY_STRING"
+    pi = "PATH_INFO"
+    req = {}
+    str = "GET %s HTTP/1.1\r\nHost: example.com\r\n\r\n"
+    {
+      "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
+      "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
+      "/1;a=b" => { qs => "", pi => "/1;a=b" },
+      "/1;a=b?" => { qs => "", pi => "/1;a=b" },
+      "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
+      "*" => { qs => "", pi => "" },
+    }.each do |uri,expect|
+      @env.clear
+      @hp.reset
+      buf = str % [ uri ]
+      req = @hp.request(@env, buf)
+      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"
+      next if uri == "*"
+      uri = URI.parse("http://example.com#{uri}")
+      assert_equal uri.query.to_s, req[qs], "#{qs} mismatch URI.parse disagrees"
+      assert_equal uri.path, req[pi], "#{pi} mismatch URI.parse disagrees"
+    end
+  end
+
+  def test_path_info_semicolon_absolute
+    qs = "QUERY_STRING"
+    pi = "PATH_INFO"
+    str = "GET http://example.com%s HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
+    {
+      "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
+      "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
+      "/1;a=b" => { qs => "", pi => "/1;a=b" },
+      "/1;a=b?" => { qs => "", pi => "/1;a=b" },
+      "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
+    }.each do |uri,expect|
+      @hp.reset
+      @env.clear
+      buf = str % [ uri ]
+      req = @hp.request(@env, buf)
+      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"
+      assert_equal expect[pi], req[pi], "#{pi} mismatch"
+    end
+  end
+
+  def test_negative_content_length
+    str = "PUT / HTTP/1.1\r\n" \
+          "Content-Length: -1\r\n" \
+          "\r\n"
+    assert_raises(Kcar::ParserError) do
+      @hp.request(@env, str)
+    end
+  end
+
+  def test_invalid_content_length
+    str = "PUT / HTTP/1.1\r\n" \
+          "Content-Length: zzzzz\r\n" \
+          "\r\n"
+    assert_raises(Kcar::ParserError) do
+      @hp.request(@env, str)
+    end
+  end
+
+  def test_ignore_version_header
+    req = @hp.request(@env, "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n")
+    expect = {
+      'REQUEST_PATH' => '/',
+      'SERVER_PROTOCOL' => 'HTTP/1.1',
+      'PATH_INFO' => '/',
+      'HTTP_VERSION' => 'HTTP/1.1',
+      'REQUEST_URI' => '/',
+      'REQUEST_METHOD' => 'GET',
+      'QUERY_STRING' => ''
+    }
+    assert_equal expect, req
+  end
+
+  def test_pipelined_requests
+    expect = {
+      'HTTP_HOST' => 'example.com',
+      'SERVER_NAME' => 'example.com',
+      'SERVER_PORT' => '80',
+      'REQUEST_PATH' => '/',
+      'SERVER_PROTOCOL' => 'HTTP/1.1',
+      'PATH_INFO' => '/',
+      'HTTP_VERSION' => 'HTTP/1.1',
+      'REQUEST_URI' => '/',
+      '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"
+    buf = req1 + req2
+    env1 = @hp.request(@env, buf)
+    assert_equal expect, env1
+    assert_equal req2, buf
+    assert_predicate @hp, :keepalive?
+    @env.clear
+    @hp.reset
+    env2 = @hp.request(@env, buf)
+    expect['HTTP_HOST'] = expect['SERVER_NAME'] = 'www.example.com'
+    assert_equal expect, env2
+    assert_equal '', buf
+  end
+end