Files
2026-08-16 21:18:45 -05:00

106 lines
2.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 <script> block.
//
// Go's html/template will not escape inside a script context, so the sequences
// that could terminate the element or be reinterpreted by a JavaScript parser
// are escaped here.
func jsonMarshal(v any) ([]byte, error) {
b, err := json.Marshal(v)
if err != nil {
return nil, err
}
s := string(b)
r := strings.NewReplacer(
"<", `<`,
">", `>`,
"&", `&`,
"", ``,
"", ``,
)
return []byte(r.Replace(s)), nil
}