package web import ( "encoding/base64" "encoding/json" "net/http" "strings" ) // Flash is a one-shot notification shown after a redirect. // // The interface uses HTTP Basic authentication and therefore has no session to // hang messages off, so a flash travels in a short-lived cookie that is // cleared as soon as it is rendered. This keeps the post/redirect/get pattern // intact: a refresh after saving never re-submits the form. type Flash struct { Level string `json:"l"` // success, danger, warning, info Message string `json:"m"` } const flashCookie = "vibedns_flash" // maxFlashCookie bounds the cookie so a very long error message cannot exceed // what browsers accept. const maxFlashCookie = 3500 // setFlash queues a message for the next page render. func setFlash(w http.ResponseWriter, r *http.Request, level, message string) { f := Flash{Level: level, Message: message} raw, err := json.Marshal([]Flash{f}) if err != nil { return } value := base64.RawURLEncoding.EncodeToString(raw) if len(value) > maxFlashCookie { short := Flash{Level: level, Message: truncate(600, message)} raw, _ = json.Marshal([]Flash{short}) value = base64.RawURLEncoding.EncodeToString(raw) } http.SetCookie(w, &http.Cookie{ Name: flashCookie, Value: value, Path: "/", HttpOnly: true, Secure: r.TLS != nil, SameSite: http.SameSiteLaxMode, MaxAge: 60, }) } // takeFlashes reads and clears any queued messages. func takeFlashes(w http.ResponseWriter, r *http.Request) []Flash { c, err := r.Cookie(flashCookie) if err != nil || c.Value == "" { return nil } // Clear it immediately so a refresh does not show the message twice. http.SetCookie(w, &http.Cookie{ Name: flashCookie, Value: "", Path: "/", HttpOnly: true, Secure: r.TLS != nil, SameSite: http.SameSiteLaxMode, MaxAge: -1, }) raw, err := base64.RawURLEncoding.DecodeString(c.Value) if err != nil { return nil } var out []Flash if err := json.Unmarshal(raw, &out); err != nil { return nil } for i := range out { switch out[i].Level { case "success", "danger", "warning", "info": default: out[i].Level = "info" } } return out } // jsonMarshal encodes a value for embedding inside a