Files
fasthttp/header_test.go
T
Erik Dubbelboer 81ebee8c79 Fix PeekKeys()
RequestHeader.PeekKeys() and ResponseHeader.PeekKeys() were both
implemented wrong. The tests were also wrong causing this to never be
noticed. They both never actually returned all header keys, this has
been fixed now.

While this is a backwards incompatible change, I'm still going to
release it. Anyone using these functions would have noticed they
didn't work as documented and probably would not have continued using
them.

Fixes https://github.com/valyala/fasthttp/issues/2044
2025-08-09 13:50:07 +02:00

3527 lines
104 KiB
Go

package fasthttp
import (
"bufio"
"bytes"
"context"
"encoding/base64"
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"reflect"
"strconv"
"strings"
"testing"
)
type slogTestHandler struct {
out string
}
func (s *slogTestHandler) Enabled(_ context.Context, level slog.Level) bool {
return true
}
func (s *slogTestHandler) Handle(ctx context.Context, record slog.Record) error { //nolint:gocritic
s.out += record.Message
for r := range record.Attrs {
s.out += " " + r.Key + ":" + r.Value.String()
}
return nil
}
func (s *slogTestHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
for _, attr := range attrs {
s.out += attr.String()
}
return &slogTestHandler{out: s.out}
}
func (s *slogTestHandler) WithGroup(name string) slog.Handler {
return &slogTestHandler{out: s.out}
}
func TestNewlineBackwardsCompatibleWarning(t *testing.T) {
h := &ResponseHeader{}
l := slog.Default()
ll := &slogTestHandler{}
slog.SetDefault(slog.New(ll))
defer slog.SetDefault(l)
DeprecatedNewlineIncludeContext.Store(true)
warnedAboutDeprecatedNewlineSeparatorLimiter.Store(0)
if err := h.Read(bufio.NewReader(bytes.NewBufferString("HTTP/1.1 200 OK\r\nContent-Type: foo/bar\nContent-Length: 12345\r\n\r\nsss"))); err != nil {
t.Fatal(err)
}
e := h.Peek(HeaderContentType)
if string(e) != "foo/bar" {
t.Fatalf("Unexpected Content-Type %q. Expected %q", e, "foo/bar")
}
expected := "Deprecated newline only separator found in header context:\"Content-Type: foo/bar\\nContent-Length: 123\""
if ll.out != expected {
t.Errorf("Expected %q, got %q", expected, ll.out)
}
ll.out = ""
DeprecatedNewlineIncludeContext.Store(false)
warnedAboutDeprecatedNewlineSeparatorLimiter.Store(0)
if err := h.Read(bufio.NewReader(bytes.NewBufferString("HTTP/1.1 200 OK\r\nContent-Type: foo/bar\nContent-Length: 12345\r\n\r\nsss"))); err != nil {
t.Fatal(err)
}
expected = "Deprecated newline only separator found in header"
if ll.out != expected {
t.Errorf("Expected %q, got %q", expected, ll.out)
}
}
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
if _, err := h.WriteTo(&buf); err != nil {
t.Fatalf("unexpected error when writing header: %v", err)
}
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
if _, err := h.WriteTo(&buf); err != nil {
t.Fatalf("unexpected error when writing header: %v", err)
}
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)
}
defer func() { _ = response.Body.Close() }()
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 TestIssue1808(t *testing.T) {
t.Parallel()
s := "HTTP/1.1 200\r\n" +
"WithTabs: \t v1 \t\r\n" + // "v1"
"WithTabs-Start: \t \t v1 \r\n" + // "v1"
"WithTabs-End: v1 \t \t\t\t\r\n" + // "v1"
"WithTabs-Multi-Line: \t v1 \t;\r\n \t v2 \t;\r\n\t v3\r\n" + // "v1 \t; v2 \t; v3"
"\r\n"
resHeader := new(ResponseHeader)
if _, err := resHeader.parse([]byte(s)); err != nil {
t.Fatalf("parse headers with tabs values failed, %v", err)
}
groundTruth := map[string]string{
"WithTabs": "v1",
"WithTabs-Start": "v1",
"WithTabs-End": "v1",
"WithTabs-Multi-Line": "v1 \t; v2 \t; v3",
}
for name, want := range groundTruth {
if got := b2s(resHeader.Peek(name)); got != want {
t.Errorf("ResponseHeader.parser() unexpected %q got: %q want: %q", name, got, want)
}
}
s = "GET / HTTP/1.1\r\n" +
"WithTabs: \t v1 \t\r\n" + // "v1"
"WithTabs-Start: \t \t v1 \r\n" + // "v1"
"WithTabs-End: v1 \t \t\t\t\r\n" + // "v1"
"WithTabs-Multi-Line: \t v1 \t;\r\n \t v2 \t;\r\n\t v3\r\n" + // "v1 \t; v2 \t; v3"
"\r\n"
reqHeader := new(RequestHeader)
if _, err := reqHeader.parse([]byte(s)); err != nil {
t.Fatalf("parse headers with tabs values failed, %v", err)
}
for name, want := range groundTruth {
if got := b2s(reqHeader.Peek(name)); got != want {
t.Errorf("RequestHeader.parser() 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: go.dev\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)
for k, v := range header.All() {
m[string(k)] = string(v)
}
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 TestResponseHeaderMultiLinePanicked(t *testing.T) {
t.Parallel()
// Input generated by fuzz testing that caused the parser to panic.
s, _ := base64.StdEncoding.DecodeString("aAEAIDoKKDoKICA6CgkKCiA6CiA6CgkpCiA6CiA6CiA6Cig6CiAgOgoJCgogOgogOgoJKQogOgogOgogOgogOgogOgoJOg86CiA6CiA6Cig6CiAyCg==")
header := new(RequestHeader)
if _, err := header.parse(s); err == nil {
t.Error("expected error, got <nil>")
}
}
func TestRequestHeaderLooseBackslashR(t *testing.T) {
t.Parallel()
s := "GET / HTTP/1.1\r\n" +
"Host: go.dev\r\n" +
"\rFoo: bar\r\n" +
"\r\n"
header := new(RequestHeader)
if _, err := header.parse([]byte(s)); err == nil {
t.Fatal("expected error, got <nil>")
}
}
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 !bytes.Equal(h.ContentType(), 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 !bytes.Equal(h.Host(), 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" +
"uSeR agent: agent\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)
}
// We accept invalid headers with a space.
// See: https://github.com/valyala/fasthttp/issues/1917
v3 := h.Peek("uSeR agent")
if !bytes.Equal(v3, []byte("agent")) {
t.Fatalf("expecting non empty value. Got %q", v3)
}
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 len(h.Host()) != 0 {
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 TestRequestDisableSpecialHeaders(t *testing.T) {
t.Parallel()
// Test original header functionality
kvs := "Host: foobar\r\n" +
"User-Agent: ua\r\n" +
"Non-Special: val\r\n" +
"\r\n"
var h RequestHeader
h.DisableSpecialHeader()
s := "GET / HTTP/1.0\r\n" + kvs
br := bufio.NewReader(bytes.NewBufferString(s))
if err := h.Read(br); err != nil {
t.Fatalf("unexpected error: %v", err)
}
// assert order of all headers preserved
if h.String() != s {
t.Fatalf("Headers not equal: %q. Expecting %q", h.String(), s)
}
h.SetCanonical([]byte("host"), []byte("notfoobar"))
if string(h.Host()) != "foobar" {
t.Fatalf("unexpected: %q. Expecting %q", h.Host(), "foobar")
}
if h.String() != "GET / HTTP/1.0\r\nHost: foobar\r\nUser-Agent: ua\r\nNon-Special: val\r\nhost: notfoobar\r\n\r\n" {
t.Fatalf("custom special header ordering failed: %q", h.String())
}
// Test body parsing with DisableSpecialHeader - should work correctly after fix
testBody := "a=b&test=123"
rawRequest := "POST /test HTTP/1.1\r\n" +
"Host: example.com\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Content-Length: " + strconv.Itoa(len(testBody)) + "\r\n" +
"\r\n" +
testBody
var req Request
req.Header.DisableSpecialHeader()
br2 := bufio.NewReader(bytes.NewBufferString(rawRequest))
if err := req.ReadLimitBody(br2, 0); err != nil {
t.Fatalf("unexpected error reading request: %v", err)
}
// Verify Content-Length is correctly parsed with DisableSpecialHeader
if req.Header.ContentLength() != len(testBody) {
t.Fatalf("ContentLength() incorrect with DisableSpecialHeader: got %d, expected %d",
req.Header.ContentLength(), len(testBody))
}
// Verify body is preserved with DisableSpecialHeader
if string(req.Body()) != testBody {
t.Fatalf("body content incorrect with DisableSpecialHeader: got %q, expected %q",
string(req.Body()), testBody)
}
}
func TestRequestDisableSpecialHeadersChunked(t *testing.T) {
t.Parallel()
testBody := "chunked-test"
rawRequest := "POST /test HTTP/1.1\r\n" +
"Host: example.com\r\n" +
"Transfer-Encoding: chunked\r\n" +
"\r\n" +
"c\r\n" +
testBody + "\r\n" +
"0\r\n\r\n"
var req Request
req.Header.DisableSpecialHeader()
br := bufio.NewReader(bytes.NewBufferString(rawRequest))
if err := req.ReadLimitBody(br, 0); err != nil {
t.Fatalf("unexpected error reading chunked request: %v", err)
}
// Verify chunked encoding is detected
if req.Header.ContentLength() != -1 {
t.Fatalf("chunked encoding not detected with DisableSpecialHeader: got %d, expected -1",
req.Header.ContentLength())
}
// Verify chunked body is preserved
if string(req.Body()) != testBody {
t.Fatalf("chunked body incorrect with DisableSpecialHeader: got %q, expected %q",
string(req.Body()), testBody)
}
}
func TestRequestDisableSpecialHeadersIdentity(t *testing.T) {
t.Parallel()
rawRequest := "GET /test HTTP/1.1\r\n" +
"Host: example.com\r\n" +
"\r\n"
var req Request
req.Header.DisableSpecialHeader()
br := bufio.NewReader(bytes.NewBufferString(rawRequest))
if err := req.ReadLimitBody(br, 0); err != nil {
t.Fatalf("unexpected error reading identity request: %v", err)
}
// Verify identity encoding is detected
if req.Header.ContentLength() != -2 {
t.Fatalf("identity encoding not detected with DisableSpecialHeader: got %d, expected -2",
req.Header.ContentLength())
}
}
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 := strconv.Itoa(i)
h.Add("Foo-Bar", v)
m[v] = struct{}{}
}
if h.Len() != 12 {
t.Fatalf("unexpected header len %d. Expecting 12", h.Len())
}
for k, v := range h.All() {
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)
}
for k, v := range h.All() {
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 := strconv.Itoa(i)
h.Add("Foo-Bar", v)
m[v] = struct{}{}
}
if h.Len() != 12 {
t.Fatalf("unexpected header len %d. Expecting 12", h.Len())
}
for k, v := range h.All() {
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)
}
for k, v := range h.All() {
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 obtained: %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 !bytes.Equal(hv, 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 obtained: %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 TestVisitHeaderParams(t *testing.T) {
t.Parallel()
testVisitHeaderParams(t, "text/plain;charset=utf-8;q=0.39", [][2]string{{"charset", "utf-8"}, {"q", "0.39"}})
testVisitHeaderParams(t, "text/plain; foo=bar ;", [][2]string{{"foo", "bar"}})
testVisitHeaderParams(t, `text/plain; foo="bar"; `, [][2]string{{"foo", "bar"}})
testVisitHeaderParams(t, `text/plain; foo="text/plain,text/html;charset=\"utf-8\""`, [][2]string{{"foo", `text/plain,text/html;charset=\"utf-8\"`}})
testVisitHeaderParams(t, "text/plain foo=bar", [][2]string{})
testVisitHeaderParams(t, "text/plain;", [][2]string{})
testVisitHeaderParams(t, "text/plain; ", [][2]string{})
testVisitHeaderParams(t, "text/plain; foo", [][2]string{})
testVisitHeaderParams(t, "text/plain; foo=", [][2]string{})
testVisitHeaderParams(t, "text/plain; =bar", [][2]string{})
testVisitHeaderParams(t, "text/plain; foo = bar", [][2]string{})
testVisitHeaderParams(t, `text/plain; foo="bar`, [][2]string{})
testVisitHeaderParams(t, "text/plain;;foo=bar", [][2]string{})
parsed := make([][2]string, 0)
VisitHeaderParams([]byte(`text/plain; foo=bar; charset=utf-8`), func(key, value []byte) bool {
parsed = append(parsed, [2]string{string(key), string(value)})
return !bytes.Equal(key, []byte("foo"))
})
if len(parsed) != 1 {
t.Fatalf("expected 1 HTTP parameter, parsed %v", len(parsed))
}
if parsed[0] != [2]string{"foo", "bar"} {
t.Fatalf("unexpected parameter %v=%v. Expecting foo=bar", parsed[0][0], parsed[0][1])
}
}
func testVisitHeaderParams(t *testing.T, header string, expectedParams [][2]string) {
parsed := make([][2]string, 0)
VisitHeaderParams([]byte(header), func(key, value []byte) bool {
parsed = append(parsed, [2]string{string(key), string(value)})
return true
})
if len(parsed) != len(expectedParams) {
t.Fatalf("expected %v HTTP parameters, parsed %v", len(expectedParams), len(parsed))
}
for i := range expectedParams {
if expectedParams[i] != parsed[i] {
t.Fatalf("unexpected parameter %v=%v. Expecting %v=%v", parsed[i][0], parsed[i][1], expectedParams[i][0], expectedParams[i][1])
}
}
}
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 cookie 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{err: errors.New("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)
// 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) {
t.Helper()
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.bufK = []byte{}
h.bufV = []byte{}
h1.bufK = []byte{}
h1.bufV = []byte{}
if !reflect.DeepEqual(&h, &h1) {
t.Fatalf("ResponseHeaderCopyTo fail, src: \n%+v\ndst: \n%+v\n", &h, &h1)
}
}
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")
h.noDefaultContentType = true
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.bufK = []byte{}
h.bufV = []byte{}
h1.bufK = []byte{}
h1.bufV = []byte{}
if !reflect.DeepEqual(&h, &h1) {
t.Fatalf("RequestHeaderCopyTo fail, src: \n%+v\ndst: \n%+v\n", &h, &h1)
}
}
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)
}
}
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 len(h1.contentType) != 0 {
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)
}
}
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, uSer aGent")
expectedTrailer := "Foo, Bar, uSer aGent"
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)
}
}
// Security tests for trailer handling vulnerability fix.
func TestTrailerSecurityVulnerabilityFix(t *testing.T) {
t.Parallel()
// Test cases for headers that should be blocked in trailers
dangerousHeaders := []struct {
name string
header string
description string
}{
{"Content-Type", "Content-Type", "off-by-one fix: exactly 'Content-Type' should be blocked"},
{"Cookie", "Cookie", "session hijacking prevention"},
{"Set-Cookie", "Set-Cookie", "session hijacking prevention"},
{"Location", "Location", "redirect attack prevention"},
{"X-Forwarded-For", "X-Forwarded-For", "IP spoofing prevention"},
{"X-Forwarded-Host", "X-Forwarded-Host", "IP spoofing prevention"},
{"X-Forwarded-Proto", "X-Forwarded-Proto", "IP spoofing prevention"},
{"X-Real-IP", "X-Real-IP", "IP spoofing prevention"},
{"X-Real-Ip", "X-Real-Ip", "IP spoofing prevention (case insensitive)"},
{"Authorization", "Authorization", "auth bypass prevention"},
{"Host", "Host", "host header attack prevention"},
{"Connection", "Connection", "connection control prevention"},
}
// Test RequestHeader AddTrailer blocking dangerous headers.
for _, tc := range dangerousHeaders {
t.Run("RequestHeader_"+tc.name, func(t *testing.T) {
var h RequestHeader
err := h.AddTrailer(tc.header)
if !errors.Is(err, ErrBadTrailer) {
t.Fatalf("Expected ErrBadTrailer for %s (%s), got: %v", tc.header, tc.description, err)
}
// Verify trailer header is empty since the dangerous header was rejected
if trailer := string(h.Peek(HeaderTrailer)); trailer != "" {
t.Fatalf("Expected empty trailer after rejecting %s, got: %q", tc.header, trailer)
}
})
}
// Test ResponseHeader AddTrailer blocking dangerous headers
for _, tc := range dangerousHeaders {
t.Run("ResponseHeader_"+tc.name, func(t *testing.T) {
var h ResponseHeader
err := h.AddTrailer(tc.header)
if !errors.Is(err, ErrBadTrailer) {
t.Fatalf("Expected ErrBadTrailer for %s (%s), got: %v", tc.header, tc.description, err)
}
// Verify trailer header is empty since the dangerous header was rejected
if trailer := string(h.Peek(HeaderTrailer)); trailer != "" {
t.Fatalf("Expected empty trailer after rejecting %s, got: %q", tc.header, trailer)
}
})
}
// Test that safe headers are still allowed
safeHeaders := []string{"Foo", "X-Custom-Safe", "My-App-Trailer", "Debug-Info"}
for _, header := range safeHeaders {
t.Run("Safe_RequestHeader_"+header, func(t *testing.T) {
var h RequestHeader
err := h.AddTrailer(header)
if err != nil {
t.Fatalf("Expected no error for safe header %s, got: %v", header, err)
}
// Verify the safe header was added to trailer
if trailer := string(h.Peek(HeaderTrailer)); trailer != header {
t.Fatalf("Expected trailer %q for safe header, got: %q", header, trailer)
}
})
t.Run("Safe_ResponseHeader_"+header, func(t *testing.T) {
var h ResponseHeader
err := h.AddTrailer(header)
if err != nil {
t.Fatalf("Expected no error for safe header %s, got: %v", header, err)
}
// Verify the safe header was added to trailer
if trailer := string(h.Peek(HeaderTrailer)); trailer != header {
t.Fatalf("Expected trailer %q for safe header, got: %q", header, trailer)
}
})
}
}
func TestTrailerParsingSecurityFix(t *testing.T) {
t.Parallel()
// Test the specific vulnerability scenario: malicious trailers should be rejected
// Test that dangerous trailers in chunked body are properly blocked
dangerousTrailers := []string{
"Content-Type: text/malicious\r\n\r\n",
"X-Forwarded-For: attacker.com\r\n\r\n",
"X-Real-IP: 1.1.1.1\r\n\r\n",
"Cookie: evil\r\n\r\n",
"Location: http://evil.com\r\n\r\n",
}
for i, trailer := range dangerousTrailers {
t.Run("Request_"+strconv.Itoa(i), func(t *testing.T) {
var h RequestHeader
r := bytes.NewBufferString(trailer)
br := bufio.NewReader(r)
err := h.ReadTrailer(br)
if err == nil {
t.Fatalf("Expected error when reading dangerous trailer, but got none: %s", trailer)
}
// The error should mention forbidden trailer
if !strings.Contains(err.Error(), "forbidden trailer") {
t.Fatalf("Expected 'forbidden trailer' error for %s, got: %v", trailer, err)
}
})
t.Run("Response_"+strconv.Itoa(i), func(t *testing.T) {
var h ResponseHeader
r := bytes.NewBufferString(trailer)
br := bufio.NewReader(r)
err := h.ReadTrailer(br)
if err == nil {
t.Fatalf("Expected error when reading dangerous trailer, but got none: %s", trailer)
}
// The error should mention forbidden trailer
if !strings.Contains(err.Error(), "forbidden trailer") {
t.Fatalf("Expected 'forbidden trailer' error for %s, got: %v", trailer, err)
}
})
}
// Test that safe trailers still work
safeTrailers := []string{
"Foo: bar\r\n\r\n",
"X-Custom-Header: value\r\n\r\n",
"Debug-Info: test\r\n\r\n",
}
for i, trailer := range safeTrailers {
t.Run("Safe_Request_"+strconv.Itoa(i), func(t *testing.T) {
var h RequestHeader
r := bytes.NewBufferString(trailer)
br := bufio.NewReader(r)
err := h.ReadTrailer(br)
if err != nil && err != io.EOF {
t.Fatalf("Expected no error for safe trailer %s, got: %v", trailer, err)
}
})
t.Run("Safe_Response_"+strconv.Itoa(i), func(t *testing.T) {
var h ResponseHeader
r := bytes.NewBufferString(trailer)
br := bufio.NewReader(r)
err := h.ReadTrailer(br)
if err != nil && err != io.EOF {
t.Fatalf("Expected no error for safe trailer %s, got: %v", trailer, err)
}
})
}
}
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)
}
for key, value := range h.Cookies() {
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. Expected %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. Expected %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
for key := range h.All() {
if string(key) == HeaderSetCookie {
cookieSeen = true
break
}
}
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. Expected %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
for key := range h.All() {
if string(key) == HeaderSetCookie {
cookieSeen = true
break
}
}
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
for key := range h.All() {
if string(key) == HeaderCookie {
cookieSeen = true
break
}
}
if !cookieSeen {
t.Fatalf("Cookie not present in VisitAll")
}
if string(h.Cookie("foo")) != "bar" {
t.Fatalf("Unexpected cookie value %q. Expected %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
for key := range h.All() {
if string(key) == HeaderCookie {
cookieSeen = true
break
}
}
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 !bytes.Equal(m, 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 r.s == "" {
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")
}
// transfer-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 transfer-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")
// 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")
}
// See https://github.com/valyala/fasthttp/issues/1909
if hasArg(h.h, HeaderTransferEncoding) {
t.Fatalf("unexpected header: 'Transfer-Encoding' should not be present in parsed headers")
}
// 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", "", "")
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", "", "")
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", "", "")
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", "", "")
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", "", "")
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", "", "")
// 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")
// no space after colon
testRequestHeaderReadSuccess(t, h, "GET /a HTTP/1.1\nHost:aaaxd\n\nsdfds",
-2, "/a", "aaaxd", "", "")
// 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",
123, "/xxx", "aaa.com", "", "")
// invalid case
testRequestHeaderReadSuccess(t, h, "GET /aaa HTTP/1.1\nhoST: bbb.com\n\naas",
-2, "/aaa", "bbb.com", "", "")
// referer
testRequestHeaderReadSuccess(t, h, "GET /asdf HTTP/1.1\nHost: aaa.com\nReferer: bb.com\n\naaa",
-2, "/asdf", "aaa.com", "bb.com", "")
// 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", "", "")
// 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")
// 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")
// 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", "", "")
// 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")
// 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", "", "")
// 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", "", "")
// 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", "", "")
// no host
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nFOObar: assdfd\r\n\r\naaa",
-2, "/foo/bar", "", "", "")
// no host, no headers
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\n\r\nfoobar",
-2, "/foo/bar", "", "", "")
// 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", "", "")
// 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", "", "")
// 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")
// 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")
}
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")
// no protocol in the first line
testResponseHeaderReadError(t, h, "GET /foo/bar\r\nHost: google.com\r\n\r\nisdD")
// zero-length headers
testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\n: zero-key\r\n\r\n")
}
func TestResponseHeaderReadErrorSecureLog(t *testing.T) {
t.Parallel()
h := &ResponseHeader{}
h.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")
// post with duplicate content-length
testRequestHeaderReadError(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")
// Zero-length header
testRequestHeaderReadError(t, h, "GET /foo/bar HTTP/1.1\r\n: zero-key\r\n\r\n")
// Invalid method
testRequestHeaderReadError(t, h, "G(ET /foo/bar HTTP/1.1\r\n: zero-key\r\n\r\n")
}
func TestRequestHeaderReadSecuredError(t *testing.T) {
t.Parallel()
h := &RequestHeader{}
h.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) {
t.Helper()
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", "", "")
}
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", "", "")
}
func testResponseHeaderReadSuccess(t *testing.T, h *ResponseHeader, headers string, expectedStatusCode, expectedContentLength int,
expectedContentType string,
) {
t.Helper()
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,
) {
t.Helper()
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) {
t.Helper()
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)
}
func TestRequestHeader_PeekAll(t *testing.T) {
t.Parallel()
h := &RequestHeader{}
h.Add(HeaderConnection, "keep-alive")
h.Add("Content-Type", "aaa")
h.Add(HeaderHost, "aaabbb")
h.Add("User-Agent", "asdfas")
h.Add("Content-Length", "1123")
h.Add("Cookie", "foobar=baz")
h.Add(HeaderTrailer, "foo, bar")
h.Add("aaa", "aaa")
h.Add("aaa", "bbb")
expectRequestHeaderAll(t, h, HeaderConnection, [][]byte{s2b("keep-alive")})
expectRequestHeaderAll(t, h, "Content-Type", [][]byte{s2b("aaa")})
expectRequestHeaderAll(t, h, HeaderHost, [][]byte{s2b("aaabbb")})
expectRequestHeaderAll(t, h, "User-Agent", [][]byte{s2b("asdfas")})
expectRequestHeaderAll(t, h, "Content-Length", [][]byte{s2b("1123")})
expectRequestHeaderAll(t, h, "Cookie", [][]byte{s2b("foobar=baz")})
expectRequestHeaderAll(t, h, HeaderTrailer, [][]byte{s2b("Foo, Bar")})
expectRequestHeaderAll(t, h, "aaa", [][]byte{s2b("aaa"), s2b("bbb")})
h.Del("Content-Type")
h.Del(HeaderHost)
h.Del("aaa")
expectRequestHeaderAll(t, h, "Content-Type", [][]byte{})
expectRequestHeaderAll(t, h, HeaderHost, [][]byte{})
expectRequestHeaderAll(t, h, "aaa", [][]byte{})
}
func expectRequestHeaderAll(t *testing.T, h *RequestHeader, key string, expectedValue [][]byte) {
if len(h.PeekAll(key)) != len(expectedValue) {
t.Fatalf("Unexpected size for key %q: %d. Expected %d", key, len(h.PeekAll(key)), len(expectedValue))
}
if !reflect.DeepEqual(h.PeekAll(key), expectedValue) {
t.Fatalf("Unexpected value for key %q: %q. Expected %q", key, h.PeekAll(key), expectedValue)
}
}
func TestResponseHeader_PeekAll(t *testing.T) {
t.Parallel()
h := &ResponseHeader{}
h.Add(HeaderContentType, "aaa/bbb")
h.Add(HeaderContentEncoding, "gzip")
h.Add(HeaderConnection, "close")
h.Add(HeaderContentLength, "1234")
h.Add(HeaderServer, "aaaa")
h.Add(HeaderSetCookie, "cccc")
h.Add("aaa", "aaa")
h.Add("aaa", "bbb")
expectResponseHeaderAll(t, h, HeaderContentType, [][]byte{s2b("aaa/bbb")})
expectResponseHeaderAll(t, h, HeaderContentEncoding, [][]byte{s2b("gzip")})
expectResponseHeaderAll(t, h, HeaderConnection, [][]byte{s2b("close")})
expectResponseHeaderAll(t, h, HeaderContentLength, [][]byte{s2b("1234")})
expectResponseHeaderAll(t, h, HeaderServer, [][]byte{s2b("aaaa")})
expectResponseHeaderAll(t, h, HeaderSetCookie, [][]byte{s2b("cccc")})
expectResponseHeaderAll(t, h, "aaa", [][]byte{s2b("aaa"), s2b("bbb")})
h.Del(HeaderContentType)
h.Del(HeaderContentEncoding)
expectResponseHeaderAll(t, h, HeaderContentType, [][]byte{defaultContentType})
expectResponseHeaderAll(t, h, HeaderContentEncoding, [][]byte{})
}
func expectResponseHeaderAll(t *testing.T, h *ResponseHeader, key string, expectedValue [][]byte) {
if len(h.PeekAll(key)) != len(expectedValue) {
t.Fatalf("Unexpected size for key %q: %d. Expected %d", key, len(h.PeekAll(key)), len(expectedValue))
}
if !reflect.DeepEqual(h.PeekAll(key), expectedValue) {
t.Fatalf("Unexpected value for key %q: %q. Expected %q", key, h.PeekAll(key), expectedValue)
}
}
func TestRequestHeader_Keys(t *testing.T) {
h := &RequestHeader{}
h.Add(HeaderConnection, "keep-alive")
h.Add("Content-Type", "aaa")
err := h.SetTrailer("aaa,bbb,ccc")
if err != nil {
t.Fatal(err)
}
actualKeys := h.PeekKeys()
expectedKeys := [][]byte{[]byte("Content-Type"), []byte("Trailer"), []byte("Connection")}
if !reflect.DeepEqual(actualKeys, expectedKeys) {
t.Fatalf("Unexpected value %q. Expected %q", actualKeys, expectedKeys)
}
actualTrailerKeys := h.PeekTrailerKeys()
expectedTrailerKeys := [][]byte{s2b("Aaa"), s2b("Bbb"), s2b("Ccc")}
if !reflect.DeepEqual(actualTrailerKeys, expectedTrailerKeys) {
t.Fatalf("Unexpected value %q. Expected %q", actualTrailerKeys, expectedTrailerKeys)
}
}
func TestResponseHeader_Keys(t *testing.T) {
h := &ResponseHeader{}
h.Add(HeaderConnection, "keep-alive")
h.Add("Content-Type", "aaa")
err := h.SetTrailer("aaa,bbb,ccc")
if err != nil {
t.Fatal(err)
}
actualKeys := h.PeekKeys()
expectedKeys := [][]byte{[]byte("Content-Type"), []byte("Trailer"), []byte("Connection")}
if !reflect.DeepEqual(actualKeys, expectedKeys) {
t.Fatalf("Unexpected value %q. Expected %q", actualKeys, expectedKeys)
}
actualTrailerKeys := h.PeekTrailerKeys()
expectedTrailerKeys := [][]byte{s2b("Aaa"), s2b("Bbb"), s2b("Ccc")}
if !reflect.DeepEqual(actualTrailerKeys, expectedTrailerKeys) {
t.Fatalf("Unexpected value %q. Expected %q", actualTrailerKeys, expectedTrailerKeys)
}
}
func TestAddVaryHeader(t *testing.T) {
t.Parallel()
var h ResponseHeader
h.addVaryBytes([]byte("Accept-Encoding"))
got := string(h.Peek("Vary"))
expected := "Accept-Encoding"
if got != expected {
t.Errorf("expected %q got %q", expected, got)
}
var buf bytes.Buffer
if _, err := h.WriteTo(&buf); err != nil {
t.Fatalf("unexpected error when writing header: %v", err)
}
if n := strings.Count(buf.String(), "Vary: "); n != 1 {
t.Errorf("Vary occurred %d times", n)
}
}
func TestAddVaryHeaderExisting(t *testing.T) {
t.Parallel()
var h ResponseHeader
h.Set("Vary", "Accept")
h.addVaryBytes([]byte("Accept-Encoding"))
got := string(h.Peek("Vary"))
expected := "Accept,Accept-Encoding"
if got != expected {
t.Errorf("expected %q got %q", expected, got)
}
var buf bytes.Buffer
if _, err := h.WriteTo(&buf); err != nil {
t.Fatalf("unexpected error when writing header: %v", err)
}
if n := strings.Count(buf.String(), "Vary: "); n != 1 {
t.Errorf("Vary occurred %d times", n)
}
}
func TestAddVaryHeaderExistingAcceptEncoding(t *testing.T) {
t.Parallel()
var h ResponseHeader
h.Set("Vary", "Accept-Encoding")
h.addVaryBytes([]byte("Accept-Encoding"))
got := string(h.Peek("Vary"))
expected := "Accept-Encoding"
if got != expected {
t.Errorf("expected %q got %q", expected, got)
}
var buf bytes.Buffer
if _, err := h.WriteTo(&buf); err != nil {
t.Fatalf("unexpected error when writing header: %v", err)
}
if n := strings.Count(buf.String(), "Vary: "); n != 1 {
t.Errorf("Vary occurred %d times", n)
}
}