diff --git a/HISTORY.md b/HISTORY.md index 285bc5db..74756853 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -23,6 +23,7 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene Changes apply to `main` branch. +- Add `iris.CookieDomain` and `iris.CookieOverride` cookie options to handle [#2309](https://github.com/kataras/iris/issues/2309). - New `x/errors.ErrorCodeName.MapErrorFunc`, `x/errors.ErrorCodeName.MapErrors` methods and `x/errors.HandleError` package-level function. # Sun, 05 Nov 2023 | v12.2.8 diff --git a/aliases.go b/aliases.go index ec0d0a45..bf0aeeed 100644 --- a/aliases.go +++ b/aliases.go @@ -515,6 +515,15 @@ var ( // A shortcut of the `cache#Cache304`. Cache304 = cache.Cache304 + // CookieOverride is a CookieOption which overrides the cookie explicitly to the given "cookie". + // + // A shortcut for the `context#CookieOverride`. + CookieOverride = context.CookieOverride + // CookieDomain is a CookieOption which sets the cookie's Domain field. + // If empty then the current domain is used. + // + // A shortcut for the `context#CookieDomain`. + CookieDomain = context.CookieDomain // CookieAllowReclaim accepts the Context itself. // If set it will add the cookie to (on `CookieSet`, `CookieSetKV`, `CookieUpsert`) // or remove the cookie from (on `CookieRemove`) the Request object too. diff --git a/context/context.go b/context/context.go index 23ef6b50..0e9546dc 100644 --- a/context/context.go +++ b/context/context.go @@ -5572,10 +5572,39 @@ func CookieIncluded(cookie *http.Cookie, cookieNames []string) bool { return true } -var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-") +// var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-") +// +// func sanitizeCookieName(n string) string { +// return cookieNameSanitizer.Replace(n) +// } -func sanitizeCookieName(n string) string { - return cookieNameSanitizer.Replace(n) +// CookieOverride is a CookieOption which overrides the cookie explicitly to the given "cookie". +// +// Usage: +// ctx.RemoveCookie("the_cookie_name", iris.CookieOverride(&http.Cookie{Domain: "example.com"})) +func CookieOverride(cookie *http.Cookie) CookieOption { // The "Cookie" word method name is reserved as it's used as an alias. + return func(_ *Context, c *http.Cookie, op uint8) { + if op == OpCookieGet { + return + } + + *cookie = *c + } +} + +// CookieDomain is a CookieOption which sets the cookie's Domain field. +// If empty then the current domain is used. +// +// Usage: +// ctx.RemoveCookie("the_cookie_name", iris.CookieDomain("example.com")) +func CookieDomain(domain string) CookieOption { + return func(_ *Context, c *http.Cookie, op uint8) { + if op == OpCookieGet { + return + } + + c.Domain = domain + } } // CookieAllowReclaim accepts the Context itself. @@ -5773,6 +5802,8 @@ const cookieOptionsContextKey = "iris.cookie.options" // cookies sent or received from the next Handler in the chain. // // Available builtin Cookie options are: +// - CookieOverride +// - CookieDomain // - CookieAllowReclaim // - CookieAllowSubdomains // - CookieSecure @@ -5946,25 +5977,30 @@ var ( CookieExpireUnlimited = time.Now().AddDate(24, 10, 10) ) -// RemoveCookie deletes a cookie by its name. It reads the cookie's path and domain -// in order to delete the cookie cross-browser. -// Reports whether the cookie was removed or not. +// RemoveCookie deletes a cookie by its name and path = "/". +// Tip: change the cookie's path to the current one by: RemoveCookie("the_cookie_name", iris.CookieCleanPath) +// +// If you intend to remove a cookie with a specific domain and value, please ensure to pass these values explicitly: +// +// ctx.RemoveCookie("the_cookie_name", iris.CookieDomain("example.com"), iris.CookiePath("/")) +// +// OR use a Cookie value instead: +// +// ctx.RemoveCookie("the_cookie_name", iris.CookieOverride(&http.Cookie{Domain: "example.com", Path: "/"})) // // Example: https://github.com/kataras/iris/tree/main/_examples/cookies/basic -func (ctx *Context) RemoveCookie(name string, options ...CookieOption) bool { - // Get the cookie from the request - c, err := ctx.Request().Cookie(name) - if err != nil { - return false - } - - // Set the cookie expiration date to a past time - c.Expires = time.Unix(0, 0) - c.MaxAge = -1 // RFC says 1 second, but let's do it -1 to make sure is working. +func (ctx *Context) RemoveCookie(name string, options ...CookieOption) { + c := &http.Cookie{Path: "/"} // Send the cookie back to the client ctx.applyCookieOptions(c, OpCookieDel, options) + c.Name = name + c.Value = "" + c.HttpOnly = true + // Set the cookie expiration date to a past time + c.Expires = CookieExpireDelete + c.MaxAge = -1 // RFC says 1 second, but let's do it -1 to make sure is working. + http.SetCookie(ctx.writer, c) - return true } // VisitAllCookies takes a visitor function which is called diff --git a/x/errors/errors.go b/x/errors/errors.go index 3e11e2e1..deb98c6f 100644 --- a/x/errors/errors.go +++ b/x/errors/errors.go @@ -152,6 +152,20 @@ func HandleError(ctx *context.Context, err error) bool { // // This method MUST be called on initialization, before HTTP server starts as // the internal map is not protected by mutex. +// +// Example Code: +// +// errors.InvalidArgument.MapErrorFunc(func(err error) error { +// stripeErr, ok := err.(*stripe.Error) +// if !ok { +// return nil +// } +// +// return &errors.Error{ +// Message: stripeErr.Msg, +// Details: stripeErr.DocURL, +// } +// }) func (e ErrorCodeName) MapErrorFunc(fn func(error) error) { errorFuncCodeMap[e] = append(errorFuncCodeMap[e], fn) }