Merge 4eecebf8bfcfdf2bde13f2757931a570e4d38529 into 8763f33c65f7df8be5b9fe7504ab7fcf20abb41d

This commit is contained in:
bound2 2025-03-28 09:14:21 +02:00 committed by GitHub
commit bd1d2ec7c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 51 additions and 9 deletions

View File

@ -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

View File

@ -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)