93 lines
2.9 KiB
Go
93 lines
2.9 KiB
Go
package web
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/config"
|
|
)
|
|
|
|
const sampleIndexHTML = `<!doctype html>
|
|
<html><head>
|
|
<title>{{ .AppName }}</title>
|
|
<meta property="og:title" content="{{ .AppName }}">
|
|
<meta property="og:description" content="{{ .Description }}">
|
|
<script>window.__MINSTREL__ = { appName: "{{ .AppName }}", description: "{{ .Description }}" };</script>
|
|
</head><body></body></html>`
|
|
|
|
func TestApplyBrandingTemplate_Defaults(t *testing.T) {
|
|
out, err := applyBrandingTemplate(sampleIndexHTML, config.BrandingConfig{
|
|
AppName: "Minstrel",
|
|
Description: "Self-hosted music server.",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("apply: %v", err)
|
|
}
|
|
body := string(out)
|
|
if !strings.Contains(body, "<title>Minstrel</title>") {
|
|
t.Errorf("missing default title; body=%s", body)
|
|
}
|
|
}
|
|
|
|
func TestApplyBrandingTemplate_CustomBranding(t *testing.T) {
|
|
out, err := applyBrandingTemplate(sampleIndexHTML, config.BrandingConfig{
|
|
AppName: "Family Jukebox",
|
|
Description: "Our home library.",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("apply: %v", err)
|
|
}
|
|
body := string(out)
|
|
if !strings.Contains(body, "<title>Family Jukebox</title>") {
|
|
t.Errorf("custom AppName not in title; body=%s", body)
|
|
}
|
|
if !strings.Contains(body, `og:title" content="Family Jukebox"`) {
|
|
t.Errorf("custom AppName not in og:title; body=%s", body)
|
|
}
|
|
if !strings.Contains(body, `appName: "Family Jukebox"`) {
|
|
t.Errorf("custom AppName not in inline window.__MINSTREL__; body=%s", body)
|
|
}
|
|
}
|
|
|
|
func TestApplyBrandingTemplate_XSSEscape_ScriptContext(t *testing.T) {
|
|
// Operator config is trusted, but defensive escaping is still required:
|
|
// an operator who doesn't realize their value is interpolated into HTML
|
|
// shouldn't be able to accidentally break out of contexts.
|
|
out, err := applyBrandingTemplate(sampleIndexHTML, config.BrandingConfig{
|
|
AppName: "</script><img onerror=\"alert(1)\">",
|
|
Description: "ok",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("apply: %v", err)
|
|
}
|
|
body := string(out)
|
|
|
|
scriptOpen := strings.Index(body, "<script>window.__MINSTREL__")
|
|
if scriptOpen < 0 {
|
|
t.Fatalf("inline script block missing; body=%s", body)
|
|
}
|
|
scriptClose := strings.Index(body[scriptOpen:], "</script>")
|
|
if scriptClose < 0 {
|
|
t.Fatalf("inline script block has no close; body=%s", body)
|
|
}
|
|
scriptBody := body[scriptOpen : scriptOpen+scriptClose]
|
|
if strings.Contains(scriptBody, "</script>") {
|
|
t.Errorf("unescaped </script> inside inline-script context: %s", scriptBody)
|
|
}
|
|
}
|
|
|
|
func TestApplyBrandingTemplate_XSSEscape_AttrContext(t *testing.T) {
|
|
out, err := applyBrandingTemplate(sampleIndexHTML, config.BrandingConfig{
|
|
AppName: `"><img onerror="alert(1)">`,
|
|
Description: "ok",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("apply: %v", err)
|
|
}
|
|
body := string(out)
|
|
// The unescaped HTML should NOT appear as a live tag in attribute context.
|
|
if strings.Contains(body, `"><img onerror="alert(1)">`) {
|
|
t.Errorf("unescaped attribute breakout: %s", body)
|
|
}
|
|
}
|