From 5ecb9af8e535543bc1cedd4667ea1c8b3ca11085 Mon Sep 17 00:00:00 2001 From: Anshul Sinha Date: Sat, 29 Mar 2025 21:16:16 +0530 Subject: [PATCH] add cookie sanitization according to RFC 6265 --- ctx.go | 44 +++++++++++++++++++++++++++++++++++++++++++- ctx_test.go | 12 ++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/ctx.go b/ctx.go index 1816185e..6a73d336 100644 --- a/ctx.go +++ b/ctx.go @@ -23,6 +23,7 @@ import ( "text/template" "time" + "github.com/gofiber/fiber/v3/log" "github.com/gofiber/utils/v2" "github.com/valyala/bytebufferpool" "github.com/valyala/fasthttp" @@ -450,7 +451,48 @@ func (c *DefaultCtx) Cookie(cookie *Cookie) { // The returned value is only valid within the handler. Do not store any references. // Make copies or use the Immutable setting to use the value outside the Handler. func (c *DefaultCtx) Cookies(key string, defaultValue ...string) string { - return defaultString(c.app.getString(c.fasthttp.Request.Header.Cookie(key)), defaultValue) + value := c.app.getString(c.fasthttp.Request.Header.Cookie(key)) + return defaultString(c.sanitizeCookieValue(value), defaultValue) +} + +// sanitizeCookieValue sanitizes a cookie value according to RFC 6265. +// It removes invalid characters from the cookie value, similar to how +// Go's standard library handles cookie values. +func (c *DefaultCtx) sanitizeCookieValue(v string) string { + var result strings.Builder + result.Grow(len(v)) + invalidChars := make(map[byte]struct{}) + + for i := 0; i < len(v); i++ { + b := v[i] + if c.validCookieValueByte(b) { + result.WriteByte(b) + } else { + invalidChars[b] = struct{}{} + } + } + + if len(invalidChars) > 0 { + var chars []string + for b := range invalidChars { + chars = append(chars, fmt.Sprintf("'%c'", b)) + } + log.Warn("invalid byte(s) %s in Cookie.Value; dropping invalid bytes", + strings.Join(chars, ", ")) + return result.String() + } + + return v +} + +// validCookieValueByte reports whether b is a valid byte in a cookie value. +// Per RFC 6265 section 4.1.1, cookie values must be ASCII +// and may not contain control characters, whitespace, double quotes, +// commas, semicolons, or backslashes. +func (c *DefaultCtx) validCookieValueByte(b byte) bool { + return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\' + // Note: commas are deliberately allowed + // See https://golang.org/issue/7243 for the discussion. } // Download transfers the file from path as an attachment. diff --git a/ctx_test.go b/ctx_test.go index 5040f4f8..71b0e89a 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -1035,6 +1035,18 @@ func Test_Ctx_Cookies(t *testing.T) { c.Request().Header.Set("Cookie", "john=doe") require.Equal(t, "doe", c.Req().Cookies("john")) require.Equal(t, "default", c.Req().Cookies("unknown", "default")) + + c.Request().Header.Set("Cookie", "special=value,with,commas") // commas are allowed + require.Equal(t, "value,with,commas", c.Req().Cookies("special")) + + c.Request().Header.Set("Cookie", "quotes=value\"with\"quotes") + require.Equal(t, "valuewithquotes", c.Req().Cookies("quotes")) + + c.Request().Header.Set("Cookie", "semicolons=value;with;semicolons") + require.Equal(t, "valuewithsemicolons", c.Req().Cookies("semicolons")) + + c.Request().Header.Set("Cookie", "backslash=value\\with\\backslash") + require.Equal(t, "valuewithbackslash", c.Req().Cookies("backslash")) } // go test -run Test_Ctx_Format