oficina / memoria Public

Clone
git clone https://amadan.net/oficina/memoria

Plain git — no account needed to clone.

Download

Download this file

1package memoria
2
3import (
4 "encoding/base64"
5 "net/http"
6 "strings"
7)
8
9// A notice is a one-shot sentence carried across a redirect in a cookie:
10// rastrillo's flash, with one difference that is the reason this file
11// exists. Each page that shows a notice has its own cookie.
12//
13// With one shared cookie, whichever page loaded first took the notice,
14// whether or not it was the page the notice was about. After an upload,
15// visiting the index spent the sentence naming the refused files, so /add
16// was left with the bare counts in its URL. A failed delete was spent by
17// a visit to /add, which has no place to show it. Worst of all, /add's
18// own background refresh spent it, and that refresh only swaps the
19// footer, so nobody saw it at all.
20//
21// A notice may name a file, and a filename is content. This cookie is the
22// one place content travels outside a page body: HttpOnly, sixty
23// seconds, spent on first display, and sent only to the browser that is
24// about to be shown the same words. It never goes into a log line, an
25// error string or a URL.
26type notice string
27
28const (
29 // addNotice is the outcome of a submission, shown on /add.
30 addNotice notice = "memoria_notice_add"
31 // indexNotice is a failed delete, shown on the index.
32 indexNotice notice = "memoria_notice_index"
33)
34
35// refreshHeader marks drop.js's background re-fetch of the page. The
36// refresh swaps only the regions marked rst-refresh, and a notice is
37// never inside one, so the re-fetch must leave the notice unspent.
38const refreshHeader = "X-Memoria-Refresh"
39
40func (n notice) set(w http.ResponseWriter, kind, message string) {
41 http.SetCookie(w, &http.Cookie{
42 Name: string(n),
43 Value: base64.RawURLEncoding.EncodeToString([]byte(kind + "\x00" + message)),
44 Path: "/",
45 HttpOnly: true,
46 SameSite: http.SameSiteLaxMode,
47 MaxAge: 60,
48 })
49}
50
51// take returns the notice and clears it. It returns nothing and clears
52// nothing on a refresh request, because the refresh never shows a notice.
53func (n notice) take(w http.ResponseWriter, r *http.Request) (kind, message string, ok bool) {
54 if r.Header.Get(refreshHeader) != "" {
55 return "", "", false
56 }
57 c, err := r.Cookie(string(n))
58 if err != nil {
59 return "", "", false
60 }
61 http.SetCookie(w, &http.Cookie{
62 Name: string(n), Value: "", Path: "/",
63 HttpOnly: true, SameSite: http.SameSiteLaxMode, MaxAge: -1,
64 })
65 data, err := base64.RawURLEncoding.DecodeString(c.Value)
66 if err != nil {
67 return "", "", false
68 }
69 kind, message, found := strings.Cut(string(data), "\x00")
70 if !found || message == "" {
71 return "", "", false
72 }
73 return kind, message, true
74}
75