mirror of
https://github.com/golang/go.git
synced 2025-10-19 11:03:18 +00:00
[release-branch.go1.25] net/http: add httpcookiemaxnum GODEBUG option to limit number of cookies parsed
When handling HTTP headers, net/http does not currently limit the number of cookies that can be parsed. The only limitation that exists is for the size of the entire HTTP header, which is controlled by MaxHeaderBytes (defaults to 1 MB). Unfortunately, this allows a malicious actor to send HTTP headers which contain a massive amount of small cookies, such that as much cookies as possible can be fitted within the MaxHeaderBytes limitation. Internally, this causes us to allocate a massive number of Cookie struct. For example, a 1 MB HTTP header with cookies that repeats "a=;" will cause an allocation of ~66 MB in the heap. This can serve as a way for malicious actors to induce memory exhaustion. To fix this, we will now limit the number of cookies we are willing to parse to 3000 by default. This behavior can be changed by setting a new GODEBUG option: GODEBUG=httpcookiemaxnum. httpcookiemaxnum can be set to allow a higher or lower cookie limit. Setting it to 0 will also allow an infinite number of cookies to be parsed. Thanks to jub0bs for reporting this issue. For #75672 Fixes #75707 Fixes CVE-2025-58186 Change-Id: Ied58b3bc8acf5d11c880f881f36ecbf1d5d52622 Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2720 Reviewed-by: Roland Shoemaker <bracewell@google.com> Reviewed-by: Damien Neil <dneil@google.com> Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2965 Reviewed-by: Nicholas Husin <husin@google.com> Commit-Queue: Roland Shoemaker <bracewell@google.com> Reviewed-on: https://go-review.googlesource.com/c/go/+/709849 TryBot-Bypass: Michael Pratt <mpratt@google.com> Reviewed-by: Carlos Amedee <carlos@golang.org> Auto-Submit: Michael Pratt <mpratt@google.com>
This commit is contained in:
parent
f0c69db15a
commit
100c5a6680
5 changed files with 206 additions and 75 deletions
|
@ -153,6 +153,16 @@ for example,
|
|||
see the [runtime documentation](/pkg/runtime#hdr-Environment_Variables)
|
||||
and the [go command documentation](/cmd/go#hdr-Build_and_test_caching).
|
||||
|
||||
### Go 1.26
|
||||
|
||||
Go 1.26 added a new `httpcookiemaxnum` setting that controls the maximum number
|
||||
of cookies that net/http will accept when parsing HTTP headers. If the number of
|
||||
cookie in a header exceeds the number set in `httpcookiemaxnum`, cookie parsing
|
||||
will fail early. The default value is `httpcookiemaxnum=3000`. Setting
|
||||
`httpcookiemaxnum=0` will allow the cookie parsing to accept an indefinite
|
||||
number of cookies. To avoid denial of service attacks, this setting and default
|
||||
was backported to Go 1.25.2 and Go 1.24.8.
|
||||
|
||||
### Go 1.25
|
||||
|
||||
Go 1.25 added a new `decoratemappings` setting that controls whether the Go
|
||||
|
|
|
@ -42,6 +42,7 @@ var All = []Info{
|
|||
{Name: "http2client", Package: "net/http"},
|
||||
{Name: "http2debug", Package: "net/http", Opaque: true},
|
||||
{Name: "http2server", Package: "net/http"},
|
||||
{Name: "httpcookiemaxnum", Package: "net/http", Changed: 24, Old: "0"},
|
||||
{Name: "httplaxcontentlength", Package: "net/http", Changed: 22, Old: "1"},
|
||||
{Name: "httpmuxgo121", Package: "net/http", Changed: 22, Old: "1"},
|
||||
{Name: "httpservecontentkeepheaders", Package: "net/http", Changed: 23, Old: "1"},
|
||||
|
|
|
@ -7,6 +7,7 @@ package http
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"internal/godebug"
|
||||
"log"
|
||||
"net"
|
||||
"net/http/internal/ascii"
|
||||
|
@ -16,6 +17,8 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
var httpcookiemaxnum = godebug.New("httpcookiemaxnum")
|
||||
|
||||
// A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
|
||||
// HTTP response or the Cookie header of an HTTP request.
|
||||
//
|
||||
|
@ -58,16 +61,37 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
errBlankCookie = errors.New("http: blank cookie")
|
||||
errEqualNotFoundInCookie = errors.New("http: '=' not found in cookie")
|
||||
errInvalidCookieName = errors.New("http: invalid cookie name")
|
||||
errInvalidCookieValue = errors.New("http: invalid cookie value")
|
||||
errBlankCookie = errors.New("http: blank cookie")
|
||||
errEqualNotFoundInCookie = errors.New("http: '=' not found in cookie")
|
||||
errInvalidCookieName = errors.New("http: invalid cookie name")
|
||||
errInvalidCookieValue = errors.New("http: invalid cookie value")
|
||||
errCookieNumLimitExceeded = errors.New("http: number of cookies exceeded limit")
|
||||
)
|
||||
|
||||
const defaultCookieMaxNum = 3000
|
||||
|
||||
func cookieNumWithinMax(cookieNum int) bool {
|
||||
withinDefaultMax := cookieNum <= defaultCookieMaxNum
|
||||
if httpcookiemaxnum.Value() == "" {
|
||||
return withinDefaultMax
|
||||
}
|
||||
if customMax, err := strconv.Atoi(httpcookiemaxnum.Value()); err == nil {
|
||||
withinCustomMax := customMax == 0 || cookieNum <= customMax
|
||||
if withinDefaultMax != withinCustomMax {
|
||||
httpcookiemaxnum.IncNonDefault()
|
||||
}
|
||||
return withinCustomMax
|
||||
}
|
||||
return withinDefaultMax
|
||||
}
|
||||
|
||||
// ParseCookie parses a Cookie header value and returns all the cookies
|
||||
// which were set in it. Since the same cookie name can appear multiple times
|
||||
// the returned Values can contain more than one value for a given key.
|
||||
func ParseCookie(line string) ([]*Cookie, error) {
|
||||
if !cookieNumWithinMax(strings.Count(line, ";") + 1) {
|
||||
return nil, errCookieNumLimitExceeded
|
||||
}
|
||||
parts := strings.Split(textproto.TrimString(line), ";")
|
||||
if len(parts) == 1 && parts[0] == "" {
|
||||
return nil, errBlankCookie
|
||||
|
@ -197,11 +221,21 @@ func ParseSetCookie(line string) (*Cookie, error) {
|
|||
|
||||
// readSetCookies parses all "Set-Cookie" values from
|
||||
// the header h and returns the successfully parsed Cookies.
|
||||
//
|
||||
// If the amount of cookies exceeds CookieNumLimit, and httpcookielimitnum
|
||||
// GODEBUG option is not explicitly turned off, this function will silently
|
||||
// fail and return an empty slice.
|
||||
func readSetCookies(h Header) []*Cookie {
|
||||
cookieCount := len(h["Set-Cookie"])
|
||||
if cookieCount == 0 {
|
||||
return []*Cookie{}
|
||||
}
|
||||
// Cookie limit was unfortunately introduced at a later point in time.
|
||||
// As such, we can only fail by returning an empty slice rather than
|
||||
// explicit error.
|
||||
if !cookieNumWithinMax(cookieCount) {
|
||||
return []*Cookie{}
|
||||
}
|
||||
cookies := make([]*Cookie, 0, cookieCount)
|
||||
for _, line := range h["Set-Cookie"] {
|
||||
if cookie, err := ParseSetCookie(line); err == nil {
|
||||
|
@ -329,13 +363,28 @@ func (c *Cookie) Valid() error {
|
|||
// readCookies parses all "Cookie" values from the header h and
|
||||
// returns the successfully parsed Cookies.
|
||||
//
|
||||
// if filter isn't empty, only cookies of that name are returned.
|
||||
// If filter isn't empty, only cookies of that name are returned.
|
||||
//
|
||||
// If the amount of cookies exceeds CookieNumLimit, and httpcookielimitnum
|
||||
// GODEBUG option is not explicitly turned off, this function will silently
|
||||
// fail and return an empty slice.
|
||||
func readCookies(h Header, filter string) []*Cookie {
|
||||
lines := h["Cookie"]
|
||||
if len(lines) == 0 {
|
||||
return []*Cookie{}
|
||||
}
|
||||
|
||||
// Cookie limit was unfortunately introduced at a later point in time.
|
||||
// As such, we can only fail by returning an empty slice rather than
|
||||
// explicit error.
|
||||
cookieCount := 0
|
||||
for _, line := range lines {
|
||||
cookieCount += strings.Count(line, ";") + 1
|
||||
}
|
||||
if !cookieNumWithinMax(cookieCount) {
|
||||
return []*Cookie{}
|
||||
}
|
||||
|
||||
cookies := make([]*Cookie, 0, len(lines)+strings.Count(lines[0], ";"))
|
||||
for _, line := range lines {
|
||||
line = textproto.TrimString(line)
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -255,16 +256,17 @@ func TestAddCookie(t *testing.T) {
|
|||
}
|
||||
|
||||
var readSetCookiesTests = []struct {
|
||||
Header Header
|
||||
Cookies []*Cookie
|
||||
header Header
|
||||
cookies []*Cookie
|
||||
godebug string
|
||||
}{
|
||||
{
|
||||
Header{"Set-Cookie": {"Cookie-1=v$1"}},
|
||||
[]*Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}},
|
||||
header: Header{"Set-Cookie": {"Cookie-1=v$1"}},
|
||||
cookies: []*Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}},
|
||||
},
|
||||
{
|
||||
Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}},
|
||||
[]*Cookie{{
|
||||
header: Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}},
|
||||
cookies: []*Cookie{{
|
||||
Name: "NID",
|
||||
Value: "99=YsDT5i3E-CXax-",
|
||||
Path: "/",
|
||||
|
@ -276,8 +278,8 @@ var readSetCookiesTests = []struct {
|
|||
}},
|
||||
},
|
||||
{
|
||||
Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}},
|
||||
[]*Cookie{{
|
||||
header: Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}},
|
||||
cookies: []*Cookie{{
|
||||
Name: ".ASPXAUTH",
|
||||
Value: "7E3AA",
|
||||
Path: "/",
|
||||
|
@ -288,8 +290,8 @@ var readSetCookiesTests = []struct {
|
|||
}},
|
||||
},
|
||||
{
|
||||
Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}},
|
||||
[]*Cookie{{
|
||||
header: Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}},
|
||||
cookies: []*Cookie{{
|
||||
Name: "ASP.NET_SessionId",
|
||||
Value: "foo",
|
||||
Path: "/",
|
||||
|
@ -298,8 +300,8 @@ var readSetCookiesTests = []struct {
|
|||
}},
|
||||
},
|
||||
{
|
||||
Header{"Set-Cookie": {"samesitedefault=foo; SameSite"}},
|
||||
[]*Cookie{{
|
||||
header: Header{"Set-Cookie": {"samesitedefault=foo; SameSite"}},
|
||||
cookies: []*Cookie{{
|
||||
Name: "samesitedefault",
|
||||
Value: "foo",
|
||||
SameSite: SameSiteDefaultMode,
|
||||
|
@ -307,8 +309,8 @@ var readSetCookiesTests = []struct {
|
|||
}},
|
||||
},
|
||||
{
|
||||
Header{"Set-Cookie": {"samesiteinvalidisdefault=foo; SameSite=invalid"}},
|
||||
[]*Cookie{{
|
||||
header: Header{"Set-Cookie": {"samesiteinvalidisdefault=foo; SameSite=invalid"}},
|
||||
cookies: []*Cookie{{
|
||||
Name: "samesiteinvalidisdefault",
|
||||
Value: "foo",
|
||||
SameSite: SameSiteDefaultMode,
|
||||
|
@ -316,8 +318,8 @@ var readSetCookiesTests = []struct {
|
|||
}},
|
||||
},
|
||||
{
|
||||
Header{"Set-Cookie": {"samesitelax=foo; SameSite=Lax"}},
|
||||
[]*Cookie{{
|
||||
header: Header{"Set-Cookie": {"samesitelax=foo; SameSite=Lax"}},
|
||||
cookies: []*Cookie{{
|
||||
Name: "samesitelax",
|
||||
Value: "foo",
|
||||
SameSite: SameSiteLaxMode,
|
||||
|
@ -325,8 +327,8 @@ var readSetCookiesTests = []struct {
|
|||
}},
|
||||
},
|
||||
{
|
||||
Header{"Set-Cookie": {"samesitestrict=foo; SameSite=Strict"}},
|
||||
[]*Cookie{{
|
||||
header: Header{"Set-Cookie": {"samesitestrict=foo; SameSite=Strict"}},
|
||||
cookies: []*Cookie{{
|
||||
Name: "samesitestrict",
|
||||
Value: "foo",
|
||||
SameSite: SameSiteStrictMode,
|
||||
|
@ -334,8 +336,8 @@ var readSetCookiesTests = []struct {
|
|||
}},
|
||||
},
|
||||
{
|
||||
Header{"Set-Cookie": {"samesitenone=foo; SameSite=None"}},
|
||||
[]*Cookie{{
|
||||
header: Header{"Set-Cookie": {"samesitenone=foo; SameSite=None"}},
|
||||
cookies: []*Cookie{{
|
||||
Name: "samesitenone",
|
||||
Value: "foo",
|
||||
SameSite: SameSiteNoneMode,
|
||||
|
@ -345,47 +347,66 @@ var readSetCookiesTests = []struct {
|
|||
// Make sure we can properly read back the Set-Cookie headers we create
|
||||
// for values containing spaces or commas:
|
||||
{
|
||||
Header{"Set-Cookie": {`special-1=a z`}},
|
||||
[]*Cookie{{Name: "special-1", Value: "a z", Raw: `special-1=a z`}},
|
||||
header: Header{"Set-Cookie": {`special-1=a z`}},
|
||||
cookies: []*Cookie{{Name: "special-1", Value: "a z", Raw: `special-1=a z`}},
|
||||
},
|
||||
{
|
||||
Header{"Set-Cookie": {`special-2=" z"`}},
|
||||
[]*Cookie{{Name: "special-2", Value: " z", Quoted: true, Raw: `special-2=" z"`}},
|
||||
header: Header{"Set-Cookie": {`special-2=" z"`}},
|
||||
cookies: []*Cookie{{Name: "special-2", Value: " z", Quoted: true, Raw: `special-2=" z"`}},
|
||||
},
|
||||
{
|
||||
Header{"Set-Cookie": {`special-3="a "`}},
|
||||
[]*Cookie{{Name: "special-3", Value: "a ", Quoted: true, Raw: `special-3="a "`}},
|
||||
header: Header{"Set-Cookie": {`special-3="a "`}},
|
||||
cookies: []*Cookie{{Name: "special-3", Value: "a ", Quoted: true, Raw: `special-3="a "`}},
|
||||
},
|
||||
{
|
||||
Header{"Set-Cookie": {`special-4=" "`}},
|
||||
[]*Cookie{{Name: "special-4", Value: " ", Quoted: true, Raw: `special-4=" "`}},
|
||||
header: Header{"Set-Cookie": {`special-4=" "`}},
|
||||
cookies: []*Cookie{{Name: "special-4", Value: " ", Quoted: true, Raw: `special-4=" "`}},
|
||||
},
|
||||
{
|
||||
Header{"Set-Cookie": {`special-5=a,z`}},
|
||||
[]*Cookie{{Name: "special-5", Value: "a,z", Raw: `special-5=a,z`}},
|
||||
header: Header{"Set-Cookie": {`special-5=a,z`}},
|
||||
cookies: []*Cookie{{Name: "special-5", Value: "a,z", Raw: `special-5=a,z`}},
|
||||
},
|
||||
{
|
||||
Header{"Set-Cookie": {`special-6=",z"`}},
|
||||
[]*Cookie{{Name: "special-6", Value: ",z", Quoted: true, Raw: `special-6=",z"`}},
|
||||
header: Header{"Set-Cookie": {`special-6=",z"`}},
|
||||
cookies: []*Cookie{{Name: "special-6", Value: ",z", Quoted: true, Raw: `special-6=",z"`}},
|
||||
},
|
||||
{
|
||||
Header{"Set-Cookie": {`special-7=a,`}},
|
||||
[]*Cookie{{Name: "special-7", Value: "a,", Raw: `special-7=a,`}},
|
||||
header: Header{"Set-Cookie": {`special-7=a,`}},
|
||||
cookies: []*Cookie{{Name: "special-7", Value: "a,", Raw: `special-7=a,`}},
|
||||
},
|
||||
{
|
||||
Header{"Set-Cookie": {`special-8=","`}},
|
||||
[]*Cookie{{Name: "special-8", Value: ",", Quoted: true, Raw: `special-8=","`}},
|
||||
header: Header{"Set-Cookie": {`special-8=","`}},
|
||||
cookies: []*Cookie{{Name: "special-8", Value: ",", Quoted: true, Raw: `special-8=","`}},
|
||||
},
|
||||
// Make sure we can properly read back the Set-Cookie headers
|
||||
// for names containing spaces:
|
||||
{
|
||||
Header{"Set-Cookie": {`special-9 =","`}},
|
||||
[]*Cookie{{Name: "special-9", Value: ",", Quoted: true, Raw: `special-9 =","`}},
|
||||
header: Header{"Set-Cookie": {`special-9 =","`}},
|
||||
cookies: []*Cookie{{Name: "special-9", Value: ",", Quoted: true, Raw: `special-9 =","`}},
|
||||
},
|
||||
// Quoted values (issue #46443)
|
||||
{
|
||||
Header{"Set-Cookie": {`cookie="quoted"`}},
|
||||
[]*Cookie{{Name: "cookie", Value: "quoted", Quoted: true, Raw: `cookie="quoted"`}},
|
||||
header: Header{"Set-Cookie": {`cookie="quoted"`}},
|
||||
cookies: []*Cookie{{Name: "cookie", Value: "quoted", Quoted: true, Raw: `cookie="quoted"`}},
|
||||
},
|
||||
{
|
||||
header: Header{"Set-Cookie": slices.Repeat([]string{"a="}, defaultCookieMaxNum+1)},
|
||||
cookies: []*Cookie{},
|
||||
},
|
||||
{
|
||||
header: Header{"Set-Cookie": slices.Repeat([]string{"a="}, 10)},
|
||||
cookies: []*Cookie{},
|
||||
godebug: "httpcookiemaxnum=5",
|
||||
},
|
||||
{
|
||||
header: Header{"Set-Cookie": strings.Split(strings.Repeat(";a=", defaultCookieMaxNum+1)[1:], ";")},
|
||||
cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false, Raw: "a="}}, defaultCookieMaxNum+1),
|
||||
godebug: "httpcookiemaxnum=0",
|
||||
},
|
||||
{
|
||||
header: Header{"Set-Cookie": strings.Split(strings.Repeat(";a=", defaultCookieMaxNum+1)[1:], ";")},
|
||||
cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false, Raw: "a="}}, defaultCookieMaxNum+1),
|
||||
godebug: fmt.Sprintf("httpcookiemaxnum=%v", defaultCookieMaxNum+1),
|
||||
},
|
||||
|
||||
// TODO(bradfitz): users have reported seeing this in the
|
||||
|
@ -405,79 +426,103 @@ func toJSON(v any) string {
|
|||
|
||||
func TestReadSetCookies(t *testing.T) {
|
||||
for i, tt := range readSetCookiesTests {
|
||||
t.Setenv("GODEBUG", tt.godebug)
|
||||
for n := 0; n < 2; n++ { // to verify readSetCookies doesn't mutate its input
|
||||
c := readSetCookies(tt.Header)
|
||||
if !reflect.DeepEqual(c, tt.Cookies) {
|
||||
t.Errorf("#%d readSetCookies: have\n%s\nwant\n%s\n", i, toJSON(c), toJSON(tt.Cookies))
|
||||
c := readSetCookies(tt.header)
|
||||
if !reflect.DeepEqual(c, tt.cookies) {
|
||||
t.Errorf("#%d readSetCookies: have\n%s\nwant\n%s\n", i, toJSON(c), toJSON(tt.cookies))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var readCookiesTests = []struct {
|
||||
Header Header
|
||||
Filter string
|
||||
Cookies []*Cookie
|
||||
header Header
|
||||
filter string
|
||||
cookies []*Cookie
|
||||
godebug string
|
||||
}{
|
||||
{
|
||||
Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
|
||||
"",
|
||||
[]*Cookie{
|
||||
header: Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
|
||||
filter: "",
|
||||
cookies: []*Cookie{
|
||||
{Name: "Cookie-1", Value: "v$1"},
|
||||
{Name: "c2", Value: "v2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
|
||||
"c2",
|
||||
[]*Cookie{
|
||||
header: Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
|
||||
filter: "c2",
|
||||
cookies: []*Cookie{
|
||||
{Name: "c2", Value: "v2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
|
||||
"",
|
||||
[]*Cookie{
|
||||
header: Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
|
||||
filter: "",
|
||||
cookies: []*Cookie{
|
||||
{Name: "Cookie-1", Value: "v$1"},
|
||||
{Name: "c2", Value: "v2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
|
||||
"c2",
|
||||
[]*Cookie{
|
||||
header: Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
|
||||
filter: "c2",
|
||||
cookies: []*Cookie{
|
||||
{Name: "c2", Value: "v2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Header{"Cookie": {`Cookie-1="v$1"; c2="v2"`}},
|
||||
"",
|
||||
[]*Cookie{
|
||||
header: Header{"Cookie": {`Cookie-1="v$1"; c2="v2"`}},
|
||||
filter: "",
|
||||
cookies: []*Cookie{
|
||||
{Name: "Cookie-1", Value: "v$1", Quoted: true},
|
||||
{Name: "c2", Value: "v2", Quoted: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
Header{"Cookie": {`Cookie-1="v$1"; c2=v2;`}},
|
||||
"",
|
||||
[]*Cookie{
|
||||
header: Header{"Cookie": {`Cookie-1="v$1"; c2=v2;`}},
|
||||
filter: "",
|
||||
cookies: []*Cookie{
|
||||
{Name: "Cookie-1", Value: "v$1", Quoted: true},
|
||||
{Name: "c2", Value: "v2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Header{"Cookie": {``}},
|
||||
"",
|
||||
[]*Cookie{},
|
||||
header: Header{"Cookie": {``}},
|
||||
filter: "",
|
||||
cookies: []*Cookie{},
|
||||
},
|
||||
// GODEBUG=httpcookiemaxnum should work regardless if all cookies are sent
|
||||
// via one "Cookie" field, or multiple fields.
|
||||
{
|
||||
header: Header{"Cookie": {strings.Repeat(";a=", defaultCookieMaxNum+1)[1:]}},
|
||||
cookies: []*Cookie{},
|
||||
},
|
||||
{
|
||||
header: Header{"Cookie": slices.Repeat([]string{"a="}, 10)},
|
||||
cookies: []*Cookie{},
|
||||
godebug: "httpcookiemaxnum=5",
|
||||
},
|
||||
{
|
||||
header: Header{"Cookie": {strings.Repeat(";a=", defaultCookieMaxNum+1)[1:]}},
|
||||
cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
|
||||
godebug: "httpcookiemaxnum=0",
|
||||
},
|
||||
{
|
||||
header: Header{"Cookie": slices.Repeat([]string{"a="}, defaultCookieMaxNum+1)},
|
||||
cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
|
||||
godebug: fmt.Sprintf("httpcookiemaxnum=%v", defaultCookieMaxNum+1),
|
||||
},
|
||||
}
|
||||
|
||||
func TestReadCookies(t *testing.T) {
|
||||
for i, tt := range readCookiesTests {
|
||||
t.Setenv("GODEBUG", tt.godebug)
|
||||
for n := 0; n < 2; n++ { // to verify readCookies doesn't mutate its input
|
||||
c := readCookies(tt.Header, tt.Filter)
|
||||
if !reflect.DeepEqual(c, tt.Cookies) {
|
||||
t.Errorf("#%d readCookies:\nhave: %s\nwant: %s\n", i, toJSON(c), toJSON(tt.Cookies))
|
||||
c := readCookies(tt.header, tt.filter)
|
||||
if !reflect.DeepEqual(c, tt.cookies) {
|
||||
t.Errorf("#%d readCookies:\nhave: %s\nwant: %s\n", i, toJSON(c), toJSON(tt.cookies))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -689,6 +734,7 @@ func TestParseCookie(t *testing.T) {
|
|||
line string
|
||||
cookies []*Cookie
|
||||
err error
|
||||
godebug string
|
||||
}{
|
||||
{
|
||||
line: "Cookie-1=v$1",
|
||||
|
@ -722,8 +768,28 @@ func TestParseCookie(t *testing.T) {
|
|||
line: "k1=\\",
|
||||
err: errInvalidCookieValue,
|
||||
},
|
||||
{
|
||||
line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:],
|
||||
err: errCookieNumLimitExceeded,
|
||||
},
|
||||
{
|
||||
line: strings.Repeat(";a=", 10)[1:],
|
||||
err: errCookieNumLimitExceeded,
|
||||
godebug: "httpcookiemaxnum=5",
|
||||
},
|
||||
{
|
||||
line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:],
|
||||
cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
|
||||
godebug: "httpcookiemaxnum=0",
|
||||
},
|
||||
{
|
||||
line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:],
|
||||
cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
|
||||
godebug: fmt.Sprintf("httpcookiemaxnum=%v", defaultCookieMaxNum+1),
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
t.Setenv("GODEBUG", tt.godebug)
|
||||
gotCookies, gotErr := ParseCookie(tt.line)
|
||||
if !errors.Is(gotErr, tt.err) {
|
||||
t.Errorf("#%d ParseCookie got error %v, want error %v", i, gotErr, tt.err)
|
||||
|
|
|
@ -282,6 +282,11 @@ Below is the full list of supported metrics, ordered lexicographically.
|
|||
The number of non-default behaviors executed by the net/http
|
||||
package due to a non-default GODEBUG=http2server=... setting.
|
||||
|
||||
/godebug/non-default-behavior/httpcookiemaxnum:events
|
||||
The number of non-default behaviors executed by the net/http
|
||||
package due to a non-default GODEBUG=httpcookiemaxnum=...
|
||||
setting.
|
||||
|
||||
/godebug/non-default-behavior/httplaxcontentlength:events
|
||||
The number of non-default behaviors executed by the net/http
|
||||
package due to a non-default GODEBUG=httplaxcontentlength=...
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue