initial commit

This commit is contained in:
2026-08-16 21:18:45 -05:00
commit 1e05a01bcf
122 changed files with 29178 additions and 0 deletions
+105
View File
@@ -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
}