package fasthttp import ( "bufio" "bytes" "encoding/base64" "errors" "fmt" "io" "net/http" "reflect" "strconv" "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 if _, err := h.WriteTo(&buf); err != nil { t.Fatalf("unexpected error when writing header: %v", err) } if n := strings.Count(buf.String(), "Content-Type: "); n != 1 { t.Errorf("Content-Type occurred %d times", n) } } func TestResponseHeaderAddContentEncoding(t *testing.T) { t.Parallel() var h ResponseHeader h.Add("Content-Encoding", "test") got := string(h.Peek("Content-Encoding")) expected := "test" if got != expected { t.Errorf("expected %q got %q", expected, got) } var buf bytes.Buffer if _, err := h.WriteTo(&buf); err != nil { t.Fatalf("unexpected error when writing header: %v", err) } if n := strings.Count(buf.String(), "Content-Encoding: "); n != 1 { t.Errorf("Content-Encoding occurred %d times", n) } } func TestResponseHeaderMultiLineValue(t *testing.T) { t.Parallel() s := "HTTP/1.1 200 SuperOK\r\n" + "EmptyValue1:\r\n" + "Content-Type: foo/bar;\r\n\tnewline;\r\n another/newline\r\n" + "Foo: Bar\r\n" + "Multi-Line: one;\r\n two\r\n" + "Values: v1;\r\n v2; v3;\r\n v4;\tv5\r\n" + "\r\n" header := new(ResponseHeader) if _, err := header.parse([]byte(s)); err != nil { t.Fatalf("parse headers with multi-line values failed, %v", err) } response, err := http.ReadResponse(bufio.NewReader(strings.NewReader(s)), nil) if err != nil { t.Fatalf("parse response using net/http failed, %v", err) } defer func() { _ = response.Body.Close() }() if !bytes.Equal(header.StatusMessage(), []byte("SuperOK")) { t.Errorf("parse status line with non-default value failed, got: '%q' want: 'SuperOK'", header.StatusMessage()) } header.SetProtocol([]byte("HTTP/3.3")) if !bytes.Equal(header.Protocol(), []byte("HTTP/3.3")) { t.Errorf("parse protocol with non-default value failed, got: '%q' want: 'HTTP/3.3'", header.Protocol()) } if !bytes.Equal(header.appendStatusLine(nil), []byte("HTTP/3.3 200 SuperOK\r\n")) { t.Errorf("parse status line with non-default value failed, got: '%q' want: 'HTTP/3.3 200 SuperOK'", header.Protocol()) } header.SetStatusMessage(nil) if !bytes.Equal(header.appendStatusLine(nil), []byte("HTTP/3.3 200 OK\r\n")) { t.Errorf("parse status line with default protocol value failed, got: '%q' want: 'HTTP/3.3 200 OK'", header.appendStatusLine(nil)) } header.SetStatusMessage(s2b(StatusMessage(200))) if !bytes.Equal(header.appendStatusLine(nil), []byte("HTTP/3.3 200 OK\r\n")) { t.Errorf("parse status line with default protocol value failed, got: '%q' want: 'HTTP/3.3 200 OK'", header.appendStatusLine(nil)) } for name, vals := range response.Header { got := string(header.Peek(name)) want := vals[0] if got != want { t.Errorf("unexpected %q got: %q want: %q", name, got, want) } } } func TestResponseHeaderMultiLineName(t *testing.T) { t.Parallel() s := "HTTP/1.1 200 OK\r\n" + "Host: go.dev\r\n" + "Gopher-New-\r\n" + " Line: This is a header on multiple lines\r\n" + "\r\n" header := new(ResponseHeader) if _, err := header.parse([]byte(s)); err != errInvalidName { m := make(map[string]string) 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 TestResponseHeaderMultiLinePanicked(t *testing.T) { t.Parallel() // Input generated by fuzz testing that caused the parser to panic. s, _ := base64.StdEncoding.DecodeString("aAEAIDoKKDoKICA6CgkKCiA6CiA6CgkpCiA6CiA6CiA6Cig6CiAgOgoJCgogOgogOgoJKQogOgogOgogOgogOgogOgoJOg86CiA6CiA6Cig6CiAyCg==") header := new(RequestHeader) if _, err := header.parse(s); err == nil { t.Error("expected error, got ") } } func TestResponseHeaderEmptyValueFromHeader(t *testing.T) { t.Parallel() var h1 ResponseHeader h1.SetContentType("foo/bar") h1.Set("EmptyValue1", "") h1.Set("EmptyValue2", " ") s := h1.String() var h ResponseHeader br := bufio.NewReader(bytes.NewBufferString(s)) if err := h.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } if !bytes.Equal(h.ContentType(), h1.ContentType()) { t.Fatalf("unexpected content-type: %q. Expecting %q", h.ContentType(), h1.ContentType()) } v1 := h.Peek("EmptyValue1") if len(v1) > 0 { t.Fatalf("expecting empty value. Got %q", v1) } v2 := h.Peek("EmptyValue2") if len(v2) > 0 { t.Fatalf("expecting empty value. Got %q", v2) } } func TestResponseHeaderEmptyValueFromString(t *testing.T) { t.Parallel() s := "HTTP/1.1 200 OK\r\n" + "EmptyValue1:\r\n" + "Content-Type: foo/bar\r\n" + "EmptyValue2: \r\n" + "\r\n" var h ResponseHeader br := bufio.NewReader(bytes.NewBufferString(s)) if err := h.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } if string(h.ContentType()) != "foo/bar" { t.Fatalf("unexpected content-type: %q. Expecting %q", h.ContentType(), "foo/bar") } v1 := h.Peek("EmptyValue1") if len(v1) > 0 { t.Fatalf("expecting empty value. Got %q", v1) } v2 := h.Peek("EmptyValue2") if len(v2) > 0 { t.Fatalf("expecting empty value. Got %q", v2) } } func TestRequestHeaderEmptyValueFromHeader(t *testing.T) { t.Parallel() var h1 RequestHeader h1.SetRequestURI("/foo/bar") h1.SetHost("foobar") h1.Set("EmptyValue1", "") h1.Set("EmptyValue2", " ") s := h1.String() var h RequestHeader br := bufio.NewReader(bytes.NewBufferString(s)) if err := h.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } if !bytes.Equal(h.Host(), h1.Host()) { t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), h1.Host()) } v1 := h.Peek("EmptyValue1") if len(v1) > 0 { t.Fatalf("expecting empty value. Got %q", v1) } v2 := h.Peek("EmptyValue2") if len(v2) > 0 { t.Fatalf("expecting empty value. Got %q", v2) } } func TestRequestHeaderEmptyValueFromString(t *testing.T) { t.Parallel() s := "GET / HTTP/1.1\r\n" + "EmptyValue1:\r\n" + "Host: foobar\r\n" + "EmptyValue2: \r\n" + "\r\n" var h RequestHeader br := bufio.NewReader(bytes.NewBufferString(s)) if err := h.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } if string(h.Host()) != "foobar" { t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar") } v1 := h.Peek("EmptyValue1") if len(v1) > 0 { t.Fatalf("expecting empty value. Got %q", v1) } v2 := h.Peek("EmptyValue2") if len(v2) > 0 { t.Fatalf("expecting empty value. Got %q", v2) } } func TestRequestRawHeaders(t *testing.T) { t.Parallel() kvs := "hOsT: foobar\r\n" + "value: b\r\n" + "\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 len(h.Host()) != 0 { t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "") } v1 := h.Peek("NoKey") if len(v1) > 0 { t.Fatalf("expecting empty value. Got %q", v1) } if raw := h.RawHeaders(); string(raw) != exp { t.Fatalf("expected header %q, got %q", exp, raw) } }) } func TestRequestDisableSpecialHeaders(t *testing.T) { t.Parallel() kvs := "Host: foobar\r\n" + "User-Agent: ua\r\n" + "Non-Special: val\r\n" + "\r\n" var h RequestHeader h.DisableSpecialHeader() s := "GET / HTTP/1.0\r\n" + kvs br := bufio.NewReader(bytes.NewBufferString(s)) if err := h.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } // assert order of all headers preserved if h.String() != s { t.Fatalf("Headers not equal: %q. Expecting %q", h.String(), s) } h.SetCanonical([]byte("host"), []byte("notfoobar")) if string(h.Host()) != "foobar" { t.Fatalf("unexpected: %q. Expecting %q", h.Host(), "foobar") } if h.String() != "GET / HTTP/1.0\r\nHost: foobar\r\nUser-Agent: ua\r\nNon-Special: val\r\nhost: notfoobar\r\n\r\n" { t.Fatalf("custom special header ordering failed: %q", h.String()) } } func TestRequestHeaderSetCookieWithSpecialChars(t *testing.T) { t.Parallel() var h RequestHeader h.Set("Cookie", "ID&14") s := h.String() if !strings.Contains(s, "Cookie: ID&14") { t.Fatalf("Missing cookie in request header: %q", s) } var h1 RequestHeader br := bufio.NewReader(bytes.NewBufferString(s)) if err := h1.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } cookie := h1.Peek(HeaderCookie) if string(cookie) != "ID&14" { t.Fatalf("unexpected cooke: %q. Expecting %q", cookie, "ID&14") } cookie = h1.Cookie("") if string(cookie) != "ID&14" { t.Fatalf("unexpected cooke: %q. Expecting %q", cookie, "ID&14") } } func TestResponseHeaderDefaultStatusCode(t *testing.T) { t.Parallel() var h ResponseHeader statusCode := h.StatusCode() if statusCode != StatusOK { t.Fatalf("unexpected status code: %d. Expecting %d", statusCode, StatusOK) } } func TestResponseHeaderDelClientCookie(t *testing.T) { t.Parallel() cookieName := "foobar" var h ResponseHeader c := AcquireCookie() c.SetKey(cookieName) c.SetValue("aasdfsdaf") h.SetCookie(c) h.DelClientCookieBytes([]byte(cookieName)) if !h.Cookie(c) { t.Fatalf("expecting cookie %q", c.Key()) } if !c.Expire().Equal(CookieExpireDelete) { t.Fatalf("unexpected cookie expiration time: %q. Expecting %q", c.Expire(), CookieExpireDelete) } if len(c.Value()) > 0 { t.Fatalf("unexpected cookie value: %q. Expecting empty value", c.Value()) } ReleaseCookie(c) } func TestResponseHeaderAdd(t *testing.T) { t.Parallel() m := make(map[string]struct{}) var h ResponseHeader h.Add("aaa", "bbb") h.Add("content-type", "xxx") m["bbb"] = struct{}{} m["xxx"] = struct{}{} for i := 0; i < 10; i++ { v := strconv.Itoa(i) h.Add("Foo-Bar", v) m[v] = struct{}{} } if h.Len() != 12 { t.Fatalf("unexpected header len %d. Expecting 12", h.Len()) } 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 := strconv.Itoa(i) h.Add("Foo-Bar", v) m[v] = struct{}{} } if h.Len() != 12 { t.Fatalf("unexpected header len %d. Expecting 12", h.Len()) } 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 obtained: %q", cv) } if h.ContentLength() != 0 { t.Fatalf("unexpected content-length: %d. Expecting 0", h.ContentLength()) } } func TestResponseHeaderDel(t *testing.T) { t.Parallel() var h ResponseHeader h.Set("Foo-Bar", "baz") h.Set("aaa", "bbb") h.Set(HeaderConnection, "keep-alive") h.Set(HeaderContentType, "aaa") h.Set(HeaderContentEncoding, "gzip") h.Set(HeaderServer, "aaabbb") h.Set(HeaderContentLength, "1123") h.Set(HeaderTrailer, "foo, bar") var c Cookie c.SetKey("foo") c.SetValue("bar") h.SetCookie(&c) h.Del("foo-bar") h.Del("connection") h.DelBytes([]byte("content-type")) h.Del(HeaderServer) h.Del("content-length") h.Del("set-cookie") h.Del("trailer") hv := h.Peek("aaa") if string(hv) != "bbb" { t.Fatalf("unexpected header value: %q. Expecting %q", hv, "bbb") } hv = h.Peek("Foo-Bar") if len(hv) > 0 { t.Fatalf("non-zero header value: %q", hv) } hv = h.Peek(HeaderConnection) if len(hv) > 0 { t.Fatalf("non-zero value: %q", hv) } hv = h.Peek(HeaderContentType) if !bytes.Equal(hv, defaultContentType) { t.Fatalf("unexpected content-type: %q. Expecting %q", hv, defaultContentType) } hv = h.Peek(HeaderContentEncoding) if string(hv) != "gzip" { t.Fatalf("unexpected content-encoding: %q. Expecting %q", hv, "gzip") } hv = h.Peek(HeaderServer) if len(hv) > 0 { t.Fatalf("non-zero value: %q", hv) } hv = h.Peek(HeaderContentLength) if len(hv) > 0 { t.Fatalf("non-zero value: %q", hv) } hv = h.Peek(HeaderTrailer) if len(hv) > 0 { t.Fatalf("non-zero value: %q", hv) } if h.Cookie(&c) { t.Fatalf("unexpected cookie obtained: %q", &c) } if h.ContentLength() != 0 { t.Fatalf("unexpected content-length: %d. Expecting 0", h.ContentLength()) } } func TestResponseHeaderSetTrailerGetBytes(t *testing.T) { t.Parallel() h := &ResponseHeader{} h.noDefaultDate = true h.Set("Foo", "bar") h.Set(HeaderTrailer, "Baz") h.Set("Baz", "test") headerBytes := h.Header() n, err := h.parseFirstLine(headerBytes) if err != nil { t.Fatalf("unexpected error: %v", err) } if string(headerBytes[n:]) != "Foo: bar\r\nTrailer: Baz\r\n\r\n" { t.Fatalf("Unexpected header: %q. Expected %q", headerBytes[n:], "Foo: bar\nTrailer: Baz\n\n") } if string(h.TrailerHeader()) != "Baz: test\r\n\r\n" { t.Fatalf("Unexpected trailer header: %q. Expected %q", h.TrailerHeader(), "Baz: test\r\n\r\n") } } func TestRequestHeaderSetTrailerGetBytes(t *testing.T) { t.Parallel() h := &RequestHeader{} h.Set("Foo", "bar") h.Set(HeaderTrailer, "Baz") h.Set("Baz", "test") headerBytes := h.Header() n, err := h.parseFirstLine(headerBytes) if err != nil { t.Fatalf("unexpected error: %v", err) } if string(headerBytes[n:]) != "Foo: bar\r\nTrailer: Baz\r\n\r\n" { t.Fatalf("Unexpected header: %q. Expected %q", headerBytes[n:], "Foo: bar\nTrailer: Baz\n\n") } if string(h.TrailerHeader()) != "Baz: test\r\n\r\n" { t.Fatalf("Unexpected trailer header: %q. Expected %q", h.TrailerHeader(), "Baz: test\r\n\r\n") } } func TestAppendNormalizedHeaderKeyBytes(t *testing.T) { t.Parallel() testAppendNormalizedHeaderKeyBytes(t, "", "") testAppendNormalizedHeaderKeyBytes(t, "Content-Type", "Content-Type") testAppendNormalizedHeaderKeyBytes(t, "foO-bAr-BAZ", "Foo-Bar-Baz") } func testAppendNormalizedHeaderKeyBytes(t *testing.T, key, expectedKey string) { buf := []byte("foobar") result := AppendNormalizedHeaderKeyBytes(buf, []byte(key)) normalizedKey := result[len(buf):] if string(normalizedKey) != expectedKey { t.Fatalf("unexpected normalized key %q. Expecting %q", normalizedKey, expectedKey) } } func TestRequestHeaderHTTP10ConnectionClose(t *testing.T) { t.Parallel() s := "GET / HTTP/1.0\r\nHost: foobar\r\n\r\n" var h RequestHeader br := bufio.NewReader(bytes.NewBufferString(s)) if err := h.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } if !h.ConnectionClose() { t.Fatalf("expecting 'Connection: close' request header") } } func TestRequestHeaderHTTP10ConnectionKeepAlive(t *testing.T) { t.Parallel() s := "GET / HTTP/1.0\r\nHost: foobar\r\nConnection: keep-alive\r\n\r\n" var h RequestHeader br := bufio.NewReader(bytes.NewBufferString(s)) if err := h.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } if h.ConnectionClose() { t.Fatalf("unexpected 'Connection: close' request header") } } func TestBufferSnippet(t *testing.T) { t.Parallel() testBufferSnippet(t, "", `""`) testBufferSnippet(t, "foobar", `"foobar"`) b := string(createFixedBody(199)) bExpected := fmt.Sprintf("%q", b) testBufferSnippet(t, b, bExpected) for i := 0; i < 10; i++ { b += "foobar" bExpected = fmt.Sprintf("%q", b) testBufferSnippet(t, b, bExpected) } b = string(createFixedBody(400)) bExpected = fmt.Sprintf("%q", b) testBufferSnippet(t, b, bExpected) for i := 0; i < 10; i++ { b += "sadfqwer" bExpected = fmt.Sprintf("%q...%q", b[:200], b[len(b)-200:]) testBufferSnippet(t, b, bExpected) } } func testBufferSnippet(t *testing.T, buf, expectedSnippet string) { snippet := bufferSnippet([]byte(buf)) if snippet != expectedSnippet { t.Fatalf("unexpected snippet %q. Expecting %q", snippet, expectedSnippet) } } func TestResponseHeaderTrailingCRLFSuccess(t *testing.T) { t.Parallel() trailingCRLF := "\r\n\r\n\r\n" s := "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 123\r\n\r\n" + trailingCRLF var r ResponseHeader br := bufio.NewReader(bytes.NewBufferString(s)) if err := r.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } // try reading the trailing CRLF. It must return EOF err := r.Read(br) if err == nil { t.Fatalf("expecting error") } if err != io.EOF { t.Fatalf("unexpected error: %v. Expecting %v", err, io.EOF) } } func TestResponseHeaderTrailingCRLFError(t *testing.T) { t.Parallel() trailingCRLF := "\r\nerror\r\n\r\n" s := "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 123\r\n\r\n" + trailingCRLF var r ResponseHeader br := bufio.NewReader(bytes.NewBufferString(s)) if err := r.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } // try reading the trailing CRLF. It must return EOF err := r.Read(br) if err == nil { t.Fatalf("expecting error") } if err == io.EOF { t.Fatalf("unexpected error: %v", err) } } func TestRequestHeaderTrailingCRLFSuccess(t *testing.T) { t.Parallel() trailingCRLF := "\r\n\r\n\r\n" s := "GET / HTTP/1.1\r\nHost: aaa.com\r\n\r\n" + trailingCRLF var r RequestHeader br := bufio.NewReader(bytes.NewBufferString(s)) if err := r.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } // try reading the trailing CRLF. It must return EOF err := r.Read(br) if err == nil { t.Fatalf("expecting error") } if err != io.EOF { t.Fatalf("unexpected error: %v. Expecting %v", err, io.EOF) } } func TestRequestHeaderTrailingCRLFError(t *testing.T) { t.Parallel() trailingCRLF := "\r\nerror\r\n\r\n" s := "GET / HTTP/1.1\r\nHost: aaa.com\r\n\r\n" + trailingCRLF var r RequestHeader br := bufio.NewReader(bytes.NewBufferString(s)) if err := r.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } // try reading the trailing CRLF. It must return EOF err := r.Read(br) if err == nil { t.Fatalf("expecting error") } if err == io.EOF { t.Fatalf("unexpected error: %v", err) } } func TestRequestHeaderReadEOF(t *testing.T) { t.Parallel() var r RequestHeader br := bufio.NewReader(&bytes.Buffer{}) err := r.Read(br) if err == nil { t.Fatalf("expecting error") } if err != io.EOF { t.Fatalf("unexpected error: %v. Expecting %v", err, io.EOF) } // incomplete request header mustn't return io.EOF br = bufio.NewReader(bytes.NewBufferString("GET ")) err = r.Read(br) if err == nil { t.Fatalf("expecting error") } if err == io.EOF { t.Fatalf("expecting non-EOF error") } } func TestResponseHeaderReadEOF(t *testing.T) { t.Parallel() var r ResponseHeader br := bufio.NewReader(&bytes.Buffer{}) err := r.Read(br) if err == nil { t.Fatalf("expecting error") } if err != io.EOF { t.Fatalf("unexpected error: %v. Expecting %v", err, io.EOF) } // incomplete response header mustn't return io.EOF br = bufio.NewReader(bytes.NewBufferString("HTTP/1.1 ")) err = r.Read(br) if err == nil { t.Fatalf("expecting error") } if err == io.EOF { t.Fatalf("expecting non-EOF error") } } func TestResponseHeaderOldVersion(t *testing.T) { t.Parallel() var h ResponseHeader s := "HTTP/1.0 200 OK\r\nContent-Length: 5\r\nContent-Type: aaa\r\n\r\n12345" s += "HTTP/1.0 200 OK\r\nContent-Length: 2\r\nContent-Type: ass\r\nConnection: keep-alive\r\n\r\n42" br := bufio.NewReader(bytes.NewBufferString(s)) if err := h.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } if !h.ConnectionClose() { t.Fatalf("expecting 'Connection: close' for the response with old http protocol") } if err := h.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } if h.ConnectionClose() { t.Fatalf("unexpected 'Connection: close' for keep-alive response with old http protocol") } } func TestRequestHeaderSetByteRange(t *testing.T) { t.Parallel() testRequestHeaderSetByteRange(t, 0, 10, "bytes=0-10") testRequestHeaderSetByteRange(t, 123, -1, "bytes=123-") testRequestHeaderSetByteRange(t, -234, 58349, "bytes=-234") } func testRequestHeaderSetByteRange(t *testing.T, startPos, endPos int, expectedV string) { var h RequestHeader h.SetByteRange(startPos, endPos) v := h.Peek(HeaderRange) if string(v) != expectedV { t.Fatalf("unexpected range: %q. Expecting %q. startPos=%d, endPos=%d", v, expectedV, startPos, endPos) } } func TestResponseHeaderSetContentRange(t *testing.T) { t.Parallel() testResponseHeaderSetContentRange(t, 0, 0, 1, "bytes 0-0/1") testResponseHeaderSetContentRange(t, 123, 456, 789, "bytes 123-456/789") } func testResponseHeaderSetContentRange(t *testing.T, startPos, endPos, contentLength int, expectedV string) { var h ResponseHeader h.SetContentRange(startPos, endPos, contentLength) v := h.Peek(HeaderContentRange) if string(v) != expectedV { t.Fatalf("unexpected content-range: %q. Expecting %q. startPos=%d, endPos=%d, contentLength=%d", v, expectedV, startPos, endPos, contentLength) } } func TestRequestHeaderHasAcceptEncoding(t *testing.T) { t.Parallel() testRequestHeaderHasAcceptEncoding(t, "", "gzip", false) testRequestHeaderHasAcceptEncoding(t, "gzip", "sdhc", false) testRequestHeaderHasAcceptEncoding(t, "deflate", "deflate", true) testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "gzi", false) testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "dhc", false) testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "sdh", false) testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "zip", false) testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "flat", false) testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "flate", false) testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "def", false) testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "gzip", true) testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "deflate", true) testRequestHeaderHasAcceptEncoding(t, "gzip, deflate, sdhc", "sdhc", true) } func testRequestHeaderHasAcceptEncoding(t *testing.T, ae, v string, resultExpected bool) { var h RequestHeader h.Set(HeaderAcceptEncoding, ae) result := h.HasAcceptEncoding(v) if result != resultExpected { t.Fatalf("unexpected result in HasAcceptEncoding(%q, %q): %v. Expecting %v", ae, v, result, resultExpected) } } func TestVisitHeaderParams(t *testing.T) { t.Parallel() testVisitHeaderParams(t, "text/plain;charset=utf-8;q=0.39", [][2]string{{"charset", "utf-8"}, {"q", "0.39"}}) testVisitHeaderParams(t, "text/plain; foo=bar ;", [][2]string{{"foo", "bar"}}) testVisitHeaderParams(t, `text/plain; foo="bar"; `, [][2]string{{"foo", "bar"}}) testVisitHeaderParams(t, `text/plain; foo="text/plain,text/html;charset=\"utf-8\""`, [][2]string{{"foo", `text/plain,text/html;charset=\"utf-8\"`}}) testVisitHeaderParams(t, "text/plain foo=bar", [][2]string{}) testVisitHeaderParams(t, "text/plain;", [][2]string{}) testVisitHeaderParams(t, "text/plain; ", [][2]string{}) testVisitHeaderParams(t, "text/plain; foo", [][2]string{}) testVisitHeaderParams(t, "text/plain; foo=", [][2]string{}) testVisitHeaderParams(t, "text/plain; =bar", [][2]string{}) testVisitHeaderParams(t, "text/plain; foo = bar", [][2]string{}) testVisitHeaderParams(t, `text/plain; foo="bar`, [][2]string{}) testVisitHeaderParams(t, "text/plain;;foo=bar", [][2]string{}) parsed := make([][2]string, 0) VisitHeaderParams([]byte(`text/plain; foo=bar; charset=utf-8`), func(key, value []byte) bool { parsed = append(parsed, [2]string{string(key), string(value)}) return !bytes.Equal(key, []byte("foo")) }) if len(parsed) != 1 { t.Fatalf("expected 1 HTTP parameter, parsed %v", len(parsed)) } if parsed[0] != [2]string{"foo", "bar"} { t.Fatalf("unexpected parameter %v=%v. Expecting foo=bar", parsed[0][0], parsed[0][1]) } } func testVisitHeaderParams(t *testing.T, header string, expectedParams [][2]string) { parsed := make([][2]string, 0) VisitHeaderParams([]byte(header), func(key, value []byte) bool { parsed = append(parsed, [2]string{string(key), string(value)}) return true }) if len(parsed) != len(expectedParams) { t.Fatalf("expected %v HTTP parameters, parsed %v", len(expectedParams), len(parsed)) } for i := range expectedParams { if expectedParams[i] != parsed[i] { t.Fatalf("unexpected parameter %v=%v. Expecting %v=%v", parsed[i][0], parsed[i][1], expectedParams[i][0], expectedParams[i][1]) } } } func TestRequestMultipartFormBoundary(t *testing.T) { t.Parallel() testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=foobar\r\n\r\n", "foobar") // incorrect content-type testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: foo/bar\r\n\r\n", "") // empty boundary testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=\r\n\r\n", "") // missing boundary testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: multipart/form-data\r\n\r\n", "") // boundary after other content-type params testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: multipart/form-data; foo=bar; boundary=--aaabb \r\n\r\n", "--aaabb") // quoted boundary testRequestMultipartFormBoundary(t, "POST / HTTP/1.1\r\nContent-Type: multipart/form-data; boundary=\"foobar\"\r\n\r\n", "foobar") var h RequestHeader h.SetMultipartFormBoundary("foobarbaz") b := h.MultipartFormBoundary() if string(b) != "foobarbaz" { t.Fatalf("unexpected boundary %q. Expecting %q", b, "foobarbaz") } } func testRequestMultipartFormBoundary(t *testing.T, s, boundary string) { var h RequestHeader r := bytes.NewBufferString(s) br := bufio.NewReader(r) if err := h.Read(br); err != nil { t.Fatalf("unexpected error: %v. s=%q, boundary=%q", err, s, boundary) } b := h.MultipartFormBoundary() if string(b) != boundary { t.Fatalf("unexpected boundary %q. Expecting %q. s=%q", b, boundary, s) } } func TestResponseHeaderConnectionUpgrade(t *testing.T) { t.Parallel() testResponseHeaderConnectionUpgrade(t, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nConnection: Upgrade, HTTP2-Settings\r\n\r\n", true, true) testResponseHeaderConnectionUpgrade(t, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nConnection: keep-alive, Upgrade\r\n\r\n", true, true) // non-http/1.1 protocol has 'connection: close' by default, which also disables 'connection: upgrade' testResponseHeaderConnectionUpgrade(t, "HTTP/1.0 200 OK\r\nContent-Length: 10\r\nConnection: Upgrade, HTTP2-Settings\r\n\r\n", false, false) // explicit keep-alive for non-http/1.1, so 'connection: upgrade' works testResponseHeaderConnectionUpgrade(t, "HTTP/1.0 200 OK\r\nContent-Length: 10\r\nConnection: Upgrade, keep-alive\r\n\r\n", true, true) // implicit keep-alive for http/1.1 testResponseHeaderConnectionUpgrade(t, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\n", false, true) // no content-length, so 'connection: close' is assumed testResponseHeaderConnectionUpgrade(t, "HTTP/1.1 200 OK\r\n\r\n", false, false) } func testResponseHeaderConnectionUpgrade(t *testing.T, s string, isUpgrade, isKeepAlive bool) { var h ResponseHeader r := bytes.NewBufferString(s) br := bufio.NewReader(r) if err := h.Read(br); err != nil { t.Fatalf("unexpected error: %v. Response header %q", err, s) } upgrade := h.ConnectionUpgrade() if upgrade != isUpgrade { t.Fatalf("unexpected 'connection: upgrade' when parsing response header: %v. Expecting %v. header %q. v=%q", upgrade, isUpgrade, s, h.Peek("Connection")) } keepAlive := !h.ConnectionClose() if keepAlive != isKeepAlive { t.Fatalf("unexpected 'connection: keep-alive' when parsing response header: %v. Expecting %v. header %q. v=%q", keepAlive, isKeepAlive, s, &h) } } func TestRequestHeaderConnectionUpgrade(t *testing.T) { t.Parallel() testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.1\r\nConnection: Upgrade, HTTP2-Settings\r\nHost: foobar.com\r\n\r\n", true, true) testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.1\r\nConnection: keep-alive,Upgrade\r\nHost: foobar.com\r\n\r\n", true, true) // non-http/1.1 has 'connection: close' by default, which resets 'connection: upgrade' testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.0\r\nConnection: Upgrade, HTTP2-Settings\r\nHost: foobar.com\r\n\r\n", false, false) // explicit 'connection: keep-alive' in non-http/1.1 testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.0\r\nConnection: foo, Upgrade, keep-alive\r\nHost: foobar.com\r\n\r\n", true, true) // no upgrade testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.1\r\nConnection: Upgradess, foobar\r\nHost: foobar.com\r\n\r\n", false, true) testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.1\r\nHost: foobar.com\r\n\r\n", false, true) // explicit connection close testRequestHeaderConnectionUpgrade(t, "GET /foobar HTTP/1.1\r\nConnection: close\r\nHost: foobar.com\r\n\r\n", false, false) } func testRequestHeaderConnectionUpgrade(t *testing.T, s string, isUpgrade, isKeepAlive bool) { var h RequestHeader r := bytes.NewBufferString(s) br := bufio.NewReader(r) if err := h.Read(br); err != nil { t.Fatalf("unexpected error: %v. Request header %q", err, s) } upgrade := h.ConnectionUpgrade() if upgrade != isUpgrade { t.Fatalf("unexpected 'connection: upgrade' when parsing request header: %v. Expecting %v. header %q", upgrade, isUpgrade, s) } keepAlive := !h.ConnectionClose() if keepAlive != isKeepAlive { t.Fatalf("unexpected 'connection: keep-alive' when parsing request header: %v. Expecting %v. header %q", keepAlive, isKeepAlive, s) } } func TestRequestHeaderProxyWithCookie(t *testing.T) { t.Parallel() // Proxy request header (read it, then write it without touching any headers). var h RequestHeader r := bytes.NewBufferString("GET /foo HTTP/1.1\r\nFoo: bar\r\nHost: aaa.com\r\nCookie: foo=bar; bazzz=aaaaaaa; x=y\r\nCookie: aqqqqq=123\r\n\r\n") br := bufio.NewReader(r) if err := h.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } w := &bytes.Buffer{} bw := bufio.NewWriter(w) if err := h.Write(bw); err != nil { t.Fatalf("unexpected error: %v", err) } if err := bw.Flush(); err != nil { t.Fatalf("unexpected error: %v", err) } var h1 RequestHeader br.Reset(w) if err := h1.Read(br); err != nil { t.Fatalf("unexpected error: %v", err) } if string(h1.RequestURI()) != "/foo" { t.Fatalf("unexpected requestURI: %q. Expecting %q", h1.RequestURI(), "/foo") } if string(h1.Host()) != "aaa.com" { t.Fatalf("unexpected host: %q. Expecting %q", h1.Host(), "aaa.com") } if string(h1.Peek("Foo")) != "bar" { t.Fatalf("unexpected Foo: %q. Expecting %q", h1.Peek("Foo"), "bar") } if string(h1.Cookie("foo")) != "bar" { t.Fatalf("unexpected cookie foo=%q. Expecting %q", h1.Cookie("foo"), "bar") } if string(h1.Cookie("bazzz")) != "aaaaaaa" { t.Fatalf("unexpected cookie bazzz=%q. Expecting %q", h1.Cookie("bazzz"), "aaaaaaa") } if string(h1.Cookie("x")) != "y" { t.Fatalf("unexpected cookie x=%q. Expecting %q", h1.Cookie("x"), "y") } if string(h1.Cookie("aqqqqq")) != "123" { t.Fatalf("unexpected cookie aqqqqq=%q. Expecting %q", h1.Cookie("aqqqqq"), "123") } } func TestResponseHeaderFirstByteReadEOF(t *testing.T) { t.Parallel() var h ResponseHeader r := &errorReader{errors.New("non-eof error")} br := bufio.NewReader(r) err := h.Read(br) if err == nil { t.Fatalf("expecting error") } if err != io.EOF { t.Fatalf("unexpected error %v. Expecting %v", err, io.EOF) } } type errorReader struct { err error } func (r *errorReader) Read(p []byte) (int, error) { return 0, r.err } func TestRequestHeaderEmptyMethod(t *testing.T) { t.Parallel() var h RequestHeader if !h.IsGet() { t.Fatalf("empty method must be equivalent to GET") } } func TestResponseHeaderHTTPVer(t *testing.T) { t.Parallel() // non-http/1.1 testResponseHeaderHTTPVer(t, "HTTP/1.0 200 OK\r\nContent-Type: aaa\r\nContent-Length: 123\r\n\r\n", true) testResponseHeaderHTTPVer(t, "HTTP/0.9 200 OK\r\nContent-Type: aaa\r\nContent-Length: 123\r\n\r\n", true) testResponseHeaderHTTPVer(t, "foobar 200 OK\r\nContent-Type: aaa\r\nContent-Length: 123\r\n\r\n", true) // http/1.1 testResponseHeaderHTTPVer(t, "HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nContent-Length: 123\r\n\r\n", false) } func TestRequestHeaderHTTPVer(t *testing.T) { t.Parallel() // non-http/1.1 testRequestHeaderHTTPVer(t, "GET / HTTP/1.0\r\nHost: aa.com\r\n\r\n", true) testRequestHeaderHTTPVer(t, "GET / HTTP/0.9\r\nHost: aa.com\r\n\r\n", true) // http/1.1 testRequestHeaderHTTPVer(t, "GET / HTTP/1.1\r\nHost: a.com\r\n\r\n", false) } func testResponseHeaderHTTPVer(t *testing.T, s string, connectionClose bool) { var h ResponseHeader r := bytes.NewBufferString(s) br := bufio.NewReader(r) if err := h.Read(br); err != nil { t.Fatalf("unexpected error: %v. response=%q", err, s) } if h.ConnectionClose() != connectionClose { t.Fatalf("unexpected connectionClose %v. Expecting %v. response=%q", h.ConnectionClose(), connectionClose, s) } } func testRequestHeaderHTTPVer(t *testing.T, s string, connectionClose bool) { t.Helper() var h RequestHeader r := bytes.NewBufferString(s) br := bufio.NewReader(r) if err := h.Read(br); err != nil { t.Fatalf("unexpected error: %v. request=%q", err, s) } if h.ConnectionClose() != connectionClose { t.Fatalf("unexpected connectionClose %v. Expecting %v. request=%q", h.ConnectionClose(), connectionClose, s) } } func TestResponseHeaderCopyTo(t *testing.T) { t.Parallel() var h ResponseHeader h.Set(HeaderSetCookie, "foo=bar") h.Set(HeaderContentType, "foobar") h.Set(HeaderContentEncoding, "gzip") h.Set("AAA-BBB", "aaaa") h.Set(HeaderTrailer, "foo, bar") var h1 ResponseHeader h.CopyTo(&h1) if !bytes.Equal(h1.Peek("Set-cookie"), h.Peek("Set-Cookie")) { t.Fatalf("unexpected cookie %q. Expected %q", h1.Peek("set-cookie"), h.Peek("set-cookie")) } if !bytes.Equal(h1.Peek(HeaderContentType), h.Peek(HeaderContentType)) { t.Fatalf("unexpected content-type %q. Expected %q", h1.Peek("content-type"), h.Peek("content-type")) } if !bytes.Equal(h1.Peek(HeaderContentEncoding), h.Peek(HeaderContentEncoding)) { t.Fatalf("unexpected content-encoding %q. Expected %q", h1.Peek("content-encoding"), h.Peek("content-encoding")) } if !bytes.Equal(h1.Peek("aaa-bbb"), h.Peek("AAA-BBB")) { t.Fatalf("unexpected aaa-bbb %q. Expected %q", h1.Peek("aaa-bbb"), h.Peek("aaa-bbb")) } if !bytes.Equal(h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer)) { t.Fatalf("unexpected trailer %q. Expected %q", h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer)) } // flush buf h.bufKV = argsKV{} h1.bufKV = argsKV{} if !reflect.DeepEqual(&h, &h1) { t.Fatalf("ResponseHeaderCopyTo fail, src: \n%+v\ndst: \n%+v\n", &h, &h1) } } func TestRequestHeaderCopyTo(t *testing.T) { t.Parallel() var h RequestHeader h.Set(HeaderCookie, "aa=bb; cc=dd") h.Set(HeaderContentType, "foobar") h.Set(HeaderContentEncoding, "gzip") h.Set(HeaderHost, "aaaa") h.Set("aaaxxx", "123") h.Set(HeaderTrailer, "foo, bar") h.noDefaultContentType = true var h1 RequestHeader h.CopyTo(&h1) if !bytes.Equal(h1.Peek("cookie"), h.Peek(HeaderCookie)) { t.Fatalf("unexpected cookie after copying: %q. Expected %q", h1.Peek("cookie"), h.Peek("cookie")) } if !bytes.Equal(h1.Peek("content-type"), h.Peek(HeaderContentType)) { t.Fatalf("unexpected content-type %q. Expected %q", h1.Peek("content-type"), h.Peek("content-type")) } if !bytes.Equal(h1.Peek("content-encoding"), h.Peek(HeaderContentEncoding)) { t.Fatalf("unexpected content-encoding %q. Expected %q", h1.Peek("content-encoding"), h.Peek("content-encoding")) } if !bytes.Equal(h1.Peek("host"), h.Peek("host")) { t.Fatalf("unexpected host %q. Expected %q", h1.Peek("host"), h.Peek("host")) } if !bytes.Equal(h1.Peek("aaaxxx"), h.Peek("aaaxxx")) { t.Fatalf("unexpected aaaxxx %q. Expected %q", h1.Peek("aaaxxx"), h.Peek("aaaxxx")) } if !bytes.Equal(h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer)) { t.Fatalf("unexpected trailer %q. Expected %q", h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer)) } // flush buf h.bufKV = argsKV{} h1.bufKV = argsKV{} if !reflect.DeepEqual(&h, &h1) { t.Fatalf("RequestHeaderCopyTo fail, src: \n%+v\ndst: \n%+v\n", &h, &h1) } } func TestResponseContentTypeNoDefaultNotEmpty(t *testing.T) { t.Parallel() var h ResponseHeader h.SetNoDefaultContentType(true) h.SetContentLength(5) headers := h.String() if strings.Contains(headers, "Content-Type: \r\n") { t.Fatalf("ResponseContentTypeNoDefaultNotEmpty fail, response: \n%+v\noutcome: \n%q\n", &h, headers) } } func TestRequestContentTypeDefaultNotEmpty(t *testing.T) { t.Parallel() var h RequestHeader h.SetMethod(MethodPost) h.SetContentLength(5) w := &bytes.Buffer{} bw := bufio.NewWriter(w) if err := h.Write(bw); err != nil { t.Fatalf("Unexpected error: %v", err) } if err := bw.Flush(); err != nil { t.Fatalf("Unexpected error: %v", err) } var h1 RequestHeader br := bufio.NewReader(w) if err := h1.Read(br); err != nil { t.Fatalf("Unexpected error: %v", err) } if string(h1.contentType) != "application/octet-stream" { t.Fatalf("unexpected Content-Type %q. Expecting %q", h1.contentType, "application/octet-stream") } } func TestRequestContentTypeNoDefault(t *testing.T) { t.Parallel() var h RequestHeader h.SetMethod(MethodDelete) h.SetNoDefaultContentType(true) w := &bytes.Buffer{} bw := bufio.NewWriter(w) if err := h.Write(bw); err != nil { t.Fatalf("Unexpected error: %v", err) } if err := bw.Flush(); err != nil { t.Fatalf("Unexpected error: %v", err) } var h1 RequestHeader br := bufio.NewReader(w) if err := h1.Read(br); err != nil { t.Fatalf("Unexpected error: %v", err) } if len(h1.contentType) != 0 { t.Fatalf("unexpected Content-Type %q. Expecting %q", h1.contentType, "") } } func TestResponseDateNoDefaultNotEmpty(t *testing.T) { t.Parallel() var h ResponseHeader h.noDefaultDate = true headers := h.String() if strings.Contains(headers, "\r\nDate: ") { t.Fatalf("ResponseDateNoDefaultNotEmpty fail, response: \n%+v\noutcome: \n%q\n", &h, headers) } } func TestRequestHeaderConnectionClose(t *testing.T) { t.Parallel() var h RequestHeader h.Set(HeaderConnection, "close") h.Set(HeaderHost, "foobar") if !h.ConnectionClose() { t.Fatalf("connection: close not set") } var w bytes.Buffer bw := bufio.NewWriter(&w) if err := h.Write(bw); err != nil { t.Fatalf("unexpected error: %v", err) } if err := bw.Flush(); err != nil { t.Fatalf("unexpected error: %v", err) } var h1 RequestHeader br := bufio.NewReader(&w) if err := h1.Read(br); err != nil { t.Fatalf("error when reading request header: %v", err) } if !h1.ConnectionClose() { t.Fatalf("unexpected connection: close value: %v", h1.ConnectionClose()) } if string(h1.Peek(HeaderConnection)) != "close" { t.Fatalf("unexpected connection value: %q. Expecting %q", h.Peek("Connection"), "close") } } func TestRequestHeaderSetCookie(t *testing.T) { t.Parallel() var h RequestHeader h.Set("Cookie", "foo=bar; baz=aaa") h.Set("cOOkie", "xx=yyy") if string(h.Cookie("foo")) != "bar" { t.Fatalf("Unexpected cookie %q. Expecting %q", h.Cookie("foo"), "bar") } if string(h.Cookie("baz")) != "aaa" { t.Fatalf("Unexpected cookie %q. Expecting %q", h.Cookie("baz"), "aaa") } if string(h.Cookie("xx")) != "yyy" { t.Fatalf("unexpected cookie %q. Expecting %q", h.Cookie("xx"), "yyy") } } func TestResponseHeaderSetCookie(t *testing.T) { t.Parallel() var h ResponseHeader h.Set("set-cookie", "foo=bar; path=/aa/bb; domain=aaa.com") h.Set(HeaderSetCookie, "aaaaa=bxx") var c Cookie c.SetKey("foo") if !h.Cookie(&c) { t.Fatalf("cannot obtain %q cookie", c.Key()) } if string(c.Value()) != "bar" { t.Fatalf("unexpected cookie value %q. Expected %q", c.Value(), "bar") } if string(c.Path()) != "/aa/bb" { t.Fatalf("unexpected cookie path %q. Expected %q", c.Path(), "/aa/bb") } if string(c.Domain()) != "aaa.com" { t.Fatalf("unexpected cookie domain %q. Expected %q", c.Domain(), "aaa.com") } c.SetKey("aaaaa") if !h.Cookie(&c) { t.Fatalf("cannot obtain %q cookie", c.Key()) } if string(c.Value()) != "bxx" { t.Fatalf("unexpected cookie value %q. Expecting %q", c.Value(), "bxx") } } func TestResponseHeaderVisitAll(t *testing.T) { t.Parallel() var h ResponseHeader r := bytes.NewBufferString("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Encoding: gzip\r\nContent-Length: 123\r\nSet-Cookie: aa=bb; path=/foo/bar\r\nSet-Cookie: ccc\r\nTrailer: Foo, Bar\r\n\r\n") br := bufio.NewReader(r) if err := h.Read(br); err != nil { t.Fatalf("Unexpected error: %v", err) } if h.Len() != 6 { t.Fatalf("Unexpected number of headers: %d. Expected 6", h.Len()) } contentLengthCount := 0 contentTypeCount := 0 contentEncodingCount := 0 cookieCount := 0 h.VisitAll(func(key, value []byte) { k := string(key) v := string(value) switch k { case HeaderContentLength: if v != string(h.Peek(k)) { t.Fatalf("unexpected content-length: %q. Expecting %q", v, h.Peek(k)) } contentLengthCount++ case HeaderContentType: if v != string(h.Peek(k)) { t.Fatalf("Unexpected content-type: %q. Expected %q", v, h.Peek(k)) } contentTypeCount++ case HeaderContentEncoding: if v != string(h.Peek(k)) { t.Fatalf("Unexpected content-encoding: %q. Expected %q", v, h.Peek(k)) } contentEncodingCount++ case HeaderSetCookie: if cookieCount == 0 && v != "aa=bb; path=/foo/bar" { t.Fatalf("unexpected cookie header: %q. Expected %q", v, "aa=bb; path=/foo/bar") } if cookieCount == 1 && v != "ccc" { t.Fatalf("unexpected cookie header: %q. Expected %q", v, "ccc") } cookieCount++ case HeaderTrailer: if v != "Foo, Bar" { t.Fatalf("Unexpected trailer header %q. Expected %q", v, "Foo, Bar") } default: t.Fatalf("unexpected header %q=%q", k, v) } }) if contentLengthCount != 1 { t.Fatalf("unexpected number of content-length headers: %d. Expected 1", contentLengthCount) } if contentTypeCount != 1 { t.Fatalf("unexpected number of content-type headers: %d. Expected 1", contentTypeCount) } if contentEncodingCount != 1 { t.Fatalf("unexpected number of content-encoding headers: %d. Expected 1", contentEncodingCount) } if cookieCount != 2 { t.Fatalf("unexpected number of cookie header: %d. Expected 2", cookieCount) } } func TestRequestHeaderVisitAll(t *testing.T) { t.Parallel() var h RequestHeader r := bytes.NewBufferString("GET / HTTP/1.1\r\nHost: aa.com\r\nXX: YYY\r\nXX: ZZ\r\nCookie: a=b; c=d\r\nTrailer: Foo, Bar\r\n\r\n") br := bufio.NewReader(r) if err := h.Read(br); err != nil { t.Fatalf("Unexpected error: %v", err) } if h.Len() != 5 { t.Fatalf("Unexpected number of header: %d. Expected 5", h.Len()) } hostCount := 0 xxCount := 0 cookieCount := 0 h.VisitAll(func(key, value []byte) { k := string(key) v := string(value) switch k { case HeaderHost: if v != string(h.Peek(k)) { t.Fatalf("Unexpected host value %q. Expected %q", v, h.Peek(k)) } hostCount++ case "Xx": if xxCount == 0 && v != "YYY" { t.Fatalf("Unexpected value %q. Expected %q", v, "YYY") } if xxCount == 1 && v != "ZZ" { t.Fatalf("Unexpected value %q. Expected %q", v, "ZZ") } xxCount++ case HeaderCookie: if v != "a=b; c=d" { t.Fatalf("Unexpected cookie %q. Expected %q", v, "a=b; c=d") } cookieCount++ case HeaderTrailer: if v != "Foo, Bar" { t.Fatalf("Unexpected trailer header %q. Expected %q", v, "Foo, Bar") } default: t.Fatalf("Unexpected header %q=%q", k, v) } }) if hostCount != 1 { t.Fatalf("Unexpected number of host headers detected %d. Expected 1", hostCount) } if xxCount != 2 { t.Fatalf("Unexpected number of xx headers detected %d. Expected 2", xxCount) } if cookieCount != 1 { t.Fatalf("Unexpected number of cookie headers %d. Expected 1", cookieCount) } } func TestRequestHeaderVisitAllInOrder(t *testing.T) { t.Parallel() var h RequestHeader r := bytes.NewBufferString("GET / HTTP/1.1\r\nContent-Type: aa\r\nCookie: a=b\r\nHost: example.com\r\nUser-Agent: xxx\r\n\r\n") br := bufio.NewReader(r) if err := h.Read(br); err != nil { t.Fatalf("Unexpected error: %v", err) } if h.Len() != 4 { t.Fatalf("Unexpected number of headers: %d. Expected 4", h.Len()) } order := []string{ HeaderContentType, HeaderCookie, HeaderHost, HeaderUserAgent, } values := []string{ "aa", "a=b", "example.com", "xxx", } h.VisitAllInOrder(func(key, value []byte) { if len(order) == 0 { t.Fatalf("no more headers expected, got %q", key) } if order[0] != string(key) { t.Fatalf("expected header %q got %q", order[0], key) } if values[0] != string(value) { t.Fatalf("expected header value %q got %q", values[0], value) } order = order[1:] values = values[1:] }) } func TestResponseHeaderAddTrailerError(t *testing.T) { t.Parallel() var h ResponseHeader err := h.AddTrailer("Foo, Content-Length , Bar,Transfer-Encoding,") 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. Expected %q", h.Cookie("foo"), "bar") } if string(h.Cookie("привет")) != "мир" { t.Fatalf("Unexpected cookie value %q. Expected %q", h.Cookie("привет"), "мир") } w := &bytes.Buffer{} bw := bufio.NewWriter(w) if err := h.Write(bw); err != nil { t.Fatalf("Unexpected error: %v", err) } if err := bw.Flush(); err != nil { t.Fatalf("Unexpected error: %v", err) } var h1 RequestHeader br := bufio.NewReader(w) if err := h1.Read(br); err != nil { t.Fatalf("Unexpected error: %v", err) } if !bytes.Equal(h1.Cookie("foo"), h.Cookie("foo")) { t.Fatalf("Unexpected cookie value %q. Expected %q", h1.Cookie("foo"), h.Cookie("foo")) } h1.DelCookie("foo") if len(h1.Cookie("foo")) > 0 { t.Fatalf("Unexpected cookie found: %q", h1.Cookie("foo")) } if !bytes.Equal(h1.Cookie("привет"), h.Cookie("привет")) { t.Fatalf("Unexpected cookie value %q. Expected %q", h1.Cookie("привет"), h.Cookie("привет")) } h1.DelCookie("привет") if len(h1.Cookie("привет")) > 0 { t.Fatalf("Unexpected cookie found: %q", h1.Cookie("привет")) } h.DelAllCookies() if len(h.Cookie("foo")) > 0 { t.Fatalf("Unexpected cookie found: %q", h.Cookie("foo")) } if len(h.Cookie("привет")) > 0 { t.Fatalf("Unexpected cookie found: %q", h.Cookie("привет")) } } func TestResponseHeaderCookieIssue4(t *testing.T) { t.Parallel() var h ResponseHeader c := AcquireCookie() c.SetKey("foo") c.SetValue("bar") h.SetCookie(c) if string(h.Peek(HeaderSetCookie)) != "foo=bar" { t.Fatalf("Unexpected Set-Cookie header %q. Expected %q", h.Peek(HeaderSetCookie), "foo=bar") } cookieSeen := false h.VisitAll(func(key, _ []byte) { if string(key) == 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. Expected %q", c.Value(), "bar") } if string(h.Peek(HeaderSetCookie)) != "foo=bar" { t.Fatalf("Unexpected Set-Cookie header %q. Expected %q", h.Peek(HeaderSetCookie), "foo=bar") } cookieSeen = false h.VisitAll(func(key, _ []byte) { if string(key) == 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, _ []byte) { if string(key) == HeaderCookie { cookieSeen = true } }) if !cookieSeen { t.Fatalf("Cookie not present in VisitAll") } if string(h.Cookie("foo")) != "bar" { t.Fatalf("Unexpected cookie value %q. Expected %q", h.Cookie("foo"), "bar") } if string(h.Peek(HeaderCookie)) != "foo=bar" { t.Fatalf("Unexpected Cookie header %q. Expected %q", h.Peek(HeaderCookie), "foo=bar") } cookieSeen = false h.VisitAll(func(key, _ []byte) { if string(key) == 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 !bytes.Equal(m, m1) { t.Fatalf("unexpected method: %q. Expecting %q", m, m1) } } func TestRequestHeaderSetGet(t *testing.T) { t.Parallel() h := &RequestHeader{} h.SetRequestURI("/aa/bbb") h.SetMethod(MethodPost) h.Set("foo", "bar") h.Set("host", "12345") h.Set("content-type", "aaa/bbb") h.Set("content-length", "1234") h.Set("user-agent", "aaabbb") h.Set("referer", "axcv") h.Set("baz", "xxxxx") h.Set("transfer-encoding", "chunked") h.Set("connection", "close") expectRequestHeaderGet(t, h, "Foo", "bar") expectRequestHeaderGet(t, h, HeaderHost, "12345") expectRequestHeaderGet(t, h, HeaderContentType, "aaa/bbb") expectRequestHeaderGet(t, h, HeaderContentLength, "1234") expectRequestHeaderGet(t, h, "USER-AGent", "aaabbb") expectRequestHeaderGet(t, h, HeaderReferer, "axcv") expectRequestHeaderGet(t, h, "baz", "xxxxx") expectRequestHeaderGet(t, h, HeaderTransferEncoding, "") expectRequestHeaderGet(t, h, "connecTION", "close") if !h.ConnectionClose() { t.Fatalf("unset connection: close") } if h.ContentLength() != 1234 { t.Fatalf("Unexpected content-length %d. Expected %d", h.ContentLength(), 1234) } w := &bytes.Buffer{} bw := bufio.NewWriter(w) err := h.Write(bw) if err != nil { t.Fatalf("Unexpected error when writing request header: %v", err) } if err := bw.Flush(); err != nil { t.Fatalf("Unexpected error when flushing request header: %v", err) } var h1 RequestHeader br := bufio.NewReader(w) if err = h1.Read(br); err != nil { t.Fatalf("Unexpected error when reading request header: %v", err) } if h1.ContentLength() != h.ContentLength() { t.Fatalf("Unexpected Content-Length %d. Expected %d", h1.ContentLength(), h.ContentLength()) } expectRequestHeaderGet(t, &h1, "Foo", "bar") expectRequestHeaderGet(t, &h1, "HOST", "12345") expectRequestHeaderGet(t, &h1, HeaderContentType, "aaa/bbb") expectRequestHeaderGet(t, &h1, HeaderContentLength, "1234") expectRequestHeaderGet(t, &h1, "USER-AGent", "aaabbb") expectRequestHeaderGet(t, &h1, HeaderReferer, "axcv") expectRequestHeaderGet(t, &h1, "baz", "xxxxx") expectRequestHeaderGet(t, &h1, HeaderTransferEncoding, "") expectRequestHeaderGet(t, &h1, HeaderConnection, "close") if !h1.ConnectionClose() { t.Fatalf("unset connection: close") } } func TestResponseHeaderSetGet(t *testing.T) { t.Parallel() h := &ResponseHeader{} h.Set("foo", "bar") h.Set("content-type", "aaa/bbb") h.Set("content-encoding", "gzip") h.Set("connection", "close") h.Set("content-length", "1234") h.Set(HeaderServer, "aaaa") h.Set("baz", "xxxxx") h.Set(HeaderTransferEncoding, "chunked") expectResponseHeaderGet(t, h, "Foo", "bar") expectResponseHeaderGet(t, h, HeaderContentType, "aaa/bbb") expectResponseHeaderGet(t, h, HeaderContentEncoding, "gzip") expectResponseHeaderGet(t, h, HeaderConnection, "close") expectResponseHeaderGet(t, h, HeaderContentLength, "1234") expectResponseHeaderGet(t, h, "seRVer", "aaaa") expectResponseHeaderGet(t, h, "baz", "xxxxx") expectResponseHeaderGet(t, h, HeaderTransferEncoding, "") if h.ContentLength() != 1234 { t.Fatalf("Unexpected content-length %d. Expected %d", h.ContentLength(), 1234) } if !h.ConnectionClose() { t.Fatalf("Unexpected Connection: close value %v. Expected %v", h.ConnectionClose(), true) } w := &bytes.Buffer{} bw := bufio.NewWriter(w) err := h.Write(bw) if err != nil { t.Fatalf("Unexpected error when writing response header: %v", err) } if err := bw.Flush(); err != nil { t.Fatalf("Unexpected error when flushing response header: %v", err) } var h1 ResponseHeader br := bufio.NewReader(w) if err = h1.Read(br); err != nil { t.Fatalf("Unexpected error when reading response header: %v", err) } if h1.ContentLength() != h.ContentLength() { t.Fatalf("Unexpected Content-Length %d. Expected %d", h1.ContentLength(), h.ContentLength()) } if h1.ConnectionClose() != h.ConnectionClose() { t.Fatalf("unexpected connection: close %v. Expected %v", h1.ConnectionClose(), h.ConnectionClose()) } expectResponseHeaderGet(t, &h1, "Foo", "bar") expectResponseHeaderGet(t, &h1, HeaderContentType, "aaa/bbb") expectResponseHeaderGet(t, &h1, HeaderContentEncoding, "gzip") expectResponseHeaderGet(t, &h1, HeaderConnection, "close") expectResponseHeaderGet(t, &h1, "seRVer", "aaaa") expectResponseHeaderGet(t, &h1, "baz", "xxxxx") } func expectRequestHeaderGet(t *testing.T, h *RequestHeader, key, expectedValue string) { if string(h.Peek(key)) != expectedValue { t.Fatalf("Unexpected value for key %q: %q. Expected %q", key, h.Peek(key), expectedValue) } } func expectResponseHeaderGet(t *testing.T, h *ResponseHeader, key, expectedValue string) { if string(h.Peek(key)) != expectedValue { t.Fatalf("Unexpected value for key %q: %q. Expected %q", key, h.Peek(key), expectedValue) } } func TestResponseHeaderConnectionClose(t *testing.T) { t.Parallel() testResponseHeaderConnectionClose(t, true) testResponseHeaderConnectionClose(t, false) } func testResponseHeaderConnectionClose(t *testing.T, connectionClose bool) { h := &ResponseHeader{} if connectionClose { h.SetConnectionClose() } h.SetContentLength(123) w := &bytes.Buffer{} bw := bufio.NewWriter(w) err := h.Write(bw) if err != nil { t.Fatalf("Unexpected error when writing response header: %v", err) } if err := bw.Flush(); err != nil { t.Fatalf("Unexpected error when flushing response header: %v", err) } var h1 ResponseHeader br := bufio.NewReader(w) err = h1.Read(br) if err != nil { t.Fatalf("Unexpected error when reading response header: %v", err) } if h1.ConnectionClose() != h.ConnectionClose() { t.Fatalf("Unexpected value for ConnectionClose: %v. Expected %v", h1.ConnectionClose(), h.ConnectionClose()) } } func TestRequestHeaderTooBig(t *testing.T) { t.Parallel() s := "GET / HTTP/1.1\r\nHost: aaa.com\r\n" + getHeaders(10500) + "\r\n" r := bytes.NewBufferString(s) br := bufio.NewReaderSize(r, 4096) h := &RequestHeader{} err := h.Read(br) if err == nil { t.Fatalf("Expecting error when reading too big header") } } func TestResponseHeaderTooBig(t *testing.T) { t.Parallel() s := "HTTP/1.1 200 OK\r\nContent-Type: sss\r\nContent-Length: 0\r\n" + getHeaders(100500) + "\r\n" r := bytes.NewBufferString(s) br := bufio.NewReaderSize(r, 4096) h := &ResponseHeader{} err := h.Read(br) if err == nil { t.Fatalf("Expecting error when reading too big header") } } type bufioPeekReader struct { s string n int } func (r *bufioPeekReader) Read(b []byte) (int, error) { if r.s == "" { return 0, io.EOF } r.n++ n := r.n if len(r.s) < n { n = len(r.s) } src := []byte(r.s[:n]) r.s = r.s[n:] n = copy(b, src) return n, nil } func TestRequestHeaderBufioPeek(t *testing.T) { t.Parallel() r := &bufioPeekReader{ s: "GET / HTTP/1.1\r\nHost: foobar.com\r\n" + getHeaders(10) + "\r\naaaa", } br := bufio.NewReaderSize(r, 4096) h := &RequestHeader{} if err := h.Read(br); err != nil { t.Fatalf("Unexpected error when reading request: %v", err) } verifyRequestHeader(t, h, -2, "/", "foobar.com", "", "") } func TestResponseHeaderBufioPeek(t *testing.T) { t.Parallel() r := &bufioPeekReader{ s: "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nContent-Type: text/plain\r\nContent-Encoding: gzip\r\n" + getHeaders(10) + "\r\n0123456789", } br := bufio.NewReaderSize(r, 4096) h := &ResponseHeader{} if err := h.Read(br); err != nil { t.Fatalf("Unexpected error when reading response: %v", err) } verifyResponseHeader(t, h, 200, 10, "text/plain", "gzip") } func getHeaders(n int) string { var h []string for i := 0; i < n; i++ { h = append(h, fmt.Sprintf("Header_%d: Value_%d\r\n", i, i)) } return strings.Join(h, "") } func TestResponseHeaderReadSuccess(t *testing.T) { t.Parallel() h := &ResponseHeader{} // straight order of content-length and content-type testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n", 200, 123, "text/html") if h.ConnectionClose() { t.Fatalf("unexpected connection: close") } // reverse order of content-length and content-type testResponseHeaderReadSuccess(t, h, "HTTP/1.1 202 OK\r\nContent-Type: text/plain; encoding=utf-8\r\nContent-Length: 543\r\nConnection: close\r\n\r\n", 202, 543, "text/plain; encoding=utf-8") if !h.ConnectionClose() { t.Fatalf("expecting connection: close") } // transfer-encoding: chunked testResponseHeaderReadSuccess(t, h, "HTTP/1.1 505 Internal error\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n", 505, -1, "text/html") if h.ConnectionClose() { t.Fatalf("unexpected connection: close") } // reverse order of content-type and transfer-encoding testResponseHeaderReadSuccess(t, h, "HTTP/1.1 343 foobar\r\nTransfer-Encoding: chunked\r\nContent-Type: text/json\r\n\r\n", 343, -1, "text/json") // additional headers testResponseHeaderReadSuccess(t, h, "HTTP/1.1 100 Continue\r\nFoobar: baz\r\nContent-Type: aaa/bbb\r\nUser-Agent: x\r\nContent-Length: 123\r\nZZZ: werer\r\n\r\n", 100, 123, "aaa/bbb") // ancient http protocol testResponseHeaderReadSuccess(t, h, "HTTP/0.9 300 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\nqqqq", 300, 123, "text/html") // lf instead of crlf testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\nContent-Length: 123\nContent-Type: text/html\n\n", 200, 123, "text/html") // 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) // 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) // 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") // no protocol in the first line testResponseHeaderReadError(t, h, "GET /foo/bar\r\nHost: google.com\r\n\r\nisdD") } 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") // post with duplicate content-length testRequestHeaderReadError(t, h, "POST /xx HTTP/1.1\r\nHost: aa\r\nContent-Type: s\r\nContent-Length: 13\r\nContent-Length: 1\r\n\r\n") } 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) { t.Helper() r := bytes.NewBufferString(headers) br := bufio.NewReader(r) err := h.Read(br) if err == nil { t.Fatalf("Expecting error when reading request header %q", headers) } // make sure request header works after error testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nHost: aaaa\r\n\r\nxxx", -2, "/foo/bar", "aaaa", "", "", 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, ) { t.Helper() r := bytes.NewBufferString(headers) br := bufio.NewReader(r) err := h.Read(br) if err != nil { t.Fatalf("Unexpected error when parsing response headers: %v. headers=%q", err, headers) } verifyResponseHeader(t, h, expectedStatusCode, expectedContentLength, expectedContentType, "") } func testRequestHeaderReadSuccess(t *testing.T, h *RequestHeader, headers string, expectedContentLength int, expectedRequestURI, expectedHost, expectedReferer, expectedContentType string, expectedTrailer map[string]string, ) { t.Helper() r := bytes.NewBufferString(headers) br := bufio.NewReader(r) err := h.Read(br) if err != nil { t.Fatalf("Unexpected error when parsing request headers: %v. headers=%q", err, headers) } verifyRequestHeader(t, h, expectedContentLength, expectedRequestURI, expectedHost, expectedReferer, expectedContentType) } func verifyResponseHeader(t *testing.T, h *ResponseHeader, expectedStatusCode, expectedContentLength int, expectedContentType, expectedContentEncoding string) { if h.StatusCode() != expectedStatusCode { t.Fatalf("Unexpected status code %d. Expected %d", h.StatusCode(), expectedStatusCode) } if h.ContentLength() != expectedContentLength { t.Fatalf("Unexpected content length %d. Expected %d", h.ContentLength(), expectedContentLength) } if string(h.ContentType()) != expectedContentType { t.Fatalf("Unexpected content type %q. Expected %q", h.ContentType(), expectedContentType) } if string(h.ContentEncoding()) != expectedContentEncoding { t.Fatalf("Unexpected content encoding %q. Expected %q", h.ContentEncoding(), expectedContentEncoding) } } func verifyResponseHeaderConnection(t *testing.T, h *ResponseHeader, expectConnection string) { if string(h.Peek(HeaderConnection)) != expectConnection { t.Fatalf("Unexpected Connection %q. Expected %q", h.Peek(HeaderConnection), expectConnection) } } func verifyRequestHeader(t *testing.T, h *RequestHeader, expectedContentLength int, expectedRequestURI, expectedHost, expectedReferer, expectedContentType string, ) { if h.ContentLength() != expectedContentLength { t.Fatalf("Unexpected Content-Length %d. Expected %d", h.ContentLength(), expectedContentLength) } if string(h.RequestURI()) != expectedRequestURI { t.Fatalf("Unexpected RequestURI %q. Expected %q", h.RequestURI(), expectedRequestURI) } if string(h.Peek(HeaderHost)) != expectedHost { t.Fatalf("Unexpected host %q. Expected %q", h.Peek(HeaderHost), expectedHost) } if string(h.Peek(HeaderReferer)) != expectedReferer { t.Fatalf("Unexpected referer %q. Expected %q", h.Peek(HeaderReferer), expectedReferer) } if string(h.Peek(HeaderContentType)) != expectedContentType { t.Fatalf("Unexpected content-type %q. Expected %q", h.Peek(HeaderContentType), expectedContentType) } } func verifyResponseTrailer(t *testing.T, h *ResponseHeader, expectedTrailers map[string]string) { for k, v := range expectedTrailers { got := h.Peek(k) if !bytes.Equal(got, []byte(v)) { t.Fatalf("Unexpected trailer %q. Expected %q. Got %q", k, v, got) } } } func verifyRequestTrailer(t *testing.T, h *RequestHeader, expectedTrailers map[string]string) { for k, v := range expectedTrailers { got := h.Peek(k) if !bytes.Equal(got, []byte(v)) { t.Fatalf("Unexpected trailer %q. Expected %q. Got %q", k, v, got) } } } func verifyTrailer(t *testing.T, r *bufio.Reader, expectedTrailers map[string]string, isReq bool) { if isReq { req := Request{} err := req.Header.ReadTrailer(r) if err == io.EOF && expectedTrailers == nil { return } if err != nil { t.Fatalf("Cannot read trailer: %v", err) } verifyRequestTrailer(t, &req.Header, expectedTrailers) return } resp := Response{} err := resp.Header.ReadTrailer(r) if err == io.EOF && expectedTrailers == nil { return } if err != nil { t.Fatalf("Cannot read trailer: %v", err) } verifyResponseTrailer(t, &resp.Header, expectedTrailers) } func TestRequestHeader_PeekAll(t *testing.T) { t.Parallel() h := &RequestHeader{} h.Add(HeaderConnection, "keep-alive") h.Add("Content-Type", "aaa") h.Add(HeaderHost, "aaabbb") h.Add("User-Agent", "asdfas") h.Add("Content-Length", "1123") h.Add("Cookie", "foobar=baz") h.Add(HeaderTrailer, "foo, bar") h.Add("aaa", "aaa") h.Add("aaa", "bbb") expectRequestHeaderAll(t, h, HeaderConnection, [][]byte{s2b("keep-alive")}) expectRequestHeaderAll(t, h, "Content-Type", [][]byte{s2b("aaa")}) expectRequestHeaderAll(t, h, HeaderHost, [][]byte{s2b("aaabbb")}) expectRequestHeaderAll(t, h, "User-Agent", [][]byte{s2b("asdfas")}) expectRequestHeaderAll(t, h, "Content-Length", [][]byte{s2b("1123")}) expectRequestHeaderAll(t, h, "Cookie", [][]byte{s2b("foobar=baz")}) expectRequestHeaderAll(t, h, HeaderTrailer, [][]byte{s2b("Foo, Bar")}) expectRequestHeaderAll(t, h, "aaa", [][]byte{s2b("aaa"), s2b("bbb")}) h.Del("Content-Type") h.Del(HeaderHost) h.Del("aaa") expectRequestHeaderAll(t, h, "Content-Type", [][]byte{}) expectRequestHeaderAll(t, h, HeaderHost, [][]byte{}) expectRequestHeaderAll(t, h, "aaa", [][]byte{}) } func expectRequestHeaderAll(t *testing.T, h *RequestHeader, key string, expectedValue [][]byte) { if len(h.PeekAll(key)) != len(expectedValue) { t.Fatalf("Unexpected size for key %q: %d. Expected %d", key, len(h.PeekAll(key)), len(expectedValue)) } if !reflect.DeepEqual(h.PeekAll(key), expectedValue) { t.Fatalf("Unexpected value for key %q: %q. Expected %q", key, h.PeekAll(key), expectedValue) } } func TestResponseHeader_PeekAll(t *testing.T) { t.Parallel() h := &ResponseHeader{} h.Add(HeaderContentType, "aaa/bbb") h.Add(HeaderContentEncoding, "gzip") h.Add(HeaderConnection, "close") h.Add(HeaderContentLength, "1234") h.Add(HeaderServer, "aaaa") h.Add(HeaderSetCookie, "cccc") h.Add("aaa", "aaa") h.Add("aaa", "bbb") expectResponseHeaderAll(t, h, HeaderContentType, [][]byte{s2b("aaa/bbb")}) expectResponseHeaderAll(t, h, HeaderContentEncoding, [][]byte{s2b("gzip")}) expectResponseHeaderAll(t, h, HeaderConnection, [][]byte{s2b("close")}) expectResponseHeaderAll(t, h, HeaderContentLength, [][]byte{s2b("1234")}) expectResponseHeaderAll(t, h, HeaderServer, [][]byte{s2b("aaaa")}) expectResponseHeaderAll(t, h, HeaderSetCookie, [][]byte{s2b("cccc")}) expectResponseHeaderAll(t, h, "aaa", [][]byte{s2b("aaa"), s2b("bbb")}) h.Del(HeaderContentType) h.Del(HeaderContentEncoding) expectResponseHeaderAll(t, h, HeaderContentType, [][]byte{defaultContentType}) expectResponseHeaderAll(t, h, HeaderContentEncoding, [][]byte{}) } func expectResponseHeaderAll(t *testing.T, h *ResponseHeader, key string, expectedValue [][]byte) { if len(h.PeekAll(key)) != len(expectedValue) { t.Fatalf("Unexpected size for key %q: %d. Expected %d", key, len(h.PeekAll(key)), len(expectedValue)) } if !reflect.DeepEqual(h.PeekAll(key), expectedValue) { t.Fatalf("Unexpected value for key %q: %q. Expected %q", key, h.PeekAll(key), expectedValue) } } func TestRequestHeader_Keys(t *testing.T) { h := &RequestHeader{} h.Add(HeaderConnection, "keep-alive") h.Add("Content-Type", "aaa") err := h.SetTrailer("aaa,bbb,ccc") if err != nil { t.Fatal(err) } actualKeys := h.PeekKeys() expectedKeys := [][]byte{s2b("keep-alive"), s2b("aaa")} if reflect.DeepEqual(actualKeys, expectedKeys) { t.Fatalf("Unexpected value %q. Expected %q", actualKeys, expectedKeys) } actualTrailerKeys := h.PeekTrailerKeys() expectedTrailerKeys := [][]byte{s2b("aaa"), s2b("bbb"), s2b("ccc")} if reflect.DeepEqual(actualTrailerKeys, expectedTrailerKeys) { t.Fatalf("Unexpected value %q. Expected %q", actualTrailerKeys, expectedTrailerKeys) } } func TestResponseHeader_Keys(t *testing.T) { h := &ResponseHeader{} h.Add(HeaderConnection, "keep-alive") h.Add("Content-Type", "aaa") err := h.SetTrailer("aaa,bbb,ccc") if err != nil { t.Fatal(err) } actualKeys := h.PeekKeys() expectedKeys := [][]byte{s2b("keep-alive"), s2b("aaa")} if reflect.DeepEqual(actualKeys, expectedKeys) { t.Fatalf("Unexpected value %q. Expected %q", actualKeys, expectedKeys) } actualTrailerKeys := h.PeekTrailerKeys() expectedTrailerKeys := [][]byte{s2b("aaa"), s2b("bbb"), s2b("ccc")} if reflect.DeepEqual(actualTrailerKeys, expectedTrailerKeys) { t.Fatalf("Unexpected value %q. Expected %q", actualTrailerKeys, expectedTrailerKeys) } } func TestAddVaryHeader(t *testing.T) { t.Parallel() var h ResponseHeader h.addVaryBytes([]byte("Accept-Encoding")) got := string(h.Peek("Vary")) expected := "Accept-Encoding" if got != expected { t.Errorf("expected %q got %q", expected, got) } var buf bytes.Buffer if _, err := h.WriteTo(&buf); err != nil { t.Fatalf("unexpected error when writing header: %v", err) } if n := strings.Count(buf.String(), "Vary: "); n != 1 { t.Errorf("Vary occurred %d times", n) } } func TestAddVaryHeaderExisting(t *testing.T) { t.Parallel() var h ResponseHeader h.Set("Vary", "Accept") h.addVaryBytes([]byte("Accept-Encoding")) got := string(h.Peek("Vary")) expected := "Accept,Accept-Encoding" if got != expected { t.Errorf("expected %q got %q", expected, got) } var buf bytes.Buffer if _, err := h.WriteTo(&buf); err != nil { t.Fatalf("unexpected error when writing header: %v", err) } if n := strings.Count(buf.String(), "Vary: "); n != 1 { t.Errorf("Vary occurred %d times", n) } } func TestAddVaryHeaderExistingAcceptEncoding(t *testing.T) { t.Parallel() var h ResponseHeader h.Set("Vary", "Accept-Encoding") h.addVaryBytes([]byte("Accept-Encoding")) got := string(h.Peek("Vary")) expected := "Accept-Encoding" if got != expected { t.Errorf("expected %q got %q", expected, got) } var buf bytes.Buffer if _, err := h.WriteTo(&buf); err != nil { t.Fatalf("unexpected error when writing header: %v", err) } if n := strings.Count(buf.String(), "Vary: "); n != 1 { t.Errorf("Vary occurred %d times", n) } }