package web // Issue 1859/AC4 (novox/hq ADR 0515 §6): one script and one stylesheet, from the page's own origin; the // content security policy allows script and style from it alone, and connect-src 'self'; no inline script // and no style attribute on any page. import ( "context" "encoding/json" "net/http" "regexp" "strings" "testing" "time" ) var ( inlineScript = regexp.MustCompile(`(?i)]*)?>`) styleAttr = regexp.MustCompile(`(?i)\sstyle\s*=|]|\son[a-z]+\s*=`) ) // 1859/AC4. func TestEveryPageLoadsScriptAndStyleFromItsOwnOriginAlone(t *testing.T) { b := (&readingStandIn{list: fullList()}).bundle(time.Date(2026, 10, 22, 7, 45, 0, 0, time.UTC)) accountsRaw, _ := accountsAnswerJSON(map[string]any{"account": "work", "kind": "imap", "address": "op@work.example", "state": "current"}) b.Accounts = func(context.Context) (json.RawMessage, error) { return accountsRaw, nil } b.AccountBucket = (&fakeAccountsBucket{}).get b.MailLessons = seatAskJSON(t, map[string]any{"lessons": []any{}}) b.MailGroups = seatAskJSON(t, map[string]any{"groups": []any{}}) b.Search = seatAskJSON(t, map[string]any{"found": []any{}}) b.Read = seatAskJSON(t, map[string]any{"account": "work", "folder": "INBOX", "uid": 101, "subject": "s", "text": "t"}) s := NewServer(testHost, nil, b) for _, path := range []string{"/", "/all", "/folder?account=work&folder=INBOX", "/search?q=x", "/mail?account=work&folder=INBOX&uid=101", "/write"} { code, body, h := get(t, s, path) csp := h.Get("Content-Security-Policy") for _, want := range []string{"script-src 'self'", "style-src 'self'", "connect-src 'self'", "default-src 'self'"} { if !strings.Contains(csp, want) { t.Fatalf("%s: the policy %q does not hold %q", path, csp, want) } } for _, tag := range inlineScript.FindAllString(body, -1) { if !strings.Contains(tag, `src="/static/app.js"`) { t.Fatalf("%s: a script that is not /static/app.js: %s", path, tag) } } if m := styleAttr.FindString(body); m != "" { t.Fatalf("%s (status %d): an inline style or handler %q:\n%s", path, code, m, body) } } _, body, _ := get(t, s, "/") if !strings.Contains(body, ``) || !strings.Contains(body, ``) { t.Fatalf("the list page does not load the one stylesheet and the one script:\n%s", body) } for path, typ := range map[string]string{"/static/app.js": "text/javascript; charset=utf-8", "/static/app.css": "text/css; charset=utf-8"} { code, body, h := get(t, s, path) if code != http.StatusOK || h.Get("Content-Type") != typ || h.Get("X-Content-Type-Options") != "nosniff" || len(body) < 100 { t.Fatalf("%s: status %d, type %q, nosniff %q, %d bytes; want 200, %s, nosniff, the file", path, code, h.Get("Content-Type"), h.Get("X-Content-Type-Options"), len(body), typ) } } if code, _, _ := get(t, s, "/static/other.js"); code != http.StatusNotFound { t.Fatalf("/static/other.js: status %d, want 404 — the two files alone are served", code) } } // 1859/AC4: the script talks only to its own origin: no address of another in it. func TestTheScriptNamesNoOtherOrigin(t *testing.T) { js, err := staticFiles.ReadFile("static/app.js") if err != nil { t.Fatal(err) } for _, word := range []string{"http://", "https://", "//cdn", "eval(", "innerHTML ="} { if strings.Contains(string(js), word) { t.Fatalf("app.js holds %q", word) } } }