initial commit
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user