Files
fasthttp/header_test.go
T
Sergey Ponomarev c9f43eaa1b Response.ContentEncoding(): store as field and avoid using Header.SetCanonical() (#1311)
* Response.ContentEncoding(): store as field

The CE is not so often used for plain APIs responses and even not so often used for static files and on the fly compression.
But still it should be checked each time.
Also having a dedicated field getter and setter simplifies code

* header.go Use shorter Response.setNonSpecial() and Request.setNonSpecial() methods instead of SetCanonical()

The change should improve performance because the setSpecialHeader() call is omitted.
As a downside on adding a new basic header field all putHeader() must be replaced with a direct getter and setter.
2022-06-05 15:47:59 +02:00

2864 lines
84 KiB
Go

package fasthttp
import (
"bufio"
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"net/http"
"reflect"
"strings"
"testing"
)
func TestResponseHeaderAddContentType(t *testing.T) {
t.Parallel()
var h ResponseHeader
h.Add("Content-Type", "test")
got := string(h.Peek("Content-Type"))
expected := "test"
if got != expected {
t.Errorf("expected %q got %q", expected, got)
}
var buf bytes.Buffer
h.WriteTo(&buf) //nolint:errcheck
if n := strings.Count(buf.String(), "Content-Type: "); n != 1 {
t.Errorf("Content-Type occurred %d times", n)
}
}
func TestResponseHeaderAddContentEncoding(t *testing.T) {
t.Parallel()
var h ResponseHeader
h.Add("Content-Encoding", "test")
got := string(h.Peek("Content-Encoding"))
expected := "test"
if got != expected {
t.Errorf("expected %q got %q", expected, got)
}
var buf bytes.Buffer
h.WriteTo(&buf) //nolint:errcheck
if n := strings.Count(buf.String(), "Content-Encoding: "); n != 1 {
t.Errorf("Content-Encoding occurred %d times", n)
}
}
func TestResponseHeaderMultiLineValue(t *testing.T) {
t.Parallel()
s := "HTTP/1.1 200 SuperOK\r\n" +
"EmptyValue1:\r\n" +
"Content-Type: foo/bar;\r\n\tnewline;\r\n another/newline\r\n" +
"Foo: Bar\r\n" +
"Multi-Line: one;\r\n two\r\n" +
"Values: v1;\r\n v2; v3;\r\n v4;\tv5\r\n" +
"\r\n"
header := new(ResponseHeader)
if _, err := header.parse([]byte(s)); err != nil {
t.Fatalf("parse headers with multi-line values failed, %v", err)
}
response, err := http.ReadResponse(bufio.NewReader(strings.NewReader(s)), nil)
if err != nil {
t.Fatalf("parse response using net/http failed, %v", err)
}
if !bytes.Equal(header.StatusMessage(), []byte("SuperOK")) {
t.Errorf("parse status line with non-default value failed, got: '%q' want: 'SuperOK'", header.StatusMessage())
}
header.SetProtocol([]byte("HTTP/3.3"))
if !bytes.Equal(header.Protocol(), []byte("HTTP/3.3")) {
t.Errorf("parse protocol with non-default value failed, got: '%q' want: 'HTTP/3.3'", header.Protocol())
}
if !bytes.Equal(header.appendStatusLine(nil), []byte("HTTP/3.3 200 SuperOK\r\n")) {
t.Errorf("parse status line with non-default value failed, got: '%q' want: 'HTTP/3.3 200 SuperOK'", header.Protocol())
}
header.SetStatusMessage(nil)
if !bytes.Equal(header.appendStatusLine(nil), []byte("HTTP/3.3 200 OK\r\n")) {
t.Errorf("parse status line with default protocol value failed, got: '%q' want: 'HTTP/3.3 200 OK'", header.appendStatusLine(nil))
}
header.SetStatusMessage(s2b(StatusMessage(200)))
if !bytes.Equal(header.appendStatusLine(nil), []byte("HTTP/3.3 200 OK\r\n")) {
t.Errorf("parse status line with default protocol value failed, got: '%q' want: 'HTTP/3.3 200 OK'", header.appendStatusLine(nil))
}
for name, vals := range response.Header {
got := string(header.Peek(name))
want := vals[0]
if got != want {
t.Errorf("unexpected %q got: %q want: %q", name, got, want)
}
}
}
func TestResponseHeaderMultiLineName(t *testing.T) {
t.Parallel()
s := "HTTP/1.1 200 OK\r\n" +
"Host: golang.org\r\n" +
"Gopher-New-\r\n" +
" Line: This is a header on multiple lines\r\n" +
"\r\n"
header := new(ResponseHeader)
if _, err := header.parse([]byte(s)); err != errInvalidName {
m := make(map[string]string)
header.VisitAll(func(key, value []byte) {
m[string(key)] = string(value)
})
t.Errorf("expected error, got %q (%v)", m, err)
}
if !bytes.Equal(header.StatusMessage(), []byte("OK")) {
t.Errorf("expected default status line, got: %q", header.StatusMessage())
}
if !bytes.Equal(header.Protocol(), []byte("HTTP/1.1")) {
t.Errorf("expected default protocol, got: %q", header.Protocol())
}
if !bytes.Equal(header.appendStatusLine(nil), []byte("HTTP/1.1 200 OK\r\n")) {
t.Errorf("parse status line with non-default value failed, got: %q want: HTTP/1.1 200 OK", header.Protocol())
}
}
func TestResponseHeaderMultiLinePaniced(t *testing.T) {
t.Parallel()
// Input generated by fuzz testing that caused the parser to panic.
s, _ := base64.StdEncoding.DecodeString("aAEAIDoKKDoKICA6CgkKCiA6CiA6CgkpCiA6CiA6CiA6Cig6CiAgOgoJCgogOgogOgoJKQogOgogOgogOgogOgogOgoJOg86CiA6CiA6Cig6CiAyCg==")
header := new(RequestHeader)
header.parse(s) //nolint:errcheck
}
func TestResponseHeaderEmptyValueFromHeader(t *testing.T) {
t.Parallel()
var h1 ResponseHeader
h1.SetContentType("foo/bar")
h1.Set("EmptyValue1", "")
h1.Set("EmptyValue2", " ")
s := h1.String()
var h ResponseHeader
br := bufio.NewReader(bytes.NewBufferString(s))
if err := h.Read(br); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(h.ContentType()) != string(h1.ContentType()) {
t.Fatalf("unexpected content-type: %q. Expecting %q", h.ContentType(), h1.ContentType())
}
v1 := h.Peek("EmptyValue1")
if len(v1) > 0 {
t.Fatalf("expecting empty value. Got %q", v1)
}
v2 := h.Peek("EmptyValue2")
if len(v2) > 0 {
t.Fatalf("expecting empty value. Got %q", v2)
}
}
func TestResponseHeaderEmptyValueFromString(t *testing.T) {
t.Parallel()
s := "HTTP/1.1 200 OK\r\n" +
"EmptyValue1:\r\n" +
"Content-Type: foo/bar\r\n" +
"EmptyValue2: \r\n" +
"\r\n"
var h ResponseHeader
br := bufio.NewReader(bytes.NewBufferString(s))
if err := h.Read(br); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(h.ContentType()) != "foo/bar" {
t.Fatalf("unexpected content-type: %q. Expecting %q", h.ContentType(), "foo/bar")
}
v1 := h.Peek("EmptyValue1")
if len(v1) > 0 {
t.Fatalf("expecting empty value. Got %q", v1)
}
v2 := h.Peek("EmptyValue2")
if len(v2) > 0 {
t.Fatalf("expecting empty value. Got %q", v2)
}
}
func TestRequestHeaderEmptyValueFromHeader(t *testing.T) {
t.Parallel()
var h1 RequestHeader
h1.SetRequestURI("/foo/bar")
h1.SetHost("foobar")
h1.Set("EmptyValue1", "")
h1.Set("EmptyValue2", " ")
s := h1.String()
var h RequestHeader
br := bufio.NewReader(bytes.NewBufferString(s))
if err := h.Read(br); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(h.Host()) != string(h1.Host()) {
t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), h1.Host())
}
v1 := h.Peek("EmptyValue1")
if len(v1) > 0 {
t.Fatalf("expecting empty value. Got %q", v1)
}
v2 := h.Peek("EmptyValue2")
if len(v2) > 0 {
t.Fatalf("expecting empty value. Got %q", v2)
}
}
func TestRequestHeaderEmptyValueFromString(t *testing.T) {
t.Parallel()
s := "GET / HTTP/1.1\r\n" +
"EmptyValue1:\r\n" +
"Host: foobar\r\n" +
"EmptyValue2: \r\n" +
"\r\n"
var h RequestHeader
br := bufio.NewReader(bytes.NewBufferString(s))
if err := h.Read(br); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(h.Host()) != "foobar" {
t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar")
}
v1 := h.Peek("EmptyValue1")
if len(v1) > 0 {
t.Fatalf("expecting empty value. Got %q", v1)
}
v2 := h.Peek("EmptyValue2")
if len(v2) > 0 {
t.Fatalf("expecting empty value. Got %q", v2)
}
}
func TestRequestRawHeaders(t *testing.T) {
t.Parallel()
kvs := "hOsT: foobar\r\n" +
"value: b\r\n" +
"\r\n"
t.Run("normalized", func(t *testing.T) {
s := "GET / HTTP/1.1\r\n" + kvs
exp := kvs
var h RequestHeader
br := bufio.NewReader(bytes.NewBufferString(s))
if err := h.Read(br); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(h.Host()) != "foobar" {
t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar")
}
v2 := h.Peek("Value")
if !bytes.Equal(v2, []byte{'b'}) {
t.Fatalf("expecting non empty value. Got %q", v2)
}
if raw := h.RawHeaders(); string(raw) != exp {
t.Fatalf("expected header %q, got %q", exp, raw)
}
})
for _, n := range []int{0, 1, 4, 8} {
t.Run(fmt.Sprintf("post-%dk", n), func(t *testing.T) {
l := 1024 * n
body := make([]byte, l)
for i := range body {
body[i] = 'a'
}
cl := fmt.Sprintf("Content-Length: %d\r\n", l)
s := "POST / HTTP/1.1\r\n" + cl + kvs + string(body)
exp := cl + kvs
var h RequestHeader
br := bufio.NewReader(bytes.NewBufferString(s))
if err := h.Read(br); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(h.Host()) != "foobar" {
t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar")
}
v2 := h.Peek("Value")
if !bytes.Equal(v2, []byte{'b'}) {
t.Fatalf("expecting non empty value. Got %q", v2)
}
if raw := h.RawHeaders(); string(raw) != exp {
t.Fatalf("expected header %q, got %q", exp, raw)
}
})
}
t.Run("http10", func(t *testing.T) {
s := "GET / HTTP/1.0\r\n" + kvs
exp := kvs
var h RequestHeader
br := bufio.NewReader(bytes.NewBufferString(s))
if err := h.Read(br); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(h.Host()) != "foobar" {
t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar")
}
v2 := h.Peek("Value")
if !bytes.Equal(v2, []byte{'b'}) {
t.Fatalf("expecting non empty value. Got %q", v2)
}
if raw := h.RawHeaders(); string(raw) != exp {
t.Fatalf("expected header %q, got %q", exp, raw)
}
})
t.Run("no-kvs", func(t *testing.T) {
s := "GET / HTTP/1.1\r\n\r\n"
exp := ""
var h RequestHeader
h.DisableNormalizing()
br := bufio.NewReader(bytes.NewBufferString(s))
if err := h.Read(br); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(h.Host()) != "" {
t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "")
}
v1 := h.Peek("NoKey")
if len(v1) > 0 {
t.Fatalf("expecting empty value. Got %q", v1)
}
if raw := h.RawHeaders(); string(raw) != exp {
t.Fatalf("expected header %q, got %q", exp, raw)
}
})
}
func TestRequestHeaderSetCookieWithSpecialChars(t *testing.T) {
t.Parallel()
var h RequestHeader
h.Set("Cookie", "ID&14")
s := h.String()
if !strings.Contains(s, "Cookie: ID&14") {
t.Fatalf("Missing cookie in request header: %q", s)
}
var h1 RequestHeader
br := bufio.NewReader(bytes.NewBufferString(s))
if err := h1.Read(br); err != nil {
t.Fatalf("unexpected error: %v", err)
}
cookie := h1.Peek(HeaderCookie)
if string(cookie) != "ID&14" {
t.Fatalf("unexpected cooke: %q. Expecting %q", cookie, "ID&14")
}
cookie = h1.Cookie("")
if string(cookie) != "ID&14" {
t.Fatalf("unexpected cooke: %q. Expecting %q", cookie, "ID&14")
}
}
func TestResponseHeaderDefaultStatusCode(t *testing.T) {
t.Parallel()
var h ResponseHeader
statusCode := h.StatusCode()
if statusCode != StatusOK {
t.Fatalf("unexpected status code: %d. Expecting %d", statusCode, StatusOK)
}
}
func TestResponseHeaderDelClientCookie(t *testing.T) {
t.Parallel()
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: %q. Expecting %q", 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) {
t.Parallel()
m := make(map[string]struct{})
var h ResponseHeader
h.Add("aaa", "bbb")
h.Add("content-type", "xxx")
m["bbb"] = struct{}{}
m["xxx"] = 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", "Content-Type":
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 ResponseHeader
if err := h1.Read(br); err != nil {
t.Fatalf("unexpected error: %v", err)
}
h.VisitAll(func(k, v []byte) {
switch string(k) {
case "Aaa", "Foo-Bar", "Content-Type":
m[string(v)] = struct{}{}
default:
t.Fatalf("unexpected key found: %q", k)
}
})
if len(m) != 12 {
t.Fatalf("unexpected number of headers: %d. Expecting 12", len(m))
}
}
func TestRequestHeaderAdd(t *testing.T) {
t.Parallel()
m := make(map[string]struct{})
var h RequestHeader
h.Add("aaa", "bbb")
h.Add("user-agent", "xxx")
m["bbb"] = struct{}{}
m["xxx"] = 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", "User-Agent":
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: %v", err)
}
h.VisitAll(func(k, v []byte) {
switch string(k) {
case "Aaa", "Foo-Bar", "User-Agent":
m[string(v)] = struct{}{}
default:
t.Fatalf("unexpected key found: %q", k)
}
})
if len(m) != 12 {
t.Fatalf("unexpected number of headers: %d. Expecting 12", len(m))
}
s1 := h1.String()
if s != s1 {
t.Fatalf("unexpected headers %q. Expecting %q", s1, s)
}
}
func TestHasHeaderValue(t *testing.T) {
t.Parallel()
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) {
t.Parallel()
var h RequestHeader
h.Set("Foo-Bar", "baz")
h.Set("aaa", "bbb")
h.Set(HeaderConnection, "keep-alive")
h.Set("Content-Type", "aaa")
h.Set(HeaderHost, "aaabbb")
h.Set("User-Agent", "asdfas")
h.Set("Content-Length", "1123")
h.Set("Cookie", "foobar=baz")
h.Set(HeaderTrailer, "foo, bar")
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")
h.Del("trailer")
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(HeaderConnection)
if len(hv) > 0 {
t.Fatalf("non-zero value: %q", hv)
}
hv = h.Peek(HeaderContentType)
if len(hv) > 0 {
t.Fatalf("non-zero value: %q", hv)
}
hv = h.Peek(HeaderHost)
if len(hv) > 0 {
t.Fatalf("non-zero value: %q", hv)
}
hv = h.Peek(HeaderUserAgent)
if len(hv) > 0 {
t.Fatalf("non-zero value: %q", hv)
}
hv = h.Peek(HeaderContentLength)
if len(hv) > 0 {
t.Fatalf("non-zero value: %q", hv)
}
hv = h.Peek(HeaderCookie)
if len(hv) > 0 {
t.Fatalf("non-zero value: %q", hv)
}
hv = h.Peek(HeaderTrailer)
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) {
t.Parallel()
var h ResponseHeader
h.Set("Foo-Bar", "baz")
h.Set("aaa", "bbb")
h.Set(HeaderConnection, "keep-alive")
h.Set(HeaderContentType, "aaa")
h.Set(HeaderContentEncoding, "gzip")
h.Set(HeaderServer, "aaabbb")
h.Set(HeaderContentLength, "1123")
h.Set(HeaderTrailer, "foo, bar")
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(HeaderServer)
h.Del("content-length")
h.Del("set-cookie")
h.Del("trailer")
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(HeaderConnection)
if len(hv) > 0 {
t.Fatalf("non-zero value: %q", hv)
}
hv = h.Peek(HeaderContentType)
if string(hv) != string(defaultContentType) {
t.Fatalf("unexpected content-type: %q. Expecting %q", hv, defaultContentType)
}
hv = h.Peek(HeaderContentEncoding)
if string(hv) != ("gzip") {
t.Fatalf("unexpected content-encoding: %q. Expecting %q", hv, "gzip")
}
hv = h.Peek(HeaderServer)
if len(hv) > 0 {
t.Fatalf("non-zero value: %q", hv)
}
hv = h.Peek(HeaderContentLength)
if len(hv) > 0 {
t.Fatalf("non-zero value: %q", hv)
}
hv = h.Peek(HeaderTrailer)
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 TestResponseHeaderSetTrailerGetBytes(t *testing.T) {
t.Parallel()
h := &ResponseHeader{}
h.noDefaultDate = true
h.Set("Foo", "bar")
h.Set(HeaderTrailer, "Baz")
h.Set("Baz", "test")
headerBytes := h.Header()
n, err := h.parseFirstLine(headerBytes)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(headerBytes[n:]) != "Foo: bar\r\nTrailer: Baz\r\n\r\n" {
t.Fatalf("Unexpected header: %q. Expected %q", headerBytes[n:], "Foo: bar\nTrailer: Baz\n\n")
}
if string(h.TrailerHeader()) != "Baz: test\r\n\r\n" {
t.Fatalf("Unexpected trailer header: %q. Expected %q", h.TrailerHeader(), "Baz: test\r\n\r\n")
}
}
func TestRequestHeaderSetTrailerGetBytes(t *testing.T) {
t.Parallel()
h := &RequestHeader{}
h.Set("Foo", "bar")
h.Set(HeaderTrailer, "Baz")
h.Set("Baz", "test")
headerBytes := h.Header()
n, err := h.parseFirstLine(headerBytes)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(headerBytes[n:]) != "Foo: bar\r\nTrailer: Baz\r\n\r\n" {
t.Fatalf("Unexpected header: %q. Expected %q", headerBytes[n:], "Foo: bar\nTrailer: Baz\n\n")
}
if string(h.TrailerHeader()) != "Baz: test\r\n\r\n" {
t.Fatalf("Unexpected trailer header: %q. Expected %q", h.TrailerHeader(), "Baz: test\r\n\r\n")
}
}
func TestAppendNormalizedHeaderKeyBytes(t *testing.T) {
t.Parallel()
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) {
t.Parallel()
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: %v", err)
}
if !h.ConnectionClose() {
t.Fatalf("expecting 'Connection: close' request header")
}
}
func TestRequestHeaderHTTP10ConnectionKeepAlive(t *testing.T) {
t.Parallel()
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: %v", err)
}
if h.ConnectionClose() {
t.Fatalf("unexpected 'Connection: close' request header")
}
}
func TestBufferSnippet(t *testing.T) {
t.Parallel()
testBufferSnippet(t, "", `""`)
testBufferSnippet(t, "foobar", `"foobar"`)
b := string(createFixedBody(199))
bExpected := fmt.Sprintf("%q", b)
testBufferSnippet(t, b, bExpected)
for i := 0; i < 10; i++ {
b += "foobar"
bExpected = fmt.Sprintf("%q", b)
testBufferSnippet(t, b, bExpected)
}
b = string(createFixedBody(400))
bExpected = fmt.Sprintf("%q", b)
testBufferSnippet(t, b, bExpected)
for i := 0; i < 10; i++ {
b += "sadfqwer"
bExpected = fmt.Sprintf("%q...%q", b[:200], b[len(b)-200:])
testBufferSnippet(t, b, bExpected)
}
}
func testBufferSnippet(t *testing.T, buf, expectedSnippet string) {
snippet := bufferSnippet([]byte(buf))
if snippet != expectedSnippet {
t.Fatalf("unexpected snippet %q. Expecting %q", snippet, expectedSnippet)
}
}
func TestResponseHeaderTrailingCRLFSuccess(t *testing.T) {
t.Parallel()
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: %v", 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: %v. Expecting %v", err, io.EOF)
}
}
func TestResponseHeaderTrailingCRLFError(t *testing.T) {
t.Parallel()
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: %v", 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: %v", err)
}
}
func TestRequestHeaderTrailingCRLFSuccess(t *testing.T) {
t.Parallel()
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: %v", 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: %v. Expecting %v", err, io.EOF)
}
}
func TestRequestHeaderTrailingCRLFError(t *testing.T) {
t.Parallel()
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: %v", 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: %v", err)
}
}
func TestRequestHeaderReadEOF(t *testing.T) {
t.Parallel()
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: %v. Expecting %v", 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) {
t.Parallel()
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: %v. Expecting %v", 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) {
t.Parallel()
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: %v", 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: %v", err)
}
if h.ConnectionClose() {
t.Fatalf("unexpected 'Connection: close' for keep-alive response with old http protocol")
}
}
func TestRequestHeaderSetByteRange(t *testing.T) {
t.Parallel()
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(HeaderRange)
if string(v) != expectedV {
t.Fatalf("unexpected range: %q. Expecting %q. startPos=%d, endPos=%d", v, expectedV, startPos, endPos)
}
}
func TestResponseHeaderSetContentRange(t *testing.T) {
t.Parallel()
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(HeaderContentRange)
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) {
t.Parallel()
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(HeaderAcceptEncoding, 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) {
t.Parallel()
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")
// quoted boundary
testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=\"foobar\"\r\n\r\n", "foobar")
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: %v. 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) {
t.Parallel()
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: %v. 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) {
t.Parallel()
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: %v. 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) {
t.Parallel()
// 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: %v", err)
}
w := &bytes.Buffer{}
bw := bufio.NewWriter(w)
if err := h.Write(bw); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := bw.Flush(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
var h1 RequestHeader
br.Reset(w)
if err := h1.Read(br); err != nil {
t.Fatalf("unexpected error: %v", 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 TestResponseHeaderFirstByteReadEOF(t *testing.T) {
t.Parallel()
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 %v. Expecting %v", 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) {
t.Parallel()
var h RequestHeader
if !h.IsGet() {
t.Fatalf("empty method must be equivalent to GET")
}
}
func TestResponseHeaderHTTPVer(t *testing.T) {
t.Parallel()
// 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) {
t.Parallel()
// 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: %v. 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: %v. 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) {
t.Parallel()
var h ResponseHeader
h.Set(HeaderSetCookie, "foo=bar")
h.Set(HeaderContentType, "foobar")
h.Set(HeaderContentEncoding, "gzip")
h.Set("AAA-BBB", "aaaa")
h.Set(HeaderTrailer, "foo, bar")
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(HeaderContentType), h.Peek(HeaderContentType)) {
t.Fatalf("unexpected content-type %q. Expected %q", h1.Peek("content-type"), h.Peek("content-type"))
}
if !bytes.Equal(h1.Peek(HeaderContentEncoding), h.Peek(HeaderContentEncoding)) {
t.Fatalf("unexpected content-encoding %q. Expected %q", h1.Peek("content-encoding"), h.Peek("content-encoding"))
}
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"))
}
if !bytes.Equal(h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer)) {
t.Fatalf("unexpected trailer %q. Expected %q", h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer))
}
// flush buf
h.bufKV = argsKV{}
h1.bufKV = argsKV{}
if !reflect.DeepEqual(h, h1) { //nolint:govet
t.Fatalf("ResponseHeaderCopyTo fail, src: \n%+v\ndst: \n%+v\n", h, h1) //nolint:govet
}
}
func TestRequestHeaderCopyTo(t *testing.T) {
t.Parallel()
var h RequestHeader
h.Set(HeaderCookie, "aa=bb; cc=dd")
h.Set(HeaderContentType, "foobar")
h.Set(HeaderContentEncoding, "gzip")
h.Set(HeaderHost, "aaaa")
h.Set("aaaxxx", "123")
h.Set(HeaderTrailer, "foo, bar")
var h1 RequestHeader
h.CopyTo(&h1)
if !bytes.Equal(h1.Peek("cookie"), h.Peek(HeaderCookie)) {
t.Fatalf("unexpected cookie after copying: %q. Expected %q", h1.Peek("cookie"), h.Peek("cookie"))
}
if !bytes.Equal(h1.Peek("content-type"), h.Peek(HeaderContentType)) {
t.Fatalf("unexpected content-type %q. Expected %q", h1.Peek("content-type"), h.Peek("content-type"))
}
if !bytes.Equal(h1.Peek("content-encoding"), h.Peek(HeaderContentEncoding)) {
t.Fatalf("unexpected content-encoding %q. Expected %q", h1.Peek("content-encoding"), h.Peek("content-encoding"))
}
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"))
}
if !bytes.Equal(h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer)) {
t.Fatalf("unexpected trailer %q. Expected %q", h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer))
}
// flush buf
h.bufKV = argsKV{}
h1.bufKV = argsKV{}
if !reflect.DeepEqual(h, h1) { //nolint:govet
t.Fatalf("RequestHeaderCopyTo fail, src: \n%+v\ndst: \n%+v\n", h, h1) //nolint:govet
}
}
func TestResponseContentTypeNoDefaultNotEmpty(t *testing.T) {
t.Parallel()
var h ResponseHeader
h.SetNoDefaultContentType(true)
h.SetContentLength(5)
headers := h.String()
if strings.Contains(headers, "Content-Type: \r\n") {
t.Fatalf("ResponseContentTypeNoDefaultNotEmpty fail, response: \n%+v\noutcome: \n%q\n", h, headers) //nolint:govet
}
}
func TestRequestContentTypeDefaultNotEmpty(t *testing.T) {
t.Parallel()
var h RequestHeader
h.SetMethod(MethodPost)
h.SetContentLength(5)
w := &bytes.Buffer{}
bw := bufio.NewWriter(w)
if err := h.Write(bw); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if err := bw.Flush(); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var h1 RequestHeader
br := bufio.NewReader(w)
if err := h1.Read(br); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if string(h1.contentType) != "application/octet-stream" {
t.Fatalf("unexpected Content-Type %q. Expecting %q", h1.contentType, "application/octet-stream")
}
}
func TestRequestContentTypeNoDefault(t *testing.T) {
t.Parallel()
var h RequestHeader
h.SetMethod(MethodDelete)
h.SetNoDefaultContentType(true)
w := &bytes.Buffer{}
bw := bufio.NewWriter(w)
if err := h.Write(bw); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if err := bw.Flush(); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var h1 RequestHeader
br := bufio.NewReader(w)
if err := h1.Read(br); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if string(h1.contentType) != "" {
t.Fatalf("unexpected Content-Type %q. Expecting %q", h1.contentType, "")
}
}
func TestResponseDateNoDefaultNotEmpty(t *testing.T) {
t.Parallel()
var h ResponseHeader
h.noDefaultDate = true
headers := h.String()
if strings.Contains(headers, "\r\nDate: ") {
t.Fatalf("ResponseDateNoDefaultNotEmpty fail, response: \n%+v\noutcome: \n%q\n", h, headers) //nolint:govet
}
}
func TestRequestHeaderConnectionClose(t *testing.T) {
t.Parallel()
var h RequestHeader
h.Set(HeaderConnection, "close")
h.Set(HeaderHost, "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: %v", err)
}
if err := bw.Flush(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
var h1 RequestHeader
br := bufio.NewReader(&w)
if err := h1.Read(br); err != nil {
t.Fatalf("error when reading request header: %v", err)
}
if !h1.ConnectionClose() {
t.Fatalf("unexpected connection: close value: %v", h1.ConnectionClose())
}
if string(h1.Peek(HeaderConnection)) != "close" {
t.Fatalf("unexpected connection value: %q. Expecting %q", h.Peek("Connection"), "close")
}
}
func TestRequestHeaderSetCookie(t *testing.T) {
t.Parallel()
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) {
t.Parallel()
var h ResponseHeader
h.Set("set-cookie", "foo=bar; path=/aa/bb; domain=aaa.com")
h.Set(HeaderSetCookie, "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) {
t.Parallel()
var h ResponseHeader
r := bytes.NewBufferString("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Encoding: gzip\r\nContent-Length: 123\r\nSet-Cookie: aa=bb; path=/foo/bar\r\nSet-Cookie: ccc\r\nTrailer: Foo, Bar\r\n\r\n")
br := bufio.NewReader(r)
if err := h.Read(br); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if h.Len() != 6 {
t.Fatalf("Unexpected number of headers: %d. Expected 6", h.Len())
}
contentLengthCount := 0
contentTypeCount := 0
contentEncodingCount := 0
cookieCount := 0
h.VisitAll(func(key, value []byte) {
k := string(key)
v := string(value)
switch k {
case HeaderContentLength:
if v != string(h.Peek(k)) {
t.Fatalf("unexpected content-length: %q. Expecting %q", v, h.Peek(k))
}
contentLengthCount++
case HeaderContentType:
if v != string(h.Peek(k)) {
t.Fatalf("Unexpected content-type: %q. Expected %q", v, h.Peek(k))
}
contentTypeCount++
case HeaderContentEncoding:
if v != string(h.Peek(k)) {
t.Fatalf("Unexpected content-encoding: %q. Expected %q", v, h.Peek(k))
}
contentEncodingCount++
case HeaderSetCookie:
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++
case HeaderTrailer:
if v != "Foo, Bar" {
t.Fatalf("Unexpected trailer header %q. Expected %q", v, "Foo, Bar")
}
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 contentEncodingCount != 1 {
t.Fatalf("unexpected number of content-encoding headers: %d. Expected 1", contentEncodingCount)
}
if cookieCount != 2 {
t.Fatalf("unexpected number of cookie header: %d. Expected 2", cookieCount)
}
}
func TestRequestHeaderVisitAll(t *testing.T) {
t.Parallel()
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\nTrailer: Foo, Bar\r\n\r\n")
br := bufio.NewReader(r)
if err := h.Read(br); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if h.Len() != 5 {
t.Fatalf("Unexpected number of header: %d. Expected 5", h.Len())
}
hostCount := 0
xxCount := 0
cookieCount := 0
h.VisitAll(func(key, value []byte) {
k := string(key)
v := string(value)
switch k {
case HeaderHost:
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 HeaderCookie:
if v != "a=b; c=d" {
t.Fatalf("Unexpected cookie %q. Expected %q", v, "a=b; c=d")
}
cookieCount++
case HeaderTrailer:
if v != "Foo, Bar" {
t.Fatalf("Unexpected trailer header %q. Expected %q", v, "Foo, Bar")
}
default:
t.Fatalf("Unexpected header %q=%q", k, v)
}
})
if hostCount != 1 {
t.Fatalf("Unexpected 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 TestRequestHeaderVisitAllInOrder(t *testing.T) {
t.Parallel()
var h RequestHeader
r := bytes.NewBufferString("GET / HTTP/1.1\r\nContent-Type: aa\r\nCookie: a=b\r\nHost: example.com\r\nUser-Agent: xxx\r\n\r\n")
br := bufio.NewReader(r)
if err := h.Read(br); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if h.Len() != 4 {
t.Fatalf("Unexpected number of headers: %d. Expected 4", h.Len())
}
order := []string{
HeaderContentType,
HeaderCookie,
HeaderHost,
HeaderUserAgent,
}
values := []string{
"aa",
"a=b",
"example.com",
"xxx",
}
h.VisitAllInOrder(func(key, value []byte) {
if len(order) == 0 {
t.Fatalf("no more headers expected, got %q", key)
}
if order[0] != string(key) {
t.Fatalf("expected header %q got %q", order[0], key)
}
if values[0] != string(value) {
t.Fatalf("expected header value %q got %q", values[0], value)
}
order = order[1:]
values = values[1:]
})
}
func TestResponseHeaderAddTrailerError(t *testing.T) {
t.Parallel()
var h ResponseHeader
err := h.AddTrailer("Foo, Content-Length , Bar,Transfer-Encoding,")
expectedTrailer := "Foo, Bar"
if !errors.Is(err, ErrBadTrailer) {
t.Fatalf("unexpected err %q. Expected %q", err, ErrBadTrailer)
}
if trailer := string(h.Peek(HeaderTrailer)); trailer != expectedTrailer {
t.Fatalf("unexpected trailer %q. Expected %q", trailer, expectedTrailer)
}
}
func TestRequestHeaderAddTrailerError(t *testing.T) {
t.Parallel()
var h RequestHeader
err := h.AddTrailer("Foo, Content-Length , Bar,Transfer-Encoding,")
expectedTrailer := "Foo, Bar"
if !errors.Is(err, ErrBadTrailer) {
t.Fatalf("unexpected err %q. Expected %q", err, ErrBadTrailer)
}
if trailer := string(h.Peek(HeaderTrailer)); trailer != expectedTrailer {
t.Fatalf("unexpected trailer %q. Expected %q", trailer, expectedTrailer)
}
}
func TestResponseHeaderCookie(t *testing.T) {
t.Parallel()
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
if err := cc.ParseBytes(value); err != nil {
t.Fatal(err)
}
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: %v", err)
}
if err := bw.Flush(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
h.DelAllCookies()
var h1 ResponseHeader
br := bufio.NewReader(w)
if err := h1.Read(br); err != nil {
t.Fatalf("unexpected error: %v", 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) {
t.Parallel()
var h RequestHeader
h.SetRequestURI("/foobar")
h.Set(HeaderHost, "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: %v", err)
}
if err := bw.Flush(); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var h1 RequestHeader
br := bufio.NewReader(w)
if err := h1.Read(br); err != nil {
t.Fatalf("Unexpected error: %v", 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 TestResponseHeaderCookieIssue4(t *testing.T) {
t.Parallel()
var h ResponseHeader
c := AcquireCookie()
c.SetKey("foo")
c.SetValue("bar")
h.SetCookie(c)
if string(h.Peek(HeaderSetCookie)) != "foo=bar" {
t.Fatalf("Unexpected Set-Cookie header %q. Expected %q", h.Peek(HeaderSetCookie), "foo=bar")
}
cookieSeen := false
h.VisitAll(func(key, value []byte) {
switch string(key) {
case HeaderSetCookie:
cookieSeen = true
}
})
if !cookieSeen {
t.Fatalf("Set-Cookie not present in VisitAll")
}
c = AcquireCookie()
c.SetKey("foo")
h.Cookie(c)
if string(c.Value()) != "bar" {
t.Fatalf("Unexpected cookie value %q. Exepcted %q", c.Value(), "bar")
}
if string(h.Peek(HeaderSetCookie)) != "foo=bar" {
t.Fatalf("Unexpected Set-Cookie header %q. Expected %q", h.Peek(HeaderSetCookie), "foo=bar")
}
cookieSeen = false
h.VisitAll(func(key, value []byte) {
switch string(key) {
case HeaderSetCookie:
cookieSeen = true
}
})
if !cookieSeen {
t.Fatalf("Set-Cookie not present in VisitAll")
}
}
func TestRequestHeaderCookieIssue313(t *testing.T) {
t.Parallel()
var h RequestHeader
h.SetRequestURI("/")
h.Set(HeaderHost, "foobar.com")
h.SetCookie("foo", "bar")
if string(h.Peek(HeaderCookie)) != "foo=bar" {
t.Fatalf("Unexpected Cookie header %q. Expected %q", h.Peek(HeaderCookie), "foo=bar")
}
cookieSeen := false
h.VisitAll(func(key, value []byte) {
switch string(key) {
case HeaderCookie:
cookieSeen = true
}
})
if !cookieSeen {
t.Fatalf("Cookie not present in VisitAll")
}
if string(h.Cookie("foo")) != "bar" {
t.Fatalf("Unexpected cookie value %q. Exepcted %q", h.Cookie("foo"), "bar")
}
if string(h.Peek(HeaderCookie)) != "foo=bar" {
t.Fatalf("Unexpected Cookie header %q. Expected %q", h.Peek(HeaderCookie), "foo=bar")
}
cookieSeen = false
h.VisitAll(func(key, value []byte) {
switch string(key) {
case HeaderCookie:
cookieSeen = true
}
})
if !cookieSeen {
t.Fatalf("Cookie not present in VisitAll")
}
}
func TestRequestHeaderMethod(t *testing.T) {
t.Parallel()
// common http methods
testRequestHeaderMethod(t, MethodGet)
testRequestHeaderMethod(t, MethodPost)
testRequestHeaderMethod(t, MethodHead)
testRequestHeaderMethod(t, MethodDelete)
// 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: %v", err)
}
m1 := h1.Method()
if string(m) != string(m1) {
t.Fatalf("unexpected method: %q. Expecting %q", m, m1)
}
}
func TestRequestHeaderSetGet(t *testing.T) {
t.Parallel()
h := &RequestHeader{}
h.SetRequestURI("/aa/bbb")
h.SetMethod(MethodPost)
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, HeaderHost, "12345")
expectRequestHeaderGet(t, h, HeaderContentType, "aaa/bbb")
expectRequestHeaderGet(t, h, HeaderContentLength, "1234")
expectRequestHeaderGet(t, h, "USER-AGent", "aaabbb")
expectRequestHeaderGet(t, h, HeaderReferer, "axcv")
expectRequestHeaderGet(t, h, "baz", "xxxxx")
expectRequestHeaderGet(t, h, HeaderTransferEncoding, "")
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: %v", err)
}
if err := bw.Flush(); err != nil {
t.Fatalf("Unexpected error when flushing request header: %v", err)
}
var h1 RequestHeader
br := bufio.NewReader(w)
if err = h1.Read(br); err != nil {
t.Fatalf("Unexpected error when reading request header: %v", 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, HeaderContentType, "aaa/bbb")
expectRequestHeaderGet(t, &h1, HeaderContentLength, "1234")
expectRequestHeaderGet(t, &h1, "USER-AGent", "aaabbb")
expectRequestHeaderGet(t, &h1, HeaderReferer, "axcv")
expectRequestHeaderGet(t, &h1, "baz", "xxxxx")
expectRequestHeaderGet(t, &h1, HeaderTransferEncoding, "")
expectRequestHeaderGet(t, &h1, HeaderConnection, "close")
if !h1.ConnectionClose() {
t.Fatalf("unset connection: close")
}
}
func TestResponseHeaderSetGet(t *testing.T) {
t.Parallel()
h := &ResponseHeader{}
h.Set("foo", "bar")
h.Set("content-type", "aaa/bbb")
h.Set("content-encoding", "gzip")
h.Set("connection", "close")
h.Set("content-length", "1234")
h.Set(HeaderServer, "aaaa")
h.Set("baz", "xxxxx")
h.Set(HeaderTransferEncoding, "chunked")
expectResponseHeaderGet(t, h, "Foo", "bar")
expectResponseHeaderGet(t, h, HeaderContentType, "aaa/bbb")
expectResponseHeaderGet(t, h, HeaderContentEncoding, "gzip")
expectResponseHeaderGet(t, h, HeaderConnection, "close")
expectResponseHeaderGet(t, h, HeaderContentLength, "1234")
expectResponseHeaderGet(t, h, "seRVer", "aaaa")
expectResponseHeaderGet(t, h, "baz", "xxxxx")
expectResponseHeaderGet(t, h, HeaderTransferEncoding, "")
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: %v", err)
}
if err := bw.Flush(); err != nil {
t.Fatalf("Unexpected error when flushing response header: %v", err)
}
var h1 ResponseHeader
br := bufio.NewReader(w)
if err = h1.Read(br); err != nil {
t.Fatalf("Unexpected error when reading response header: %v", 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, HeaderContentType, "aaa/bbb")
expectResponseHeaderGet(t, &h1, HeaderContentEncoding, "gzip")
expectResponseHeaderGet(t, &h1, HeaderConnection, "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) {
t.Parallel()
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: %v", err)
}
if err := bw.Flush(); err != nil {
t.Fatalf("Unexpected error when flushing response header: %v", err)
}
var h1 ResponseHeader
br := bufio.NewReader(w)
err = h1.Read(br)
if err != nil {
t.Fatalf("Unexpected error when reading response header: %v", err)
}
if h1.ConnectionClose() != h.ConnectionClose() {
t.Fatalf("Unexpected value for ConnectionClose: %v. Expected %v", h1.ConnectionClose(), h.ConnectionClose())
}
}
func TestRequestHeaderTooBig(t *testing.T) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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: %v", err)
}
verifyRequestHeader(t, h, -2, "/", "foobar.com", "", "")
}
func TestResponseHeaderBufioPeek(t *testing.T) {
t.Parallel()
r := &bufioPeekReader{
s: "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nContent-Type: text/plain\r\nContent-Encoding: gzip\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: %v", err)
}
verifyResponseHeader(t, h, 200, 10, "text/plain", "gzip")
}
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) {
t.Parallel()
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")
// 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")
// 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")
// No space after colon
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\nContent-Length:34\nContent-Type: sss\n\naaaa",
200, 34, "sss")
// 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")
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")
// 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")
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")
if !h.ConnectionClose() {
t.Fatalf("expecting connection: close for identity response")
}
// no content-type
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\r\nContent-Length: 123\r\n\r\nfoiaaa",
400, 123, string(defaultContentType))
// no content-type and no default
h.SetNoDefaultContentType(true)
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\r\nContent-Length: 123\r\n\r\nfoiaaa",
400, 123, "")
h.SetNoDefaultContentType(false)
// no headers
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\n\r\naaaabbb",
200, -2, string(defaultContentType))
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")
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")
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) {
t.Parallel()
h := &RequestHeader{}
// simple headers
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nHost: google.com\r\n\r\n",
-2, "/foo/bar", "google.com", "", "", nil)
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",
-2, "/a/bar", "gole.com", "", "", nil)
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",
-2, "/bar", "gole", "", "", nil)
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",
-2, "/aa", "bb", "", "", nil)
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",
-2, "/aabar", "ole.com", "", "", nil)
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",
-2, "/foo/bar", "google.com", "", "", nil)
// 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", nil)
// 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",
-2, "/a", "aaa", "", "", nil)
// no space after colon
testRequestHeaderReadSuccess(t, h, "GET /a HTTP/1.1\nHost:aaaxd\n\nsdfds",
-2, "/a", "aaaxd", "", "", nil)
// 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", "", "", nil)
// get with non-zero content-length
testRequestHeaderReadSuccess(t, h, "GET /xxx HTTP/1.1\nHost: aaa.com\nContent-Length: 123\n\n",
123, "/xxx", "aaa.com", "", "", nil)
// invalid case
testRequestHeaderReadSuccess(t, h, "GET /aaa HTTP/1.1\nhoST: bbb.com\n\naas",
-2, "/aaa", "bbb.com", "", "", nil)
// referer
testRequestHeaderReadSuccess(t, h, "GET /asdf HTTP/1.1\nHost: aaa.com\nReferer: bb.com\n\naaa",
-2, "/asdf", "aaa.com", "bb.com", "", nil)
// duplicate host
testRequestHeaderReadSuccess(t, h, "GET /aa HTTP/1.1\r\nHost: aaaaaa.com\r\nHost: bb.com\r\n\r\n",
-2, "/aa", "bb.com", "", "", nil)
// 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", nil)
// 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", nil)
// 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",
-2, "/aaa", "bbb.com", "", "aaab", nil)
// non-post with content-length
testRequestHeaderReadSuccess(t, h, "HEAD / HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 123\r\n\r\n",
123, "/", "aaa.com", "", "", nil)
// 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",
123, "/aa", "aa.com", "", "abd/test", nil)
// 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",
-2, "http://gooGle.com/foO/%20bar?xxx#aaa", "aa.cOM", "", "", nil)
// no protocol in the first line
testRequestHeaderReadSuccess(t, h, "GET /foo/bar\r\nHost: google.com\r\n\r\nisdD",
-2, "/foo/bar", "google.com", "", "", nil)
// 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",
-2, "/aaa", "aaa.com", "", "", nil)
// request uri with spaces
testRequestHeaderReadSuccess(t, h, "GET /foo/ bar baz HTTP/1.1\r\nHost: aa.com\r\n\r\nxxx",
-2, "/foo/ bar baz", "aa.com", "", "", nil)
// no host
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nFOObar: assdfd\r\n\r\naaa",
-2, "/foo/bar", "", "", "", nil)
// no host, no headers
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\n\r\nfoobar",
-2, "/foo/bar", "", "", "", nil)
// 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", "", "", nil)
// 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", "", "", nil)
// 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", nil)
// invalid method
testRequestHeaderReadSuccess(t, h, "POST /foo/bar HTTP/1.1\r\nHost: google.com\r\n\r\nmnbv",
-2, "/foo/bar", "google.com", "", "", nil)
// 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", nil)
}
func TestResponseHeaderReadError(t *testing.T) {
t.Parallel()
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")
// non-numeric content-length
testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: faaa\r\nContent-Type: text/html\r\n\r\nfoobar")
testResponseHeaderReadError(t, h, "HTTP/1.1 201 OK\r\nContent-Length: 123aa\r\nContent-Type: text/ht\r\n\r\naaa")
testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: aa124\r\nContent-Type: html\r\n\r\nxx")
// 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")
// forbidden trailer
testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: -1\r\nTrailer: Foo, Content-Length\r\n\r\n")
}
func TestResponseHeaderReadErrorSecureLog(t *testing.T) {
t.Parallel()
h := &ResponseHeader{
secureErrorLogMessage: true,
}
// incorrect first line
testResponseHeaderReadSecuredError(t, h, "fo")
testResponseHeaderReadSecuredError(t, h, "foobarbaz")
testResponseHeaderReadSecuredError(t, h, "HTTP/1.1")
testResponseHeaderReadSecuredError(t, h, "HTTP/1.1 ")
testResponseHeaderReadSecuredError(t, h, "HTTP/1.1 s")
// non-numeric status code
testResponseHeaderReadSecuredError(t, h, "HTTP/1.1 foobar OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n")
testResponseHeaderReadSecuredError(t, h, "HTTP/1.1 123foobar OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n")
testResponseHeaderReadSecuredError(t, h, "HTTP/1.1 foobar344 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n")
// no headers
testResponseHeaderReadSecuredError(t, h, "HTTP/1.1 200 OK\r\n")
// no trailing crlf
testResponseHeaderReadSecuredError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n")
}
func TestRequestHeaderReadError(t *testing.T) {
t.Parallel()
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")
// post with invalid content-length
testRequestHeaderReadError(t, h, "POST /a HTTP/1.1\r\nHost: bb\r\nContent-Type: aa\r\nContent-Length: dff\r\n\r\nqwerty")
// forbidden trailer
testRequestHeaderReadError(t, h, "POST /a HTTP/1.1\r\nContent-Length: -1\r\nTrailer: Foo, Content-Length\r\n\r\n")
}
func TestRequestHeaderReadSecuredError(t *testing.T) {
t.Parallel()
h := &RequestHeader{
secureErrorLogMessage: true,
}
// incorrect first line
testRequestHeaderReadSecuredError(t, h, "fo")
testRequestHeaderReadSecuredError(t, h, "GET ")
testRequestHeaderReadSecuredError(t, h, "GET / HTTP/1.1\r")
// missing RequestURI
testRequestHeaderReadSecuredError(t, h, "GET HTTP/1.1\r\nHost: google.com\r\n\r\n")
// post with invalid content-length
testRequestHeaderReadSecuredError(t, h, "POST /a HTTP/1.1\r\nHost: bb\r\nContent-Type: aa\r\nContent-Length: dff\r\n\r\nqwerty")
}
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")
}
func testResponseHeaderReadSecuredError(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)
}
if strings.Contains(err.Error(), headers) {
t.Fatalf("Not expecting header content in err %q", err)
}
// 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")
}
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",
-2, "/foo/bar", "aaaa", "", "", nil)
}
func testRequestHeaderReadSecuredError(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)
}
if strings.Contains(err.Error(), headers) {
t.Fatalf("Not expecting header content in err %q", err)
}
// make sure request header works after error
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nHost: aaaa\r\n\r\nxxx",
-2, "/foo/bar", "aaaa", "", "", nil)
}
func testResponseHeaderReadSuccess(t *testing.T, h *ResponseHeader, headers string, expectedStatusCode, expectedContentLength int,
expectedContentType string) {
r := bytes.NewBufferString(headers)
br := bufio.NewReader(r)
err := h.Read(br)
if err != nil {
t.Fatalf("Unexpected error when parsing response headers: %v. headers=%q", err, headers)
}
verifyResponseHeader(t, h, expectedStatusCode, expectedContentLength, expectedContentType, "")
}
func testRequestHeaderReadSuccess(t *testing.T, h *RequestHeader, headers string, expectedContentLength int,
expectedRequestURI, expectedHost, expectedReferer, expectedContentType string, expectedTrailer map[string]string) {
r := bytes.NewBufferString(headers)
br := bufio.NewReader(r)
err := h.Read(br)
if err != nil {
t.Fatalf("Unexpected error when parsing request headers: %v. headers=%q", err, headers)
}
verifyRequestHeader(t, h, expectedContentLength, expectedRequestURI, expectedHost, expectedReferer, expectedContentType)
}
func verifyResponseHeader(t *testing.T, h *ResponseHeader, expectedStatusCode, expectedContentLength int, expectedContentType, expectedContentEncoding 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.ContentType()) != expectedContentType {
t.Fatalf("Unexpected content type %q. Expected %q", h.ContentType(), expectedContentType)
}
if string(h.ContentEncoding()) != expectedContentEncoding {
t.Fatalf("Unexpected content encoding %q. Expected %q", h.ContentEncoding(), expectedContentEncoding)
}
}
func verifyResponseHeaderConnection(t *testing.T, h *ResponseHeader, expectConnection string) {
if string(h.Peek(HeaderConnection)) != expectConnection {
t.Fatalf("Unexpected Connection %q. Expected %q", h.Peek(HeaderConnection), expectConnection)
}
}
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(HeaderHost)) != expectedHost {
t.Fatalf("Unexpected host %q. Expected %q", h.Peek(HeaderHost), expectedHost)
}
if string(h.Peek(HeaderReferer)) != expectedReferer {
t.Fatalf("Unexpected referer %q. Expected %q", h.Peek(HeaderReferer), expectedReferer)
}
if string(h.Peek(HeaderContentType)) != expectedContentType {
t.Fatalf("Unexpected content-type %q. Expected %q", h.Peek(HeaderContentType), expectedContentType)
}
}
func verifyResponseTrailer(t *testing.T, h *ResponseHeader, expectedTrailers map[string]string) {
for k, v := range expectedTrailers {
got := h.Peek(k)
if !bytes.Equal(got, []byte(v)) {
t.Fatalf("Unexpected trailer %q. Expected %q. Got %q", k, v, got)
}
}
}
func verifyRequestTrailer(t *testing.T, h *RequestHeader, expectedTrailers map[string]string) {
for k, v := range expectedTrailers {
got := h.Peek(k)
if !bytes.Equal(got, []byte(v)) {
t.Fatalf("Unexpected trailer %q. Expected %q. Got %q", k, v, got)
}
}
}
func verifyTrailer(t *testing.T, r *bufio.Reader, expectedTrailers map[string]string, isReq bool) {
if isReq {
req := Request{}
err := req.Header.ReadTrailer(r)
if err == io.EOF && expectedTrailers == nil {
return
}
if err != nil {
t.Fatalf("Cannot read trailer: %v", err)
}
verifyRequestTrailer(t, &req.Header, expectedTrailers)
return
}
resp := Response{}
err := resp.Header.ReadTrailer(r)
if err == io.EOF && expectedTrailers == nil {
return
}
if err != nil {
t.Fatalf("Cannot read trailer: %v", err)
}
verifyResponseTrailer(t, &resp.Header, expectedTrailers)
}