mirror of
https://github.com/gin-gonic/gin.git
synced 2025-04-06 03:57:46 +08:00
implement partitioned cookies
implement cookie validation with error logging refactor c.SetCookie to accept option pattern modifications as vararg refactor c.SetSameSite to use option pattern under the hood
This commit is contained in:
parent
8763f33c65
commit
4eecebf8bf
46
context.go
46
context.go
@ -6,9 +6,11 @@ package gin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"log/slog"
|
||||
"math"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
@ -47,6 +49,20 @@ const ContextKey = "_gin-gonic/gin/contextkey"
|
||||
|
||||
type ContextKeyType int
|
||||
|
||||
type CookieOption func(*http.Cookie)
|
||||
|
||||
func WithPartitionedCookie(partitioned bool) CookieOption {
|
||||
return func(cookie *http.Cookie) {
|
||||
cookie.Partitioned = partitioned
|
||||
}
|
||||
}
|
||||
|
||||
func WithSameSiteCookie(sameSite http.SameSite) CookieOption {
|
||||
return func(cookie *http.Cookie) {
|
||||
cookie.SameSite = sameSite
|
||||
}
|
||||
}
|
||||
|
||||
const ContextRequestKey ContextKeyType = 0
|
||||
|
||||
// abortIndex represents a typical value used in abort functions.
|
||||
@ -87,9 +103,9 @@ type Context struct {
|
||||
// or PUT body parameters.
|
||||
formCache url.Values
|
||||
|
||||
// SameSite allows a server to define a cookie attribute making it impossible for
|
||||
// the browser to send this cookie along with cross-site requests.
|
||||
sameSite http.SameSite
|
||||
// cookieOptions set up additional cookie parameters to be used with http.Cookie
|
||||
// this is needed to prevent breaking existing implementations
|
||||
cookieOptions []CookieOption
|
||||
}
|
||||
|
||||
/************************************/
|
||||
@ -108,7 +124,7 @@ func (c *Context) reset() {
|
||||
c.Accepted = nil
|
||||
c.queryCache = nil
|
||||
c.formCache = nil
|
||||
c.sameSite = 0
|
||||
c.cookieOptions = make([]CookieOption, 0)
|
||||
*c.params = (*c.params)[:0]
|
||||
*c.skippedNodes = (*c.skippedNodes)[:0]
|
||||
}
|
||||
@ -1005,26 +1021,38 @@ func (c *Context) GetRawData() ([]byte, error) {
|
||||
|
||||
// SetSameSite with cookie
|
||||
func (c *Context) SetSameSite(samesite http.SameSite) {
|
||||
c.sameSite = samesite
|
||||
c.cookieOptions = append(c.cookieOptions, WithSameSiteCookie(samesite))
|
||||
}
|
||||
|
||||
// SetCookie adds a Set-Cookie header to the ResponseWriter's headers.
|
||||
// The provided cookie must have a valid Name. Invalid cookies may be
|
||||
// silently dropped.
|
||||
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {
|
||||
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool, options ...CookieOption) {
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
http.SetCookie(c.Writer, &http.Cookie{
|
||||
|
||||
cookie := &http.Cookie{
|
||||
Name: name,
|
||||
Value: url.QueryEscape(value),
|
||||
MaxAge: maxAge,
|
||||
Path: path,
|
||||
Domain: domain,
|
||||
SameSite: c.sameSite,
|
||||
Secure: secure,
|
||||
HttpOnly: httpOnly,
|
||||
})
|
||||
}
|
||||
|
||||
for _, option := range c.cookieOptions {
|
||||
option(cookie)
|
||||
}
|
||||
for _, option := range options {
|
||||
option(cookie)
|
||||
}
|
||||
if err := cookie.Valid(); err != nil {
|
||||
slog.Error(fmt.Sprintf("invalid cookie: %v", err))
|
||||
}
|
||||
|
||||
http.SetCookie(c.Writer, cookie)
|
||||
}
|
||||
|
||||
// Cookie returns the named cookie provided in the request or
|
||||
|
@ -206,6 +206,7 @@ func TestContextReset(t *testing.T) {
|
||||
c.Params = Params{Param{}}
|
||||
c.Error(errors.New("test")) //nolint: errcheck
|
||||
c.Set("foo", "bar")
|
||||
c.SetSameSite(http.SameSiteLaxMode)
|
||||
c.reset()
|
||||
|
||||
assert.False(t, c.IsAborted())
|
||||
@ -215,6 +216,7 @@ func TestContextReset(t *testing.T) {
|
||||
assert.Empty(t, c.Errors.Errors())
|
||||
assert.Empty(t, c.Errors.ByType(ErrorTypeAny))
|
||||
assert.Empty(t, c.Params)
|
||||
assert.Empty(t, c.cookieOptions)
|
||||
assert.EqualValues(t, c.index, -1)
|
||||
assert.Equal(t, c.Writer.(*responseWriter), &c.writermem)
|
||||
}
|
||||
@ -866,6 +868,18 @@ func TestContextSetCookie(t *testing.T) {
|
||||
assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure; SameSite=Lax", c.Writer.Header().Get("Set-Cookie"))
|
||||
}
|
||||
|
||||
func TestContextSetCookieWithOptions(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.SetCookie("user", "gin", 1, "/", "localhost", true, true, WithSameSiteCookie(http.SameSiteLaxMode), WithPartitionedCookie(true))
|
||||
assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure; SameSite=Lax; Partitioned", c.Writer.Header().Get("Set-Cookie"))
|
||||
}
|
||||
|
||||
func TestContextSetCookieWithNonSecurePartition(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.SetCookie("user", "gin", 1, "/", "localhost", false, true, WithPartitionedCookie(true))
|
||||
assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Partitioned", c.Writer.Header().Get("Set-Cookie"))
|
||||
}
|
||||
|
||||
func TestContextSetCookiePathEmpty(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.SetSameSite(http.SameSiteLaxMode)
|
||||
|
Loading…
x
Reference in New Issue
Block a user