// SPDX-License-Identifier: AGPL-3.0-or-later package app import ( "bytes" "compress/gzip" "encoding/json" "fmt" "image" "image/color" "image/png" "io" "net/http" "os" "path/filepath" "regexp" "strconv" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "bnema.dev/ermite/internal/domain" "bnema.dev/ermite/internal/ports" ) // Trojan Source: a right-to-left override and an isolate around code. const webTrojan = "package main\n\nfunc main() {\n\taccess := \"user\u202e \u2066// admin only\u2069 \u2066\"\n\t_ = access\n}\n" func tinyPNG(t *testing.T) []byte { t.Helper() img := image.NewRGBA(image.Rect(0, 0, 2, 2)) img.Set(0, 0, color.RGBA{R: 255, A: 255}) var b bytes.Buffer require.NoError(t, png.Encode(&b, img)) return b.Bytes() } // webFilesRepo is the repository of the tree, blob and raw tests: files of // every kind the pages treat differently, a directory README, a branch and a // tag with a slash. func (h *harness) webFilesRepo(name string) { h.t.Helper() _, err := h.forge.Repos.Create(h.ctx, domain.Root(), ports.CreateRepoParams{ Name: h.name(name), Description: "Files of every kind", Visibility: domain.VisibilityPublic, }) require.NoError(h.t, err) wt := h.t.TempDir() h.mustGit(wt, "init", "-q", "--initial-branch=main") h.writeFile(wt, "README.md", []byte("# Files\n\nSee [the guide](docs/guide.md).\n")) h.writeFile(wt, "main.go", []byte("package main\n\nimport \"fmt\"\n\n// main prints.\nfunc main() {\n\tfmt.Println(\"hi \", 42)\n}\n")) h.writeFile(wt, "docs/README.md", []byte("# Docs\n\nBack to [the guide](guide.md) and [root](../main.go).\n")) h.writeFile(wt, "docs/guide.md", []byte("# Guide\n\n## Steps\n\nRun it.\n\n```sh\nmake all\n```\n")) h.writeFile(wt, "src/lib/util.go", []byte("package lib\n")) h.writeFile(wt, "notes.txt", []byte("plain notes\nsecond line\n")) h.writeFile(wt, "big.txt", bytes.Repeat([]byte("0123456789abcdef\n"), 70000)) // 1.1 MiB h.writeFile(wt, "lines.txt", bytes.Repeat([]byte("x\n"), 20005)) h.writeFile(wt, "data.bin", []byte("\x00\x01\x02binary\x00")) h.writeFile(wt, "pic.png", tinyPNG(h.t)) h.writeFile(wt, "logo.svg", []byte(``)) h.writeFile(wt, "page.html", []byte("\n")) h.writeFile(wt, "evil.go", []byte(webTrojan)) h.writeFile(wt, "empty.txt", nil) h.writeFile(wt, "fake.png", []byte("\n")) h.writeFile(wt, "fake.jpg", []byte("GIF89a\x00\x00binary\x00")) h.mustGit(wt, "tag", "v1.0") h.mustGit(wt, "checkout", "-q", "-b", "feature/x") h.writeFile(wt, "x/y.txt", []byte("on the feature branch\n")) h.mustGit(wt, "push", "-q", h.urlAuth(adminToken, "/"+name+".git"), "main", "feature/x", "--tags") } // AC1: the tree and blob pages render for a fixture repository; the HTML is // compared after normalising times and ids. func TestWebFilesGolden(t *testing.T) { h := newHarness(t, harnessOpts{}) h.webFilesRepo("files") for _, tt := range []struct { name, path string status int }{ {"tree-root", "/files/tree/main", 200}, {"tree-subdir", "/files/tree/main/docs", 200}, {"tree-nested", "/files/tree/main/src/lib", 200}, {"tree-slash-ref", "/files/tree/feature/x/x", 200}, {"blob-code", "/files/blob/main/main.go", 200}, {"blob-markdown", "/files/blob/main/docs/guide.md", 200}, {"blob-text", "/files/blob/main/notes.txt", 200}, {"blob-too-large", "/files/blob/main/big.txt", 200}, {"blob-binary", "/files/blob/main/data.bin", 200}, {"blob-image", "/files/blob/main/pic.png", 200}, {"blob-svg", "/files/blob/main/logo.svg", 200}, {"blob-trojan", "/files/blob/main/evil.go", 200}, {"blob-empty", "/files/blob/main/empty.txt", 200}, } { t.Run(tt.name, func(t *testing.T) { status, hdr, body := h.get(tt.path) require.Equal(t, tt.status, status) assert.Equal(t, "no-cache", hdr.Get("Cache-Control")) assert.Contains(t, hdr.Get("Content-Type"), "text/html") assertNoInline(t, body) golden(t, tt.name, normalise(body)) }) } } // AC1, AC2, AC7: ?rows answers the entries of a directory as table rows only, // for the Files view to insert under the directory; a private repository is // the 404 of a missing one. func TestWebTreeRows(t *testing.T) { h := newHarness(t, harnessOpts{}) h.webFilesRepo("files") h.seed("secret-sauce", domain.VisibilityPrivate, "dir/a.txt") // The page links its directories to their rows. _, _, page := h.get("/files/tree/main") assert.Contains(t, page, `href="/files/tree/main/docs" data-expand="/files/tree/main/docs?rows" aria-expanded="false"`) assert.Contains(t, page, `