Commit 3d5703ba authored by Stephan Renatus's avatar Stephan Renatus Committed by Brad Fitzpatrick

net/http: add support for SameSite option in http.Cookie

The same-site cookie attribute prevents a cookie from being sent along with
cross-site requests. The main goal is mitigate the risk of cross-origin
information leakage and provides some protection against cross-site request
forgery attacks.

This change adds the option to http.Cookie so it can be stored and
passed to HTTP clients.

Spec: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00

Fixes #15867

Based on
https://github.com/reedloden/go/commit/eb31a0f063c80058bbb3abff4ca09b3565985500
by Reed Loden <reed@hackerone.com>

Change-Id: I98c8a9a92358b2f632990576879759e3aff38cff
Reviewed-on: https://go-review.googlesource.com/79919
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent 4da84adc
...@@ -31,10 +31,25 @@ type Cookie struct { ...@@ -31,10 +31,25 @@ type Cookie struct {
MaxAge int MaxAge int
Secure bool Secure bool
HttpOnly bool HttpOnly bool
SameSite SameSite
Raw string Raw string
Unparsed []string // Raw text of unparsed attribute-value pairs Unparsed []string // Raw text of unparsed attribute-value pairs
} }
// SameSite allows a server define a cookie attribute making it impossible to
// the browser send this cookie along with cross-site requests. The main goal
// is mitigate the risk of cross-origin information leakage, and provides some
// protection against cross-site request forgery attacks.
//
// See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.
type SameSite int
const (
SameSiteDefaultMode SameSite = iota + 1
SameSiteLaxMode
SameSiteStrictMode
)
// readSetCookies parses all "Set-Cookie" values from // readSetCookies parses all "Set-Cookie" values from
// the header h and returns the successfully parsed Cookies. // the header h and returns the successfully parsed Cookies.
func readSetCookies(h Header) []*Cookie { func readSetCookies(h Header) []*Cookie {
...@@ -83,6 +98,17 @@ func readSetCookies(h Header) []*Cookie { ...@@ -83,6 +98,17 @@ func readSetCookies(h Header) []*Cookie {
continue continue
} }
switch lowerAttr { switch lowerAttr {
case "samesite":
lowerVal := strings.ToLower(val)
switch lowerVal {
case "lax":
c.SameSite = SameSiteLaxMode
case "strict":
c.SameSite = SameSiteStrictMode
default:
c.SameSite = SameSiteDefaultMode
}
continue
case "secure": case "secure":
c.Secure = true c.Secure = true
continue continue
...@@ -184,6 +210,14 @@ func (c *Cookie) String() string { ...@@ -184,6 +210,14 @@ func (c *Cookie) String() string {
if c.Secure { if c.Secure {
b.WriteString("; Secure") b.WriteString("; Secure")
} }
switch c.SameSite {
case SameSiteDefaultMode:
b.WriteString("; SameSite")
case SameSiteLaxMode:
b.WriteString("; SameSite=Lax")
case SameSiteStrictMode:
b.WriteString("; SameSite=Strict")
}
return b.String() return b.String()
} }
......
...@@ -65,6 +65,18 @@ var writeSetCookiesTests = []struct { ...@@ -65,6 +65,18 @@ var writeSetCookiesTests = []struct {
&Cookie{Name: "cookie-11", Value: "invalid-expiry", Expires: time.Date(1600, 1, 1, 1, 1, 1, 1, time.UTC)}, &Cookie{Name: "cookie-11", Value: "invalid-expiry", Expires: time.Date(1600, 1, 1, 1, 1, 1, 1, time.UTC)},
"cookie-11=invalid-expiry", "cookie-11=invalid-expiry",
}, },
{
&Cookie{Name: "cookie-12", Value: "samesite-default", SameSite: SameSiteDefaultMode},
"cookie-12=samesite-default; SameSite",
},
{
&Cookie{Name: "cookie-13", Value: "samesite-lax", SameSite: SameSiteLaxMode},
"cookie-13=samesite-lax; SameSite=Lax",
},
{
&Cookie{Name: "cookie-14", Value: "samesite-strict", SameSite: SameSiteStrictMode},
"cookie-14=samesite-strict; SameSite=Strict",
},
// The "special" cookies have values containing commas or spaces which // The "special" cookies have values containing commas or spaces which
// are disallowed by RFC 6265 but are common in the wild. // are disallowed by RFC 6265 but are common in the wild.
{ {
...@@ -241,6 +253,33 @@ var readSetCookiesTests = []struct { ...@@ -241,6 +253,33 @@ var readSetCookiesTests = []struct {
Raw: "ASP.NET_SessionId=foo; path=/; HttpOnly", Raw: "ASP.NET_SessionId=foo; path=/; HttpOnly",
}}, }},
}, },
{
Header{"Set-Cookie": {"samesitedefault=foo; SameSite"}},
[]*Cookie{{
Name: "samesitedefault",
Value: "foo",
SameSite: SameSiteDefaultMode,
Raw: "samesitedefault=foo; SameSite",
}},
},
{
Header{"Set-Cookie": {"samesitelax=foo; SameSite=Lax"}},
[]*Cookie{{
Name: "samesitelax",
Value: "foo",
SameSite: SameSiteLaxMode,
Raw: "samesitelax=foo; SameSite=Lax",
}},
},
{
Header{"Set-Cookie": {"samesitestrict=foo; SameSite=Strict"}},
[]*Cookie{{
Name: "samesitestrict",
Value: "foo",
SameSite: SameSiteStrictMode,
Raw: "samesitestrict=foo; SameSite=Strict",
}},
},
// Make sure we can properly read back the Set-Cookie headers we create // Make sure we can properly read back the Set-Cookie headers we create
// for values containing spaces or commas: // for values containing spaces or commas:
{ {
......
...@@ -93,6 +93,7 @@ type entry struct { ...@@ -93,6 +93,7 @@ type entry struct {
Value string Value string
Domain string Domain string
Path string Path string
SameSite string
Secure bool Secure bool
HttpOnly bool HttpOnly bool
Persistent bool Persistent bool
...@@ -418,6 +419,15 @@ func (j *Jar) newEntry(c *http.Cookie, now time.Time, defPath, host string) (e e ...@@ -418,6 +419,15 @@ func (j *Jar) newEntry(c *http.Cookie, now time.Time, defPath, host string) (e e
e.Secure = c.Secure e.Secure = c.Secure
e.HttpOnly = c.HttpOnly e.HttpOnly = c.HttpOnly
switch c.SameSite {
case http.SameSiteDefaultMode:
e.SameSite = "SameSite"
case http.SameSiteStrictMode:
e.SameSite = "SameSite=Strict"
case http.SameSiteLaxMode:
e.SameSite = "SameSite=Lax"
}
return e, false, nil return e, false, nil
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment