Skip to content

Commit c0b2617

Browse files
Copilotsixcolorsgaby
authored
Fix Cookie SameSite constants to Pascal case per RFC specification (#3608)
Co-authored-by: sixcolors <[email protected]> Co-authored-by: gaby <[email protected]> Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: Jason McNeil <[email protected]>
1 parent b839032 commit c0b2617

File tree

7 files changed

+321
-34
lines changed

7 files changed

+321
-34
lines changed

constants.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,9 +309,9 @@ const (
309309
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7
310310
const (
311311
CookieSameSiteDisabled = "disabled" // not in RFC, just control "SameSite" attribute will not be set.
312-
CookieSameSiteLaxMode = "lax"
313-
CookieSameSiteStrictMode = "strict"
314-
CookieSameSiteNoneMode = "none"
312+
CookieSameSiteLaxMode = "Lax"
313+
CookieSameSiteStrictMode = "Strict"
314+
CookieSameSiteNoneMode = "None"
315315
)
316316

317317
// Route Constraints

ctx.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -432,14 +432,16 @@ func (c *DefaultCtx) Cookie(cookie *Cookie) {
432432

433433
var sameSite http.SameSite
434434

435-
switch utils.ToLower(cookie.SameSite) {
436-
case CookieSameSiteStrictMode:
435+
switch {
436+
case utils.EqualFold(cookie.SameSite, CookieSameSiteStrictMode):
437437
sameSite = http.SameSiteStrictMode
438-
case CookieSameSiteNoneMode:
438+
case utils.EqualFold(cookie.SameSite, CookieSameSiteNoneMode):
439439
sameSite = http.SameSiteNoneMode
440-
case CookieSameSiteDisabled:
440+
// SameSite=None requires Secure=true per RFC and browser requirements
441+
cookie.Secure = true
442+
case utils.EqualFold(cookie.SameSite, CookieSameSiteDisabled):
441443
sameSite = 0
442-
case CookieSameSiteLaxMode:
444+
case utils.EqualFold(cookie.SameSite, CookieSameSiteLaxMode):
443445
sameSite = http.SameSiteLaxMode
444446
default:
445447
sameSite = http.SameSiteLaxMode

ctx_test.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,165 @@ func Test_Ctx_Cookie_StrictPartitioned(t *testing.T) {
11971197
)
11981198
}
11991199

1200+
// go test -run Test_Ctx_Cookie_SameSite_CaseInsensitive
1201+
func Test_Ctx_Cookie_SameSite_CaseInsensitive(t *testing.T) {
1202+
t.Parallel()
1203+
app := New()
1204+
c := app.AcquireCtx(&fasthttp.RequestCtx{})
1205+
1206+
tests := []struct {
1207+
name string
1208+
input string
1209+
expected string
1210+
}{
1211+
// Test case-insensitive Strict
1212+
{"Strict lowercase", "strict", "SameSite=Strict"},
1213+
{"Strict uppercase", "STRICT", "SameSite=Strict"},
1214+
{"Strict mixed case", "StRiCt", "SameSite=Strict"},
1215+
{"Strict proper case", "Strict", "SameSite=Strict"},
1216+
1217+
// Test case-insensitive Lax
1218+
{"Lax lowercase", "lax", "SameSite=Lax"},
1219+
{"Lax uppercase", "LAX", "SameSite=Lax"},
1220+
{"Lax mixed case", "LaX", "SameSite=Lax"},
1221+
{"Lax proper case", "Lax", "SameSite=Lax"},
1222+
1223+
// Test case-insensitive None
1224+
{"None lowercase", "none", "SameSite=None"},
1225+
{"None uppercase", "NONE", "SameSite=None"},
1226+
{"None mixed case", "NoNe", "SameSite=None"},
1227+
{"None proper case", "None", "SameSite=None"},
1228+
1229+
// Test case-insensitive disabled
1230+
{"Disabled lowercase", "disabled", ""},
1231+
{"Disabled uppercase", "DISABLED", ""},
1232+
{"Disabled mixed case", "DiSaBlEd", ""},
1233+
{"Disabled proper case", "disabled", ""},
1234+
1235+
// Test invalid values default to Lax
1236+
{"Invalid value", "invalid", "SameSite=Lax"},
1237+
{"Empty value", "", "SameSite=Lax"},
1238+
}
1239+
1240+
for _, tc := range tests {
1241+
t.Run(tc.name, func(t *testing.T) {
1242+
t.Parallel()
1243+
// Reset response
1244+
c.Response().Reset()
1245+
1246+
cookie := &Cookie{
1247+
Name: "test",
1248+
Value: "value",
1249+
SameSite: tc.input,
1250+
}
1251+
c.Res().Cookie(cookie)
1252+
1253+
setCookieHeader := c.Res().Get(HeaderSetCookie)
1254+
if tc.expected == "" {
1255+
// For disabled, SameSite should not appear in the header
1256+
require.NotContains(t, setCookieHeader, "SameSite")
1257+
} else {
1258+
// For all other cases, the expected SameSite should appear
1259+
require.Contains(t, setCookieHeader, tc.expected)
1260+
}
1261+
})
1262+
}
1263+
}
1264+
1265+
// go test -run Test_Ctx_Cookie_SameSite_None_Secure
1266+
func Test_Ctx_Cookie_SameSite_None_Secure(t *testing.T) {
1267+
t.Parallel()
1268+
1269+
testCases := []struct {
1270+
name string
1271+
cookie *Cookie
1272+
expectedInHeader string
1273+
shouldBeSecure bool
1274+
}{
1275+
{
1276+
name: "Empty value",
1277+
cookie: &Cookie{
1278+
Name: "test",
1279+
Value: "value",
1280+
SameSite: "",
1281+
},
1282+
expectedInHeader: "SameSite=Lax",
1283+
shouldBeSecure: false,
1284+
},
1285+
{
1286+
name: "None uppercase",
1287+
cookie: &Cookie{
1288+
Name: "test",
1289+
Value: "value",
1290+
SameSite: "None",
1291+
},
1292+
expectedInHeader: "SameSite=None",
1293+
shouldBeSecure: true,
1294+
},
1295+
{
1296+
name: "None lowercase",
1297+
cookie: &Cookie{
1298+
Name: "test",
1299+
Value: "value",
1300+
SameSite: "none",
1301+
},
1302+
expectedInHeader: "SameSite=None",
1303+
shouldBeSecure: true,
1304+
},
1305+
{
1306+
name: "Lax proper case",
1307+
cookie: &Cookie{
1308+
Name: "test",
1309+
Value: "value",
1310+
SameSite: "Lax",
1311+
},
1312+
expectedInHeader: "SameSite=Lax",
1313+
shouldBeSecure: false,
1314+
},
1315+
{
1316+
name: "Strict uppercase",
1317+
cookie: &Cookie{
1318+
Name: "test",
1319+
Value: "value",
1320+
SameSite: "STRICT",
1321+
},
1322+
expectedInHeader: "SameSite=Strict",
1323+
shouldBeSecure: false,
1324+
},
1325+
{
1326+
name: "Disabled Secure",
1327+
cookie: &Cookie{
1328+
Name: "test",
1329+
Value: "value",
1330+
SameSite: "none",
1331+
Secure: false,
1332+
},
1333+
expectedInHeader: "SameSite=None",
1334+
shouldBeSecure: true,
1335+
},
1336+
}
1337+
1338+
for _, tc := range testCases {
1339+
t.Run(tc.name, func(t *testing.T) {
1340+
t.Parallel()
1341+
app := New()
1342+
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
1343+
defer app.ReleaseCtx(ctx)
1344+
1345+
ctx.Cookie(tc.cookie)
1346+
1347+
cookie := string(ctx.Response().Header.PeekCookie(tc.cookie.Name))
1348+
require.Contains(t, cookie, tc.expectedInHeader)
1349+
1350+
if tc.shouldBeSecure {
1351+
require.Contains(t, cookie, "secure")
1352+
} else {
1353+
require.NotContains(t, cookie, "secure")
1354+
}
1355+
})
1356+
}
1357+
}
1358+
12001359
// go test -v -run=^$ -bench=Benchmark_Ctx_Cookie -benchmem -count=4
12011360
func Benchmark_Ctx_Cookie(b *testing.B) {
12021361
app := New()

docs/api/ctx.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1547,7 +1547,7 @@ app.Get("/set", func(c fiber.Ctx) error {
15471547
Value: "randomvalue",
15481548
Expires: time.Now().Add(24 * time.Hour),
15491549
HTTPOnly: true,
1550-
SameSite: "lax",
1550+
SameSite: "Lax",
15511551
})
15521552

15531553
// ...
@@ -1559,7 +1559,7 @@ app.Get("/delete", func(c fiber.Ctx) error {
15591559
// Set expiry date to the past
15601560
Expires: time.Now().Add(-(time.Hour * 2)),
15611561
HTTPOnly: true,
1562-
SameSite: "lax",
1562+
SameSite: "Lax",
15631563
})
15641564

15651565
// ...
@@ -1604,6 +1604,16 @@ app.Get("/", func(c fiber.Ctx) error {
16041604
})
16051605
```
16061606

1607+
:::info
1608+
When setting a cookie with `SameSite=None`, Fiber automatically sets `Secure=true` as required by RFC 6265bis and modern browsers. This ensures compliance with the "None" SameSite policy which mandates that cookies must be sent over secure connections.
1609+
1610+
For more information, see:
1611+
1612+
- [Mozilla Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#none)
1613+
- [Chrome Documentation](https://developers.google.com/search/blog/2020/01/get-ready-for-new-samesitenone-secure)
1614+
1615+
:::
1616+
16071617
:::info
16081618
Partitioned cookies allow partitioning the cookie jar by top-level site, enhancing user privacy by preventing cookies from being shared across different sites. This feature is particularly useful in scenarios where a user interacts with embedded third-party services that should not have access to the main site's cookies. You can check out [CHIPS](https://developers.google.com/privacy-sandbox/3pcd/chips) for more information.
16091619
:::

docs/whats_new.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@ testConfig := fiber.TestConfig{
452452
### New Features
453453

454454
- Cookie now allows Partitioned cookies for [CHIPS](https://developers.google.com/privacy-sandbox/3pcd/chips) support. CHIPS (Cookies Having Independent Partitioned State) is a feature that improves privacy by allowing cookies to be partitioned by top-level site, mitigating cross-site tracking.
455+
- Cookie automatic security enforcement: When setting a cookie with `SameSite=None`, Fiber automatically sets `Secure=true` as required by RFC 6265bis and modern browsers (Chrome, Firefox, Safari). This ensures compliance with the "None" SameSite policy. See [Mozilla docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#none) and [Chrome docs](https://developers.google.com/search/blog/2020/01/get-ready-for-new-samesitenone-secure) for details.
455456
- Context now implements [context.Context](https://pkg.go.dev/context#Context).
456457

457458
### New Methods

middleware/session/session.go

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,8 @@ func (s *Session) setSession() {
378378
s.ctx.Response().Header.SetBytesV(s.config.sessionName, []byte(s.id))
379379
} else {
380380
fcookie := fasthttp.AcquireCookie()
381+
defer fasthttp.ReleaseCookie(fcookie)
382+
381383
fcookie.SetKey(s.config.sessionName)
382384
fcookie.SetValue(s.id)
383385
fcookie.SetPath(s.config.CookiePath)
@@ -388,19 +390,9 @@ func (s *Session) setSession() {
388390
fcookie.SetMaxAge(int(s.idleTimeout.Seconds()))
389391
fcookie.SetExpire(time.Now().Add(s.idleTimeout))
390392
}
391-
fcookie.SetSecure(s.config.CookieSecure)
392-
fcookie.SetHTTPOnly(s.config.CookieHTTPOnly)
393-
394-
switch utils.ToLower(s.config.CookieSameSite) {
395-
case "strict":
396-
fcookie.SetSameSite(fasthttp.CookieSameSiteStrictMode)
397-
case "none":
398-
fcookie.SetSameSite(fasthttp.CookieSameSiteNoneMode)
399-
default:
400-
fcookie.SetSameSite(fasthttp.CookieSameSiteLaxMode)
401-
}
393+
394+
s.setCookieAttributes(fcookie)
402395
s.ctx.Response().Header.SetCookie(fcookie)
403-
fasthttp.ReleaseCookie(fcookie)
404396
}
405397
}
406398

@@ -417,28 +409,41 @@ func (s *Session) delSession() {
417409
s.ctx.Response().Header.DelCookie(s.config.sessionName)
418410

419411
fcookie := fasthttp.AcquireCookie()
412+
defer fasthttp.ReleaseCookie(fcookie)
413+
420414
fcookie.SetKey(s.config.sessionName)
421415
fcookie.SetPath(s.config.CookiePath)
422416
fcookie.SetDomain(s.config.CookieDomain)
423417
fcookie.SetMaxAge(-1)
424418
fcookie.SetExpire(time.Now().Add(-1 * time.Minute))
425-
fcookie.SetSecure(s.config.CookieSecure)
426-
fcookie.SetHTTPOnly(s.config.CookieHTTPOnly)
427-
428-
switch utils.ToLower(s.config.CookieSameSite) {
429-
case "strict":
430-
fcookie.SetSameSite(fasthttp.CookieSameSiteStrictMode)
431-
case "none":
432-
fcookie.SetSameSite(fasthttp.CookieSameSiteNoneMode)
433-
default:
434-
fcookie.SetSameSite(fasthttp.CookieSameSiteLaxMode)
435-
}
436419

420+
s.setCookieAttributes(fcookie)
437421
s.ctx.Response().Header.SetCookie(fcookie)
438-
fasthttp.ReleaseCookie(fcookie)
439422
}
440423
}
441424

425+
// setCookieAttributes sets the cookie attributes based on the session config.
426+
func (s *Session) setCookieAttributes(fcookie *fasthttp.Cookie) {
427+
// Set SameSite attribute
428+
switch {
429+
case utils.EqualFold(s.config.CookieSameSite, fiber.CookieSameSiteStrictMode):
430+
fcookie.SetSameSite(fasthttp.CookieSameSiteStrictMode)
431+
case utils.EqualFold(s.config.CookieSameSite, fiber.CookieSameSiteNoneMode):
432+
fcookie.SetSameSite(fasthttp.CookieSameSiteNoneMode)
433+
default:
434+
fcookie.SetSameSite(fasthttp.CookieSameSiteLaxMode)
435+
}
436+
437+
// The Secure attribute is required for SameSite=None
438+
if fcookie.SameSite() == fasthttp.CookieSameSiteNoneMode {
439+
fcookie.SetSecure(true)
440+
} else {
441+
fcookie.SetSecure(s.config.CookieSecure)
442+
}
443+
444+
fcookie.SetHTTPOnly(s.config.CookieHTTPOnly)
445+
}
446+
442447
// decodeSessionData decodes session data from raw bytes
443448
//
444449
// Parameters:

0 commit comments

Comments
 (0)