Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support SameSite cookie attribute #488

Merged
merged 3 commits into from Dec 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions cookie.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ var (
CookieExpireUnlimited = zeroTime
)

// CookieSameSite is an enum for the mode in which the SameSite flag should be set for the given cookie.
// See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.
type CookieSameSite int
This conversation was marked as resolved.
Show resolved Hide resolved
const (
// CookieSameSiteDisabled removes the SameSite flag
CookieSameSiteDisabled CookieSameSite = iota
// CookieSameSiteDefaultMode sets the SameSite flag
CookieSameSiteDefaultMode
// CookieSameSiteLaxMode sets the SameSite flag with the "Lax" parameter
CookieSameSiteLaxMode
// CookieSameSiteStrictMode sets the SameSite flag with the "Strict" parameter
CookieSameSiteStrictMode
)

// AcquireCookie returns an empty Cookie object from the pool.
//
// The returned object may be returned back to the pool with ReleaseCookie.
Expand Down Expand Up @@ -58,6 +72,7 @@ type Cookie struct {

httpOnly bool
secure bool
sameSite CookieSameSite

bufKV argsKV
buf []byte
Expand All @@ -74,6 +89,7 @@ func (c *Cookie) CopyTo(src *Cookie) {
c.path = append(c.path[:0], src.path...)
c.httpOnly = src.httpOnly
c.secure = src.secure
c.sameSite = src.sameSite
}

// HTTPOnly returns true if the cookie is http only.
Expand All @@ -96,6 +112,16 @@ func (c *Cookie) SetSecure(secure bool) {
c.secure = secure
}

// SameSite returns the SameSite mode.
func (c *Cookie) SameSite() CookieSameSite {
return c.sameSite
}

// SetSameSite sets the cookie's SameSite flag to the given value.
func (c *Cookie) SetSameSite(mode CookieSameSite) {
c.sameSite = mode
}

// Path returns cookie path.
func (c *Cookie) Path() []byte {
return c.path
Expand Down Expand Up @@ -209,6 +235,7 @@ func (c *Cookie) Reset() {
c.path = c.path[:0]
c.httpOnly = false
c.secure = false
c.sameSite = CookieSameSiteDisabled
}

// AppendBytes appends cookie representation to dst and returns
Expand Down Expand Up @@ -246,6 +273,21 @@ func (c *Cookie) AppendBytes(dst []byte) []byte {
dst = append(dst, ';', ' ')
dst = append(dst, strCookieSecure...)
}
switch c.sameSite {
case CookieSameSiteDefaultMode:
dst = append(dst, ';', ' ')
dst = append(dst, strCookieSameSite...)
case CookieSameSiteLaxMode:
dst = append(dst, ';', ' ')
dst = append(dst, strCookieSameSite...)
dst = append(dst, '=')
dst = append(dst, strCookieSameSiteLax...)
case CookieSameSiteStrictMode:
dst = append(dst, ';', ' ')
dst = append(dst, strCookieSameSite...)
dst = append(dst, '=')
dst = append(dst, strCookieSameSiteStrict...)
}
return dst
}

Expand Down Expand Up @@ -330,6 +372,21 @@ func (c *Cookie) ParseBytes(src []byte) error {
if caseInsensitiveCompare(strCookiePath, kv.key) {
c.path = append(c.path[:0], kv.value...)
}

case 's': // "samesite"
if caseInsensitiveCompare(strCookieSameSite, kv.key) {
// Case insensitive switch on first char
switch kv.value[0] | 0x20 {
case 'l': // "lax"
if caseInsensitiveCompare(strCookieSameSiteLax, kv.value) {
c.sameSite = CookieSameSiteLaxMode
}
case 's': // "strict"
if caseInsensitiveCompare(strCookieSameSiteStrict, kv.value) {
c.sameSite = CookieSameSiteStrictMode
}
}
}
}

} else if len(kv.value) != 0 {
Expand All @@ -343,6 +400,8 @@ func (c *Cookie) ParseBytes(src []byte) error {
case 's': // "secure"
if caseInsensitiveCompare(strCookieSecure, kv.value) {
c.secure = true
} else if caseInsensitiveCompare(strCookieSameSite, kv.value) {
c.sameSite = CookieSameSiteDefaultMode
}
}
} // else empty or no match
Expand Down
50 changes: 49 additions & 1 deletion cookie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func TestCookieSecure(t *testing.T) {
if err := c.Parse("foo=bar"); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if c.HTTPOnly() {
if c.Secure() {
t.Fatalf("Unexpected secure flag set")
}
s = c.String()
Expand All @@ -85,6 +85,54 @@ func TestCookieSecure(t *testing.T) {
}
}

func TestCookieSameSite(t *testing.T) {
var c Cookie

if err := c.Parse("foo=bar; samesite"); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if c.SameSite() != CookieSameSiteDefaultMode {
t.Fatalf("SameSite must be set")
}
s := c.String()
if !strings.Contains(s, "; SameSite") {
t.Fatalf("missing SameSite flag in cookie %q", s)
}

if err := c.Parse("foo=bar; samesite=lax"); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if c.SameSite() != CookieSameSiteLaxMode {
t.Fatalf("SameSite Lax Mode must be set")
}
s = c.String()
if !strings.Contains(s, "; SameSite=Lax") {
t.Fatalf("missing SameSite flag in cookie %q", s)
}

if err := c.Parse("foo=bar; samesite=strict"); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if c.SameSite() != CookieSameSiteStrictMode {
t.Fatalf("SameSite Strict Mode must be set")
}
s = c.String()
if !strings.Contains(s, "; SameSite=Strict") {
t.Fatalf("missing SameSite flag in cookie %q", s)
}

if err := c.Parse("foo=bar"); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if c.SameSite() != CookieSameSiteDisabled {
t.Fatalf("Unexpected SameSite flag set")
}
s = c.String()
if strings.Contains(s, "SameSite") {
t.Fatalf("unexpected SameSite flag in cookie %q", s)
}
}

func TestCookieMaxAge(t *testing.T) {
var c Cookie

Expand Down
17 changes: 10 additions & 7 deletions strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,16 @@ var (
strRange = []byte("Range")
strContentRange = []byte("Content-Range")

strCookieExpires = []byte("expires")
strCookieDomain = []byte("domain")
strCookiePath = []byte("path")
strCookieHTTPOnly = []byte("HttpOnly")
strCookieSecure = []byte("secure")
strCookieMaxAge = []byte("max-age")

strCookieExpires = []byte("expires")
strCookieDomain = []byte("domain")
strCookiePath = []byte("path")
strCookieHTTPOnly = []byte("HttpOnly")
strCookieSecure = []byte("secure")
strCookieMaxAge = []byte("max-age")
strCookieSameSite = []byte("SameSite")
strCookieSameSiteLax = []byte("Lax")
strCookieSameSiteStrict = []byte("Strict")

strClose = []byte("close")
strGzip = []byte("gzip")
strDeflate = []byte("deflate")
Expand Down