1973 lines
61 KiB
Go
1973 lines
61 KiB
Go
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)
|
|
}
|
|
}
|