clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

scope tenant prefixes with an HTML tokenizer

Dami committed 3408716
Rewrite root-relative href, src, action and fx-action attributes on start
tags only, leaving scripts and text alone. The signer bridge already
scopes its own requests through localPath.
commit
3408716c6332a8604afde0285761b34e0dd16e7c
parent
204be272d9f5a6b51737cd14ae23ac22d1320c06
author
Dami <dami.etoile@gmail.com>
4 files changed, +81 -15
Ainternal/webui/prefix.go39+++++++++++++++++++++++++++++
Ainternal/webui/prefix_test.go41++++++++++++++++++++++++++++++
Minternal/webui/webui.go14----------
Minternal/webui/webui_test.go2+-
A internal/webui/prefix.go +39 -0
A internal/webui/prefix_test.go +41 -0
@@ -0,0 +1,41 @@
1+package webui2+3+import (4+ "strings"5+ "testing"6+)7+8+func TestInjectBasePreservesExternalURLsAndContent(t *testing.T) {9+ for _, tc := range []struct {10+ name, input, want string11+ }{12+ {"local link", `<a href="/repo?path=a%26b">code</a>`, `<a href="/r/alice/repo?path=a%26b">code</a>`},13+ {"external link", `<a href="//example.org/file">external</a>`, `<a href="//example.org/file">external</a>`},14+ {"external image", `<img src="//example.org/icon.png">`, `<img src="//example.org/icon.png">`},15+ {"already scoped", `<a href="/r/alice/files">files</a>`, `<a href="/r/alice/files">files</a>`},16+ {"script source", `<script src="/fixi.js"></script>`, `<script src="/r/alice/fixi.js"></script>`},17+ {"script text", `<script>const example = ' href="/example"';</script>`, `<script>const example = ' href="/example"';</script>`},18+ {"textarea", `<textarea> href="/example"</textarea>`, `<textarea> href="/example"</textarea>`},19+ {"local action", `<button fx-action="/connect/fragment">preview</button>`, `<button fx-action="/r/alice/connect/fragment">preview</button>`},20+ } {21+ t.Run(tc.name, func(t *testing.T) {22+ if got := injectBase(tc.input, "/r/alice"); got != tc.want {23+ t.Fatalf("got %q, want %q", got, tc.want)24+ }25+ })26+ }27+}28+29+func FuzzInjectBasePreservesText(f *testing.F) {30+ for _, seed := range []string{"hello", ` href="/file"`, `fetch("/example")`, `new URL("/file")`, "emoji 🧪", "a\x00b"} {31+ f.Add(seed)32+ }33+ f.Fuzz(func(t *testing.T, content string) {34+ // Textarea content can contain quotes and URL examples literally.35+ content = strings.ReplaceAll(content, "<", "&lt;")36+ input := "<textarea>" + content + "</textarea>"37+ if got := injectBase(input, "/r/alice"); got != input {38+ t.Fatalf("content changed: got %q, want %q", got, input)39+ }40+ })41+}
M internal/webui/webui.go +0 -14
M internal/webui/webui_test.go +1 -1