fix: escape variant names, single-pass StripTags, WASM security contract
- Escape variant name in Responsive.Render HTML attribute (XSS fix) - Rewrite StripTags to single-pass O(n) space collapsing - Document Raw() security contract in WASM entry point - Add TestAttr_NonElement coverage - Fix Makefile WASM target to rebuild on source changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
18d2933315
commit
9bc1fa7c69
5 changed files with 31 additions and 11 deletions
2
Makefile
2
Makefile
|
|
@ -11,7 +11,7 @@ test:
|
|||
|
||||
wasm: $(WASM_OUT)
|
||||
|
||||
$(WASM_OUT):
|
||||
$(WASM_OUT): $(shell find . -name '*.go' -not -path './dist/*')
|
||||
@mkdir -p dist
|
||||
GOOS=js GOARCH=wasm go build -ldflags="-s -w" -o $(WASM_OUT) ./cmd/wasm/
|
||||
@RAW=$$(stat -c%s "$(WASM_OUT)" 2>/dev/null || stat -f%z "$(WASM_OUT)"); \
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ import (
|
|||
html "forge.lthn.ai/core/go-html"
|
||||
)
|
||||
|
||||
// renderToString builds an HLCRF layout from JS arguments and returns HTML.
|
||||
// Slot content is injected via Raw() — the caller is responsible for sanitisation.
|
||||
// This is intentional: the WASM module is a rendering engine for trusted content
|
||||
// produced server-side or by the application's own templates.
|
||||
func renderToString(_ js.Value, args []js.Value) any {
|
||||
if len(args) < 1 {
|
||||
return ""
|
||||
|
|
|
|||
|
|
@ -183,6 +183,14 @@ func TestElNode_MultipleAttrs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAttr_NonElement(t *testing.T) {
|
||||
node := Attr(Raw("text"), "class", "x")
|
||||
got := node.Render(NewContext())
|
||||
if got != "text" {
|
||||
t.Errorf("Attr on non-element should return unchanged, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchNode(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
cases := map[string]Node{
|
||||
|
|
|
|||
22
pipeline.go
22
pipeline.go
|
|
@ -7,30 +7,38 @@ import (
|
|||
)
|
||||
|
||||
// StripTags removes HTML tags from rendered output, returning plain text.
|
||||
// Tag boundaries are replaced with a single space; result is trimmed.
|
||||
// Tag boundaries are collapsed into single spaces; result is trimmed.
|
||||
// Does not handle script/style element content (go-html does not generate these).
|
||||
func StripTags(html string) string {
|
||||
var b strings.Builder
|
||||
inTag := false
|
||||
prevSpace := true // starts true to trim leading space
|
||||
for _, r := range html {
|
||||
if r == '<' {
|
||||
inTag = true
|
||||
b.WriteByte(' ')
|
||||
continue
|
||||
}
|
||||
if r == '>' {
|
||||
inTag = false
|
||||
if !prevSpace {
|
||||
b.WriteByte(' ')
|
||||
prevSpace = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !inTag {
|
||||
if r == ' ' || r == '\t' || r == '\n' {
|
||||
if !prevSpace {
|
||||
b.WriteByte(' ')
|
||||
prevSpace = true
|
||||
}
|
||||
} else {
|
||||
b.WriteRune(r)
|
||||
prevSpace = false
|
||||
}
|
||||
}
|
||||
// Collapse multiple spaces into one.
|
||||
result := b.String()
|
||||
for strings.Contains(result, " ") {
|
||||
result = strings.ReplaceAll(result, " ", " ")
|
||||
}
|
||||
return strings.TrimSpace(result)
|
||||
return strings.TrimSpace(b.String())
|
||||
}
|
||||
|
||||
// Imprint renders a node tree to HTML, strips tags, tokenises the text,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ func (r *Responsive) Render(ctx *Context) string {
|
|||
var b strings.Builder
|
||||
for _, v := range r.variants {
|
||||
b.WriteString(`<div data-variant="`)
|
||||
b.WriteString(v.name)
|
||||
b.WriteString(escapeAttr(v.name))
|
||||
b.WriteString(`">`)
|
||||
b.WriteString(v.layout.Render(ctx))
|
||||
b.WriteString(`</div>`)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue