mirror of
https://github.com/valyala/fasthttp.git
synced 2026-06-15 16:07:51 +03:00
c9f43eaa1b
* Response.ContentEncoding(): store as field The CE is not so often used for plain APIs responses and even not so often used for static files and on the fly compression. But still it should be checked each time. Also having a dedicated field getter and setter simplifies code * header.go Use shorter Response.setNonSpecial() and Request.setNonSpecial() methods instead of SetCanonical() The change should improve performance because the setSpecialHeader() call is omitted. As a downside on adding a new basic header field all putHeader() must be replaced with a direct getter and setter.
2864 lines
84 KiB
Go
2864 lines
84 KiB
Go
package fasthttp
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestResponseHeaderAddContentType(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h ResponseHeader
|
|
h.Add("Content-Type", "test")
|
|
|
|
got := string(h.Peek("Content-Type"))
|
|
expected := "test"
|
|
if got != expected {
|
|
t.Errorf("expected %q got %q", expected, got)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
h.WriteTo(&buf) //nolint:errcheck
|
|
|
|
if n := strings.Count(buf.String(), "Content-Type: "); n != 1 {
|
|
t.Errorf("Content-Type occurred %d times", n)
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderAddContentEncoding(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h ResponseHeader
|
|
h.Add("Content-Encoding", "test")
|
|
|
|
got := string(h.Peek("Content-Encoding"))
|
|
expected := "test"
|
|
if got != expected {
|
|
t.Errorf("expected %q got %q", expected, got)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
h.WriteTo(&buf) //nolint:errcheck
|
|
|
|
if n := strings.Count(buf.String(), "Content-Encoding: "); n != 1 {
|
|
t.Errorf("Content-Encoding occurred %d times", n)
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderMultiLineValue(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
s := "HTTP/1.1 200 SuperOK\r\n" +
|
|
"EmptyValue1:\r\n" +
|
|
"Content-Type: foo/bar;\r\n\tnewline;\r\n another/newline\r\n" +
|
|
"Foo: Bar\r\n" +
|
|
"Multi-Line: one;\r\n two\r\n" +
|
|
"Values: v1;\r\n v2; v3;\r\n v4;\tv5\r\n" +
|
|
"\r\n"
|
|
header := new(ResponseHeader)
|
|
if _, err := header.parse([]byte(s)); err != nil {
|
|
t.Fatalf("parse headers with multi-line values failed, %v", err)
|
|
}
|
|
response, err := http.ReadResponse(bufio.NewReader(strings.NewReader(s)), nil)
|
|
if err != nil {
|
|
t.Fatalf("parse response using net/http failed, %v", err)
|
|
}
|
|
|
|
if !bytes.Equal(header.StatusMessage(), []byte("SuperOK")) {
|
|
t.Errorf("parse status line with non-default value failed, got: '%q' want: 'SuperOK'", header.StatusMessage())
|
|
}
|
|
|
|
header.SetProtocol([]byte("HTTP/3.3"))
|
|
if !bytes.Equal(header.Protocol(), []byte("HTTP/3.3")) {
|
|
t.Errorf("parse protocol with non-default value failed, got: '%q' want: 'HTTP/3.3'", header.Protocol())
|
|
}
|
|
|
|
if !bytes.Equal(header.appendStatusLine(nil), []byte("HTTP/3.3 200 SuperOK\r\n")) {
|
|
t.Errorf("parse status line with non-default value failed, got: '%q' want: 'HTTP/3.3 200 SuperOK'", header.Protocol())
|
|
}
|
|
|
|
header.SetStatusMessage(nil)
|
|
|
|
if !bytes.Equal(header.appendStatusLine(nil), []byte("HTTP/3.3 200 OK\r\n")) {
|
|
t.Errorf("parse status line with default protocol value failed, got: '%q' want: 'HTTP/3.3 200 OK'", header.appendStatusLine(nil))
|
|
}
|
|
|
|
header.SetStatusMessage(s2b(StatusMessage(200)))
|
|
|
|
if !bytes.Equal(header.appendStatusLine(nil), []byte("HTTP/3.3 200 OK\r\n")) {
|
|
t.Errorf("parse status line with default protocol value failed, got: '%q' want: 'HTTP/3.3 200 OK'", header.appendStatusLine(nil))
|
|
}
|
|
|
|
for name, vals := range response.Header {
|
|
got := string(header.Peek(name))
|
|
want := vals[0]
|
|
|
|
if got != want {
|
|
t.Errorf("unexpected %q got: %q want: %q", name, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderMultiLineName(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
s := "HTTP/1.1 200 OK\r\n" +
|
|
"Host: golang.org\r\n" +
|
|
"Gopher-New-\r\n" +
|
|
" Line: This is a header on multiple lines\r\n" +
|
|
"\r\n"
|
|
header := new(ResponseHeader)
|
|
if _, err := header.parse([]byte(s)); err != errInvalidName {
|
|
m := make(map[string]string)
|
|
header.VisitAll(func(key, value []byte) {
|
|
m[string(key)] = string(value)
|
|
})
|
|
t.Errorf("expected error, got %q (%v)", m, err)
|
|
}
|
|
|
|
if !bytes.Equal(header.StatusMessage(), []byte("OK")) {
|
|
t.Errorf("expected default status line, got: %q", header.StatusMessage())
|
|
}
|
|
|
|
if !bytes.Equal(header.Protocol(), []byte("HTTP/1.1")) {
|
|
t.Errorf("expected default protocol, got: %q", header.Protocol())
|
|
}
|
|
|
|
if !bytes.Equal(header.appendStatusLine(nil), []byte("HTTP/1.1 200 OK\r\n")) {
|
|
t.Errorf("parse status line with non-default value failed, got: %q want: HTTP/1.1 200 OK", header.Protocol())
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderMultiLinePaniced(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Input generated by fuzz testing that caused the parser to panic.
|
|
s, _ := base64.StdEncoding.DecodeString("aAEAIDoKKDoKICA6CgkKCiA6CiA6CgkpCiA6CiA6CiA6Cig6CiAgOgoJCgogOgogOgoJKQogOgogOgogOgogOgogOgoJOg86CiA6CiA6Cig6CiAyCg==")
|
|
header := new(RequestHeader)
|
|
header.parse(s) //nolint:errcheck
|
|
}
|
|
|
|
func TestResponseHeaderEmptyValueFromHeader(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h1 ResponseHeader
|
|
h1.SetContentType("foo/bar")
|
|
h1.Set("EmptyValue1", "")
|
|
h1.Set("EmptyValue2", " ")
|
|
s := h1.String()
|
|
|
|
var h ResponseHeader
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if string(h.ContentType()) != string(h1.ContentType()) {
|
|
t.Fatalf("unexpected content-type: %q. Expecting %q", h.ContentType(), h1.ContentType())
|
|
}
|
|
v1 := h.Peek("EmptyValue1")
|
|
if len(v1) > 0 {
|
|
t.Fatalf("expecting empty value. Got %q", v1)
|
|
}
|
|
v2 := h.Peek("EmptyValue2")
|
|
if len(v2) > 0 {
|
|
t.Fatalf("expecting empty value. Got %q", v2)
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderEmptyValueFromString(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
s := "HTTP/1.1 200 OK\r\n" +
|
|
"EmptyValue1:\r\n" +
|
|
"Content-Type: foo/bar\r\n" +
|
|
"EmptyValue2: \r\n" +
|
|
"\r\n"
|
|
|
|
var h ResponseHeader
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if string(h.ContentType()) != "foo/bar" {
|
|
t.Fatalf("unexpected content-type: %q. Expecting %q", h.ContentType(), "foo/bar")
|
|
}
|
|
v1 := h.Peek("EmptyValue1")
|
|
if len(v1) > 0 {
|
|
t.Fatalf("expecting empty value. Got %q", v1)
|
|
}
|
|
v2 := h.Peek("EmptyValue2")
|
|
if len(v2) > 0 {
|
|
t.Fatalf("expecting empty value. Got %q", v2)
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderEmptyValueFromHeader(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h1 RequestHeader
|
|
h1.SetRequestURI("/foo/bar")
|
|
h1.SetHost("foobar")
|
|
h1.Set("EmptyValue1", "")
|
|
h1.Set("EmptyValue2", " ")
|
|
s := h1.String()
|
|
|
|
var h RequestHeader
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if string(h.Host()) != string(h1.Host()) {
|
|
t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), h1.Host())
|
|
}
|
|
v1 := h.Peek("EmptyValue1")
|
|
if len(v1) > 0 {
|
|
t.Fatalf("expecting empty value. Got %q", v1)
|
|
}
|
|
v2 := h.Peek("EmptyValue2")
|
|
if len(v2) > 0 {
|
|
t.Fatalf("expecting empty value. Got %q", v2)
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderEmptyValueFromString(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
s := "GET / HTTP/1.1\r\n" +
|
|
"EmptyValue1:\r\n" +
|
|
"Host: foobar\r\n" +
|
|
"EmptyValue2: \r\n" +
|
|
"\r\n"
|
|
var h RequestHeader
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if string(h.Host()) != "foobar" {
|
|
t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar")
|
|
}
|
|
v1 := h.Peek("EmptyValue1")
|
|
if len(v1) > 0 {
|
|
t.Fatalf("expecting empty value. Got %q", v1)
|
|
}
|
|
v2 := h.Peek("EmptyValue2")
|
|
if len(v2) > 0 {
|
|
t.Fatalf("expecting empty value. Got %q", v2)
|
|
}
|
|
}
|
|
|
|
func TestRequestRawHeaders(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
kvs := "hOsT: foobar\r\n" +
|
|
"value: b\r\n" +
|
|
"\r\n"
|
|
t.Run("normalized", func(t *testing.T) {
|
|
s := "GET / HTTP/1.1\r\n" + kvs
|
|
exp := kvs
|
|
var h RequestHeader
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if string(h.Host()) != "foobar" {
|
|
t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar")
|
|
}
|
|
v2 := h.Peek("Value")
|
|
if !bytes.Equal(v2, []byte{'b'}) {
|
|
t.Fatalf("expecting non empty value. Got %q", v2)
|
|
}
|
|
if raw := h.RawHeaders(); string(raw) != exp {
|
|
t.Fatalf("expected header %q, got %q", exp, raw)
|
|
}
|
|
})
|
|
for _, n := range []int{0, 1, 4, 8} {
|
|
t.Run(fmt.Sprintf("post-%dk", n), func(t *testing.T) {
|
|
l := 1024 * n
|
|
body := make([]byte, l)
|
|
for i := range body {
|
|
body[i] = 'a'
|
|
}
|
|
cl := fmt.Sprintf("Content-Length: %d\r\n", l)
|
|
s := "POST / HTTP/1.1\r\n" + cl + kvs + string(body)
|
|
exp := cl + kvs
|
|
var h RequestHeader
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if string(h.Host()) != "foobar" {
|
|
t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar")
|
|
}
|
|
v2 := h.Peek("Value")
|
|
if !bytes.Equal(v2, []byte{'b'}) {
|
|
t.Fatalf("expecting non empty value. Got %q", v2)
|
|
}
|
|
if raw := h.RawHeaders(); string(raw) != exp {
|
|
t.Fatalf("expected header %q, got %q", exp, raw)
|
|
}
|
|
})
|
|
}
|
|
t.Run("http10", func(t *testing.T) {
|
|
s := "GET / HTTP/1.0\r\n" + kvs
|
|
exp := kvs
|
|
var h RequestHeader
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if string(h.Host()) != "foobar" {
|
|
t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar")
|
|
}
|
|
v2 := h.Peek("Value")
|
|
if !bytes.Equal(v2, []byte{'b'}) {
|
|
t.Fatalf("expecting non empty value. Got %q", v2)
|
|
}
|
|
if raw := h.RawHeaders(); string(raw) != exp {
|
|
t.Fatalf("expected header %q, got %q", exp, raw)
|
|
}
|
|
})
|
|
t.Run("no-kvs", func(t *testing.T) {
|
|
s := "GET / HTTP/1.1\r\n\r\n"
|
|
exp := ""
|
|
var h RequestHeader
|
|
h.DisableNormalizing()
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if string(h.Host()) != "" {
|
|
t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "")
|
|
}
|
|
v1 := h.Peek("NoKey")
|
|
if len(v1) > 0 {
|
|
t.Fatalf("expecting empty value. Got %q", v1)
|
|
}
|
|
if raw := h.RawHeaders(); string(raw) != exp {
|
|
t.Fatalf("expected header %q, got %q", exp, raw)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRequestHeaderSetCookieWithSpecialChars(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h RequestHeader
|
|
h.Set("Cookie", "ID&14")
|
|
s := h.String()
|
|
|
|
if !strings.Contains(s, "Cookie: ID&14") {
|
|
t.Fatalf("Missing cookie in request header: %q", s)
|
|
}
|
|
|
|
var h1 RequestHeader
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := h1.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
cookie := h1.Peek(HeaderCookie)
|
|
if string(cookie) != "ID&14" {
|
|
t.Fatalf("unexpected cooke: %q. Expecting %q", cookie, "ID&14")
|
|
}
|
|
|
|
cookie = h1.Cookie("")
|
|
if string(cookie) != "ID&14" {
|
|
t.Fatalf("unexpected cooke: %q. Expecting %q", cookie, "ID&14")
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderDefaultStatusCode(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h ResponseHeader
|
|
statusCode := h.StatusCode()
|
|
if statusCode != StatusOK {
|
|
t.Fatalf("unexpected status code: %d. Expecting %d", statusCode, StatusOK)
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderDelClientCookie(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cookieName := "foobar"
|
|
|
|
var h ResponseHeader
|
|
c := AcquireCookie()
|
|
c.SetKey(cookieName)
|
|
c.SetValue("aasdfsdaf")
|
|
h.SetCookie(c)
|
|
|
|
h.DelClientCookieBytes([]byte(cookieName))
|
|
if !h.Cookie(c) {
|
|
t.Fatalf("expecting cookie %q", c.Key())
|
|
}
|
|
if !c.Expire().Equal(CookieExpireDelete) {
|
|
t.Fatalf("unexpected cookie expiration time: %q. Expecting %q", c.Expire(), CookieExpireDelete)
|
|
}
|
|
if len(c.Value()) > 0 {
|
|
t.Fatalf("unexpected cookie value: %q. Expecting empty value", c.Value())
|
|
}
|
|
ReleaseCookie(c)
|
|
}
|
|
|
|
func TestResponseHeaderAdd(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := make(map[string]struct{})
|
|
var h ResponseHeader
|
|
h.Add("aaa", "bbb")
|
|
h.Add("content-type", "xxx")
|
|
m["bbb"] = struct{}{}
|
|
m["xxx"] = struct{}{}
|
|
for i := 0; i < 10; i++ {
|
|
v := fmt.Sprintf("%d", i)
|
|
h.Add("Foo-Bar", v)
|
|
m[v] = struct{}{}
|
|
}
|
|
if h.Len() != 12 {
|
|
t.Fatalf("unexpected header len %d. Expecting 12", h.Len())
|
|
}
|
|
|
|
h.VisitAll(func(k, v []byte) {
|
|
switch string(k) {
|
|
case "Aaa", "Foo-Bar", "Content-Type":
|
|
if _, ok := m[string(v)]; !ok {
|
|
t.Fatalf("unexpected value found %q. key %q", v, k)
|
|
}
|
|
delete(m, string(v))
|
|
default:
|
|
t.Fatalf("unexpected key found: %q", k)
|
|
}
|
|
})
|
|
if len(m) > 0 {
|
|
t.Fatalf("%d headers are missed", len(m))
|
|
}
|
|
|
|
s := h.String()
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
var h1 ResponseHeader
|
|
if err := h1.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
h.VisitAll(func(k, v []byte) {
|
|
switch string(k) {
|
|
case "Aaa", "Foo-Bar", "Content-Type":
|
|
m[string(v)] = struct{}{}
|
|
default:
|
|
t.Fatalf("unexpected key found: %q", k)
|
|
}
|
|
})
|
|
if len(m) != 12 {
|
|
t.Fatalf("unexpected number of headers: %d. Expecting 12", len(m))
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderAdd(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
m := make(map[string]struct{})
|
|
var h RequestHeader
|
|
h.Add("aaa", "bbb")
|
|
h.Add("user-agent", "xxx")
|
|
m["bbb"] = struct{}{}
|
|
m["xxx"] = struct{}{}
|
|
for i := 0; i < 10; i++ {
|
|
v := fmt.Sprintf("%d", i)
|
|
h.Add("Foo-Bar", v)
|
|
m[v] = struct{}{}
|
|
}
|
|
if h.Len() != 12 {
|
|
t.Fatalf("unexpected header len %d. Expecting 12", h.Len())
|
|
}
|
|
|
|
h.VisitAll(func(k, v []byte) {
|
|
switch string(k) {
|
|
case "Aaa", "Foo-Bar", "User-Agent":
|
|
if _, ok := m[string(v)]; !ok {
|
|
t.Fatalf("unexpected value found %q. key %q", v, k)
|
|
}
|
|
delete(m, string(v))
|
|
default:
|
|
t.Fatalf("unexpected key found: %q", k)
|
|
}
|
|
})
|
|
if len(m) > 0 {
|
|
t.Fatalf("%d headers are missed", len(m))
|
|
}
|
|
|
|
s := h.String()
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
var h1 RequestHeader
|
|
if err := h1.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
h.VisitAll(func(k, v []byte) {
|
|
switch string(k) {
|
|
case "Aaa", "Foo-Bar", "User-Agent":
|
|
m[string(v)] = struct{}{}
|
|
default:
|
|
t.Fatalf("unexpected key found: %q", k)
|
|
}
|
|
})
|
|
if len(m) != 12 {
|
|
t.Fatalf("unexpected number of headers: %d. Expecting 12", len(m))
|
|
}
|
|
s1 := h1.String()
|
|
if s != s1 {
|
|
t.Fatalf("unexpected headers %q. Expecting %q", s1, s)
|
|
}
|
|
}
|
|
|
|
func TestHasHeaderValue(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testHasHeaderValue(t, "foobar", "foobar", true)
|
|
testHasHeaderValue(t, "foobar", "foo", false)
|
|
testHasHeaderValue(t, "foobar", "bar", false)
|
|
testHasHeaderValue(t, "keep-alive, Upgrade", "keep-alive", true)
|
|
testHasHeaderValue(t, "keep-alive , Upgrade", "Upgrade", true)
|
|
testHasHeaderValue(t, "keep-alive, Upgrade", "Upgrade-foo", false)
|
|
testHasHeaderValue(t, "keep-alive, Upgrade", "Upgr", false)
|
|
testHasHeaderValue(t, "foo , bar, baz ,", "foo", true)
|
|
testHasHeaderValue(t, "foo , bar, baz ,", "bar", true)
|
|
testHasHeaderValue(t, "foo , bar, baz ,", "baz", true)
|
|
testHasHeaderValue(t, "foo , bar, baz ,", "ba", false)
|
|
testHasHeaderValue(t, "foo, ", "", true)
|
|
testHasHeaderValue(t, "foo", "", false)
|
|
}
|
|
|
|
func testHasHeaderValue(t *testing.T, s, value string, has bool) {
|
|
ok := hasHeaderValue([]byte(s), []byte(value))
|
|
if ok != has {
|
|
t.Fatalf("unexpected hasHeaderValue(%q, %q)=%v. Expecting %v", s, value, ok, has)
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderDel(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h RequestHeader
|
|
h.Set("Foo-Bar", "baz")
|
|
h.Set("aaa", "bbb")
|
|
h.Set(HeaderConnection, "keep-alive")
|
|
h.Set("Content-Type", "aaa")
|
|
h.Set(HeaderHost, "aaabbb")
|
|
h.Set("User-Agent", "asdfas")
|
|
h.Set("Content-Length", "1123")
|
|
h.Set("Cookie", "foobar=baz")
|
|
h.Set(HeaderTrailer, "foo, bar")
|
|
|
|
h.Del("foo-bar")
|
|
h.Del("connection")
|
|
h.DelBytes([]byte("content-type"))
|
|
h.Del("Host")
|
|
h.Del("user-agent")
|
|
h.Del("content-length")
|
|
h.Del("cookie")
|
|
h.Del("trailer")
|
|
|
|
hv := h.Peek("aaa")
|
|
if string(hv) != "bbb" {
|
|
t.Fatalf("unexpected header value: %q. Expecting %q", hv, "bbb")
|
|
}
|
|
hv = h.Peek("Foo-Bar")
|
|
if len(hv) > 0 {
|
|
t.Fatalf("non-zero value: %q", hv)
|
|
}
|
|
hv = h.Peek(HeaderConnection)
|
|
if len(hv) > 0 {
|
|
t.Fatalf("non-zero value: %q", hv)
|
|
}
|
|
hv = h.Peek(HeaderContentType)
|
|
if len(hv) > 0 {
|
|
t.Fatalf("non-zero value: %q", hv)
|
|
}
|
|
hv = h.Peek(HeaderHost)
|
|
if len(hv) > 0 {
|
|
t.Fatalf("non-zero value: %q", hv)
|
|
}
|
|
hv = h.Peek(HeaderUserAgent)
|
|
if len(hv) > 0 {
|
|
t.Fatalf("non-zero value: %q", hv)
|
|
}
|
|
hv = h.Peek(HeaderContentLength)
|
|
if len(hv) > 0 {
|
|
t.Fatalf("non-zero value: %q", hv)
|
|
}
|
|
hv = h.Peek(HeaderCookie)
|
|
if len(hv) > 0 {
|
|
t.Fatalf("non-zero value: %q", hv)
|
|
}
|
|
hv = h.Peek(HeaderTrailer)
|
|
if len(hv) > 0 {
|
|
t.Fatalf("non-zero value: %q", hv)
|
|
}
|
|
|
|
cv := h.Cookie("foobar")
|
|
if len(cv) > 0 {
|
|
t.Fatalf("unexpected cookie obtianed: %q", cv)
|
|
}
|
|
if h.ContentLength() != 0 {
|
|
t.Fatalf("unexpected content-length: %d. Expecting 0", h.ContentLength())
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderDel(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h ResponseHeader
|
|
h.Set("Foo-Bar", "baz")
|
|
h.Set("aaa", "bbb")
|
|
h.Set(HeaderConnection, "keep-alive")
|
|
h.Set(HeaderContentType, "aaa")
|
|
h.Set(HeaderContentEncoding, "gzip")
|
|
h.Set(HeaderServer, "aaabbb")
|
|
h.Set(HeaderContentLength, "1123")
|
|
h.Set(HeaderTrailer, "foo, bar")
|
|
|
|
var c Cookie
|
|
c.SetKey("foo")
|
|
c.SetValue("bar")
|
|
h.SetCookie(&c)
|
|
|
|
h.Del("foo-bar")
|
|
h.Del("connection")
|
|
h.DelBytes([]byte("content-type"))
|
|
h.Del(HeaderServer)
|
|
h.Del("content-length")
|
|
h.Del("set-cookie")
|
|
h.Del("trailer")
|
|
|
|
hv := h.Peek("aaa")
|
|
if string(hv) != "bbb" {
|
|
t.Fatalf("unexpected header value: %q. Expecting %q", hv, "bbb")
|
|
}
|
|
hv = h.Peek("Foo-Bar")
|
|
if len(hv) > 0 {
|
|
t.Fatalf("non-zero header value: %q", hv)
|
|
}
|
|
hv = h.Peek(HeaderConnection)
|
|
if len(hv) > 0 {
|
|
t.Fatalf("non-zero value: %q", hv)
|
|
}
|
|
hv = h.Peek(HeaderContentType)
|
|
if string(hv) != string(defaultContentType) {
|
|
t.Fatalf("unexpected content-type: %q. Expecting %q", hv, defaultContentType)
|
|
}
|
|
hv = h.Peek(HeaderContentEncoding)
|
|
if string(hv) != ("gzip") {
|
|
t.Fatalf("unexpected content-encoding: %q. Expecting %q", hv, "gzip")
|
|
}
|
|
hv = h.Peek(HeaderServer)
|
|
if len(hv) > 0 {
|
|
t.Fatalf("non-zero value: %q", hv)
|
|
}
|
|
hv = h.Peek(HeaderContentLength)
|
|
if len(hv) > 0 {
|
|
t.Fatalf("non-zero value: %q", hv)
|
|
}
|
|
hv = h.Peek(HeaderTrailer)
|
|
if len(hv) > 0 {
|
|
t.Fatalf("non-zero value: %q", hv)
|
|
}
|
|
|
|
if h.Cookie(&c) {
|
|
t.Fatalf("unexpected cookie obtianed: %q", &c)
|
|
}
|
|
if h.ContentLength() != 0 {
|
|
t.Fatalf("unexpected content-length: %d. Expecting 0", h.ContentLength())
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderSetTrailerGetBytes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
h := &ResponseHeader{}
|
|
h.noDefaultDate = true
|
|
h.Set("Foo", "bar")
|
|
h.Set(HeaderTrailer, "Baz")
|
|
h.Set("Baz", "test")
|
|
|
|
headerBytes := h.Header()
|
|
n, err := h.parseFirstLine(headerBytes)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if string(headerBytes[n:]) != "Foo: bar\r\nTrailer: Baz\r\n\r\n" {
|
|
t.Fatalf("Unexpected header: %q. Expected %q", headerBytes[n:], "Foo: bar\nTrailer: Baz\n\n")
|
|
}
|
|
if string(h.TrailerHeader()) != "Baz: test\r\n\r\n" {
|
|
t.Fatalf("Unexpected trailer header: %q. Expected %q", h.TrailerHeader(), "Baz: test\r\n\r\n")
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderSetTrailerGetBytes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
h := &RequestHeader{}
|
|
h.Set("Foo", "bar")
|
|
h.Set(HeaderTrailer, "Baz")
|
|
h.Set("Baz", "test")
|
|
|
|
headerBytes := h.Header()
|
|
n, err := h.parseFirstLine(headerBytes)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if string(headerBytes[n:]) != "Foo: bar\r\nTrailer: Baz\r\n\r\n" {
|
|
t.Fatalf("Unexpected header: %q. Expected %q", headerBytes[n:], "Foo: bar\nTrailer: Baz\n\n")
|
|
}
|
|
if string(h.TrailerHeader()) != "Baz: test\r\n\r\n" {
|
|
t.Fatalf("Unexpected trailer header: %q. Expected %q", h.TrailerHeader(), "Baz: test\r\n\r\n")
|
|
}
|
|
}
|
|
|
|
func TestAppendNormalizedHeaderKeyBytes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testAppendNormalizedHeaderKeyBytes(t, "", "")
|
|
testAppendNormalizedHeaderKeyBytes(t, "Content-Type", "Content-Type")
|
|
testAppendNormalizedHeaderKeyBytes(t, "foO-bAr-BAZ", "Foo-Bar-Baz")
|
|
}
|
|
|
|
func testAppendNormalizedHeaderKeyBytes(t *testing.T, key, expectedKey string) {
|
|
buf := []byte("foobar")
|
|
result := AppendNormalizedHeaderKeyBytes(buf, []byte(key))
|
|
normalizedKey := result[len(buf):]
|
|
if string(normalizedKey) != expectedKey {
|
|
t.Fatalf("unexpected normalized key %q. Expecting %q", normalizedKey, expectedKey)
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderHTTP10ConnectionClose(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
s := "GET / HTTP/1.0\r\nHost: foobar\r\n\r\n"
|
|
var h RequestHeader
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if !h.ConnectionClose() {
|
|
t.Fatalf("expecting 'Connection: close' request header")
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderHTTP10ConnectionKeepAlive(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
s := "GET / HTTP/1.0\r\nHost: foobar\r\nConnection: keep-alive\r\n\r\n"
|
|
var h RequestHeader
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if h.ConnectionClose() {
|
|
t.Fatalf("unexpected 'Connection: close' request header")
|
|
}
|
|
}
|
|
|
|
func TestBufferSnippet(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testBufferSnippet(t, "", `""`)
|
|
testBufferSnippet(t, "foobar", `"foobar"`)
|
|
|
|
b := string(createFixedBody(199))
|
|
bExpected := fmt.Sprintf("%q", b)
|
|
testBufferSnippet(t, b, bExpected)
|
|
for i := 0; i < 10; i++ {
|
|
b += "foobar"
|
|
bExpected = fmt.Sprintf("%q", b)
|
|
testBufferSnippet(t, b, bExpected)
|
|
}
|
|
|
|
b = string(createFixedBody(400))
|
|
bExpected = fmt.Sprintf("%q", b)
|
|
testBufferSnippet(t, b, bExpected)
|
|
for i := 0; i < 10; i++ {
|
|
b += "sadfqwer"
|
|
bExpected = fmt.Sprintf("%q...%q", b[:200], b[len(b)-200:])
|
|
testBufferSnippet(t, b, bExpected)
|
|
}
|
|
}
|
|
|
|
func testBufferSnippet(t *testing.T, buf, expectedSnippet string) {
|
|
snippet := bufferSnippet([]byte(buf))
|
|
if snippet != expectedSnippet {
|
|
t.Fatalf("unexpected snippet %q. Expecting %q", snippet, expectedSnippet)
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderTrailingCRLFSuccess(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
trailingCRLF := "\r\n\r\n\r\n"
|
|
s := "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 123\r\n\r\n" + trailingCRLF
|
|
|
|
var r ResponseHeader
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := r.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// try reading the trailing CRLF. It must return EOF
|
|
err := r.Read(br)
|
|
if err == nil {
|
|
t.Fatalf("expecting error")
|
|
}
|
|
if err != io.EOF {
|
|
t.Fatalf("unexpected error: %v. Expecting %v", err, io.EOF)
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderTrailingCRLFError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
trailingCRLF := "\r\nerror\r\n\r\n"
|
|
s := "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 123\r\n\r\n" + trailingCRLF
|
|
|
|
var r ResponseHeader
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := r.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// try reading the trailing CRLF. It must return EOF
|
|
err := r.Read(br)
|
|
if err == nil {
|
|
t.Fatalf("expecting error")
|
|
}
|
|
if err == io.EOF {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderTrailingCRLFSuccess(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
trailingCRLF := "\r\n\r\n\r\n"
|
|
s := "GET / HTTP/1.1\r\nHost: aaa.com\r\n\r\n" + trailingCRLF
|
|
|
|
var r RequestHeader
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := r.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// try reading the trailing CRLF. It must return EOF
|
|
err := r.Read(br)
|
|
if err == nil {
|
|
t.Fatalf("expecting error")
|
|
}
|
|
if err != io.EOF {
|
|
t.Fatalf("unexpected error: %v. Expecting %v", err, io.EOF)
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderTrailingCRLFError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
trailingCRLF := "\r\nerror\r\n\r\n"
|
|
s := "GET / HTTP/1.1\r\nHost: aaa.com\r\n\r\n" + trailingCRLF
|
|
|
|
var r RequestHeader
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := r.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// try reading the trailing CRLF. It must return EOF
|
|
err := r.Read(br)
|
|
if err == nil {
|
|
t.Fatalf("expecting error")
|
|
}
|
|
if err == io.EOF {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderReadEOF(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var r RequestHeader
|
|
|
|
br := bufio.NewReader(&bytes.Buffer{})
|
|
err := r.Read(br)
|
|
if err == nil {
|
|
t.Fatalf("expecting error")
|
|
}
|
|
if err != io.EOF {
|
|
t.Fatalf("unexpected error: %v. Expecting %v", err, io.EOF)
|
|
}
|
|
|
|
// incomplete request header mustn't return io.EOF
|
|
br = bufio.NewReader(bytes.NewBufferString("GET "))
|
|
err = r.Read(br)
|
|
if err == nil {
|
|
t.Fatalf("expecting error")
|
|
}
|
|
if err == io.EOF {
|
|
t.Fatalf("expecting non-EOF error")
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderReadEOF(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var r ResponseHeader
|
|
|
|
br := bufio.NewReader(&bytes.Buffer{})
|
|
err := r.Read(br)
|
|
if err == nil {
|
|
t.Fatalf("expecting error")
|
|
}
|
|
if err != io.EOF {
|
|
t.Fatalf("unexpected error: %v. Expecting %v", err, io.EOF)
|
|
}
|
|
|
|
// incomplete response header mustn't return io.EOF
|
|
br = bufio.NewReader(bytes.NewBufferString("HTTP/1.1 "))
|
|
err = r.Read(br)
|
|
if err == nil {
|
|
t.Fatalf("expecting error")
|
|
}
|
|
if err == io.EOF {
|
|
t.Fatalf("expecting non-EOF error")
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderOldVersion(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h ResponseHeader
|
|
|
|
s := "HTTP/1.0 200 OK\r\nContent-Length: 5\r\nContent-Type: aaa\r\n\r\n12345"
|
|
s += "HTTP/1.0 200 OK\r\nContent-Length: 2\r\nContent-Type: ass\r\nConnection: keep-alive\r\n\r\n42"
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !h.ConnectionClose() {
|
|
t.Fatalf("expecting 'Connection: close' for the response with old http protocol")
|
|
}
|
|
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if h.ConnectionClose() {
|
|
t.Fatalf("unexpected 'Connection: close' for keep-alive response with old http protocol")
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderSetByteRange(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testRequestHeaderSetByteRange(t, 0, 10, "bytes=0-10")
|
|
testRequestHeaderSetByteRange(t, 123, -1, "bytes=123-")
|
|
testRequestHeaderSetByteRange(t, -234, 58349, "bytes=-234")
|
|
}
|
|
|
|
func testRequestHeaderSetByteRange(t *testing.T, startPos, endPos int, expectedV string) {
|
|
var h RequestHeader
|
|
h.SetByteRange(startPos, endPos)
|
|
v := h.Peek(HeaderRange)
|
|
if string(v) != expectedV {
|
|
t.Fatalf("unexpected range: %q. Expecting %q. startPos=%d, endPos=%d", v, expectedV, startPos, endPos)
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderSetContentRange(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testResponseHeaderSetContentRange(t, 0, 0, 1, "bytes 0-0/1")
|
|
testResponseHeaderSetContentRange(t, 123, 456, 789, "bytes 123-456/789")
|
|
}
|
|
|
|
func testResponseHeaderSetContentRange(t *testing.T, startPos, endPos, contentLength int, expectedV string) {
|
|
var h ResponseHeader
|
|
h.SetContentRange(startPos, endPos, contentLength)
|
|
v := h.Peek(HeaderContentRange)
|
|
if string(v) != expectedV {
|
|
t.Fatalf("unexpected content-range: %q. Expecting %q. startPos=%d, endPos=%d, contentLength=%d",
|
|
v, expectedV, startPos, endPos, contentLength)
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderHasAcceptEncoding(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testRequestHeaderHasAcceptEncoding(t, "", "gzip", false)
|
|
testRequestHeaderHasAcceptEncoding(t, "gzip", "sdhc", false)
|
|
testRequestHeaderHasAcceptEncoding(t, "deflate", "deflate", true)
|
|
testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "gzi", false)
|
|
testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "dhc", false)
|
|
testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "sdh", false)
|
|
testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "zip", false)
|
|
testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "flat", false)
|
|
testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "flate", false)
|
|
testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "def", false)
|
|
testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "gzip", true)
|
|
testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "deflate", true)
|
|
testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "sdhc", true)
|
|
}
|
|
|
|
func testRequestHeaderHasAcceptEncoding(t *testing.T, ae, v string, resultExpected bool) {
|
|
var h RequestHeader
|
|
h.Set(HeaderAcceptEncoding, ae)
|
|
result := h.HasAcceptEncoding(v)
|
|
if result != resultExpected {
|
|
t.Fatalf("unexpected result in HasAcceptEncoding(%q, %q): %v. Expecting %v", ae, v, result, resultExpected)
|
|
}
|
|
}
|
|
|
|
func TestRequestMultipartFormBoundary(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=foobar\r\n\r\n", "foobar")
|
|
|
|
// incorrect content-type
|
|
testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: foo/bar\r\n\r\n", "")
|
|
|
|
// empty boundary
|
|
testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=\r\n\r\n", "")
|
|
|
|
// missing boundary
|
|
testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: multipart/form-data\r\n\r\n", "")
|
|
|
|
// boundary after other content-type params
|
|
testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: multipart/form-data; foo=bar; boundary=--aaabb \r\n\r\n", "--aaabb")
|
|
|
|
// quoted boundary
|
|
testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=\"foobar\"\r\n\r\n", "foobar")
|
|
|
|
var h RequestHeader
|
|
h.SetMultipartFormBoundary("foobarbaz")
|
|
b := h.MultipartFormBoundary()
|
|
if string(b) != "foobarbaz" {
|
|
t.Fatalf("unexpected boundary %q. Expecting %q", b, "foobarbaz")
|
|
}
|
|
}
|
|
|
|
func testRequestMultipartFormBoundary(t *testing.T, s, boundary string) {
|
|
var h RequestHeader
|
|
r := bytes.NewBufferString(s)
|
|
br := bufio.NewReader(r)
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v. s=%q, boundary=%q", err, s, boundary)
|
|
}
|
|
|
|
b := h.MultipartFormBoundary()
|
|
if string(b) != boundary {
|
|
t.Fatalf("unexpected boundary %q. Expecting %q. s=%q", b, boundary, s)
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderConnectionUpgrade(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testResponseHeaderConnectionUpgrade(t, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nConnection: Upgrade, HTTP2-Settings\r\n\r\n",
|
|
true, true)
|
|
testResponseHeaderConnectionUpgrade(t, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nConnection: keep-alive, Upgrade\r\n\r\n",
|
|
true, true)
|
|
|
|
// non-http/1.1 protocol has 'connection: close' by default, which also disables 'connection: upgrade'
|
|
testResponseHeaderConnectionUpgrade(t, "HTTP/1.0 200 OK\r\nContent-Length: 10\r\nConnection: Upgrade, HTTP2-Settings\r\n\r\n",
|
|
false, false)
|
|
|
|
// explicit keep-alive for non-http/1.1, so 'connection: upgrade' works
|
|
testResponseHeaderConnectionUpgrade(t, "HTTP/1.0 200 OK\r\nContent-Length: 10\r\nConnection: Upgrade, keep-alive\r\n\r\n",
|
|
true, true)
|
|
|
|
// implicit keep-alive for http/1.1
|
|
testResponseHeaderConnectionUpgrade(t, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\n", false, true)
|
|
|
|
// no content-length, so 'connection: close' is assumed
|
|
testResponseHeaderConnectionUpgrade(t, "HTTP/1.1 200 OK\r\n\r\n", false, false)
|
|
}
|
|
|
|
func testResponseHeaderConnectionUpgrade(t *testing.T, s string, isUpgrade, isKeepAlive bool) {
|
|
var h ResponseHeader
|
|
|
|
r := bytes.NewBufferString(s)
|
|
br := bufio.NewReader(r)
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v. Response header %q", err, s)
|
|
}
|
|
upgrade := h.ConnectionUpgrade()
|
|
if upgrade != isUpgrade {
|
|
t.Fatalf("unexpected 'connection: upgrade' when parsing response header: %v. Expecting %v. header %q. v=%q",
|
|
upgrade, isUpgrade, s, h.Peek("Connection"))
|
|
}
|
|
keepAlive := !h.ConnectionClose()
|
|
if keepAlive != isKeepAlive {
|
|
t.Fatalf("unexpected 'connection: keep-alive' when parsing response header: %v. Expecting %v. header %q. v=%q",
|
|
keepAlive, isKeepAlive, s, &h)
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderConnectionUpgrade(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.1\r\nConnection: Upgrade, HTTP2-Settings\r\nHost: foobar.com\r\n\r\n",
|
|
true, true)
|
|
testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.1\r\nConnection: keep-alive,Upgrade\r\nHost: foobar.com\r\n\r\n",
|
|
true, true)
|
|
|
|
// non-http/1.1 has 'connection: close' by default, which resets 'connection: upgrade'
|
|
testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.0\r\nConnection: Upgrade, HTTP2-Settings\r\nHost: foobar.com\r\n\r\n",
|
|
false, false)
|
|
|
|
// explicit 'connection: keep-alive' in non-http/1.1
|
|
testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.0\r\nConnection: foo, Upgrade, keep-alive\r\nHost: foobar.com\r\n\r\n",
|
|
true, true)
|
|
|
|
// no upgrade
|
|
testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.1\r\nConnection: Upgradess, foobar\r\nHost: foobar.com\r\n\r\n",
|
|
false, true)
|
|
testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.1\r\nHost: foobar.com\r\n\r\n",
|
|
false, true)
|
|
|
|
// explicit connection close
|
|
testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.1\r\nConnection: close\r\nHost: foobar.com\r\n\r\n",
|
|
false, false)
|
|
}
|
|
|
|
func testRequestHeaderConnectionUpgrade(t *testing.T, s string, isUpgrade, isKeepAlive bool) {
|
|
var h RequestHeader
|
|
|
|
r := bytes.NewBufferString(s)
|
|
br := bufio.NewReader(r)
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v. Request header %q", err, s)
|
|
}
|
|
upgrade := h.ConnectionUpgrade()
|
|
if upgrade != isUpgrade {
|
|
t.Fatalf("unexpected 'connection: upgrade' when parsing request header: %v. Expecting %v. header %q",
|
|
upgrade, isUpgrade, s)
|
|
}
|
|
keepAlive := !h.ConnectionClose()
|
|
if keepAlive != isKeepAlive {
|
|
t.Fatalf("unexpected 'connection: keep-alive' when parsing request header: %v. Expecting %v. header %q",
|
|
keepAlive, isKeepAlive, s)
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderProxyWithCookie(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Proxy request header (read it, then write it without touching any headers).
|
|
var h RequestHeader
|
|
r := bytes.NewBufferString("GET /foo HTTP/1.1\r\nFoo: bar\r\nHost: aaa.com\r\nCookie: foo=bar; bazzz=aaaaaaa; x=y\r\nCookie: aqqqqq=123\r\n\r\n")
|
|
br := bufio.NewReader(r)
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
w := &bytes.Buffer{}
|
|
bw := bufio.NewWriter(w)
|
|
if err := h.Write(bw); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if err := bw.Flush(); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
var h1 RequestHeader
|
|
br.Reset(w)
|
|
if err := h1.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if string(h1.RequestURI()) != "/foo" {
|
|
t.Fatalf("unexpected requestURI: %q. Expecting %q", h1.RequestURI(), "/foo")
|
|
}
|
|
if string(h1.Host()) != "aaa.com" {
|
|
t.Fatalf("unexpected host: %q. Expecting %q", h1.Host(), "aaa.com")
|
|
}
|
|
if string(h1.Peek("Foo")) != "bar" {
|
|
t.Fatalf("unexpected Foo: %q. Expecting %q", h1.Peek("Foo"), "bar")
|
|
}
|
|
if string(h1.Cookie("foo")) != "bar" {
|
|
t.Fatalf("unexpected coookie foo=%q. Expecting %q", h1.Cookie("foo"), "bar")
|
|
}
|
|
if string(h1.Cookie("bazzz")) != "aaaaaaa" {
|
|
t.Fatalf("unexpected cookie bazzz=%q. Expecting %q", h1.Cookie("bazzz"), "aaaaaaa")
|
|
}
|
|
if string(h1.Cookie("x")) != "y" {
|
|
t.Fatalf("unexpected cookie x=%q. Expecting %q", h1.Cookie("x"), "y")
|
|
}
|
|
if string(h1.Cookie("aqqqqq")) != "123" {
|
|
t.Fatalf("unexpected cookie aqqqqq=%q. Expecting %q", h1.Cookie("aqqqqq"), "123")
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderFirstByteReadEOF(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h ResponseHeader
|
|
|
|
r := &errorReader{fmt.Errorf("non-eof error")}
|
|
br := bufio.NewReader(r)
|
|
err := h.Read(br)
|
|
if err == nil {
|
|
t.Fatalf("expecting error")
|
|
}
|
|
if err != io.EOF {
|
|
t.Fatalf("unexpected error %v. Expecting %v", err, io.EOF)
|
|
}
|
|
}
|
|
|
|
type errorReader struct {
|
|
err error
|
|
}
|
|
|
|
func (r *errorReader) Read(p []byte) (int, error) {
|
|
return 0, r.err
|
|
}
|
|
|
|
func TestRequestHeaderEmptyMethod(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h RequestHeader
|
|
|
|
if !h.IsGet() {
|
|
t.Fatalf("empty method must be equivalent to GET")
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderHTTPVer(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// non-http/1.1
|
|
testResponseHeaderHTTPVer(t, "HTTP/1.0 200 OK\r\nContent-Type: aaa\r\nContent-Length: 123\r\n\r\n", true)
|
|
testResponseHeaderHTTPVer(t, "HTTP/0.9 200 OK\r\nContent-Type: aaa\r\nContent-Length: 123\r\n\r\n", true)
|
|
testResponseHeaderHTTPVer(t, "foobar 200 OK\r\nContent-Type: aaa\r\nContent-Length: 123\r\n\r\n", true)
|
|
|
|
// http/1.1
|
|
testResponseHeaderHTTPVer(t, "HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nContent-Length: 123\r\n\r\n", false)
|
|
}
|
|
|
|
func TestRequestHeaderHTTPVer(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// non-http/1.1
|
|
testRequestHeaderHTTPVer(t, "GET / HTTP/1.0\r\nHost: aa.com\r\n\r\n", true)
|
|
testRequestHeaderHTTPVer(t, "GET / HTTP/0.9\r\nHost: aa.com\r\n\r\n", true)
|
|
testRequestHeaderHTTPVer(t, "GET / foobar\r\nHost: aa.com\r\n\r\n", true)
|
|
|
|
// empty http version
|
|
testRequestHeaderHTTPVer(t, "GET /\r\nHost: aaa.com\r\n\r\n", true)
|
|
testRequestHeaderHTTPVer(t, "GET / \r\nHost: aaa.com\r\n\r\n", true)
|
|
|
|
// http/1.1
|
|
testRequestHeaderHTTPVer(t, "GET / HTTP/1.1\r\nHost: a.com\r\n\r\n", false)
|
|
}
|
|
|
|
func testResponseHeaderHTTPVer(t *testing.T, s string, connectionClose bool) {
|
|
var h ResponseHeader
|
|
|
|
r := bytes.NewBufferString(s)
|
|
br := bufio.NewReader(r)
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v. response=%q", err, s)
|
|
}
|
|
if h.ConnectionClose() != connectionClose {
|
|
t.Fatalf("unexpected connectionClose %v. Expecting %v. response=%q", h.ConnectionClose(), connectionClose, s)
|
|
}
|
|
}
|
|
|
|
func testRequestHeaderHTTPVer(t *testing.T, s string, connectionClose bool) {
|
|
var h RequestHeader
|
|
|
|
r := bytes.NewBufferString(s)
|
|
br := bufio.NewReader(r)
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v. request=%q", err, s)
|
|
}
|
|
if h.ConnectionClose() != connectionClose {
|
|
t.Fatalf("unexpected connectionClose %v. Expecting %v. request=%q", h.ConnectionClose(), connectionClose, s)
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderCopyTo(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h ResponseHeader
|
|
|
|
h.Set(HeaderSetCookie, "foo=bar")
|
|
h.Set(HeaderContentType, "foobar")
|
|
h.Set(HeaderContentEncoding, "gzip")
|
|
h.Set("AAA-BBB", "aaaa")
|
|
h.Set(HeaderTrailer, "foo, bar")
|
|
|
|
var h1 ResponseHeader
|
|
h.CopyTo(&h1)
|
|
if !bytes.Equal(h1.Peek("Set-cookie"), h.Peek("Set-Cookie")) {
|
|
t.Fatalf("unexpected cookie %q. Expected %q", h1.Peek("set-cookie"), h.Peek("set-cookie"))
|
|
}
|
|
if !bytes.Equal(h1.Peek(HeaderContentType), h.Peek(HeaderContentType)) {
|
|
t.Fatalf("unexpected content-type %q. Expected %q", h1.Peek("content-type"), h.Peek("content-type"))
|
|
}
|
|
if !bytes.Equal(h1.Peek(HeaderContentEncoding), h.Peek(HeaderContentEncoding)) {
|
|
t.Fatalf("unexpected content-encoding %q. Expected %q", h1.Peek("content-encoding"), h.Peek("content-encoding"))
|
|
}
|
|
if !bytes.Equal(h1.Peek("aaa-bbb"), h.Peek("AAA-BBB")) {
|
|
t.Fatalf("unexpected aaa-bbb %q. Expected %q", h1.Peek("aaa-bbb"), h.Peek("aaa-bbb"))
|
|
}
|
|
if !bytes.Equal(h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer)) {
|
|
t.Fatalf("unexpected trailer %q. Expected %q", h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer))
|
|
}
|
|
|
|
// flush buf
|
|
h.bufKV = argsKV{}
|
|
h1.bufKV = argsKV{}
|
|
|
|
if !reflect.DeepEqual(h, h1) { //nolint:govet
|
|
t.Fatalf("ResponseHeaderCopyTo fail, src: \n%+v\ndst: \n%+v\n", h, h1) //nolint:govet
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderCopyTo(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h RequestHeader
|
|
|
|
h.Set(HeaderCookie, "aa=bb; cc=dd")
|
|
h.Set(HeaderContentType, "foobar")
|
|
h.Set(HeaderContentEncoding, "gzip")
|
|
h.Set(HeaderHost, "aaaa")
|
|
h.Set("aaaxxx", "123")
|
|
h.Set(HeaderTrailer, "foo, bar")
|
|
|
|
var h1 RequestHeader
|
|
h.CopyTo(&h1)
|
|
if !bytes.Equal(h1.Peek("cookie"), h.Peek(HeaderCookie)) {
|
|
t.Fatalf("unexpected cookie after copying: %q. Expected %q", h1.Peek("cookie"), h.Peek("cookie"))
|
|
}
|
|
if !bytes.Equal(h1.Peek("content-type"), h.Peek(HeaderContentType)) {
|
|
t.Fatalf("unexpected content-type %q. Expected %q", h1.Peek("content-type"), h.Peek("content-type"))
|
|
}
|
|
if !bytes.Equal(h1.Peek("content-encoding"), h.Peek(HeaderContentEncoding)) {
|
|
t.Fatalf("unexpected content-encoding %q. Expected %q", h1.Peek("content-encoding"), h.Peek("content-encoding"))
|
|
}
|
|
if !bytes.Equal(h1.Peek("host"), h.Peek("host")) {
|
|
t.Fatalf("unexpected host %q. Expected %q", h1.Peek("host"), h.Peek("host"))
|
|
}
|
|
if !bytes.Equal(h1.Peek("aaaxxx"), h.Peek("aaaxxx")) {
|
|
t.Fatalf("unexpected aaaxxx %q. Expected %q", h1.Peek("aaaxxx"), h.Peek("aaaxxx"))
|
|
}
|
|
if !bytes.Equal(h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer)) {
|
|
t.Fatalf("unexpected trailer %q. Expected %q", h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer))
|
|
}
|
|
|
|
// flush buf
|
|
h.bufKV = argsKV{}
|
|
h1.bufKV = argsKV{}
|
|
|
|
if !reflect.DeepEqual(h, h1) { //nolint:govet
|
|
t.Fatalf("RequestHeaderCopyTo fail, src: \n%+v\ndst: \n%+v\n", h, h1) //nolint:govet
|
|
}
|
|
}
|
|
|
|
func TestResponseContentTypeNoDefaultNotEmpty(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h ResponseHeader
|
|
|
|
h.SetNoDefaultContentType(true)
|
|
h.SetContentLength(5)
|
|
|
|
headers := h.String()
|
|
|
|
if strings.Contains(headers, "Content-Type: \r\n") {
|
|
t.Fatalf("ResponseContentTypeNoDefaultNotEmpty fail, response: \n%+v\noutcome: \n%q\n", h, headers) //nolint:govet
|
|
}
|
|
}
|
|
|
|
func TestRequestContentTypeDefaultNotEmpty(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h RequestHeader
|
|
h.SetMethod(MethodPost)
|
|
h.SetContentLength(5)
|
|
|
|
w := &bytes.Buffer{}
|
|
bw := bufio.NewWriter(w)
|
|
if err := h.Write(bw); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := bw.Flush(); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var h1 RequestHeader
|
|
br := bufio.NewReader(w)
|
|
if err := h1.Read(br); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if string(h1.contentType) != "application/octet-stream" {
|
|
t.Fatalf("unexpected Content-Type %q. Expecting %q", h1.contentType, "application/octet-stream")
|
|
}
|
|
}
|
|
|
|
func TestRequestContentTypeNoDefault(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h RequestHeader
|
|
h.SetMethod(MethodDelete)
|
|
h.SetNoDefaultContentType(true)
|
|
|
|
w := &bytes.Buffer{}
|
|
bw := bufio.NewWriter(w)
|
|
if err := h.Write(bw); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := bw.Flush(); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var h1 RequestHeader
|
|
br := bufio.NewReader(w)
|
|
if err := h1.Read(br); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if string(h1.contentType) != "" {
|
|
t.Fatalf("unexpected Content-Type %q. Expecting %q", h1.contentType, "")
|
|
}
|
|
}
|
|
|
|
func TestResponseDateNoDefaultNotEmpty(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h ResponseHeader
|
|
|
|
h.noDefaultDate = true
|
|
|
|
headers := h.String()
|
|
|
|
if strings.Contains(headers, "\r\nDate: ") {
|
|
t.Fatalf("ResponseDateNoDefaultNotEmpty fail, response: \n%+v\noutcome: \n%q\n", h, headers) //nolint:govet
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderConnectionClose(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h RequestHeader
|
|
|
|
h.Set(HeaderConnection, "close")
|
|
h.Set(HeaderHost, "foobar")
|
|
if !h.ConnectionClose() {
|
|
t.Fatalf("connection: close not set")
|
|
}
|
|
|
|
var w bytes.Buffer
|
|
bw := bufio.NewWriter(&w)
|
|
if err := h.Write(bw); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if err := bw.Flush(); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
var h1 RequestHeader
|
|
br := bufio.NewReader(&w)
|
|
if err := h1.Read(br); err != nil {
|
|
t.Fatalf("error when reading request header: %v", err)
|
|
}
|
|
|
|
if !h1.ConnectionClose() {
|
|
t.Fatalf("unexpected connection: close value: %v", h1.ConnectionClose())
|
|
}
|
|
if string(h1.Peek(HeaderConnection)) != "close" {
|
|
t.Fatalf("unexpected connection value: %q. Expecting %q", h.Peek("Connection"), "close")
|
|
}
|
|
|
|
}
|
|
|
|
func TestRequestHeaderSetCookie(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h RequestHeader
|
|
|
|
h.Set("Cookie", "foo=bar; baz=aaa")
|
|
h.Set("cOOkie", "xx=yyy")
|
|
|
|
if string(h.Cookie("foo")) != "bar" {
|
|
t.Fatalf("Unexpected cookie %q. Expecting %q", h.Cookie("foo"), "bar")
|
|
}
|
|
if string(h.Cookie("baz")) != "aaa" {
|
|
t.Fatalf("Unexpected cookie %q. Expecting %q", h.Cookie("baz"), "aaa")
|
|
}
|
|
if string(h.Cookie("xx")) != "yyy" {
|
|
t.Fatalf("unexpected cookie %q. Expecting %q", h.Cookie("xx"), "yyy")
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderSetCookie(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h ResponseHeader
|
|
|
|
h.Set("set-cookie", "foo=bar; path=/aa/bb; domain=aaa.com")
|
|
h.Set(HeaderSetCookie, "aaaaa=bxx")
|
|
|
|
var c Cookie
|
|
c.SetKey("foo")
|
|
if !h.Cookie(&c) {
|
|
t.Fatalf("cannot obtain %q cookie", c.Key())
|
|
}
|
|
if string(c.Value()) != "bar" {
|
|
t.Fatalf("unexpected cookie value %q. Expected %q", c.Value(), "bar")
|
|
}
|
|
if string(c.Path()) != "/aa/bb" {
|
|
t.Fatalf("unexpected cookie path %q. Expected %q", c.Path(), "/aa/bb")
|
|
}
|
|
if string(c.Domain()) != "aaa.com" {
|
|
t.Fatalf("unexpected cookie domain %q. Expected %q", c.Domain(), "aaa.com")
|
|
}
|
|
|
|
c.SetKey("aaaaa")
|
|
if !h.Cookie(&c) {
|
|
t.Fatalf("cannot obtain %q cookie", c.Key())
|
|
}
|
|
if string(c.Value()) != "bxx" {
|
|
t.Fatalf("unexpected cookie value %q. Expecting %q", c.Value(), "bxx")
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderVisitAll(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h ResponseHeader
|
|
|
|
r := bytes.NewBufferString("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Encoding: gzip\r\nContent-Length: 123\r\nSet-Cookie: aa=bb; path=/foo/bar\r\nSet-Cookie: ccc\r\nTrailer: Foo, Bar\r\n\r\n")
|
|
br := bufio.NewReader(r)
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if h.Len() != 6 {
|
|
t.Fatalf("Unexpected number of headers: %d. Expected 6", h.Len())
|
|
}
|
|
contentLengthCount := 0
|
|
contentTypeCount := 0
|
|
contentEncodingCount := 0
|
|
cookieCount := 0
|
|
h.VisitAll(func(key, value []byte) {
|
|
k := string(key)
|
|
v := string(value)
|
|
switch k {
|
|
case HeaderContentLength:
|
|
if v != string(h.Peek(k)) {
|
|
t.Fatalf("unexpected content-length: %q. Expecting %q", v, h.Peek(k))
|
|
}
|
|
contentLengthCount++
|
|
case HeaderContentType:
|
|
if v != string(h.Peek(k)) {
|
|
t.Fatalf("Unexpected content-type: %q. Expected %q", v, h.Peek(k))
|
|
}
|
|
contentTypeCount++
|
|
case HeaderContentEncoding:
|
|
if v != string(h.Peek(k)) {
|
|
t.Fatalf("Unexpected content-encoding: %q. Expected %q", v, h.Peek(k))
|
|
}
|
|
contentEncodingCount++
|
|
case HeaderSetCookie:
|
|
if cookieCount == 0 && v != "aa=bb; path=/foo/bar" {
|
|
t.Fatalf("unexpected cookie header: %q. Expected %q", v, "aa=bb; path=/foo/bar")
|
|
}
|
|
if cookieCount == 1 && v != "ccc" {
|
|
t.Fatalf("unexpected cookie header: %q. Expected %q", v, "ccc")
|
|
}
|
|
cookieCount++
|
|
case HeaderTrailer:
|
|
if v != "Foo, Bar" {
|
|
t.Fatalf("Unexpected trailer header %q. Expected %q", v, "Foo, Bar")
|
|
}
|
|
default:
|
|
t.Fatalf("unexpected header %q=%q", k, v)
|
|
}
|
|
})
|
|
if contentLengthCount != 1 {
|
|
t.Fatalf("unexpected number of content-length headers: %d. Expected 1", contentLengthCount)
|
|
}
|
|
if contentTypeCount != 1 {
|
|
t.Fatalf("unexpected number of content-type headers: %d. Expected 1", contentTypeCount)
|
|
}
|
|
if contentEncodingCount != 1 {
|
|
t.Fatalf("unexpected number of content-encoding headers: %d. Expected 1", contentEncodingCount)
|
|
}
|
|
if cookieCount != 2 {
|
|
t.Fatalf("unexpected number of cookie header: %d. Expected 2", cookieCount)
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderVisitAll(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h RequestHeader
|
|
|
|
r := bytes.NewBufferString("GET / HTTP/1.1\r\nHost: aa.com\r\nXX: YYY\r\nXX: ZZ\r\nCookie: a=b; c=d\r\nTrailer: Foo, Bar\r\n\r\n")
|
|
br := bufio.NewReader(r)
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if h.Len() != 5 {
|
|
t.Fatalf("Unexpected number of header: %d. Expected 5", h.Len())
|
|
}
|
|
hostCount := 0
|
|
xxCount := 0
|
|
cookieCount := 0
|
|
h.VisitAll(func(key, value []byte) {
|
|
k := string(key)
|
|
v := string(value)
|
|
switch k {
|
|
case HeaderHost:
|
|
if v != string(h.Peek(k)) {
|
|
t.Fatalf("Unexpected host value %q. Expected %q", v, h.Peek(k))
|
|
}
|
|
hostCount++
|
|
case "Xx":
|
|
if xxCount == 0 && v != "YYY" {
|
|
t.Fatalf("Unexpected value %q. Expected %q", v, "YYY")
|
|
}
|
|
if xxCount == 1 && v != "ZZ" {
|
|
t.Fatalf("Unexpected value %q. Expected %q", v, "ZZ")
|
|
}
|
|
xxCount++
|
|
case HeaderCookie:
|
|
if v != "a=b; c=d" {
|
|
t.Fatalf("Unexpected cookie %q. Expected %q", v, "a=b; c=d")
|
|
}
|
|
cookieCount++
|
|
case HeaderTrailer:
|
|
if v != "Foo, Bar" {
|
|
t.Fatalf("Unexpected trailer header %q. Expected %q", v, "Foo, Bar")
|
|
}
|
|
default:
|
|
t.Fatalf("Unexpected header %q=%q", k, v)
|
|
}
|
|
})
|
|
if hostCount != 1 {
|
|
t.Fatalf("Unexpected number of host headers detected %d. Expected 1", hostCount)
|
|
}
|
|
if xxCount != 2 {
|
|
t.Fatalf("Unexpected number of xx headers detected %d. Expected 2", xxCount)
|
|
}
|
|
if cookieCount != 1 {
|
|
t.Fatalf("Unexpected number of cookie headers %d. Expected 1", cookieCount)
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderVisitAllInOrder(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h RequestHeader
|
|
|
|
r := bytes.NewBufferString("GET / HTTP/1.1\r\nContent-Type: aa\r\nCookie: a=b\r\nHost: example.com\r\nUser-Agent: xxx\r\n\r\n")
|
|
br := bufio.NewReader(r)
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if h.Len() != 4 {
|
|
t.Fatalf("Unexpected number of headers: %d. Expected 4", h.Len())
|
|
}
|
|
|
|
order := []string{
|
|
HeaderContentType,
|
|
HeaderCookie,
|
|
HeaderHost,
|
|
HeaderUserAgent,
|
|
}
|
|
values := []string{
|
|
"aa",
|
|
"a=b",
|
|
"example.com",
|
|
"xxx",
|
|
}
|
|
|
|
h.VisitAllInOrder(func(key, value []byte) {
|
|
if len(order) == 0 {
|
|
t.Fatalf("no more headers expected, got %q", key)
|
|
}
|
|
if order[0] != string(key) {
|
|
t.Fatalf("expected header %q got %q", order[0], key)
|
|
}
|
|
if values[0] != string(value) {
|
|
t.Fatalf("expected header value %q got %q", values[0], value)
|
|
}
|
|
order = order[1:]
|
|
values = values[1:]
|
|
})
|
|
}
|
|
|
|
func TestResponseHeaderAddTrailerError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h ResponseHeader
|
|
err := h.AddTrailer("Foo, Content-Length , Bar,Transfer-Encoding,")
|
|
expectedTrailer := "Foo, Bar"
|
|
|
|
if !errors.Is(err, ErrBadTrailer) {
|
|
t.Fatalf("unexpected err %q. Expected %q", err, ErrBadTrailer)
|
|
}
|
|
if trailer := string(h.Peek(HeaderTrailer)); trailer != expectedTrailer {
|
|
t.Fatalf("unexpected trailer %q. Expected %q", trailer, expectedTrailer)
|
|
}
|
|
|
|
}
|
|
|
|
func TestRequestHeaderAddTrailerError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h RequestHeader
|
|
err := h.AddTrailer("Foo, Content-Length , Bar,Transfer-Encoding,")
|
|
expectedTrailer := "Foo, Bar"
|
|
|
|
if !errors.Is(err, ErrBadTrailer) {
|
|
t.Fatalf("unexpected err %q. Expected %q", err, ErrBadTrailer)
|
|
}
|
|
if trailer := string(h.Peek(HeaderTrailer)); trailer != expectedTrailer {
|
|
t.Fatalf("unexpected trailer %q. Expected %q", trailer, expectedTrailer)
|
|
}
|
|
|
|
}
|
|
|
|
func TestResponseHeaderCookie(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h ResponseHeader
|
|
var c Cookie
|
|
|
|
c.SetKey("foobar")
|
|
c.SetValue("aaa")
|
|
h.SetCookie(&c)
|
|
|
|
c.SetKey("йцук")
|
|
c.SetDomain("foobar.com")
|
|
h.SetCookie(&c)
|
|
|
|
c.Reset()
|
|
c.SetKey("foobar")
|
|
if !h.Cookie(&c) {
|
|
t.Fatalf("Cannot find cookie %q", c.Key())
|
|
}
|
|
|
|
var expectedC1 Cookie
|
|
expectedC1.SetKey("foobar")
|
|
expectedC1.SetValue("aaa")
|
|
if !equalCookie(&expectedC1, &c) {
|
|
t.Fatalf("unexpected cookie\n%#v\nExpected\n%#v\n", &c, &expectedC1)
|
|
}
|
|
|
|
c.SetKey("йцук")
|
|
if !h.Cookie(&c) {
|
|
t.Fatalf("cannot find cookie %q", c.Key())
|
|
}
|
|
|
|
var expectedC2 Cookie
|
|
expectedC2.SetKey("йцук")
|
|
expectedC2.SetValue("aaa")
|
|
expectedC2.SetDomain("foobar.com")
|
|
if !equalCookie(&expectedC2, &c) {
|
|
t.Fatalf("unexpected cookie\n%v\nExpected\n%v\n", &c, &expectedC2)
|
|
}
|
|
|
|
h.VisitAllCookie(func(key, value []byte) {
|
|
var cc Cookie
|
|
if err := cc.ParseBytes(value); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !bytes.Equal(key, cc.Key()) {
|
|
t.Fatalf("Unexpected cookie key %q. Expected %q", key, cc.Key())
|
|
}
|
|
switch {
|
|
case bytes.Equal(key, []byte("foobar")):
|
|
if !equalCookie(&expectedC1, &cc) {
|
|
t.Fatalf("unexpected cookie\n%v\nExpected\n%v\n", &cc, &expectedC1)
|
|
}
|
|
case bytes.Equal(key, []byte("йцук")):
|
|
if !equalCookie(&expectedC2, &cc) {
|
|
t.Fatalf("unexpected cookie\n%v\nExpected\n%v\n", &cc, &expectedC2)
|
|
}
|
|
default:
|
|
t.Fatalf("unexpected cookie key %q", key)
|
|
}
|
|
})
|
|
|
|
w := &bytes.Buffer{}
|
|
bw := bufio.NewWriter(w)
|
|
if err := h.Write(bw); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if err := bw.Flush(); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
h.DelAllCookies()
|
|
|
|
var h1 ResponseHeader
|
|
br := bufio.NewReader(w)
|
|
if err := h1.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
c.SetKey("foobar")
|
|
if !h1.Cookie(&c) {
|
|
t.Fatalf("Cannot find cookie %q", c.Key())
|
|
}
|
|
if !equalCookie(&expectedC1, &c) {
|
|
t.Fatalf("unexpected cookie\n%v\nExpected\n%v\n", &c, &expectedC1)
|
|
}
|
|
|
|
h1.DelCookie("foobar")
|
|
if h.Cookie(&c) {
|
|
t.Fatalf("Unexpected cookie found: %v", &c)
|
|
}
|
|
if h1.Cookie(&c) {
|
|
t.Fatalf("Unexpected cookie found: %v", &c)
|
|
}
|
|
|
|
c.SetKey("йцук")
|
|
if !h1.Cookie(&c) {
|
|
t.Fatalf("cannot find cookie %q", c.Key())
|
|
}
|
|
if !equalCookie(&expectedC2, &c) {
|
|
t.Fatalf("unexpected cookie\n%v\nExpected\n%v\n", &c, &expectedC2)
|
|
}
|
|
|
|
h1.DelCookie("йцук")
|
|
if h.Cookie(&c) {
|
|
t.Fatalf("Unexpected cookie found: %v", &c)
|
|
}
|
|
if h1.Cookie(&c) {
|
|
t.Fatalf("Unexpected cookie found: %v", &c)
|
|
}
|
|
}
|
|
|
|
func equalCookie(c1, c2 *Cookie) bool {
|
|
if !bytes.Equal(c1.Key(), c2.Key()) {
|
|
return false
|
|
}
|
|
if !bytes.Equal(c1.Value(), c2.Value()) {
|
|
return false
|
|
}
|
|
if !c1.Expire().Equal(c2.Expire()) {
|
|
return false
|
|
}
|
|
if !bytes.Equal(c1.Domain(), c2.Domain()) {
|
|
return false
|
|
}
|
|
if !bytes.Equal(c1.Path(), c2.Path()) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func TestRequestHeaderCookie(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h RequestHeader
|
|
h.SetRequestURI("/foobar")
|
|
h.Set(HeaderHost, "foobar.com")
|
|
|
|
h.SetCookie("foo", "bar")
|
|
h.SetCookie("привет", "мир")
|
|
|
|
if string(h.Cookie("foo")) != "bar" {
|
|
t.Fatalf("Unexpected cookie value %q. Exepcted %q", h.Cookie("foo"), "bar")
|
|
}
|
|
if string(h.Cookie("привет")) != "мир" {
|
|
t.Fatalf("Unexpected cookie value %q. Expected %q", h.Cookie("привет"), "мир")
|
|
}
|
|
|
|
w := &bytes.Buffer{}
|
|
bw := bufio.NewWriter(w)
|
|
if err := h.Write(bw); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := bw.Flush(); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var h1 RequestHeader
|
|
br := bufio.NewReader(w)
|
|
if err := h1.Read(br); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if !bytes.Equal(h1.Cookie("foo"), h.Cookie("foo")) {
|
|
t.Fatalf("Unexpected cookie value %q. Exepcted %q", h1.Cookie("foo"), h.Cookie("foo"))
|
|
}
|
|
h1.DelCookie("foo")
|
|
if len(h1.Cookie("foo")) > 0 {
|
|
t.Fatalf("Unexpected cookie found: %q", h1.Cookie("foo"))
|
|
}
|
|
if !bytes.Equal(h1.Cookie("привет"), h.Cookie("привет")) {
|
|
t.Fatalf("Unexpected cookie value %q. Expected %q", h1.Cookie("привет"), h.Cookie("привет"))
|
|
}
|
|
h1.DelCookie("привет")
|
|
if len(h1.Cookie("привет")) > 0 {
|
|
t.Fatalf("Unexpected cookie found: %q", h1.Cookie("привет"))
|
|
}
|
|
|
|
h.DelAllCookies()
|
|
if len(h.Cookie("foo")) > 0 {
|
|
t.Fatalf("Unexpected cookie found: %q", h.Cookie("foo"))
|
|
}
|
|
if len(h.Cookie("привет")) > 0 {
|
|
t.Fatalf("Unexpected cookie found: %q", h.Cookie("привет"))
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderCookieIssue4(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h ResponseHeader
|
|
|
|
c := AcquireCookie()
|
|
c.SetKey("foo")
|
|
c.SetValue("bar")
|
|
h.SetCookie(c)
|
|
|
|
if string(h.Peek(HeaderSetCookie)) != "foo=bar" {
|
|
t.Fatalf("Unexpected Set-Cookie header %q. Expected %q", h.Peek(HeaderSetCookie), "foo=bar")
|
|
}
|
|
cookieSeen := false
|
|
h.VisitAll(func(key, value []byte) {
|
|
switch string(key) {
|
|
case HeaderSetCookie:
|
|
cookieSeen = true
|
|
}
|
|
})
|
|
if !cookieSeen {
|
|
t.Fatalf("Set-Cookie not present in VisitAll")
|
|
}
|
|
|
|
c = AcquireCookie()
|
|
c.SetKey("foo")
|
|
h.Cookie(c)
|
|
if string(c.Value()) != "bar" {
|
|
t.Fatalf("Unexpected cookie value %q. Exepcted %q", c.Value(), "bar")
|
|
}
|
|
|
|
if string(h.Peek(HeaderSetCookie)) != "foo=bar" {
|
|
t.Fatalf("Unexpected Set-Cookie header %q. Expected %q", h.Peek(HeaderSetCookie), "foo=bar")
|
|
}
|
|
cookieSeen = false
|
|
h.VisitAll(func(key, value []byte) {
|
|
switch string(key) {
|
|
case HeaderSetCookie:
|
|
cookieSeen = true
|
|
}
|
|
})
|
|
if !cookieSeen {
|
|
t.Fatalf("Set-Cookie not present in VisitAll")
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderCookieIssue313(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var h RequestHeader
|
|
h.SetRequestURI("/")
|
|
h.Set(HeaderHost, "foobar.com")
|
|
|
|
h.SetCookie("foo", "bar")
|
|
|
|
if string(h.Peek(HeaderCookie)) != "foo=bar" {
|
|
t.Fatalf("Unexpected Cookie header %q. Expected %q", h.Peek(HeaderCookie), "foo=bar")
|
|
}
|
|
cookieSeen := false
|
|
h.VisitAll(func(key, value []byte) {
|
|
switch string(key) {
|
|
case HeaderCookie:
|
|
cookieSeen = true
|
|
}
|
|
})
|
|
if !cookieSeen {
|
|
t.Fatalf("Cookie not present in VisitAll")
|
|
}
|
|
|
|
if string(h.Cookie("foo")) != "bar" {
|
|
t.Fatalf("Unexpected cookie value %q. Exepcted %q", h.Cookie("foo"), "bar")
|
|
}
|
|
|
|
if string(h.Peek(HeaderCookie)) != "foo=bar" {
|
|
t.Fatalf("Unexpected Cookie header %q. Expected %q", h.Peek(HeaderCookie), "foo=bar")
|
|
}
|
|
cookieSeen = false
|
|
h.VisitAll(func(key, value []byte) {
|
|
switch string(key) {
|
|
case HeaderCookie:
|
|
cookieSeen = true
|
|
}
|
|
})
|
|
if !cookieSeen {
|
|
t.Fatalf("Cookie not present in VisitAll")
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderMethod(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// common http methods
|
|
testRequestHeaderMethod(t, MethodGet)
|
|
testRequestHeaderMethod(t, MethodPost)
|
|
testRequestHeaderMethod(t, MethodHead)
|
|
testRequestHeaderMethod(t, MethodDelete)
|
|
|
|
// non-http methods
|
|
testRequestHeaderMethod(t, "foobar")
|
|
testRequestHeaderMethod(t, "ABC")
|
|
}
|
|
|
|
func testRequestHeaderMethod(t *testing.T, expectedMethod string) {
|
|
var h RequestHeader
|
|
h.SetMethod(expectedMethod)
|
|
m := h.Method()
|
|
if string(m) != expectedMethod {
|
|
t.Fatalf("unexpected method: %q. Expecting %q", m, expectedMethod)
|
|
}
|
|
|
|
s := h.String()
|
|
var h1 RequestHeader
|
|
br := bufio.NewReader(bytes.NewBufferString(s))
|
|
if err := h1.Read(br); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
m1 := h1.Method()
|
|
if string(m) != string(m1) {
|
|
t.Fatalf("unexpected method: %q. Expecting %q", m, m1)
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderSetGet(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
h := &RequestHeader{}
|
|
h.SetRequestURI("/aa/bbb")
|
|
h.SetMethod(MethodPost)
|
|
h.Set("foo", "bar")
|
|
h.Set("host", "12345")
|
|
h.Set("content-type", "aaa/bbb")
|
|
h.Set("content-length", "1234")
|
|
h.Set("user-agent", "aaabbb")
|
|
h.Set("referer", "axcv")
|
|
h.Set("baz", "xxxxx")
|
|
h.Set("transfer-encoding", "chunked")
|
|
h.Set("connection", "close")
|
|
|
|
expectRequestHeaderGet(t, h, "Foo", "bar")
|
|
expectRequestHeaderGet(t, h, HeaderHost, "12345")
|
|
expectRequestHeaderGet(t, h, HeaderContentType, "aaa/bbb")
|
|
expectRequestHeaderGet(t, h, HeaderContentLength, "1234")
|
|
expectRequestHeaderGet(t, h, "USER-AGent", "aaabbb")
|
|
expectRequestHeaderGet(t, h, HeaderReferer, "axcv")
|
|
expectRequestHeaderGet(t, h, "baz", "xxxxx")
|
|
expectRequestHeaderGet(t, h, HeaderTransferEncoding, "")
|
|
expectRequestHeaderGet(t, h, "connecTION", "close")
|
|
if !h.ConnectionClose() {
|
|
t.Fatalf("unset connection: close")
|
|
}
|
|
|
|
if h.ContentLength() != 1234 {
|
|
t.Fatalf("Unexpected content-length %d. Expected %d", h.ContentLength(), 1234)
|
|
}
|
|
|
|
w := &bytes.Buffer{}
|
|
bw := bufio.NewWriter(w)
|
|
err := h.Write(bw)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error when writing request header: %v", err)
|
|
}
|
|
if err := bw.Flush(); err != nil {
|
|
t.Fatalf("Unexpected error when flushing request header: %v", err)
|
|
}
|
|
|
|
var h1 RequestHeader
|
|
br := bufio.NewReader(w)
|
|
if err = h1.Read(br); err != nil {
|
|
t.Fatalf("Unexpected error when reading request header: %v", err)
|
|
}
|
|
|
|
if h1.ContentLength() != h.ContentLength() {
|
|
t.Fatalf("Unexpected Content-Length %d. Expected %d", h1.ContentLength(), h.ContentLength())
|
|
}
|
|
|
|
expectRequestHeaderGet(t, &h1, "Foo", "bar")
|
|
expectRequestHeaderGet(t, &h1, "HOST", "12345")
|
|
expectRequestHeaderGet(t, &h1, HeaderContentType, "aaa/bbb")
|
|
expectRequestHeaderGet(t, &h1, HeaderContentLength, "1234")
|
|
expectRequestHeaderGet(t, &h1, "USER-AGent", "aaabbb")
|
|
expectRequestHeaderGet(t, &h1, HeaderReferer, "axcv")
|
|
expectRequestHeaderGet(t, &h1, "baz", "xxxxx")
|
|
expectRequestHeaderGet(t, &h1, HeaderTransferEncoding, "")
|
|
expectRequestHeaderGet(t, &h1, HeaderConnection, "close")
|
|
if !h1.ConnectionClose() {
|
|
t.Fatalf("unset connection: close")
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderSetGet(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
h := &ResponseHeader{}
|
|
h.Set("foo", "bar")
|
|
h.Set("content-type", "aaa/bbb")
|
|
h.Set("content-encoding", "gzip")
|
|
h.Set("connection", "close")
|
|
h.Set("content-length", "1234")
|
|
h.Set(HeaderServer, "aaaa")
|
|
h.Set("baz", "xxxxx")
|
|
h.Set(HeaderTransferEncoding, "chunked")
|
|
|
|
expectResponseHeaderGet(t, h, "Foo", "bar")
|
|
expectResponseHeaderGet(t, h, HeaderContentType, "aaa/bbb")
|
|
expectResponseHeaderGet(t, h, HeaderContentEncoding, "gzip")
|
|
expectResponseHeaderGet(t, h, HeaderConnection, "close")
|
|
expectResponseHeaderGet(t, h, HeaderContentLength, "1234")
|
|
expectResponseHeaderGet(t, h, "seRVer", "aaaa")
|
|
expectResponseHeaderGet(t, h, "baz", "xxxxx")
|
|
expectResponseHeaderGet(t, h, HeaderTransferEncoding, "")
|
|
|
|
if h.ContentLength() != 1234 {
|
|
t.Fatalf("Unexpected content-length %d. Expected %d", h.ContentLength(), 1234)
|
|
}
|
|
if !h.ConnectionClose() {
|
|
t.Fatalf("Unexpected Connection: close value %v. Expected %v", h.ConnectionClose(), true)
|
|
}
|
|
|
|
w := &bytes.Buffer{}
|
|
bw := bufio.NewWriter(w)
|
|
err := h.Write(bw)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error when writing response header: %v", err)
|
|
}
|
|
if err := bw.Flush(); err != nil {
|
|
t.Fatalf("Unexpected error when flushing response header: %v", err)
|
|
}
|
|
|
|
var h1 ResponseHeader
|
|
br := bufio.NewReader(w)
|
|
if err = h1.Read(br); err != nil {
|
|
t.Fatalf("Unexpected error when reading response header: %v", err)
|
|
}
|
|
|
|
if h1.ContentLength() != h.ContentLength() {
|
|
t.Fatalf("Unexpected Content-Length %d. Expected %d", h1.ContentLength(), h.ContentLength())
|
|
}
|
|
if h1.ConnectionClose() != h.ConnectionClose() {
|
|
t.Fatalf("unexpected connection: close %v. Expected %v", h1.ConnectionClose(), h.ConnectionClose())
|
|
}
|
|
|
|
expectResponseHeaderGet(t, &h1, "Foo", "bar")
|
|
expectResponseHeaderGet(t, &h1, HeaderContentType, "aaa/bbb")
|
|
expectResponseHeaderGet(t, &h1, HeaderContentEncoding, "gzip")
|
|
expectResponseHeaderGet(t, &h1, HeaderConnection, "close")
|
|
expectResponseHeaderGet(t, &h1, "seRVer", "aaaa")
|
|
expectResponseHeaderGet(t, &h1, "baz", "xxxxx")
|
|
}
|
|
|
|
func expectRequestHeaderGet(t *testing.T, h *RequestHeader, key, expectedValue string) {
|
|
if string(h.Peek(key)) != expectedValue {
|
|
t.Fatalf("Unexpected value for key %q: %q. Expected %q", key, h.Peek(key), expectedValue)
|
|
}
|
|
}
|
|
|
|
func expectResponseHeaderGet(t *testing.T, h *ResponseHeader, key, expectedValue string) {
|
|
if string(h.Peek(key)) != expectedValue {
|
|
t.Fatalf("Unexpected value for key %q: %q. Expected %q", key, h.Peek(key), expectedValue)
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderConnectionClose(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testResponseHeaderConnectionClose(t, true)
|
|
testResponseHeaderConnectionClose(t, false)
|
|
}
|
|
|
|
func testResponseHeaderConnectionClose(t *testing.T, connectionClose bool) {
|
|
h := &ResponseHeader{}
|
|
if connectionClose {
|
|
h.SetConnectionClose()
|
|
}
|
|
h.SetContentLength(123)
|
|
|
|
w := &bytes.Buffer{}
|
|
bw := bufio.NewWriter(w)
|
|
err := h.Write(bw)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error when writing response header: %v", err)
|
|
}
|
|
if err := bw.Flush(); err != nil {
|
|
t.Fatalf("Unexpected error when flushing response header: %v", err)
|
|
}
|
|
|
|
var h1 ResponseHeader
|
|
br := bufio.NewReader(w)
|
|
err = h1.Read(br)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error when reading response header: %v", err)
|
|
}
|
|
if h1.ConnectionClose() != h.ConnectionClose() {
|
|
t.Fatalf("Unexpected value for ConnectionClose: %v. Expected %v", h1.ConnectionClose(), h.ConnectionClose())
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderTooBig(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
s := "GET / HTTP/1.1\r\nHost: aaa.com\r\n" + getHeaders(10500) + "\r\n"
|
|
r := bytes.NewBufferString(s)
|
|
br := bufio.NewReaderSize(r, 4096)
|
|
h := &RequestHeader{}
|
|
err := h.Read(br)
|
|
if err == nil {
|
|
t.Fatalf("Expecting error when reading too big header")
|
|
}
|
|
}
|
|
|
|
func TestResponseHeaderTooBig(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
s := "HTTP/1.1 200 OK\r\nContent-Type: sss\r\nContent-Length: 0\r\n" + getHeaders(100500) + "\r\n"
|
|
r := bytes.NewBufferString(s)
|
|
br := bufio.NewReaderSize(r, 4096)
|
|
h := &ResponseHeader{}
|
|
err := h.Read(br)
|
|
if err == nil {
|
|
t.Fatalf("Expecting error when reading too big header")
|
|
}
|
|
}
|
|
|
|
type bufioPeekReader struct {
|
|
s string
|
|
n int
|
|
}
|
|
|
|
func (r *bufioPeekReader) Read(b []byte) (int, error) {
|
|
if len(r.s) == 0 {
|
|
return 0, io.EOF
|
|
}
|
|
|
|
r.n++
|
|
n := r.n
|
|
if len(r.s) < n {
|
|
n = len(r.s)
|
|
}
|
|
src := []byte(r.s[:n])
|
|
r.s = r.s[n:]
|
|
n = copy(b, src)
|
|
return n, nil
|
|
}
|
|
|
|
func TestRequestHeaderBufioPeek(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
r := &bufioPeekReader{
|
|
s: "GET / HTTP/1.1\r\nHost: foobar.com\r\n" + getHeaders(10) + "\r\naaaa",
|
|
}
|
|
br := bufio.NewReaderSize(r, 4096)
|
|
h := &RequestHeader{}
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("Unexpected error when reading request: %v", err)
|
|
}
|
|
verifyRequestHeader(t, h, -2, "/", "foobar.com", "", "")
|
|
}
|
|
|
|
func TestResponseHeaderBufioPeek(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
r := &bufioPeekReader{
|
|
s: "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nContent-Type: text/plain\r\nContent-Encoding: gzip\r\n" + getHeaders(10) + "\r\n0123456789",
|
|
}
|
|
br := bufio.NewReaderSize(r, 4096)
|
|
h := &ResponseHeader{}
|
|
if err := h.Read(br); err != nil {
|
|
t.Fatalf("Unexpected error when reading response: %v", err)
|
|
}
|
|
verifyResponseHeader(t, h, 200, 10, "text/plain", "gzip")
|
|
}
|
|
|
|
func getHeaders(n int) string {
|
|
var h []string
|
|
for i := 0; i < n; i++ {
|
|
h = append(h, fmt.Sprintf("Header_%d: Value_%d\r\n", i, i))
|
|
}
|
|
return strings.Join(h, "")
|
|
}
|
|
|
|
func TestResponseHeaderReadSuccess(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
h := &ResponseHeader{}
|
|
|
|
// straight order of content-length and content-type
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n",
|
|
200, 123, "text/html")
|
|
if h.ConnectionClose() {
|
|
t.Fatalf("unexpected connection: close")
|
|
}
|
|
|
|
// reverse order of content-length and content-type
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 202 OK\r\nContent-Type: text/plain; encoding=utf-8\r\nContent-Length: 543\r\nConnection: close\r\n\r\n",
|
|
202, 543, "text/plain; encoding=utf-8")
|
|
if !h.ConnectionClose() {
|
|
t.Fatalf("expecting connection: close")
|
|
}
|
|
|
|
// tranfer-encoding: chunked
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 505 Internal error\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n",
|
|
505, -1, "text/html")
|
|
if h.ConnectionClose() {
|
|
t.Fatalf("unexpected connection: close")
|
|
}
|
|
|
|
// reverse order of content-type and tranfer-encoding
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 343 foobar\r\nTransfer-Encoding: chunked\r\nContent-Type: text/json\r\n\r\n",
|
|
343, -1, "text/json")
|
|
|
|
// additional headers
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 100 Continue\r\nFoobar: baz\r\nContent-Type: aaa/bbb\r\nUser-Agent: x\r\nContent-Length: 123\r\nZZZ: werer\r\n\r\n",
|
|
100, 123, "aaa/bbb")
|
|
|
|
// ancient http protocol
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/0.9 300 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\nqqqq",
|
|
300, 123, "text/html")
|
|
|
|
// lf instead of crlf
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\nContent-Length: 123\nContent-Type: text/html\n\n",
|
|
200, 123, "text/html")
|
|
|
|
// Zero-length headers with mixed crlf and lf
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\nContent-Length: 345\nZero-Value: \r\nContent-Type: aaa\n: zero-key\r\n\r\nooa",
|
|
400, 345, "aaa")
|
|
|
|
// No space after colon
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\nContent-Length:34\nContent-Type: sss\n\naaaa",
|
|
200, 34, "sss")
|
|
|
|
// invalid case
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\nconTEnt-leNGTH: 123\nConTENT-TYPE: ass\n\n",
|
|
400, 123, "ass")
|
|
|
|
// duplicate content-length
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 456\r\nContent-Type: foo/bar\r\nContent-Length: 321\r\n\r\n",
|
|
200, 321, "foo/bar")
|
|
|
|
// duplicate content-type
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 234\r\nContent-Type: foo/bar\r\nContent-Type: baz/bar\r\n\r\n",
|
|
200, 234, "baz/bar")
|
|
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 300 OK\r\nContent-Type: foo/barr\r\nTransfer-Encoding: chunked\r\nContent-Length: 354\r\n\r\n",
|
|
300, -1, "foo/barr")
|
|
|
|
// duplicate transfer-encoding: chunked
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\nTransfer-Encoding: chunked\r\n\r\n",
|
|
200, -1, "text/html")
|
|
|
|
// no reason string in the first line
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 456\r\nContent-Type: xxx/yyy\r\nContent-Length: 134\r\n\r\naaaxxx",
|
|
456, 134, "xxx/yyy")
|
|
|
|
// blank lines before the first line
|
|
testResponseHeaderReadSuccess(t, h, "\r\nHTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 0\r\n\r\nsss",
|
|
200, 0, "aa")
|
|
if h.ConnectionClose() {
|
|
t.Fatalf("unexpected connection: close")
|
|
}
|
|
|
|
// no content-length (informational responses)
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 101 OK\r\n\r\n",
|
|
101, -2, "text/plain; charset=utf-8")
|
|
if h.ConnectionClose() {
|
|
t.Fatalf("expecting connection: keep-alive for informational response")
|
|
}
|
|
|
|
// no content-length (no-content responses)
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 204 OK\r\n\r\n",
|
|
204, -2, "text/plain; charset=utf-8")
|
|
if h.ConnectionClose() {
|
|
t.Fatalf("expecting connection: keep-alive for no-content response")
|
|
}
|
|
|
|
// no content-length (not-modified responses)
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 304 OK\r\n\r\n",
|
|
304, -2, "text/plain; charset=utf-8")
|
|
if h.ConnectionClose() {
|
|
t.Fatalf("expecting connection: keep-alive for not-modified response")
|
|
}
|
|
|
|
// no content-length (identity transfer-encoding)
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: foo/bar\r\n\r\nabcdefg",
|
|
200, -2, "foo/bar")
|
|
if !h.ConnectionClose() {
|
|
t.Fatalf("expecting connection: close for identity response")
|
|
}
|
|
|
|
// no content-type
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\r\nContent-Length: 123\r\n\r\nfoiaaa",
|
|
400, 123, string(defaultContentType))
|
|
|
|
// no content-type and no default
|
|
h.SetNoDefaultContentType(true)
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\r\nContent-Length: 123\r\n\r\nfoiaaa",
|
|
400, 123, "")
|
|
h.SetNoDefaultContentType(false)
|
|
|
|
// no headers
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\n\r\naaaabbb",
|
|
200, -2, string(defaultContentType))
|
|
if !h.IsHTTP11() {
|
|
t.Fatalf("expecting http/1.1 protocol")
|
|
}
|
|
|
|
// ancient http protocol
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.0 203 OK\r\nContent-Length: 123\r\nContent-Type: foobar\r\n\r\naaa",
|
|
203, 123, "foobar")
|
|
if h.IsHTTP11() {
|
|
t.Fatalf("ancient protocol must be non-http/1.1")
|
|
}
|
|
if !h.ConnectionClose() {
|
|
t.Fatalf("expecting connection: close for ancient protocol")
|
|
}
|
|
|
|
// ancient http protocol with 'Connection: keep-alive' header.
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.0 403 aa\r\nContent-Length: 0\r\nContent-Type: 2\r\nConnection: Keep-Alive\r\n\r\nww",
|
|
403, 0, "2")
|
|
if h.IsHTTP11() {
|
|
t.Fatalf("ancient protocol must be non-http/1.1")
|
|
}
|
|
if h.ConnectionClose() {
|
|
t.Fatalf("expecting connection: keep-alive for ancient protocol")
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaderReadSuccess(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
h := &RequestHeader{}
|
|
|
|
// simple headers
|
|
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nHost: google.com\r\n\r\n",
|
|
-2, "/foo/bar", "google.com", "", "", nil)
|
|
if h.ConnectionClose() {
|
|
t.Fatalf("unexpected connection: close header")
|
|
}
|
|
|
|
// simple headers with body
|
|
testRequestHeaderReadSuccess(t, h, "GET /a/bar HTTP/1.1\r\nHost: gole.com\r\nconneCTION: close\r\n\r\nfoobar",
|
|
-2, "/a/bar", "gole.com", "", "", nil)
|
|
if !h.ConnectionClose() {
|
|
t.Fatalf("connection: close unset")
|
|
}
|
|
|
|
// ancient http protocol
|
|
testRequestHeaderReadSuccess(t, h, "GET /bar HTTP/1.0\r\nHost: gole\r\n\r\npppp",
|
|
-2, "/bar", "gole", "", "", nil)
|
|
if h.IsHTTP11() {
|
|
t.Fatalf("ancient http protocol cannot be http/1.1")
|
|
}
|
|
if !h.ConnectionClose() {
|
|
t.Fatalf("expecting connectionClose for ancient http protocol")
|
|
}
|
|
|
|
// ancient http protocol with 'Connection: keep-alive' header
|
|
testRequestHeaderReadSuccess(t, h, "GET /aa HTTP/1.0\r\nHost: bb\r\nConnection: keep-alive\r\n\r\nxxx",
|
|
-2, "/aa", "bb", "", "", nil)
|
|
if h.IsHTTP11() {
|
|
t.Fatalf("ancient http protocol cannot be http/1.1")
|
|
}
|
|
if h.ConnectionClose() {
|
|
t.Fatalf("unexpected 'connection: close' for ancient http protocol")
|
|
}
|
|
|
|
// complex headers with body
|
|
testRequestHeaderReadSuccess(t, h, "GET /aabar HTTP/1.1\r\nAAA: bbb\r\nHost: ole.com\r\nAA: bb\r\n\r\nzzz",
|
|
-2, "/aabar", "ole.com", "", "", nil)
|
|
if !h.IsHTTP11() {
|
|
t.Fatalf("expecting http/1.1 protocol")
|
|
}
|
|
if h.ConnectionClose() {
|
|
t.Fatalf("unexpected connection: close")
|
|
}
|
|
|
|
// lf instead of crlf
|
|
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\nHost: google.com\n\n",
|
|
-2, "/foo/bar", "google.com", "", "", nil)
|
|
|
|
// post method
|
|
testRequestHeaderReadSuccess(t, h, "POST /aaa?bbb HTTP/1.1\r\nHost: foobar.com\r\nContent-Length: 1235\r\nContent-Type: aaa\r\n\r\nabcdef",
|
|
1235, "/aaa?bbb", "foobar.com", "", "aaa", nil)
|
|
|
|
// zero-length headers with mixed crlf and lf
|
|
testRequestHeaderReadSuccess(t, h, "GET /a HTTP/1.1\nHost: aaa\r\nZero: \n: Zero-Value\n\r\nxccv",
|
|
-2, "/a", "aaa", "", "", nil)
|
|
|
|
// no space after colon
|
|
testRequestHeaderReadSuccess(t, h, "GET /a HTTP/1.1\nHost:aaaxd\n\nsdfds",
|
|
-2, "/a", "aaaxd", "", "", nil)
|
|
|
|
// get with zero content-length
|
|
testRequestHeaderReadSuccess(t, h, "GET /xxx HTTP/1.1\nHost: aaa.com\nContent-Length: 0\n\n",
|
|
0, "/xxx", "aaa.com", "", "", nil)
|
|
|
|
// get with non-zero content-length
|
|
testRequestHeaderReadSuccess(t, h, "GET /xxx HTTP/1.1\nHost: aaa.com\nContent-Length: 123\n\n",
|
|
123, "/xxx", "aaa.com", "", "", nil)
|
|
|
|
// invalid case
|
|
testRequestHeaderReadSuccess(t, h, "GET /aaa HTTP/1.1\nhoST: bbb.com\n\naas",
|
|
-2, "/aaa", "bbb.com", "", "", nil)
|
|
|
|
// referer
|
|
testRequestHeaderReadSuccess(t, h, "GET /asdf HTTP/1.1\nHost: aaa.com\nReferer: bb.com\n\naaa",
|
|
-2, "/asdf", "aaa.com", "bb.com", "", nil)
|
|
|
|
// duplicate host
|
|
testRequestHeaderReadSuccess(t, h, "GET /aa HTTP/1.1\r\nHost: aaaaaa.com\r\nHost: bb.com\r\n\r\n",
|
|
-2, "/aa", "bb.com", "", "", nil)
|
|
|
|
// post with duplicate content-type
|
|
testRequestHeaderReadSuccess(t, h, "POST /a HTTP/1.1\r\nHost: aa\r\nContent-Type: ab\r\nContent-Length: 123\r\nContent-Type: xx\r\n\r\n",
|
|
123, "/a", "aa", "", "xx", nil)
|
|
|
|
// post with duplicate content-length
|
|
testRequestHeaderReadSuccess(t, h, "POST /xx HTTP/1.1\r\nHost: aa\r\nContent-Type: s\r\nContent-Length: 13\r\nContent-Length: 1\r\n\r\n",
|
|
1, "/xx", "aa", "", "s", nil)
|
|
|
|
// non-post with content-type
|
|
testRequestHeaderReadSuccess(t, h, "GET /aaa HTTP/1.1\r\nHost: bbb.com\r\nContent-Type: aaab\r\n\r\n",
|
|
-2, "/aaa", "bbb.com", "", "aaab", nil)
|
|
|
|
// non-post with content-length
|
|
testRequestHeaderReadSuccess(t, h, "HEAD / HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 123\r\n\r\n",
|
|
123, "/", "aaa.com", "", "", nil)
|
|
|
|
// non-post with content-type and content-length
|
|
testRequestHeaderReadSuccess(t, h, "GET /aa HTTP/1.1\r\nHost: aa.com\r\nContent-Type: abd/test\r\nContent-Length: 123\r\n\r\n",
|
|
123, "/aa", "aa.com", "", "abd/test", nil)
|
|
|
|
// request uri with hostname
|
|
testRequestHeaderReadSuccess(t, h, "GET http://gooGle.com/foO/%20bar?xxx#aaa HTTP/1.1\r\nHost: aa.cOM\r\n\r\ntrail",
|
|
-2, "http://gooGle.com/foO/%20bar?xxx#aaa", "aa.cOM", "", "", nil)
|
|
|
|
// no protocol in the first line
|
|
testRequestHeaderReadSuccess(t, h, "GET /foo/bar\r\nHost: google.com\r\n\r\nisdD",
|
|
-2, "/foo/bar", "google.com", "", "", nil)
|
|
|
|
// blank lines before the first line
|
|
testRequestHeaderReadSuccess(t, h, "\r\n\n\r\nGET /aaa HTTP/1.1\r\nHost: aaa.com\r\n\r\nsss",
|
|
-2, "/aaa", "aaa.com", "", "", nil)
|
|
|
|
// request uri with spaces
|
|
testRequestHeaderReadSuccess(t, h, "GET /foo/ bar baz HTTP/1.1\r\nHost: aa.com\r\n\r\nxxx",
|
|
-2, "/foo/ bar baz", "aa.com", "", "", nil)
|
|
|
|
// no host
|
|
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nFOObar: assdfd\r\n\r\naaa",
|
|
-2, "/foo/bar", "", "", "", nil)
|
|
|
|
// no host, no headers
|
|
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\n\r\nfoobar",
|
|
-2, "/foo/bar", "", "", "", nil)
|
|
|
|
// post without content-length and content-type
|
|
testRequestHeaderReadSuccess(t, h, "POST /aaa HTTP/1.1\r\nHost: aaa.com\r\n\r\nzxc",
|
|
-2, "/aaa", "aaa.com", "", "", nil)
|
|
|
|
// post without content-type
|
|
testRequestHeaderReadSuccess(t, h, "POST /abc HTTP/1.1\r\nHost: aa.com\r\nContent-Length: 123\r\n\r\npoiuy",
|
|
123, "/abc", "aa.com", "", "", nil)
|
|
|
|
// post without content-length
|
|
testRequestHeaderReadSuccess(t, h, "POST /abc HTTP/1.1\r\nHost: aa.com\r\nContent-Type: adv\r\n\r\n123456",
|
|
-2, "/abc", "aa.com", "", "adv", nil)
|
|
|
|
// invalid method
|
|
testRequestHeaderReadSuccess(t, h, "POST /foo/bar HTTP/1.1\r\nHost: google.com\r\n\r\nmnbv",
|
|
-2, "/foo/bar", "google.com", "", "", nil)
|
|
|
|
// put request
|
|
testRequestHeaderReadSuccess(t, h, "PUT /faa HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 123\r\nContent-Type: aaa\r\n\r\nxwwere",
|
|
123, "/faa", "aaa.com", "", "aaa", nil)
|
|
}
|
|
|
|
func TestResponseHeaderReadError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
h := &ResponseHeader{}
|
|
|
|
// incorrect first line
|
|
testResponseHeaderReadError(t, h, "")
|
|
testResponseHeaderReadError(t, h, "fo")
|
|
testResponseHeaderReadError(t, h, "foobarbaz")
|
|
testResponseHeaderReadError(t, h, "HTTP/1.1")
|
|
testResponseHeaderReadError(t, h, "HTTP/1.1 ")
|
|
testResponseHeaderReadError(t, h, "HTTP/1.1 s")
|
|
|
|
// non-numeric status code
|
|
testResponseHeaderReadError(t, h, "HTTP/1.1 foobar OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n")
|
|
testResponseHeaderReadError(t, h, "HTTP/1.1 123foobar OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n")
|
|
testResponseHeaderReadError(t, h, "HTTP/1.1 foobar344 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n")
|
|
|
|
// non-numeric content-length
|
|
testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: faaa\r\nContent-Type: text/html\r\n\r\nfoobar")
|
|
testResponseHeaderReadError(t, h, "HTTP/1.1 201 OK\r\nContent-Length: 123aa\r\nContent-Type: text/ht\r\n\r\naaa")
|
|
testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: aa124\r\nContent-Type: html\r\n\r\nxx")
|
|
|
|
// no headers
|
|
testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\n")
|
|
|
|
// no trailing crlf
|
|
testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n")
|
|
|
|
// forbidden trailer
|
|
testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: -1\r\nTrailer: Foo, Content-Length\r\n\r\n")
|
|
}
|
|
|
|
func TestResponseHeaderReadErrorSecureLog(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
h := &ResponseHeader{
|
|
secureErrorLogMessage: true,
|
|
}
|
|
|
|
// incorrect first line
|
|
testResponseHeaderReadSecuredError(t, h, "fo")
|
|
testResponseHeaderReadSecuredError(t, h, "foobarbaz")
|
|
testResponseHeaderReadSecuredError(t, h, "HTTP/1.1")
|
|
testResponseHeaderReadSecuredError(t, h, "HTTP/1.1 ")
|
|
testResponseHeaderReadSecuredError(t, h, "HTTP/1.1 s")
|
|
|
|
// non-numeric status code
|
|
testResponseHeaderReadSecuredError(t, h, "HTTP/1.1 foobar OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n")
|
|
testResponseHeaderReadSecuredError(t, h, "HTTP/1.1 123foobar OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n")
|
|
testResponseHeaderReadSecuredError(t, h, "HTTP/1.1 foobar344 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n")
|
|
|
|
// no headers
|
|
testResponseHeaderReadSecuredError(t, h, "HTTP/1.1 200 OK\r\n")
|
|
|
|
// no trailing crlf
|
|
testResponseHeaderReadSecuredError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n")
|
|
}
|
|
|
|
func TestRequestHeaderReadError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
h := &RequestHeader{}
|
|
|
|
// incorrect first line
|
|
testRequestHeaderReadError(t, h, "")
|
|
testRequestHeaderReadError(t, h, "fo")
|
|
testRequestHeaderReadError(t, h, "GET ")
|
|
testRequestHeaderReadError(t, h, "GET / HTTP/1.1\r")
|
|
|
|
// missing RequestURI
|
|
testRequestHeaderReadError(t, h, "GET HTTP/1.1\r\nHost: google.com\r\n\r\n")
|
|
|
|
// post with invalid content-length
|
|
testRequestHeaderReadError(t, h, "POST /a HTTP/1.1\r\nHost: bb\r\nContent-Type: aa\r\nContent-Length: dff\r\n\r\nqwerty")
|
|
|
|
// forbidden trailer
|
|
testRequestHeaderReadError(t, h, "POST /a HTTP/1.1\r\nContent-Length: -1\r\nTrailer: Foo, Content-Length\r\n\r\n")
|
|
}
|
|
|
|
func TestRequestHeaderReadSecuredError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
h := &RequestHeader{
|
|
secureErrorLogMessage: true,
|
|
}
|
|
|
|
// incorrect first line
|
|
testRequestHeaderReadSecuredError(t, h, "fo")
|
|
testRequestHeaderReadSecuredError(t, h, "GET ")
|
|
testRequestHeaderReadSecuredError(t, h, "GET / HTTP/1.1\r")
|
|
|
|
// missing RequestURI
|
|
testRequestHeaderReadSecuredError(t, h, "GET HTTP/1.1\r\nHost: google.com\r\n\r\n")
|
|
|
|
// post with invalid content-length
|
|
testRequestHeaderReadSecuredError(t, h, "POST /a HTTP/1.1\r\nHost: bb\r\nContent-Type: aa\r\nContent-Length: dff\r\n\r\nqwerty")
|
|
}
|
|
|
|
func testResponseHeaderReadError(t *testing.T, h *ResponseHeader, headers string) {
|
|
r := bytes.NewBufferString(headers)
|
|
br := bufio.NewReader(r)
|
|
err := h.Read(br)
|
|
if err == nil {
|
|
t.Fatalf("Expecting error when reading response header %q", headers)
|
|
}
|
|
// make sure response header works after error
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: foo/bar\r\nContent-Length: 12345\r\n\r\nsss",
|
|
200, 12345, "foo/bar")
|
|
}
|
|
|
|
func testResponseHeaderReadSecuredError(t *testing.T, h *ResponseHeader, headers string) {
|
|
r := bytes.NewBufferString(headers)
|
|
br := bufio.NewReader(r)
|
|
err := h.Read(br)
|
|
if err == nil {
|
|
t.Fatalf("Expecting error when reading response header %q", headers)
|
|
}
|
|
if strings.Contains(err.Error(), headers) {
|
|
t.Fatalf("Not expecting header content in err %q", err)
|
|
}
|
|
// make sure response header works after error
|
|
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: foo/bar\r\nContent-Length: 12345\r\n\r\nsss",
|
|
200, 12345, "foo/bar")
|
|
}
|
|
|
|
func testRequestHeaderReadError(t *testing.T, h *RequestHeader, headers string) {
|
|
r := bytes.NewBufferString(headers)
|
|
br := bufio.NewReader(r)
|
|
err := h.Read(br)
|
|
if err == nil {
|
|
t.Fatalf("Expecting error when reading request header %q", headers)
|
|
}
|
|
|
|
// make sure request header works after error
|
|
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nHost: aaaa\r\n\r\nxxx",
|
|
-2, "/foo/bar", "aaaa", "", "", nil)
|
|
}
|
|
|
|
func testRequestHeaderReadSecuredError(t *testing.T, h *RequestHeader, headers string) {
|
|
r := bytes.NewBufferString(headers)
|
|
br := bufio.NewReader(r)
|
|
err := h.Read(br)
|
|
if err == nil {
|
|
t.Fatalf("Expecting error when reading request header %q", headers)
|
|
}
|
|
if strings.Contains(err.Error(), headers) {
|
|
t.Fatalf("Not expecting header content in err %q", err)
|
|
}
|
|
// make sure request header works after error
|
|
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nHost: aaaa\r\n\r\nxxx",
|
|
-2, "/foo/bar", "aaaa", "", "", nil)
|
|
}
|
|
|
|
func testResponseHeaderReadSuccess(t *testing.T, h *ResponseHeader, headers string, expectedStatusCode, expectedContentLength int,
|
|
expectedContentType string) {
|
|
r := bytes.NewBufferString(headers)
|
|
br := bufio.NewReader(r)
|
|
err := h.Read(br)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error when parsing response headers: %v. headers=%q", err, headers)
|
|
}
|
|
verifyResponseHeader(t, h, expectedStatusCode, expectedContentLength, expectedContentType, "")
|
|
}
|
|
|
|
func testRequestHeaderReadSuccess(t *testing.T, h *RequestHeader, headers string, expectedContentLength int,
|
|
expectedRequestURI, expectedHost, expectedReferer, expectedContentType string, expectedTrailer map[string]string) {
|
|
r := bytes.NewBufferString(headers)
|
|
br := bufio.NewReader(r)
|
|
err := h.Read(br)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error when parsing request headers: %v. headers=%q", err, headers)
|
|
}
|
|
verifyRequestHeader(t, h, expectedContentLength, expectedRequestURI, expectedHost, expectedReferer, expectedContentType)
|
|
}
|
|
|
|
func verifyResponseHeader(t *testing.T, h *ResponseHeader, expectedStatusCode, expectedContentLength int, expectedContentType, expectedContentEncoding string) {
|
|
if h.StatusCode() != expectedStatusCode {
|
|
t.Fatalf("Unexpected status code %d. Expected %d", h.StatusCode(), expectedStatusCode)
|
|
}
|
|
if h.ContentLength() != expectedContentLength {
|
|
t.Fatalf("Unexpected content length %d. Expected %d", h.ContentLength(), expectedContentLength)
|
|
}
|
|
if string(h.ContentType()) != expectedContentType {
|
|
t.Fatalf("Unexpected content type %q. Expected %q", h.ContentType(), expectedContentType)
|
|
}
|
|
if string(h.ContentEncoding()) != expectedContentEncoding {
|
|
t.Fatalf("Unexpected content encoding %q. Expected %q", h.ContentEncoding(), expectedContentEncoding)
|
|
}
|
|
}
|
|
|
|
func verifyResponseHeaderConnection(t *testing.T, h *ResponseHeader, expectConnection string) {
|
|
if string(h.Peek(HeaderConnection)) != expectConnection {
|
|
t.Fatalf("Unexpected Connection %q. Expected %q", h.Peek(HeaderConnection), expectConnection)
|
|
}
|
|
}
|
|
|
|
func verifyRequestHeader(t *testing.T, h *RequestHeader, expectedContentLength int,
|
|
expectedRequestURI, expectedHost, expectedReferer, expectedContentType string) {
|
|
if h.ContentLength() != expectedContentLength {
|
|
t.Fatalf("Unexpected Content-Length %d. Expected %d", h.ContentLength(), expectedContentLength)
|
|
}
|
|
if string(h.RequestURI()) != expectedRequestURI {
|
|
t.Fatalf("Unexpected RequestURI %q. Expected %q", h.RequestURI(), expectedRequestURI)
|
|
}
|
|
if string(h.Peek(HeaderHost)) != expectedHost {
|
|
t.Fatalf("Unexpected host %q. Expected %q", h.Peek(HeaderHost), expectedHost)
|
|
}
|
|
if string(h.Peek(HeaderReferer)) != expectedReferer {
|
|
t.Fatalf("Unexpected referer %q. Expected %q", h.Peek(HeaderReferer), expectedReferer)
|
|
}
|
|
if string(h.Peek(HeaderContentType)) != expectedContentType {
|
|
t.Fatalf("Unexpected content-type %q. Expected %q", h.Peek(HeaderContentType), expectedContentType)
|
|
}
|
|
}
|
|
|
|
func verifyResponseTrailer(t *testing.T, h *ResponseHeader, expectedTrailers map[string]string) {
|
|
for k, v := range expectedTrailers {
|
|
got := h.Peek(k)
|
|
if !bytes.Equal(got, []byte(v)) {
|
|
t.Fatalf("Unexpected trailer %q. Expected %q. Got %q", k, v, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func verifyRequestTrailer(t *testing.T, h *RequestHeader, expectedTrailers map[string]string) {
|
|
for k, v := range expectedTrailers {
|
|
got := h.Peek(k)
|
|
if !bytes.Equal(got, []byte(v)) {
|
|
t.Fatalf("Unexpected trailer %q. Expected %q. Got %q", k, v, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func verifyTrailer(t *testing.T, r *bufio.Reader, expectedTrailers map[string]string, isReq bool) {
|
|
if isReq {
|
|
req := Request{}
|
|
err := req.Header.ReadTrailer(r)
|
|
if err == io.EOF && expectedTrailers == nil {
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("Cannot read trailer: %v", err)
|
|
}
|
|
verifyRequestTrailer(t, &req.Header, expectedTrailers)
|
|
return
|
|
}
|
|
|
|
resp := Response{}
|
|
err := resp.Header.ReadTrailer(r)
|
|
if err == io.EOF && expectedTrailers == nil {
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("Cannot read trailer: %v", err)
|
|
}
|
|
verifyResponseTrailer(t, &resp.Header, expectedTrailers)
|
|
}
|