package markup
import (
"strings"
"testing"
)
// A document that ships its diagrams already drawn keeps them. The
// document policy admits no <svg> of its own, so these used to be
// removed without a word: zit's submanifest guide rendered its prose
// about the diagrams and none of the diagrams.
func TestAPreRenderedDiagramSurvives(t *testing.T) {
doc := `<h1>Guide</h1><p>Before.</p>` +
`<svg xmlns="http://www.w3.org/2000/svg" class="pikchr" viewBox="0 0 100 50" width="100" height="50">` +
`<path d="M1,2 L30,40 Z" fill="none" style="stroke:rgb(0,0,0);stroke-width:2"/>` +
`<text x="10" y="20" text-anchor="middle">box</text></svg>` +
`<p>After.</p>`
got := SanitizeDoc(doc)
for _, want := range []string{"<svg", "</svg>", `d="M1,2 L30,40 Z"`, ">box<", "Before.", "After."} {
if !strings.Contains(got, want) {
t.Errorf("FAIL: the rendered document lost %q\n%s", want, got)
}
}
// Two diagrams are two diagrams: the match must not swallow
// everything between them.
two := doc + doc
if n := strings.Count(SanitizeDoc(two), "<svg"); n != 2 {
t.Errorf("FAIL: %d diagrams survived two, want 2", n)
}
}
// The gate does not move. Everything outside Pikchr's own vocabulary is
// stripped, including the things that make SVG an XSS surface.
func TestHostileSVGIsStillStripped(t *testing.T) {
for _, hostile := range []string{
`<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script></svg>`,
`<svg xmlns="http://www.w3.org/2000/svg"><path d="M1,1" onload="alert(1)"/></svg>`,
`<svg xmlns="http://www.w3.org/2000/svg"><foreignObject><body>hi</body></foreignObject></svg>`,
`<svg xmlns="http://www.w3.org/2000/svg"><a xlink:href="javascript:alert(1)"><text x="1" y="1">go</text></a></svg>`,
`<svg xmlns="http://www.w3.org/2000/svg"><image href="https://elsewhere/x.png"/></svg>`,
} {
got := SanitizeDoc("<p>doc</p>" + hostile)
for _, banned := range []string{"<script", "onload=", "foreignObject", "javascript:", "<image", "xlink"} {
if strings.Contains(got, banned) {
t.Errorf("FAIL: %q survived sanitising:\n%s", banned, got)
}
}
// The prose is still there: a hostile diagram costs the
// diagram, not the page.
if !strings.Contains(got, "doc") {
t.Errorf("FAIL: the document was lost with the diagram:\n%s", got)
}
}
}
// Script elsewhere in the document is still refused — lifting the SVG
// out must not carry anything else past the document policy.
func TestTheDocumentPolicyStillApplies(t *testing.T) {
got := SanitizeDoc(`<svg xmlns="http://www.w3.org/2000/svg"><circle cx="1" cy="1" r="1"/></svg>` +
`<script>alert(1)</script><p onclick="alert(1)">hi</p>`)
if strings.Contains(got, "<script") || strings.Contains(got, "onclick") {
t.Errorf("FAIL: the document policy stopped applying:\n%s", got)
}
if !strings.Contains(got, "<svg") {
t.Errorf("FAIL: the diagram was lost:\n%s", got)
}
}
// A document with no diagrams behaves exactly as it did.
func TestADocumentWithoutDiagramsIsUnchanged(t *testing.T) {
doc := `<h1>Plain</h1><p>Nothing drawn here.</p>`
if SanitizeDoc(doc) != Sanitize(doc) {
t.Errorf("FAIL: a document with no diagrams took a different path")
}
}