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, "<script>", "<img src=x onerror=alert(1)>", `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", "\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"`)
}
|