Download
136 lines 5 KiBGoModified 2026-10-18 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
package render_test

import (
	"regexp"
	"strings"
	"testing"
)

func TestHighlightParity(t *testing.T) {
	e := newEnv(t, "proj", "")
	e.seed()
	e.render()
	re := regexp.MustCompile(`(?s)<code class="chroma">(.*?)</code>`)
	fromFile := re.FindStringSubmatch(e.read(e.page("x.lua")))
	fromFence := re.FindStringSubmatch(e.read(e.page("")))
	if fromFile == nil || fromFence == nil {
		t.Fatal("no highlighted code found")
	}
	if fromFile[1] != fromFence[1] {
		t.Errorf("file page and fenced block differ:\n%s\n---\n%s", fromFile[1], fromFence[1])
	}
	if !strings.Contains(fromFile[1], `class="kd">local`) {
		t.Errorf("lua not highlighted: %s", fromFile[1])
	}
}

func TestMarkdownSafety(t *testing.T) {
	e := newEnv(t, "proj", "")
	e.write("README.md", "<script>alert(1)</script>\n\ntext <img src=x onerror=alert(1)> end\n\n[up](../../etc/passwd) [abs](/other) [mail](mailto:a@b.c)\n")
	e.commit("evil")
	e.push(false)
	e.render()
	root := e.read(e.page(""))
	mustNotContain(t, "readme", root, "<script>alert", "<img src=x")
	mustContain(t, "readme", root, "&lt;script&gt;", "&lt;img src=x onerror=alert(1)&gt;", `href="../../etc/passwd"`, `href="/code/proj/other"`, `href="mailto:a@b.c"`)
}

func TestRawCopiesAndThemedSVG(t *testing.T) {
	e := newEnv(t, "proj", "")
	e.seed()
	e.render()
	// Raw copies keep their real names; httpd sandboxes them.
	e.mustExist(e.raw("foo.html"), e.raw("note.svg"))
	e.mustNotExist(e.raw("foo.html.txt"))
	mustNotContain(t, "page", e.read(e.page("src/main.c")), "http-equiv")
	mustContain(t, "svg page", e.read(e.page("note.svg")), `<img src="/svg/proj/note.svg"`, `href="/raw/proj/note.svg"`)
	themed := e.read("svg/proj/note.svg")
	mustContain(t, "themed svg", themed, `viewBox="0 0 1 1"><style>:root{color:#`, `fill="currentColor"`)
	if e.read(e.raw("note.svg")) == themed {
		t.Error("raw svg should stay byte-exact")
	}
}

func TestLargeFiles(t *testing.T) {
	e := newEnv(t, "proj", "raw_max_bytes: 64\nhighlight_max_bytes: 16\n")
	e.write("big.txt", strings.Repeat("x", 100)+"\n")
	e.write("mid.txt", "line one\nline two\nline three\n")
	e.write("small.txt", "tiny\n")
	e.write("unterminated.txt", "one\ntwo")
	e.commit("sizes")
	e.push(false)
	e.render()

	big := e.read(e.page("big.txt"))
	// Pages count lines as the listings do, wc -l style, even for a file
	// too large to read whole.
	mustContain(t, "big", big, "too large to serve", `class="file file-large `, "<span>1 line</span>")
	mustNotContain(t, "big", big, `<table class="code">`, `href="/raw/proj/big.txt"`, "Binary file")
	e.mustNotExist(e.raw("big.txt"), e.raw("big.txt.gz"))

	mid := e.read(e.page("mid.txt"))
	mustContain(t, "mid", mid, "too large to display", `href="/raw/proj/mid.txt"`, "3 lines")
	mustNotContain(t, "mid", mid, `<table class="code">`)
	e.mustExist(e.raw("mid.txt"))

	mustContain(t, "small", e.read(e.page("small.txt")), `<table class="code">`)
	mustContain(t, "unterminated", e.read(e.page("unterminated.txt")), "<span>1 line</span>", `id="L2"`)
	mustContain(t, "listing", e.read(e.page("")), `<span class="amount">1 line</span>`)
}

// TestRawNameCollisions: committed files whose names look like another
// file's derived outputs (X.gz) must survive renders,
// sweeps and the deletion of their neighbour, whatever order workers run in.
func TestRawNameCollisions(t *testing.T) {
	e := newEnv(t, "proj", "")
	e.write("LICENSE", "license\n")
	e.write("LICENSE.txt", "license txt\n")
	e.write("dist/a.tar", "tar\n")
	e.write("dist/a.tar.gz", "\x1f\x8b not really gzip\n")
	e.commit("collisions")
	e.push(false)
	check := func(when string) {
		t.Helper()
		for p, want := range map[string]string{
			"LICENSE": "license\n", "LICENSE.txt": "license txt\n",
			"dist/a.tar": "tar\n", "dist/a.tar.gz": "\x1f\x8b not really gzip\n",
		} {
			if got := e.read(e.raw(p)); got != want {
				t.Errorf("%s: raw %s = %q, want %q", when, p, got, want)
			}
		}
	}
	e.render()
	check("first render")
	if _, err := e.site.RenderRepo("proj", true); err != nil {
		t.Fatal(err)
	}
	check("full render")

	// Deleting the neighbours must not take the survivors with them.
	e.rm("LICENSE")
	e.rm("dist/a.tar")
	e.commit("remove")
	e.push(false)
	e.render()
	e.mustNotExist(e.raw("LICENSE"), e.raw("dist/a.tar"), e.page("LICENSE"))
	for p, want := range map[string]string{"LICENSE.txt": "license txt\n", "dist/a.tar.gz": "\x1f\x8b not really gzip\n"} {
		if got := e.read(e.raw(p)); got != want {
			t.Errorf("after delete: raw %s = %q, want %q", p, got, want)
		}
	}
}

func TestScriptedSVGPreviewed(t *testing.T) {
	e := newEnv(t, "proj", "")
	e.write("evil.svg", `<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script><rect width="1" height="1"/></svg>`)
	e.write("README.md", "![e](evil.svg)\n")
	e.commit("svgs")
	e.push(false)
	e.render()
	// <img> runs no script and httpd sandboxes /raw and /svg, so a
	// scripted SVG is previewed like any other.
	e.mustExist(e.raw("evil.svg"), "svg/proj/evil.svg")
	mustContain(t, "page", e.read(e.page("evil.svg")), `<img src="/svg/proj/evil.svg"`)
	mustContain(t, "readme", e.read(e.page("")), `src="/svg/proj/evil.svg"`)
}