about summary refs log tree commit homepage
path: root/test/unit/test_http_parser_ng.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/unit/test_http_parser_ng.rb')
-rw-r--r--test/unit/test_http_parser_ng.rb420
1 files changed, 420 insertions, 0 deletions
diff --git a/test/unit/test_http_parser_ng.rb b/test/unit/test_http_parser_ng.rb
new file mode 100644
index 0000000..bb61e7f
--- /dev/null
+++ b/test/unit/test_http_parser_ng.rb
@@ -0,0 +1,420 @@
+# -*- encoding: binary -*-
+
+# coding: binary
+require 'test/test_helper'
+require 'digest/md5'
+
+include Unicorn
+
+class HttpParserNgTest < Test::Unit::TestCase
+
+  def setup
+    @parser = HttpParser.new
+  end
+
+  def test_identity_byte_headers
+    req = {}
+    str = "PUT / HTTP/1.1\r\n"
+    str << "Content-Length: 123\r\n"
+    str << "\r"
+    hdr = ""
+    str.each_byte { |byte|
+      assert_nil @parser.headers(req, hdr << byte.chr)
+    }
+    hdr << "\n"
+    assert_equal req.object_id, @parser.headers(req, hdr).object_id
+    assert_equal '123', req['CONTENT_LENGTH']
+    assert_equal 0, hdr.size
+    assert ! @parser.keepalive?
+    assert @parser.headers?
+    assert 123, @parser.content_length
+  end
+
+  def test_identity_step_headers
+    req = {}
+    str = "PUT / HTTP/1.1\r\n"
+    assert ! @parser.headers(req, str)
+    str << "Content-Length: 123\r\n"
+    assert ! @parser.headers(req, str)
+    str << "\r\n"
+    assert_equal req.object_id, @parser.headers(req, str).object_id
+    assert_equal '123', req['CONTENT_LENGTH']
+    assert_equal 0, str.size
+    assert ! @parser.keepalive?
+    assert @parser.headers?
+  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
+    assert_equal '123', req['CONTENT_LENGTH']
+    assert_equal 0, str.size
+    assert ! @parser.keepalive?
+  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
+    assert_equal '123', req['CONTENT_LENGTH']
+    assert_equal 123, str.size
+    assert_equal body, str
+    tmp = ''
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal 0, str.size
+    assert_equal tmp, body
+    assert_equal "", @parser.filter_body(tmp, str)
+    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
+    assert_equal 1, str.size
+    assert_equal 'a', str
+    tmp = ''
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal "", str
+    assert_equal "a", tmp
+    str << ' ' * 122
+    rv = @parser.filter_body(tmp, str)
+    assert_equal 122, tmp.size
+    assert_nil rv
+    assert_equal "", str
+    assert_equal str.object_id, @parser.filter_body(tmp, str).object_id
+    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
+    assert_equal 2, str.size
+    assert_equal 'aG', str
+    tmp = ''
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal "G", str
+    assert_equal "G", @parser.filter_body(tmp, str)
+    assert_equal 1, tmp.size
+    assert_equal "a", tmp
+    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)
+    assert_equal 0, str.size
+    tmp = ""
+    assert_nil @parser.filter_body(tmp, "6")
+    assert_equal 0, tmp.size
+    assert_nil @parser.filter_body(tmp, rv = "\r\n")
+    assert_equal 0, rv.size
+    assert_equal 0, tmp.size
+    tmp = ""
+    assert_nil @parser.filter_body(tmp, "..")
+    assert_equal "..", tmp
+    assert_nil @parser.filter_body(tmp, "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 ! @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)
+    assert_equal 0, str.size
+    tmp = ""
+    assert_nil @parser.filter_body(tmp, "6")
+    assert_equal 0, tmp.size
+    assert_nil @parser.filter_body(tmp, rv = "\r\n")
+    assert_equal "", rv
+    assert_equal 0, tmp.size
+    tmp = ""
+    assert_nil @parser.filter_body(tmp, "..")
+    assert_equal 2, tmp.size
+    assert_equal "..", tmp
+    assert_nil @parser.filter_body(tmp, "abcd\r\n1")
+    assert_equal "abcd", tmp
+    assert_nil @parser.filter_body(tmp, "\r")
+    assert_equal "", tmp
+    assert_nil @parser.filter_body(tmp, "\n")
+    assert_equal "", tmp
+    assert_nil @parser.filter_body(tmp, "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_equal "GET", rv
+    assert_equal buf.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)
+    tmp = ''
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal '', str
+    str = ' ' * 16300
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal '', str
+    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 @parser.body_eof?
+    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)
+    tmp = ''
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal 'a..', tmp
+    rv = @parser.filter_body(tmp, str)
+    assert_equal rv.object_id, str.object_id
+    assert ! @parser.keepalive?
+  end
+
+  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
+    tmp = ''
+    buf = ''
+    body = ''
+    str = str[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")
+    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)
+    assert_equal 'Content-MD5', req['HTTP_TRAILER']
+    assert_nil req['HTTP_CONTENT_MD5']
+    tmp = ''
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal 'a..', tmp
+    md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
+    rv = @parser.filter_body(tmp, str)
+    assert_equal rv.object_id, str.object_id
+    assert_equal '', str
+    md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
+    str << md5_hdr
+    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 / ")
+    assert_equal "GET / ", str
+    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)
+    assert_equal 'Content-MD5', req['HTTP_TRAILER']
+    assert_nil req['HTTP_CONTENT_MD5']
+    tmp = ''
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal 'a..', tmp
+    md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
+    rv = @parser.filter_body(tmp, str)
+    assert_equal rv.object_id, str.object_id
+    assert_equal '', str
+    assert_nil @parser.trailers(req, str)
+    md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
+    md5_hdr.each_byte { |byte|
+      str << byte.chr
+      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 << "\n")
+  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)
+    assert_nil @parser.content_length
+    assert_nothing_raised { @parser.filter_body('', str) }
+    assert ! @parser.keepalive?
+  end
+
+  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) }
+    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)
+    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?
+  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)
+    assert_nil @parser.content_length
+    assert_raise(HttpParserError) { @parser.filter_body('', str) }
+    assert ! @parser.keepalive?
+  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?
+  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)
+    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?
+  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 = {}
+    assert_equal req, @parser.headers(req, str)
+    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
+    expect = {
+      "SERVER_NAME"=>"localhost",
+      "rack.url_scheme"=>"http",
+      "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_PORT"=>"80",
+      "SERVER_PROTOCOL"=>"HTTP/0.9",
+      "REQUEST_METHOD"=>"GET",
+      "QUERY_STRING"=>""
+    }
+    assert_equal expect, req
+    assert ! parser.headers?
+  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|
+      assert_equal req, @parser.headers(req.clear, str % [ uri ])
+      @parser.reset
+      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"
+    req = {}
+    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|
+      assert_equal req, @parser.headers(req.clear, str % [ uri ])
+      @parser.reset
+      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
+
+end