Skip to content
quarry Artifact [5311d85f6d]
Login

Artifact [5311d85f6d]

Artifact 5311d85f6d8d73cd80134d870c7bc83bbfa7b2284b74fbdd44b204699eb59af3:


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")
	}
}