package fasthttp

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"strings"
	"testing"
)

func TestResponseHeaderDefaultStatusCode(t *testing.T) {
	var h ResponseHeader
	statusCode := h.StatusCode()
	if statusCode != StatusOK {
		t.Fatalf("unexpected status code: %d. Expecting %d", statusCode, StatusOK)
	}
}

func TestResponseHeaderDelClientCookie(t *testing.T) {
	cookieName := "foobar"

	var h ResponseHeader
	c := AcquireCookie()
	c.SetKey(cookieName)
	c.SetValue("aasdfsdaf")
	h.SetCookie(c)

	h.DelClientCookieBytes([]byte(cookieName))
	if !h.Cookie(c) {
		t.Fatalf("expecting cookie %q", c.Key())
	}
	if !c.Expire().Equal(CookieExpireDelete) {
		t.Fatalf("unexpected cookie expiration time: %s. Expecting %s", c.Expire(), CookieExpireDelete)
	}
	if len(c.Value()) > 0 {
		t.Fatalf("unexpected cookie value: %q. Expecting empty value", c.Value())
	}
	ReleaseCookie(c)
}

func TestResponseHeaderAdd(t *testing.T) {
	m := make(map[string]struct{})
	var h ResponseHeader
	h.Add("aaa", "bbb")
	m["bbb"] = struct{}{}
	for i := 0; i < 10; i++ {
		v := fmt.Sprintf("%d", i)
		h.Add("Foo-Bar", v)
		m[v] = struct{}{}
	}
	if h.Len() != 12 {
		t.Fatalf("unexpected header len %d. Expecting 12", h.Len())
	}

	h.VisitAll(func(k, v []byte) {
		switch string(k) {
		case "Aaa", "Foo-Bar":
			if _, ok := m[string(v)]; !ok {
				t.Fatalf("unexpected value found %q. key %q", v, k)
			}
			delete(m, string(v))
		case "Content-Type":
		default:
			t.Fatalf("unexpected key found: %q", k)
		}
	})
	if len(m) > 0 {
		t.Fatalf("%d headers are missed", len(m))
	}

	s := h.String()
	br := bufio.NewReader(bytes.NewBufferString(s))
	var h1 ResponseHeader
	if err := h1.Read(br); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	h.VisitAll(func(k, v []byte) {
		switch string(k) {
		case "Aaa", "Foo-Bar":
			m[string(v)] = struct{}{}
		case "Content-Type":
		default:
			t.Fatalf("unexpected key found: %q", k)
		}
	})
	if len(m) != 11 {
		t.Fatalf("unexpected number of headers: %d. Expecting 11", len(m))
	}
}

func TestRequestHeaderAdd(t *testing.T) {
	m := make(map[string]struct{})
	var h RequestHeader
	h.Add("aaa", "bbb")
	m["bbb"] = struct{}{}
	for i := 0; i < 10; i++ {
		v := fmt.Sprintf("%d", i)
		h.Add("Foo-Bar", v)
		m[v] = struct{}{}
	}
	if h.Len() != 11 {
		t.Fatalf("unexpected header len %d. Expecting 11", h.Len())
	}

	h.VisitAll(func(k, v []byte) {
		switch string(k) {
		case "Aaa", "Foo-Bar":
			if _, ok := m[string(v)]; !ok {
				t.Fatalf("unexpected value found %q. key %q", v, k)
			}
			delete(m, string(v))
		default:
			t.Fatalf("unexpected key found: %q", k)
		}
	})
	if len(m) > 0 {
		t.Fatalf("%d headers are missed", len(m))
	}

	s := h.String()
	br := bufio.NewReader(bytes.NewBufferString(s))
	var h1 RequestHeader
	if err := h1.Read(br); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	h.VisitAll(func(k, v []byte) {
		switch string(k) {
		case "Aaa", "Foo-Bar":
			m[string(v)] = struct{}{}
		case "User-Agent":
		default:
			t.Fatalf("unexpected key found: %q", k)
		}
	})
	if len(m) != 11 {
		t.Fatalf("unexpected number of headers: %d. Expecting 11", len(m))
	}
	s1 := h1.String()
	if s != s1 {
		t.Fatalf("unexpected headers %q. Expecting %q", s1, s)
	}
}

func TestHasHeaderValue(t *testing.T) {
	testHasHeaderValue(t, "foobar", "foobar", true)
	testHasHeaderValue(t, "foobar", "foo", false)
	testHasHeaderValue(t, "foobar", "bar", false)
	testHasHeaderValue(t, "keep-alive, Upgrade", "keep-alive", true)
	testHasHeaderValue(t, "keep-alive  ,    Upgrade", "Upgrade", true)
	testHasHeaderValue(t, "keep-alive, Upgrade", "Upgrade-foo", false)
	testHasHeaderValue(t, "keep-alive, Upgrade", "Upgr", false)
	testHasHeaderValue(t, "foo  ,   bar,  baz   ,", "foo", true)
	testHasHeaderValue(t, "foo  ,   bar,  baz   ,", "bar", true)
	testHasHeaderValue(t, "foo  ,   bar,  baz   ,", "baz", true)
	testHasHeaderValue(t, "foo  ,   bar,  baz   ,", "ba", false)
	testHasHeaderValue(t, "foo, ", "", true)
	testHasHeaderValue(t, "foo", "", false)
}

func testHasHeaderValue(t *testing.T, s, value string, has bool) {
	ok := hasHeaderValue([]byte(s), []byte(value))
	if ok != has {
		t.Fatalf("unexpected hasHeaderValue(%q, %q)=%v. Expecting %v", s, value, ok, has)
	}
}

func TestRequestHeaderDel(t *testing.T) {
	var h RequestHeader
	h.Set("Foo-Bar", "baz")
	h.Set("aaa", "bbb")
	h.Set("Connection", "keep-alive")
	h.Set("Content-Type", "aaa")
	h.Set("Host", "aaabbb")
	h.Set("User-Agent", "asdfas")
	h.Set("Content-Length", "1123")
	h.Set("Cookie", "foobar=baz")

	h.Del("foo-bar")
	h.Del("connection")
	h.DelBytes([]byte("content-type"))
	h.Del("Host")
	h.Del("user-agent")
	h.Del("content-length")
	h.Del("cookie")

	hv := h.Peek("aaa")
	if string(hv) != "bbb" {
		t.Fatalf("unexpected header value: %q. Expecting %q", hv, "bbb")
	}
	hv = h.Peek("Foo-Bar")
	if len(hv) > 0 {
		t.Fatalf("non-zero value: %q", hv)
	}
	hv = h.Peek("Connection")
	if len(hv) > 0 {
		t.Fatalf("non-zero value: %q", hv)
	}
	hv = h.Peek("Content-Type")
	if len(hv) > 0 {
		t.Fatalf("non-zero value: %q", hv)
	}
	hv = h.Peek("Host")
	if len(hv) > 0 {
		t.Fatalf("non-zero value: %q", hv)
	}
	hv = h.Peek("User-Agent")
	if len(hv) > 0 {
		t.Fatalf("non-zero value: %q", hv)
	}
	hv = h.Peek("Content-Length")
	if len(hv) > 0 {
		t.Fatalf("non-zero value: %q", hv)
	}
	hv = h.Peek("Cookie")
	if len(hv) > 0 {
		t.Fatalf("non-zero value: %q", hv)
	}

	cv := h.Cookie("foobar")
	if len(cv) > 0 {
		t.Fatalf("unexpected cookie obtianed: %q", cv)
	}
	if h.ContentLength() != 0 {
		t.Fatalf("unexpected content-length: %d. Expecting 0", h.ContentLength())
	}
}

func TestResponseHeaderDel(t *testing.T) {
	var h ResponseHeader
	h.Set("Foo-Bar", "baz")
	h.Set("aaa", "bbb")
	h.Set("Connection", "keep-alive")
	h.Set("Content-Type", "aaa")
	h.Set("Server", "aaabbb")
	h.Set("Content-Length", "1123")

	var c Cookie
	c.SetKey("foo")
	c.SetValue("bar")
	h.SetCookie(&c)

	h.Del("foo-bar")
	h.Del("connection")
	h.DelBytes([]byte("content-type"))
	h.Del("Server")
	h.Del("content-length")
	h.Del("set-cookie")

	hv := h.Peek("aaa")
	if string(hv) != "bbb" {
		t.Fatalf("unexpected header value: %q. Expecting %q", hv, "bbb")
	}
	hv = h.Peek("Foo-Bar")
	if len(hv) > 0 {
		t.Fatalf("non-zero header value: %q", hv)
	}
	hv = h.Peek("Connection")
	if len(hv) > 0 {
		t.Fatalf("non-zero value: %q", hv)
	}
	hv = h.Peek("Content-Type")
	if string(hv) != string(defaultContentType) {
		t.Fatalf("unexpected content-type: %q. Expecting %q", hv, defaultContentType)
	}
	hv = h.Peek("Server")
	if len(hv) > 0 {
		t.Fatalf("non-zero value: %q", hv)
	}
	hv = h.Peek("Content-Length")
	if len(hv) > 0 {
		t.Fatalf("non-zero value: %q", hv)
	}

	if h.Cookie(&c) {
		t.Fatalf("unexpected cookie obtianed: %q", &c)
	}
	if h.ContentLength() != 0 {
		t.Fatalf("unexpected content-length: %d. Expecting 0", h.ContentLength())
	}
}

func TestAppendNormalizedHeaderKeyBytes(t *testing.T) {
	testAppendNormalizedHeaderKeyBytes(t, "", "")
	testAppendNormalizedHeaderKeyBytes(t, "Content-Type", "Content-Type")
	testAppendNormalizedHeaderKeyBytes(t, "foO-bAr-BAZ", "Foo-Bar-Baz")
}

func testAppendNormalizedHeaderKeyBytes(t *testing.T, key, expectedKey string) {
	buf := []byte("foobar")
	result := AppendNormalizedHeaderKeyBytes(buf, []byte(key))
	normalizedKey := result[len(buf):]
	if string(normalizedKey) != expectedKey {
		t.Fatalf("unexpected normalized key %q. Expecting %q", normalizedKey, expectedKey)
	}
}

func TestRequestHeaderHTTP10ConnectionClose(t *testing.T) {
	s := "GET / HTTP/1.0\r\nHost: foobar\r\n\r\n"
	var h RequestHeader
	br := bufio.NewReader(bytes.NewBufferString(s))
	if err := h.Read(br); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	if !h.connectionCloseFast() {
		t.Fatalf("expecting 'Connection: close' request header")
	}
	if !h.ConnectionClose() {
		t.Fatalf("expecting 'Connection: close' request header")
	}
}

func TestRequestHeaderHTTP10ConnectionKeepAlive(t *testing.T) {
	s := "GET / HTTP/1.0\r\nHost: foobar\r\nConnection: keep-alive\r\n\r\n"
	var h RequestHeader
	br := bufio.NewReader(bytes.NewBufferString(s))
	if err := h.Read(br); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	if h.ConnectionClose() {
		t.Fatalf("unexpected 'Connection: close' request header")
	}
}

func TestBufferStartEnd(t *testing.T) {
	testBufferStartEnd(t, "", "", "")
	testBufferStartEnd(t, "foobar", "foobar", "")

	b := string(createFixedBody(199))
	testBufferStartEnd(t, b, b, "")
	for i := 0; i < 10; i++ {
		b += "foobar"
		testBufferStartEnd(t, b, b, "")
	}

	b = string(createFixedBody(400))
	testBufferStartEnd(t, b, b, "")
	for i := 0; i < 10; i++ {
		b += "sadfqwer"
		testBufferStartEnd(t, b, b[:200], b[len(b)-200:])
	}
}

func testBufferStartEnd(t *testing.T, buf, expectedStart, expectedEnd string) {
	start, end := bufferStartEnd([]byte(buf))
	if string(start) != expectedStart {
		t.Fatalf("unexpected start %q. Expecting %q. buf %q", start, expectedStart, buf)
	}
	if string(end) != expectedEnd {
		t.Fatalf("unexpected end %q. Expecting %q. buf %q", end, expectedEnd, buf)
	}
}

func TestResponseHeaderTrailingCRLFSuccess(t *testing.T) {
	trailingCRLF := "\r\n\r\n\r\n"
	s := "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 123\r\n\r\n" + trailingCRLF

	var r ResponseHeader
	br := bufio.NewReader(bytes.NewBufferString(s))
	if err := r.Read(br); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	// try reading the trailing CRLF. It must return EOF
	err := r.Read(br)
	if err == nil {
		t.Fatalf("expecting error")
	}
	if err != io.EOF {
		t.Fatalf("unexpected error: %s. Expecting %s", err, io.EOF)
	}
}

func TestResponseHeaderTrailingCRLFError(t *testing.T) {
	trailingCRLF := "\r\nerror\r\n\r\n"
	s := "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 123\r\n\r\n" + trailingCRLF

	var r ResponseHeader
	br := bufio.NewReader(bytes.NewBufferString(s))
	if err := r.Read(br); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	// try reading the trailing CRLF. It must return EOF
	err := r.Read(br)
	if err == nil {
		t.Fatalf("expecting error")
	}
	if err == io.EOF {
		t.Fatalf("unexpected error: %s", err)
	}
}

func TestRequestHeaderTrailingCRLFSuccess(t *testing.T) {
	trailingCRLF := "\r\n\r\n\r\n"
	s := "GET / HTTP/1.1\r\nHost: aaa.com\r\n\r\n" + trailingCRLF

	var r RequestHeader
	br := bufio.NewReader(bytes.NewBufferString(s))
	if err := r.Read(br); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	// try reading the trailing CRLF. It must return EOF
	err := r.Read(br)
	if err == nil {
		t.Fatalf("expecting error")
	}
	if err != io.EOF {
		t.Fatalf("unexpected error: %s. Expecting %s", err, io.EOF)
	}
}

func TestRequestHeaderTrailingCRLFError(t *testing.T) {
	trailingCRLF := "\r\nerror\r\n\r\n"
	s := "GET / HTTP/1.1\r\nHost: aaa.com\r\n\r\n" + trailingCRLF

	var r RequestHeader
	br := bufio.NewReader(bytes.NewBufferString(s))
	if err := r.Read(br); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	// try reading the trailing CRLF. It must return EOF
	err := r.Read(br)
	if err == nil {
		t.Fatalf("expecting error")
	}
	if err == io.EOF {
		t.Fatalf("unexpected error: %s", err)
	}
}

func TestRequestHeaderReadEOF(t *testing.T) {
	var r RequestHeader

	br := bufio.NewReader(&bytes.Buffer{})
	err := r.Read(br)
	if err == nil {
		t.Fatalf("expecting error")
	}
	if err != io.EOF {
		t.Fatalf("unexpected error: %s. Expecting %s", err, io.EOF)
	}

	// incomplete request header mustn't return io.EOF
	br = bufio.NewReader(bytes.NewBufferString("GET "))
	err = r.Read(br)
	if err == nil {
		t.Fatalf("expecting error")
	}
	if err == io.EOF {
		t.Fatalf("expecting non-EOF error")
	}
}

func TestResponseHeaderReadEOF(t *testing.T) {
	var r ResponseHeader

	br := bufio.NewReader(&bytes.Buffer{})
	err := r.Read(br)
	if err == nil {
		t.Fatalf("expecting error")
	}
	if err != io.EOF {
		t.Fatalf("unexpected error: %s. Expecting %s", err, io.EOF)
	}

	// incomplete response header mustn't return io.EOF
	br = bufio.NewReader(bytes.NewBufferString("HTTP/1.1 "))
	err = r.Read(br)
	if err == nil {
		t.Fatalf("expecting error")
	}
	if err == io.EOF {
		t.Fatalf("expecting non-EOF error")
	}
}

func TestResponseHeaderOldVersion(t *testing.T) {
	var h ResponseHeader

	s := "HTTP/1.0 200 OK\r\nContent-Length: 5\r\nContent-Type: aaa\r\n\r\n12345"
	s += "HTTP/1.0 200 OK\r\nContent-Length: 2\r\nContent-Type: ass\r\nConnection: keep-alive\r\n\r\n42"
	br := bufio.NewReader(bytes.NewBufferString(s))
	if err := h.Read(br); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	if !h.ConnectionClose() {
		t.Fatalf("expecting 'Connection: close' for the response with old http protocol")
	}

	if err := h.Read(br); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	if h.ConnectionClose() {
		t.Fatalf("unexpected 'Connection: close' for keep-alive response with old http protocol")
	}
}

func TestRequestHeaderSetByteRange(t *testing.T) {
	testRequestHeaderSetByteRange(t, 0, 10, "bytes=0-10")
	testRequestHeaderSetByteRange(t, 123, -1, "bytes=123-")
	testRequestHeaderSetByteRange(t, -234, 58349, "bytes=-234")
}

func testRequestHeaderSetByteRange(t *testing.T, startPos, endPos int, expectedV string) {
	var h RequestHeader
	h.SetByteRange(startPos, endPos)
	v := h.Peek("Range")
	if string(v) != expectedV {
		t.Fatalf("unexpected range: %q. Expecting %q. startPos=%d, endPos=%d", v, expectedV, startPos, endPos)
	}
}

func TestResponseHeaderSetContentRange(t *testing.T) {
	testResponseHeaderSetContentRange(t, 0, 0, 1, "bytes 0-0/1")
	testResponseHeaderSetContentRange(t, 123, 456, 789, "bytes 123-456/789")
}

func testResponseHeaderSetContentRange(t *testing.T, startPos, endPos, contentLength int, expectedV string) {
	var h ResponseHeader
	h.SetContentRange(startPos, endPos, contentLength)
	v := h.Peek("Content-Range")
	if string(v) != expectedV {
		t.Fatalf("unexpected content-range: %q. Expecting %q. startPos=%d, endPos=%d, contentLength=%d",
			v, expectedV, startPos, endPos, contentLength)
	}
}

func TestRequestHeaderHasAcceptEncoding(t *testing.T) {
	testRequestHeaderHasAcceptEncoding(t, "", "gzip", false)
	testRequestHeaderHasAcceptEncoding(t, "gzip", "sdhc", false)
	testRequestHeaderHasAcceptEncoding(t, "deflate", "deflate", true)
	testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "gzi", false)
	testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "dhc", false)
	testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "sdh", false)
	testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "zip", false)
	testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "flat", false)
	testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "flate", false)
	testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "def", false)
	testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "gzip", true)
	testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "deflate", true)
	testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "sdhc", true)
}

func testRequestHeaderHasAcceptEncoding(t *testing.T, ae, v string, resultExpected bool) {
	var h RequestHeader
	h.Set("Accept-Encoding", ae)
	result := h.HasAcceptEncoding(v)
	if result != resultExpected {
		t.Fatalf("unexpected result in HasAcceptEncoding(%q, %q): %v. Expecting %v", ae, v, result, resultExpected)
	}
}

func TestRequestMultipartFormBoundary(t *testing.T) {
	testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=foobar\r\n\r\n", "foobar")

	// incorrect content-type
	testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: foo/bar\r\n\r\n", "")

	// empty boundary
	testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=\r\n\r\n", "")

	// missing boundary
	testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: multipart/form-data\r\n\r\n", "")

	// boundary after other content-type params
	testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: multipart/form-data;   foo=bar;   boundary=--aaabb  \r\n\r\n", "--aaabb")

	var h RequestHeader
	h.SetMultipartFormBoundary("foobarbaz")
	b := h.MultipartFormBoundary()
	if string(b) != "foobarbaz" {
		t.Fatalf("unexpected boundary %q. Expecting %q", b, "foobarbaz")
	}
}

func testRequestMultipartFormBoundary(t *testing.T, s, boundary string) {
	var h RequestHeader
	r := bytes.NewBufferString(s)
	br := bufio.NewReader(r)
	if err := h.Read(br); err != nil {
		t.Fatalf("unexpected error: %s. s=%q, boundary=%q", err, s, boundary)
	}

	b := h.MultipartFormBoundary()
	if string(b) != boundary {
		t.Fatalf("unexpected boundary %q. Expecting %q. s=%q", b, boundary, s)
	}
}

func TestResponseHeaderConnectionUpgrade(t *testing.T) {
	testResponseHeaderConnectionUpgrade(t, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nConnection: Upgrade, HTTP2-Settings\r\n\r\n",
		true, true)
	testResponseHeaderConnectionUpgrade(t, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nConnection: keep-alive, Upgrade\r\n\r\n",
		true, true)

	// non-http/1.1 protocol has 'connection: close' by default, which also disables 'connection: upgrade'
	testResponseHeaderConnectionUpgrade(t, "HTTP/1.0 200 OK\r\nContent-Length: 10\r\nConnection: Upgrade, HTTP2-Settings\r\n\r\n",
		false, false)

	// explicit keep-alive for non-http/1.1, so 'connection: upgrade' works
	testResponseHeaderConnectionUpgrade(t, "HTTP/1.0 200 OK\r\nContent-Length: 10\r\nConnection: Upgrade, keep-alive\r\n\r\n",
		true, true)

	// implicit keep-alive for http/1.1
	testResponseHeaderConnectionUpgrade(t, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\n", false, true)

	// no content-length, so 'connection: close' is assumed
	testResponseHeaderConnectionUpgrade(t, "HTTP/1.1 200 OK\r\n\r\n", false, false)
}

func testResponseHeaderConnectionUpgrade(t *testing.T, s string, isUpgrade, isKeepAlive bool) {
	var h ResponseHeader

	r := bytes.NewBufferString(s)
	br := bufio.NewReader(r)
	if err := h.Read(br); err != nil {
		t.Fatalf("unexpected error: %s. Response header %q", err, s)
	}
	upgrade := h.ConnectionUpgrade()
	if upgrade != isUpgrade {
		t.Fatalf("unexpected 'connection: upgrade' when parsing response header: %v. Expecting %v. header %q. v=%q",
			upgrade, isUpgrade, s, h.Peek("Connection"))
	}
	keepAlive := !h.ConnectionClose()
	if keepAlive != isKeepAlive {
		t.Fatalf("unexpected 'connection: keep-alive' when parsing response header: %v. Expecting %v. header %q. v=%q",
			keepAlive, isKeepAlive, s, &h)
	}
}

func TestRequestHeaderConnectionUpgrade(t *testing.T) {
	testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.1\r\nConnection: Upgrade, HTTP2-Settings\r\nHost: foobar.com\r\n\r\n",
		true, true)
	testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.1\r\nConnection: keep-alive,Upgrade\r\nHost: foobar.com\r\n\r\n",
		true, true)

	// non-http/1.1 has 'connection: close' by default, which resets 'connection: upgrade'
	testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.0\r\nConnection: Upgrade, HTTP2-Settings\r\nHost: foobar.com\r\n\r\n",
		false, false)

	// explicit 'connection: keep-alive' in non-http/1.1
	testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.0\r\nConnection: foo, Upgrade, keep-alive\r\nHost: foobar.com\r\n\r\n",
		true, true)

	// no upgrade
	testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.1\r\nConnection: Upgradess, foobar\r\nHost: foobar.com\r\n\r\n",
		false, true)
	testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.1\r\nHost: foobar.com\r\n\r\n",
		false, true)

	// explicit connection close
	testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.1\r\nConnection: close\r\nHost: foobar.com\r\n\r\n",
		false, false)
}

func testRequestHeaderConnectionUpgrade(t *testing.T, s string, isUpgrade, isKeepAlive bool) {
	var h RequestHeader

	r := bytes.NewBufferString(s)
	br := bufio.NewReader(r)
	if err := h.Read(br); err != nil {
		t.Fatalf("unexpected error: %s. Request header %q", err, s)
	}
	upgrade := h.ConnectionUpgrade()
	if upgrade != isUpgrade {
		t.Fatalf("unexpected 'connection: upgrade' when parsing request header: %v. Expecting %v. header %q",
			upgrade, isUpgrade, s)
	}
	keepAlive := !h.ConnectionClose()
	if keepAlive != isKeepAlive {
		t.Fatalf("unexpected 'connection: keep-alive' when parsing request header: %v. Expecting %v. header %q",
			keepAlive, isKeepAlive, s)
	}
}

func TestRequestHeaderProxyWithCookie(t *testing.T) {
	// Proxy request header (read it, then write it without touching any headers).
	var h RequestHeader
	r := bytes.NewBufferString("GET /foo HTTP/1.1\r\nFoo: bar\r\nHost: aaa.com\r\nCookie: foo=bar; bazzz=aaaaaaa; x=y\r\nCookie: aqqqqq=123\r\n\r\n")
	br := bufio.NewReader(r)
	if err := h.Read(br); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	w := &bytes.Buffer{}
	bw := bufio.NewWriter(w)
	if err := h.Write(bw); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	if err := bw.Flush(); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	var h1 RequestHeader
	br.Reset(w)
	if err := h1.Read(br); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	if string(h1.RequestURI()) != "/foo" {
		t.Fatalf("unexpected requestURI: %q. Expecting %q", h1.RequestURI(), "/foo")
	}
	if string(h1.Host()) != "aaa.com" {
		t.Fatalf("unexpected host: %q. Expecting %q", h1.Host(), "aaa.com")
	}
	if string(h1.Peek("Foo")) != "bar" {
		t.Fatalf("unexpected Foo: %q. Expecting %q", h1.Peek("Foo"), "bar")
	}
	if string(h1.Cookie("foo")) != "bar" {
		t.Fatalf("unexpected coookie foo=%q. Expecting %q", h1.Cookie("foo"), "bar")
	}
	if string(h1.Cookie("bazzz")) != "aaaaaaa" {
		t.Fatalf("unexpected cookie bazzz=%q. Expecting %q", h1.Cookie("bazzz"), "aaaaaaa")
	}
	if string(h1.Cookie("x")) != "y" {
		t.Fatalf("unexpected cookie x=%q. Expecting %q", h1.Cookie("x"), "y")
	}
	if string(h1.Cookie("aqqqqq")) != "123" {
		t.Fatalf("unexpected cookie aqqqqq=%q. Expecting %q", h1.Cookie("aqqqqq"), "123")
	}
}

func TestPeekRawHeader(t *testing.T) {
	// empty header
	testPeekRawHeader(t, "", "Foo-Bar", "")

	// different case
	testPeekRawHeader(t, "Content-Length: 3443\r\n", "content-length", "")

	// no trailing crlf
	testPeekRawHeader(t, "Content-Length: 234", "Content-Length", "")

	// single header
	testPeekRawHeader(t, "Content-Length: 12345\r\n", "Content-Length", "12345")

	// multiple headers
	testPeekRawHeader(t, "Host: foobar\r\nContent-Length: 434\r\nFoo: bar\r\n\r\n", "Content-Length", "434")

	// lf without cr
	testPeekRawHeader(t, "Foo: bar\nConnection: close\nAaa: bbb\ncc: ddd\n", "Connection", "close")
}

func testPeekRawHeader(t *testing.T, rawHeaders, key string, expectedValue string) {
	v := peekRawHeader([]byte(rawHeaders), []byte(key))
	if string(v) != expectedValue {
		t.Fatalf("unexpected raw headers value %q. Expected %q. key %q, rawHeaders %q", v, expectedValue, key, rawHeaders)
	}
}

func TestResponseHeaderFirstByteReadEOF(t *testing.T) {
	var h ResponseHeader

	r := &errorReader{fmt.Errorf("non-eof error")}
	br := bufio.NewReader(r)
	err := h.Read(br)
	if err == nil {
		t.Fatalf("expecting error")
	}
	if err != io.EOF {
		t.Fatalf("unexpected error %s. Expecting %s", err, io.EOF)
	}
}

func TestRequestHeaderFirstByteReadEOF(t *testing.T) {
	var h RequestHeader

	r := &errorReader{fmt.Errorf("non-eof error")}
	br := bufio.NewReader(r)
	err := h.Read(br)
	if err == nil {
		t.Fatalf("expecting error")
	}
	if err != io.EOF {
		t.Fatalf("unexpected error %s. Expecting %s", err, io.EOF)
	}
}

type errorReader struct {
	err error
}

func (r *errorReader) Read(p []byte) (int, error) {
	return 0, r.err
}

func TestRequestHeaderEmptyMethod(t *testing.T) {
	var h RequestHeader

	if !h.IsGet() {
		t.Fatalf("empty method must be equivalent to GET")
	}
	if h.IsPost() {
		t.Fatalf("empty method cannot be POST")
	}
	if h.IsHead() {
		t.Fatalf("empty method cannot be HEAD")
	}
	if h.IsDelete() {
		t.Fatalf("empty method cannot be DELETE")
	}
}

func TestResponseHeaderHTTPVer(t *testing.T) {
	// non-http/1.1
	testResponseHeaderHTTPVer(t, "HTTP/1.0 200 OK\r\nContent-Type: aaa\r\nContent-Length: 123\r\n\r\n", true)
	testResponseHeaderHTTPVer(t, "HTTP/0.9 200 OK\r\nContent-Type: aaa\r\nContent-Length: 123\r\n\r\n", true)
	testResponseHeaderHTTPVer(t, "foobar 200 OK\r\nContent-Type: aaa\r\nContent-Length: 123\r\n\r\n", true)

	// http/1.1
	testResponseHeaderHTTPVer(t, "HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nContent-Length: 123\r\n\r\n", false)
}

func TestRequestHeaderHTTPVer(t *testing.T) {
	// non-http/1.1
	testRequestHeaderHTTPVer(t, "GET / HTTP/1.0\r\nHost: aa.com\r\n\r\n", true)
	testRequestHeaderHTTPVer(t, "GET / HTTP/0.9\r\nHost: aa.com\r\n\r\n", true)
	testRequestHeaderHTTPVer(t, "GET / foobar\r\nHost: aa.com\r\n\r\n", true)

	// empty http version
	testRequestHeaderHTTPVer(t, "GET /\r\nHost: aaa.com\r\n\r\n", true)
	testRequestHeaderHTTPVer(t, "GET / \r\nHost: aaa.com\r\n\r\n", true)

	// http/1.1
	testRequestHeaderHTTPVer(t, "GET / HTTP/1.1\r\nHost: a.com\r\n\r\n", false)
}

func testResponseHeaderHTTPVer(t *testing.T, s string, connectionClose bool) {
	var h ResponseHeader

	r := bytes.NewBufferString(s)
	br := bufio.NewReader(r)
	if err := h.Read(br); err != nil {
		t.Fatalf("unexpected error: %s. response=%q", err, s)
	}
	if h.ConnectionClose() != connectionClose {
		t.Fatalf("unexpected connectionClose %v. Expecting %v. response=%q", h.ConnectionClose(), connectionClose, s)
	}
}

func testRequestHeaderHTTPVer(t *testing.T, s string, connectionClose bool) {
	var h RequestHeader

	r := bytes.NewBufferString(s)
	br := bufio.NewReader(r)
	if err := h.Read(br); err != nil {
		t.Fatalf("unexpected error: %s. request=%q", err, s)
	}
	if h.ConnectionClose() != connectionClose {
		t.Fatalf("unexpected connectionClose %v. Expecting %v. request=%q", h.ConnectionClose(), connectionClose, s)
	}
}

func TestResponseHeaderCopyTo(t *testing.T) {
	var h ResponseHeader

	h.Set("Set-Cookie", "foo=bar")
	h.Set("Content-Type", "foobar")
	h.Set("AAA-BBB", "aaaa")

	var h1 ResponseHeader
	h.CopyTo(&h1)
	if !bytes.Equal(h1.Peek("Set-cookie"), h.Peek("Set-Cookie")) {
		t.Fatalf("unexpected cookie %q. Expected %q", h1.Peek("set-cookie"), h.Peek("set-cookie"))
	}
	if !bytes.Equal(h1.Peek("Content-Type"), h.Peek("Content-Type")) {
		t.Fatalf("unexpected content-type %q. Expected %q", h1.Peek("content-type"), h.Peek("content-type"))
	}
	if !bytes.Equal(h1.Peek("aaa-bbb"), h.Peek("AAA-BBB")) {
		t.Fatalf("unexpected aaa-bbb %q. Expected %q", h1.Peek("aaa-bbb"), h.Peek("aaa-bbb"))
	}
}

func TestRequestHeaderCopyTo(t *testing.T) {
	var h RequestHeader

	h.Set("Cookie", "aa=bb; cc=dd")
	h.Set("Content-Type", "foobar")
	h.Set("Host", "aaaa")
	h.Set("aaaxxx", "123")

	var h1 RequestHeader
	h.CopyTo(&h1)
	if !bytes.Equal(h1.Peek("cookie"), h.Peek("Cookie")) {
		t.Fatalf("unexpected cookie after copying: %q. Expected %q", h1.Peek("cookie"), h.Peek("cookie"))
	}
	if !bytes.Equal(h1.Peek("content-type"), h.Peek("Content-Type")) {
		t.Fatalf("unexpected content-type %q. Expected %q", h1.Peek("content-type"), h.Peek("content-type"))
	}
	if !bytes.Equal(h1.Peek("host"), h.Peek("host")) {
		t.Fatalf("unexpected host %q. Expected %q", h1.Peek("host"), h.Peek("host"))
	}
	if !bytes.Equal(h1.Peek("aaaxxx"), h.Peek("aaaxxx")) {
		t.Fatalf("unexpected aaaxxx %q. Expected %q", h1.Peek("aaaxxx"), h.Peek("aaaxxx"))
	}
}

func TestRequestHeaderConnectionClose(t *testing.T) {
	var h RequestHeader

	h.Set("Connection", "close")
	h.Set("Host", "foobar")
	if !h.ConnectionClose() {
		t.Fatalf("connection: close not set")
	}

	var w bytes.Buffer
	bw := bufio.NewWriter(&w)
	if err := h.Write(bw); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	if err := bw.Flush(); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	var h1 RequestHeader
	br := bufio.NewReader(&w)
	if err := h1.Read(br); err != nil {
		t.Fatalf("error when reading request header: %s", err)
	}

	if !h1.ConnectionClose() {
		t.Fatalf("unexpected connection: close value: %v", h1.ConnectionClose())
	}
	if string(h1.Peek("Connection")) != "close" {
		t.Fatalf("unexpected connection value: %q. Expecting %q", h.Peek("Connection"), "close")
	}
}

func TestRequestHeaderSetCookie(t *testing.T) {
	var h RequestHeader

	h.Set("Cookie", "foo=bar; baz=aaa")
	h.Set("cOOkie", "xx=yyy")

	if string(h.Cookie("foo")) != "bar" {
		t.Fatalf("Unexpected cookie %q. Expecting %q", h.Cookie("foo"), "bar")
	}
	if string(h.Cookie("baz")) != "aaa" {
		t.Fatalf("Unexpected cookie %q. Expecting %q", h.Cookie("baz"), "aaa")
	}
	if string(h.Cookie("xx")) != "yyy" {
		t.Fatalf("unexpected cookie %q. Expecting %q", h.Cookie("xx"), "yyy")
	}
}

func TestResponseHeaderSetCookie(t *testing.T) {
	var h ResponseHeader

	h.Set("set-cookie", "foo=bar; path=/aa/bb; domain=aaa.com")
	h.Set("Set-Cookie", "aaaaa=bxx")

	var c Cookie
	c.SetKey("foo")
	if !h.Cookie(&c) {
		t.Fatalf("cannot obtain %q cookie", c.Key())
	}
	if string(c.Value()) != "bar" {
		t.Fatalf("unexpected cookie value %q. Expected %q", c.Value(), "bar")
	}
	if string(c.Path()) != "/aa/bb" {
		t.Fatalf("unexpected cookie path %q. Expected %q", c.Path(), "/aa/bb")
	}
	if string(c.Domain()) != "aaa.com" {
		t.Fatalf("unexpected cookie domain %q. Expected %q", c.Domain(), "aaa.com")
	}

	c.SetKey("aaaaa")
	if !h.Cookie(&c) {
		t.Fatalf("cannot obtain %q cookie", c.Key())
	}
	if string(c.Value()) != "bxx" {
		t.Fatalf("unexpected cookie value %q. Expecting %q", c.Value(), "bxx")
	}
}

func TestResponseHeaderVisitAll(t *testing.T) {
	var h ResponseHeader

	r := bytes.NewBufferString("HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 123\r\nSet-Cookie: aa=bb; path=/foo/bar\r\nSet-Cookie: ccc\r\n\r\n")
	br := bufio.NewReader(r)
	if err := h.Read(br); err != nil {
		t.Fatalf("Unepxected error: %s", err)
	}

	if h.Len() != 4 {
		t.Fatalf("Unexpected number of headers: %d. Expected 4", h.Len())
	}
	contentLengthCount := 0
	contentTypeCount := 0
	cookieCount := 0
	h.VisitAll(func(key, value []byte) {
		k := string(key)
		v := string(value)
		switch k {
		case "Content-Length":
			if v != string(h.Peek(k)) {
				t.Fatalf("unexpected content-length: %q. Expecting %q", v, h.Peek(k))
			}
			contentLengthCount++
		case "Content-Type":
			if v != string(h.Peek(k)) {
				t.Fatalf("Unexpected content-type: %q. Expected %q", v, h.Peek(k))
			}
			contentTypeCount++
		case "Set-Cookie":
			if cookieCount == 0 && v != "aa=bb; path=/foo/bar" {
				t.Fatalf("unexpected cookie header: %q. Expected %q", v, "aa=bb; path=/foo/bar")
			}
			if cookieCount == 1 && v != "ccc" {
				t.Fatalf("unexpected cookie header: %q. Expected %q", v, "ccc")
			}
			cookieCount++
		default:
			t.Fatalf("unexpected header %q=%q", k, v)
		}
	})
	if contentLengthCount != 1 {
		t.Fatalf("unexpected number of content-length headers: %d. Expected 1", contentLengthCount)
	}
	if contentTypeCount != 1 {
		t.Fatalf("unexpected number of content-type headers: %d. Expected 1", contentTypeCount)
	}
	if cookieCount != 2 {
		t.Fatalf("unexpected number of cookie header: %d. Expected 2", cookieCount)
	}
}

func TestRequestHeaderVisitAll(t *testing.T) {
	var h RequestHeader

	r := bytes.NewBufferString("GET / HTTP/1.1\r\nHost: aa.com\r\nXX: YYY\r\nXX: ZZ\r\nCookie: a=b; c=d\r\n\r\n")
	br := bufio.NewReader(r)
	if err := h.Read(br); err != nil {
		t.Fatalf("Unexpected error: %s", err)
	}

	if h.Len() != 4 {
		t.Fatalf("Unexpected number of header: %d. Expected 4", h.Len())
	}
	hostCount := 0
	xxCount := 0
	cookieCount := 0
	h.VisitAll(func(key, value []byte) {
		k := string(key)
		v := string(value)
		switch k {
		case "Host":
			if v != string(h.Peek(k)) {
				t.Fatalf("Unexpected host value %q. Expected %q", v, h.Peek(k))
			}
			hostCount++
		case "Xx":
			if xxCount == 0 && v != "YYY" {
				t.Fatalf("Unexpected value %q. Expected %q", v, "YYY")
			}
			if xxCount == 1 && v != "ZZ" {
				t.Fatalf("Unexpected value %q. Expected %q", v, "ZZ")
			}
			xxCount++
		case "Cookie":
			if v != "a=b; c=d" {
				t.Fatalf("Unexpected cookie %q. Expected %q", v, "a=b; c=d")
			}
			cookieCount++
		default:
			t.Fatalf("Unepxected header %q=%q", k, v)
		}
	})
	if hostCount != 1 {
		t.Fatalf("Unepxected number of host headers detected %d. Expected 1", hostCount)
	}
	if xxCount != 2 {
		t.Fatalf("Unexpected number of xx headers detected %d. Expected 2", xxCount)
	}
	if cookieCount != 1 {
		t.Fatalf("Unexpected number of cookie headers %d. Expected 1", cookieCount)
	}
}

func TestResponseHeaderCookie(t *testing.T) {
	var h ResponseHeader
	var c Cookie

	c.SetKey("foobar")
	c.SetValue("aaa")
	h.SetCookie(&c)

	c.SetKey("йцук")
	c.SetDomain("foobar.com")
	h.SetCookie(&c)

	c.Reset()
	c.SetKey("foobar")
	if !h.Cookie(&c) {
		t.Fatalf("Cannot find cookie %q", c.Key())
	}

	var expectedC1 Cookie
	expectedC1.SetKey("foobar")
	expectedC1.SetValue("aaa")
	if !equalCookie(&expectedC1, &c) {
		t.Fatalf("unexpected cookie\n%#v\nExpected\n%#v\n", &c, &expectedC1)
	}

	c.SetKey("йцук")
	if !h.Cookie(&c) {
		t.Fatalf("cannot find cookie %q", c.Key())
	}

	var expectedC2 Cookie
	expectedC2.SetKey("йцук")
	expectedC2.SetValue("aaa")
	expectedC2.SetDomain("foobar.com")
	if !equalCookie(&expectedC2, &c) {
		t.Fatalf("unexpected cookie\n%v\nExpected\n%v\n", &c, &expectedC2)
	}

	h.VisitAllCookie(func(key, value []byte) {
		var cc Cookie
		cc.ParseBytes(value)
		if !bytes.Equal(key, cc.Key()) {
			t.Fatalf("Unexpected cookie key %q. Expected %q", key, cc.Key())
		}
		switch {
		case bytes.Equal(key, []byte("foobar")):
			if !equalCookie(&expectedC1, &cc) {
				t.Fatalf("unexpected cookie\n%v\nExpected\n%v\n", &cc, &expectedC1)
			}
		case bytes.Equal(key, []byte("йцук")):
			if !equalCookie(&expectedC2, &cc) {
				t.Fatalf("unexpected cookie\n%v\nExpected\n%v\n", &cc, &expectedC2)
			}
		default:
			t.Fatalf("unexpected cookie key %q", key)
		}
	})

	w := &bytes.Buffer{}
	bw := bufio.NewWriter(w)
	if err := h.Write(bw); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	if err := bw.Flush(); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	h.DelAllCookies()

	var h1 ResponseHeader
	br := bufio.NewReader(w)
	if err := h1.Read(br); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	c.SetKey("foobar")
	if !h1.Cookie(&c) {
		t.Fatalf("Cannot find cookie %q", c.Key())
	}
	if !equalCookie(&expectedC1, &c) {
		t.Fatalf("unexpected cookie\n%v\nExpected\n%v\n", &c, &expectedC1)
	}

	h1.DelCookie("foobar")
	if h.Cookie(&c) {
		t.Fatalf("Unexpected cookie found: %v", &c)
	}
	if h1.Cookie(&c) {
		t.Fatalf("Unexpected cookie found: %v", &c)
	}

	c.SetKey("йцук")
	if !h1.Cookie(&c) {
		t.Fatalf("cannot find cookie %q", c.Key())
	}
	if !equalCookie(&expectedC2, &c) {
		t.Fatalf("unexpected cookie\n%v\nExpected\n%v\n", &c, &expectedC2)
	}

	h1.DelCookie("йцук")
	if h.Cookie(&c) {
		t.Fatalf("Unexpected cookie found: %v", &c)
	}
	if h1.Cookie(&c) {
		t.Fatalf("Unexpected cookie found: %v", &c)
	}
}

func equalCookie(c1, c2 *Cookie) bool {
	if !bytes.Equal(c1.Key(), c2.Key()) {
		return false
	}
	if !bytes.Equal(c1.Value(), c2.Value()) {
		return false
	}
	if !c1.Expire().Equal(c2.Expire()) {
		return false
	}
	if !bytes.Equal(c1.Domain(), c2.Domain()) {
		return false
	}
	if !bytes.Equal(c1.Path(), c2.Path()) {
		return false
	}
	return true
}

func TestRequestHeaderCookie(t *testing.T) {
	var h RequestHeader
	h.SetRequestURI("/foobar")
	h.Set("Host", "foobar.com")

	h.SetCookie("foo", "bar")
	h.SetCookie("привет", "мир")

	if string(h.Cookie("foo")) != "bar" {
		t.Fatalf("Unexpected cookie value %q. Exepcted %q", h.Cookie("foo"), "bar")
	}
	if string(h.Cookie("привет")) != "мир" {
		t.Fatalf("Unexpected cookie value %q. Expected %q", h.Cookie("привет"), "мир")
	}

	w := &bytes.Buffer{}
	bw := bufio.NewWriter(w)
	if err := h.Write(bw); err != nil {
		t.Fatalf("Unexpected error: %s", err)
	}
	if err := bw.Flush(); err != nil {
		t.Fatalf("Unexpected error: %s", err)
	}

	var h1 RequestHeader
	br := bufio.NewReader(w)
	if err := h1.Read(br); err != nil {
		t.Fatalf("Unexpected error: %s", err)
	}

	if !bytes.Equal(h1.Cookie("foo"), h.Cookie("foo")) {
		t.Fatalf("Unexpected cookie value %q. Exepcted %q", h1.Cookie("foo"), h.Cookie("foo"))
	}
	h1.DelCookie("foo")
	if len(h1.Cookie("foo")) > 0 {
		t.Fatalf("Unexpected cookie found: %q", h1.Cookie("foo"))
	}
	if !bytes.Equal(h1.Cookie("привет"), h.Cookie("привет")) {
		t.Fatalf("Unexpected cookie value %q. Expected %q", h1.Cookie("привет"), h.Cookie("привет"))
	}
	h1.DelCookie("привет")
	if len(h1.Cookie("привет")) > 0 {
		t.Fatalf("Unexpected cookie found: %q", h1.Cookie("привет"))
	}

	h.DelAllCookies()
	if len(h.Cookie("foo")) > 0 {
		t.Fatalf("Unexpected cookie found: %q", h.Cookie("foo"))
	}
	if len(h.Cookie("привет")) > 0 {
		t.Fatalf("Unexpected cookie found: %q", h.Cookie("привет"))
	}
}

func TestRequestHeaderMethod(t *testing.T) {
	// common http methods
	testRequestHeaderMethod(t, "GET")
	testRequestHeaderMethod(t, "POST")
	testRequestHeaderMethod(t, "HEAD")
	testRequestHeaderMethod(t, "DELETE")

	// non-http methods
	testRequestHeaderMethod(t, "foobar")
	testRequestHeaderMethod(t, "ABC")
}

func testRequestHeaderMethod(t *testing.T, expectedMethod string) {
	var h RequestHeader
	h.SetMethod(expectedMethod)
	m := h.Method()
	if string(m) != expectedMethod {
		t.Fatalf("unexpected method: %q. Expecting %q", m, expectedMethod)
	}

	s := h.String()
	var h1 RequestHeader
	br := bufio.NewReader(bytes.NewBufferString(s))
	if err := h1.Read(br); err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	m1 := h1.Method()
	if string(m) != string(m1) {
		t.Fatalf("unexpected method: %q. Expecting %q", m, m1)
	}
}

func TestRequestHeaderSetGet(t *testing.T) {
	h := &RequestHeader{}
	h.SetRequestURI("/aa/bbb")
	h.SetMethod("POST")
	h.Set("foo", "bar")
	h.Set("host", "12345")
	h.Set("content-type", "aaa/bbb")
	h.Set("content-length", "1234")
	h.Set("user-agent", "aaabbb")
	h.Set("referer", "axcv")
	h.Set("baz", "xxxxx")
	h.Set("transfer-encoding", "chunked")
	h.Set("connection", "close")

	expectRequestHeaderGet(t, h, "Foo", "bar")
	expectRequestHeaderGet(t, h, "Host", "12345")
	expectRequestHeaderGet(t, h, "Content-Type", "aaa/bbb")
	expectRequestHeaderGet(t, h, "Content-Length", "1234")
	expectRequestHeaderGet(t, h, "USER-AGent", "aaabbb")
	expectRequestHeaderGet(t, h, "Referer", "axcv")
	expectRequestHeaderGet(t, h, "baz", "xxxxx")
	expectRequestHeaderGet(t, h, "Transfer-Encoding", "")
	expectRequestHeaderGet(t, h, "connecTION", "close")
	if !h.ConnectionClose() {
		t.Fatalf("unset connection: close")
	}

	if h.ContentLength() != 1234 {
		t.Fatalf("Unexpected content-length %d. Expected %d", h.ContentLength(), 1234)
	}

	w := &bytes.Buffer{}
	bw := bufio.NewWriter(w)
	err := h.Write(bw)
	if err != nil {
		t.Fatalf("Unexpected error when writing request header: %s", err)
	}
	if err := bw.Flush(); err != nil {
		t.Fatalf("Unexpected error when flushing request header: %s", err)
	}

	var h1 RequestHeader
	br := bufio.NewReader(w)
	if err = h1.Read(br); err != nil {
		t.Fatalf("Unexpected error when reading request header: %s", err)
	}

	if h1.ContentLength() != h.ContentLength() {
		t.Fatalf("Unexpected Content-Length %d. Expected %d", h1.ContentLength(), h.ContentLength())
	}

	expectRequestHeaderGet(t, &h1, "Foo", "bar")
	expectRequestHeaderGet(t, &h1, "HOST", "12345")
	expectRequestHeaderGet(t, &h1, "Content-Type", "aaa/bbb")
	expectRequestHeaderGet(t, &h1, "Content-Length", "1234")
	expectRequestHeaderGet(t, &h1, "USER-AGent", "aaabbb")
	expectRequestHeaderGet(t, &h1, "Referer", "axcv")
	expectRequestHeaderGet(t, &h1, "baz", "xxxxx")
	expectRequestHeaderGet(t, &h1, "Transfer-Encoding", "")
	expectRequestHeaderGet(t, &h1, "Connection", "close")
	if !h1.ConnectionClose() {
		t.Fatalf("unset connection: close")
	}
}

func TestResponseHeaderSetGet(t *testing.T) {
	h := &ResponseHeader{}
	h.Set("foo", "bar")
	h.Set("content-type", "aaa/bbb")
	h.Set("connection", "close")
	h.Set("content-length", "1234")
	h.Set("Server", "aaaa")
	h.Set("baz", "xxxxx")
	h.Set("Transfer-Encoding", "chunked")

	expectResponseHeaderGet(t, h, "Foo", "bar")
	expectResponseHeaderGet(t, h, "Content-Type", "aaa/bbb")
	expectResponseHeaderGet(t, h, "Connection", "close")
	expectResponseHeaderGet(t, h, "Content-Length", "1234")
	expectResponseHeaderGet(t, h, "seRVer", "aaaa")
	expectResponseHeaderGet(t, h, "baz", "xxxxx")
	expectResponseHeaderGet(t, h, "Transfer-Encoding", "")

	if h.ContentLength() != 1234 {
		t.Fatalf("Unexpected content-length %d. Expected %d", h.ContentLength(), 1234)
	}
	if !h.ConnectionClose() {
		t.Fatalf("Unexpected Connection: close value %v. Expected %v", h.ConnectionClose(), true)
	}

	w := &bytes.Buffer{}
	bw := bufio.NewWriter(w)
	err := h.Write(bw)
	if err != nil {
		t.Fatalf("Unexpected error when writing response header: %s", err)
	}
	if err := bw.Flush(); err != nil {
		t.Fatalf("Unexpected error when flushing response header: %s", err)
	}

	var h1 ResponseHeader
	br := bufio.NewReader(w)
	if err = h1.Read(br); err != nil {
		t.Fatalf("Unexpected error when reading response header: %s", err)
	}

	if h1.ContentLength() != h.ContentLength() {
		t.Fatalf("Unexpected Content-Length %d. Expected %d", h1.ContentLength(), h.ContentLength())
	}
	if h1.ConnectionClose() != h.ConnectionClose() {
		t.Fatalf("unexpected connection: close %v. Expected %v", h1.ConnectionClose(), h.ConnectionClose())
	}

	expectResponseHeaderGet(t, &h1, "Foo", "bar")
	expectResponseHeaderGet(t, &h1, "Content-Type", "aaa/bbb")
	expectResponseHeaderGet(t, &h1, "Connection", "close")
	expectResponseHeaderGet(t, &h1, "seRVer", "aaaa")
	expectResponseHeaderGet(t, &h1, "baz", "xxxxx")
}

func expectRequestHeaderGet(t *testing.T, h *RequestHeader, key, expectedValue string) {
	if string(h.Peek(key)) != expectedValue {
		t.Fatalf("Unexpected value for key %q: %q. Expected %q", key, h.Peek(key), expectedValue)
	}
}

func expectResponseHeaderGet(t *testing.T, h *ResponseHeader, key, expectedValue string) {
	if string(h.Peek(key)) != expectedValue {
		t.Fatalf("Unexpected value for key %q: %q. Expected %q", key, h.Peek(key), expectedValue)
	}
}

func TestResponseHeaderConnectionClose(t *testing.T) {
	testResponseHeaderConnectionClose(t, true)
	testResponseHeaderConnectionClose(t, false)
}

func testResponseHeaderConnectionClose(t *testing.T, connectionClose bool) {
	h := &ResponseHeader{}
	if connectionClose {
		h.SetConnectionClose()
	}
	h.SetContentLength(123)

	w := &bytes.Buffer{}
	bw := bufio.NewWriter(w)
	err := h.Write(bw)
	if err != nil {
		t.Fatalf("Unexpected error when writing response header: %s", err)
	}
	if err := bw.Flush(); err != nil {
		t.Fatalf("Unexpected error when flushing response header: %s", err)
	}

	var h1 ResponseHeader
	br := bufio.NewReader(w)
	err = h1.Read(br)
	if err != nil {
		t.Fatalf("Unexpected error when reading response header: %s", err)
	}
	if h1.ConnectionClose() != h.ConnectionClose() {
		t.Fatalf("Unexpected value for ConnectionClose: %v. Expected %v", h1.ConnectionClose(), h.ConnectionClose())
	}
}

func TestRequestHeaderTooBig(t *testing.T) {
	s := "GET / HTTP/1.1\r\nHost: aaa.com\r\n" + getHeaders(10500) + "\r\n"
	r := bytes.NewBufferString(s)
	br := bufio.NewReaderSize(r, 4096)
	h := &RequestHeader{}
	err := h.Read(br)
	if err == nil {
		t.Fatalf("Expecting error when reading too big header")
	}
}

func TestResponseHeaderTooBig(t *testing.T) {
	s := "HTTP/1.1 200 OK\r\nContent-Type: sss\r\nContent-Length: 0\r\n" + getHeaders(100500) + "\r\n"
	r := bytes.NewBufferString(s)
	br := bufio.NewReaderSize(r, 4096)
	h := &ResponseHeader{}
	err := h.Read(br)
	if err == nil {
		t.Fatalf("Expecting error when reading too big header")
	}
}

type bufioPeekReader struct {
	s string
	n int
}

func (r *bufioPeekReader) Read(b []byte) (int, error) {
	if len(r.s) == 0 {
		return 0, io.EOF
	}

	r.n++
	n := r.n
	if len(r.s) < n {
		n = len(r.s)
	}
	src := []byte(r.s[:n])
	r.s = r.s[n:]
	n = copy(b, src)
	return n, nil
}

func TestRequestHeaderBufioPeek(t *testing.T) {
	r := &bufioPeekReader{
		s: "GET / HTTP/1.1\r\nHost: foobar.com\r\n" + getHeaders(10) + "\r\naaaa",
	}
	br := bufio.NewReaderSize(r, 4096)
	h := &RequestHeader{}
	if err := h.Read(br); err != nil {
		t.Fatalf("Unexpected error when reading request: %s", err)
	}
	verifyRequestHeader(t, h, 0, "/", "foobar.com", "", "")
	verifyTrailer(t, br, "aaaa")
}

func TestResponseHeaderBufioPeek(t *testing.T) {
	r := &bufioPeekReader{
		s: "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nContent-Type: aaa\r\n" + getHeaders(10) + "\r\n0123456789",
	}
	br := bufio.NewReaderSize(r, 4096)
	h := &ResponseHeader{}
	if err := h.Read(br); err != nil {
		t.Fatalf("Unexpected error when reading response: %s", err)
	}
	verifyResponseHeader(t, h, 200, 10, "aaa")
	verifyTrailer(t, br, "0123456789")
}

func getHeaders(n int) string {
	var h []string
	for i := 0; i < n; i++ {
		h = append(h, fmt.Sprintf("Header_%d: Value_%d\r\n", i, i))
	}
	return strings.Join(h, "")
}

func TestResponseHeaderReadSuccess(t *testing.T) {
	h := &ResponseHeader{}

	// straight order of content-length and content-type
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n",
		200, 123, "text/html", "")
	if h.ConnectionClose() {
		t.Fatalf("unexpected connection: close")
	}

	// reverse order of content-length and content-type
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 202 OK\r\nContent-Type: text/plain; encoding=utf-8\r\nContent-Length: 543\r\nConnection: close\r\n\r\n",
		202, 543, "text/plain; encoding=utf-8", "")
	if !h.ConnectionClose() {
		t.Fatalf("expecting connection: close")
	}

	// tranfer-encoding: chunked
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 505 Internal error\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n",
		505, -1, "text/html", "")
	if h.ConnectionClose() {
		t.Fatalf("unexpected connection: close")
	}

	// reverse order of content-type and tranfer-encoding
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 343 foobar\r\nTransfer-Encoding: chunked\r\nContent-Type: text/json\r\n\r\n",
		343, -1, "text/json", "")

	// additional headers
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 100 Continue\r\nFoobar: baz\r\nContent-Type: aaa/bbb\r\nUser-Agent: x\r\nContent-Length: 123\r\nZZZ: werer\r\n\r\n",
		100, 123, "aaa/bbb", "")

	// trailer (aka body)
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 32245\r\n\r\nqwert aaa",
		200, 32245, "text/plain", "qwert aaa")

	// ancient http protocol
	testResponseHeaderReadSuccess(t, h, "HTTP/0.9 300 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\nqqqq",
		300, 123, "text/html", "qqqq")

	// lf instead of crlf
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\nContent-Length: 123\nContent-Type: text/html\n\n",
		200, 123, "text/html", "")

	// Zero-length headers with mixed crlf and lf
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\nContent-Length: 345\nZero-Value: \r\nContent-Type: aaa\n: zero-key\r\n\r\nooa",
		400, 345, "aaa", "ooa")

	// No space after colon
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\nContent-Length:34\nContent-Type: sss\n\naaaa",
		200, 34, "sss", "aaaa")

	// invalid case
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\nconTEnt-leNGTH: 123\nConTENT-TYPE: ass\n\n",
		400, 123, "ass", "")

	// duplicate content-length
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 456\r\nContent-Type: foo/bar\r\nContent-Length: 321\r\n\r\n",
		200, 321, "foo/bar", "")

	// duplicate content-type
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 234\r\nContent-Type: foo/bar\r\nContent-Type: baz/bar\r\n\r\n",
		200, 234, "baz/bar", "")

	// both transfer-encoding: chunked and content-length
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: foo/bar\r\nContent-Length: 123\r\nTransfer-Encoding: chunked\r\n\r\n",
		200, -1, "foo/bar", "")

	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 300 OK\r\nContent-Type: foo/barr\r\nTransfer-Encoding: chunked\r\nContent-Length: 354\r\n\r\n",
		300, -1, "foo/barr", "")

	// duplicate transfer-encoding: chunked
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\nTransfer-Encoding: chunked\r\n\r\n",
		200, -1, "text/html", "")

	// no reason string in the first line
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 456\r\nContent-Type: xxx/yyy\r\nContent-Length: 134\r\n\r\naaaxxx",
		456, 134, "xxx/yyy", "aaaxxx")

	// blank lines before the first line
	testResponseHeaderReadSuccess(t, h, "\r\nHTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 0\r\n\r\nsss",
		200, 0, "aa", "sss")
	if h.ConnectionClose() {
		t.Fatalf("unexpected connection: close")
	}

	// no content-length (informational responses)
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 101 OK\r\n\r\n",
		101, -2, "text/plain; charset=utf-8", "")
	if h.ConnectionClose() {
		t.Fatalf("expecting connection: keep-alive for informational response")
	}

	// no content-length (no-content responses)
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 204 OK\r\n\r\n",
		204, -2, "text/plain; charset=utf-8", "")
	if h.ConnectionClose() {
		t.Fatalf("expecting connection: keep-alive for no-content response")
	}

	// no content-length (not-modified responses)
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 304 OK\r\n\r\n",
		304, -2, "text/plain; charset=utf-8", "")
	if h.ConnectionClose() {
		t.Fatalf("expecting connection: keep-alive for not-modified response")
	}

	// no content-length (identity transfer-encoding)
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: foo/bar\r\n\r\nabcdefg",
		200, -2, "foo/bar", "abcdefg")
	if !h.ConnectionClose() {
		t.Fatalf("expecting connection: close for identity response")
	}

	// non-numeric content-length
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: faaa\r\nContent-Type: text/html\r\n\r\nfoobar",
		200, -2, "text/html", "foobar")
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 201 OK\r\nContent-Length: 123aa\r\nContent-Type: text/ht\r\n\r\naaa",
		201, -2, "text/ht", "aaa")
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: aa124\r\nContent-Type: html\r\n\r\nxx",
		200, -2, "html", "xx")

	// no content-type
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\r\nContent-Length: 123\r\n\r\nfoiaaa",
		400, 123, string(defaultContentType), "foiaaa")

	// no headers
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\n\r\naaaabbb",
		200, -2, string(defaultContentType), "aaaabbb")
	if !h.IsHTTP11() {
		t.Fatalf("expecting http/1.1 protocol")
	}

	// ancient http protocol
	testResponseHeaderReadSuccess(t, h, "HTTP/1.0 203 OK\r\nContent-Length: 123\r\nContent-Type: foobar\r\n\r\naaa",
		203, 123, "foobar", "aaa")
	if h.IsHTTP11() {
		t.Fatalf("ancient protocol must be non-http/1.1")
	}
	if !h.ConnectionClose() {
		t.Fatalf("expecting connection: close for ancient protocol")
	}

	// ancient http protocol with 'Connection: keep-alive' header.
	testResponseHeaderReadSuccess(t, h, "HTTP/1.0 403 aa\r\nContent-Length: 0\r\nContent-Type: 2\r\nConnection: Keep-Alive\r\n\r\nww",
		403, 0, "2", "ww")
	if h.IsHTTP11() {
		t.Fatalf("ancient protocol must be non-http/1.1")
	}
	if h.ConnectionClose() {
		t.Fatalf("expecting connection: keep-alive for ancient protocol")
	}
}

func TestRequestHeaderReadSuccess(t *testing.T) {
	h := &RequestHeader{}

	// simple headers
	testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nHost: google.com\r\n\r\n",
		0, "/foo/bar", "google.com", "", "", "")
	if h.ConnectionClose() {
		t.Fatalf("unexpected connection: close header")
	}

	// simple headers with body
	testRequestHeaderReadSuccess(t, h, "GET /a/bar HTTP/1.1\r\nHost: gole.com\r\nconneCTION: close\r\n\r\nfoobar",
		0, "/a/bar", "gole.com", "", "", "foobar")
	if !h.ConnectionClose() {
		t.Fatalf("connection: close unset")
	}

	// ancient http protocol
	testRequestHeaderReadSuccess(t, h, "GET /bar HTTP/1.0\r\nHost: gole\r\n\r\npppp",
		0, "/bar", "gole", "", "", "pppp")
	if h.IsHTTP11() {
		t.Fatalf("ancient http protocol cannot be http/1.1")
	}
	if !h.ConnectionClose() {
		t.Fatalf("expecting connectionClose for ancient http protocol")
	}

	// ancient http protocol with 'Connection: keep-alive' header
	testRequestHeaderReadSuccess(t, h, "GET /aa HTTP/1.0\r\nHost: bb\r\nConnection: keep-alive\r\n\r\nxxx",
		0, "/aa", "bb", "", "", "xxx")
	if h.IsHTTP11() {
		t.Fatalf("ancient http protocol cannot be http/1.1")
	}
	if h.ConnectionClose() {
		t.Fatalf("unexpected 'connection: close' for ancient http protocol")
	}

	// complex headers with body
	testRequestHeaderReadSuccess(t, h, "GET /aabar HTTP/1.1\r\nAAA: bbb\r\nHost: ole.com\r\nAA: bb\r\n\r\nzzz",
		0, "/aabar", "ole.com", "", "", "zzz")
	if !h.IsHTTP11() {
		t.Fatalf("expecting http/1.1 protocol")
	}
	if h.ConnectionClose() {
		t.Fatalf("unexpected connection: close")
	}

	// lf instead of crlf
	testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\nHost: google.com\n\n",
		0, "/foo/bar", "google.com", "", "", "")

	// post method
	testRequestHeaderReadSuccess(t, h, "POST /aaa?bbb HTTP/1.1\r\nHost: foobar.com\r\nContent-Length: 1235\r\nContent-Type: aaa\r\n\r\nabcdef",
		1235, "/aaa?bbb", "foobar.com", "", "aaa", "abcdef")

	// zero-length headers with mixed crlf and lf
	testRequestHeaderReadSuccess(t, h, "GET /a HTTP/1.1\nHost: aaa\r\nZero: \n: Zero-Value\n\r\nxccv",
		0, "/a", "aaa", "", "", "xccv")

	// no space after colon
	testRequestHeaderReadSuccess(t, h, "GET /a HTTP/1.1\nHost:aaaxd\n\nsdfds",
		0, "/a", "aaaxd", "", "", "sdfds")

	// get with zero content-length
	testRequestHeaderReadSuccess(t, h, "GET /xxx HTTP/1.1\nHost: aaa.com\nContent-Length: 0\n\n",
		0, "/xxx", "aaa.com", "", "", "")

	// get with non-zero content-length
	testRequestHeaderReadSuccess(t, h, "GET /xxx HTTP/1.1\nHost: aaa.com\nContent-Length: 123\n\n",
		0, "/xxx", "aaa.com", "", "", "")

	// invalid case
	testRequestHeaderReadSuccess(t, h, "GET /aaa HTTP/1.1\nhoST: bbb.com\n\naas",
		0, "/aaa", "bbb.com", "", "", "aas")

	// referer
	testRequestHeaderReadSuccess(t, h, "GET /asdf HTTP/1.1\nHost: aaa.com\nReferer: bb.com\n\naaa",
		0, "/asdf", "aaa.com", "bb.com", "", "aaa")

	// duplicate host
	testRequestHeaderReadSuccess(t, h, "GET /aa HTTP/1.1\r\nHost: aaaaaa.com\r\nHost: bb.com\r\n\r\n",
		0, "/aa", "bb.com", "", "", "")

	// post with duplicate content-type
	testRequestHeaderReadSuccess(t, h, "POST /a HTTP/1.1\r\nHost: aa\r\nContent-Type: ab\r\nContent-Length: 123\r\nContent-Type: xx\r\n\r\n",
		123, "/a", "aa", "", "xx", "")

	// post with duplicate content-length
	testRequestHeaderReadSuccess(t, h, "POST /xx HTTP/1.1\r\nHost: aa\r\nContent-Type: s\r\nContent-Length: 13\r\nContent-Length: 1\r\n\r\n",
		1, "/xx", "aa", "", "s", "")

	// non-post with content-type
	testRequestHeaderReadSuccess(t, h, "GET /aaa HTTP/1.1\r\nHost: bbb.com\r\nContent-Type: aaab\r\n\r\n",
		0, "/aaa", "bbb.com", "", "aaab", "")

	// non-post with content-length
	testRequestHeaderReadSuccess(t, h, "HEAD / HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 123\r\n\r\n",
		0, "/", "aaa.com", "", "", "")

	// non-post with content-type and content-length
	testRequestHeaderReadSuccess(t, h, "GET /aa HTTP/1.1\r\nHost: aa.com\r\nContent-Type: abd/test\r\nContent-Length: 123\r\n\r\n",
		0, "/aa", "aa.com", "", "abd/test", "")

	// request uri with hostname
	testRequestHeaderReadSuccess(t, h, "GET http://gooGle.com/foO/%20bar?xxx#aaa HTTP/1.1\r\nHost: aa.cOM\r\n\r\ntrail",
		0, "http://gooGle.com/foO/%20bar?xxx#aaa", "aa.cOM", "", "", "trail")

	// no protocol in the first line
	testRequestHeaderReadSuccess(t, h, "GET /foo/bar\r\nHost: google.com\r\n\r\nisdD",
		0, "/foo/bar", "google.com", "", "", "isdD")

	// blank lines before the first line
	testRequestHeaderReadSuccess(t, h, "\r\n\n\r\nGET /aaa HTTP/1.1\r\nHost: aaa.com\r\n\r\nsss",
		0, "/aaa", "aaa.com", "", "", "sss")

	// request uri with spaces
	testRequestHeaderReadSuccess(t, h, "GET /foo/ bar baz HTTP/1.1\r\nHost: aa.com\r\n\r\nxxx",
		0, "/foo/ bar baz", "aa.com", "", "", "xxx")

	// no host
	testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nFOObar: assdfd\r\n\r\naaa",
		0, "/foo/bar", "", "", "", "aaa")

	// no host, no headers
	testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\n\r\nfoobar",
		0, "/foo/bar", "", "", "", "foobar")

	// post with invalid content-length
	testRequestHeaderReadSuccess(t, h, "POST /a HTTP/1.1\r\nHost: bb\r\nContent-Type: aa\r\nContent-Length: dff\r\n\r\nqwerty",
		-2, "/a", "bb", "", "aa", "qwerty")

	// post without content-length and content-type
	testRequestHeaderReadSuccess(t, h, "POST /aaa HTTP/1.1\r\nHost: aaa.com\r\n\r\nzxc",
		-2, "/aaa", "aaa.com", "", "", "zxc")

	// post without content-type
	testRequestHeaderReadSuccess(t, h, "POST /abc HTTP/1.1\r\nHost: aa.com\r\nContent-Length: 123\r\n\r\npoiuy",
		123, "/abc", "aa.com", "", "", "poiuy")

	// post without content-length
	testRequestHeaderReadSuccess(t, h, "POST /abc HTTP/1.1\r\nHost: aa.com\r\nContent-Type: adv\r\n\r\n123456",
		-2, "/abc", "aa.com", "", "adv", "123456")

	// invalid method
	testRequestHeaderReadSuccess(t, h, "POST /foo/bar HTTP/1.1\r\nHost: google.com\r\n\r\nmnbv",
		-2, "/foo/bar", "google.com", "", "", "mnbv")

	// put request
	testRequestHeaderReadSuccess(t, h, "PUT /faa HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 123\r\nContent-Type: aaa\r\n\r\nxwwere",
		123, "/faa", "aaa.com", "", "aaa", "xwwere")
}

func TestResponseHeaderReadError(t *testing.T) {
	h := &ResponseHeader{}

	// incorrect first line
	testResponseHeaderReadError(t, h, "")
	testResponseHeaderReadError(t, h, "fo")
	testResponseHeaderReadError(t, h, "foobarbaz")
	testResponseHeaderReadError(t, h, "HTTP/1.1")
	testResponseHeaderReadError(t, h, "HTTP/1.1 ")
	testResponseHeaderReadError(t, h, "HTTP/1.1 s")

	// non-numeric status code
	testResponseHeaderReadError(t, h, "HTTP/1.1 foobar OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n")
	testResponseHeaderReadError(t, h, "HTTP/1.1 123foobar OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n")
	testResponseHeaderReadError(t, h, "HTTP/1.1 foobar344 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n")

	// no headers
	testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\n")

	// no trailing crlf
	testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n")
}

func TestRequestHeaderReadError(t *testing.T) {
	h := &RequestHeader{}

	// incorrect first line
	testRequestHeaderReadError(t, h, "")
	testRequestHeaderReadError(t, h, "fo")
	testRequestHeaderReadError(t, h, "GET ")
	testRequestHeaderReadError(t, h, "GET / HTTP/1.1\r")

	// missing RequestURI
	testRequestHeaderReadError(t, h, "GET  HTTP/1.1\r\nHost: google.com\r\n\r\n")
}

func testResponseHeaderReadError(t *testing.T, h *ResponseHeader, headers string) {
	r := bytes.NewBufferString(headers)
	br := bufio.NewReader(r)
	err := h.Read(br)
	if err == nil {
		t.Fatalf("Expecting error when reading response header %q", headers)
	}

	// make sure response header works after error
	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: foo/bar\r\nContent-Length: 12345\r\n\r\nsss",
		200, 12345, "foo/bar", "sss")
}

func testRequestHeaderReadError(t *testing.T, h *RequestHeader, headers string) {
	r := bytes.NewBufferString(headers)
	br := bufio.NewReader(r)
	err := h.Read(br)
	if err == nil {
		t.Fatalf("Expecting error when reading request header %q", headers)
	}

	// make sure request header works after error
	testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nHost: aaaa\r\n\r\nxxx",
		0, "/foo/bar", "aaaa", "", "", "xxx")
}

func testResponseHeaderReadSuccess(t *testing.T, h *ResponseHeader, headers string, expectedStatusCode, expectedContentLength int,
	expectedContentType, expectedTrailer string) {
	r := bytes.NewBufferString(headers)
	br := bufio.NewReader(r)
	err := h.Read(br)
	if err != nil {
		t.Fatalf("Unexpected error when parsing response headers: %s. headers=%q", err, headers)
	}
	verifyResponseHeader(t, h, expectedStatusCode, expectedContentLength, expectedContentType)
	verifyTrailer(t, br, expectedTrailer)
}

func testRequestHeaderReadSuccess(t *testing.T, h *RequestHeader, headers string, expectedContentLength int,
	expectedRequestURI, expectedHost, expectedReferer, expectedContentType, expectedTrailer string) {
	r := bytes.NewBufferString(headers)
	br := bufio.NewReader(r)
	err := h.Read(br)
	if err != nil {
		t.Fatalf("Unexpected error when parsing request headers: %s. headers=%q", err, headers)
	}
	verifyRequestHeader(t, h, expectedContentLength, expectedRequestURI, expectedHost, expectedReferer, expectedContentType)
	verifyTrailer(t, br, expectedTrailer)
}

func verifyResponseHeader(t *testing.T, h *ResponseHeader, expectedStatusCode, expectedContentLength int, expectedContentType string) {
	if h.StatusCode() != expectedStatusCode {
		t.Fatalf("Unexpected status code %d. Expected %d", h.StatusCode(), expectedStatusCode)
	}
	if h.ContentLength() != expectedContentLength {
		t.Fatalf("Unexpected content length %d. Expected %d", h.ContentLength(), expectedContentLength)
	}
	if string(h.Peek("Content-Type")) != expectedContentType {
		t.Fatalf("Unexpected content type %q. Expected %q", h.Peek("Content-Type"), expectedContentType)
	}
}

func verifyRequestHeader(t *testing.T, h *RequestHeader, expectedContentLength int,
	expectedRequestURI, expectedHost, expectedReferer, expectedContentType string) {
	if h.ContentLength() != expectedContentLength {
		t.Fatalf("Unexpected Content-Length %d. Expected %d", h.ContentLength(), expectedContentLength)
	}
	if string(h.RequestURI()) != expectedRequestURI {
		t.Fatalf("Unexpected RequestURI %q. Expected %q", h.RequestURI(), expectedRequestURI)
	}
	if string(h.Peek("Host")) != expectedHost {
		t.Fatalf("Unexpected host %q. Expected %q", h.Peek("Host"), expectedHost)
	}
	if string(h.Peek("Referer")) != expectedReferer {
		t.Fatalf("Unexpected referer %q. Expected %q", h.Peek("Referer"), expectedReferer)
	}
	if string(h.Peek("Content-Type")) != expectedContentType {
		t.Fatalf("Unexpected content-type %q. Expected %q", h.Peek("Content-Type"), expectedContentType)
	}
}

func verifyTrailer(t *testing.T, r *bufio.Reader, expectedTrailer string) {
	trailer, err := ioutil.ReadAll(r)
	if err != nil {
		t.Fatalf("Cannot read trailer: %s", err)
	}
	if !bytes.Equal(trailer, []byte(expectedTrailer)) {
		t.Fatalf("Unexpected trailer %q. Expected %q", trailer, expectedTrailer)
	}
}
