package zitcli
import (
"context"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// A file whose name begins with a dash was unreachable through every
// content route: zit read the name as an option, so the forge answered
// 500 for a repository that held one, and quarry's own guard then
// refused the name outright rather than serve someone else's help text.
// zit 0.3.25 honours "--" in every verb, and the call sites now put one
// in front of the segment that came from the URL. So the name is a name.
//
// The exploit stays closed in the same test, because the two halves are
// one decision: WITH the marker a dashed name is content, and WITHOUT
// one the guard still refuses it rather than letting it be parsed.
func TestADashedNameIsAName(t *testing.T) {
if _, err := exec.LookPath("zit"); err != nil {
t.Skip("zit not on PATH")
}
dir := t.TempDir()
sh := func(args ...string) {
t.Helper()
cmd := exec.Command("zit", args...)
cmd.Dir = dir
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("zit %v: %v\n%s", args, err, out)
}
}
sh("init")
for name, body := range map[string]string{
"-x.txt": "dashed body\n",
"--help": "not the help text\n",
"plain.txt": "plain body\n",
} {
if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o644); err != nil {
t.Fatal(err)
}
sh("add", "--", name)
}
sh("commit", "-m", "dashed names")
c := New(dir)
ctx := context.Background()
for _, tc := range []struct{ path, want string }{
{"-x.txt", "dashed body"},
{"--help", "not the help text"},
} {
got, err := c.Cat(ctx, tc.path, "trunk")
if err != nil {
t.Errorf("FAIL: Cat(%q): %v", tc.path, err)
continue
}
if !strings.Contains(string(got), tc.want) {
t.Errorf("FAIL: Cat(%q) = %q, want the file's own body", tc.path, got)
}
// The distinguishing evidence: zit's help mentions its verbs.
if strings.Contains(string(got), "try 'zit help'") {
t.Errorf("FAIL: Cat(%q) served zit's own output as content", tc.path)
}
}
names, err := c.LsNames(ctx, "trunk")
if err != nil {
t.Fatalf("LsNames: %v", err)
}
var listed []string
for _, n := range names {
listed = append(listed, n.Name)
}
for _, want := range []string{"-x.txt", "--help", "plain.txt"} {
if !contains(listed, want) {
t.Errorf("FAIL: %q is not in the listing %v", want, listed)
}
}
// Without a marker the guard is still the thing standing in front of
// the parser: this is the shape the exploit took.
if err := checkArgs([]string{"cat", "-R", dir, "--help", "trunk"}); err == nil {
t.Error("FAIL: an unmarked --help would reach zit as an option")
}
t.Log("ok: dashed names are content, and an unmarked one is still refused")
}
func contains(ss []string, want string) bool {
for _, s := range ss {
if s == want {
return true
}
}
return false
}
// The listing of a SUBDIRECTORY is the case the guard broke: quarry
// passes "--under=<path>" as one token, the allowlist compared whole
// tokens, and so every directory page on every repository answered 404
// while the suite stayed green — nothing listed a directory through a
// real zit.
func TestListingASubdirectory(t *testing.T) {
if _, err := exec.LookPath("zit"); err != nil {
t.Skip("zit not on PATH")
}
dir := t.TempDir()
sh := func(args ...string) {
t.Helper()
cmd := exec.Command("zit", args...)
cmd.Dir = dir
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("zit %v: %v\n%s", args, err, out)
}
}
sh("init")
if err := os.MkdirAll(filepath.Join(dir, "internal", "deep"), 0o755); err != nil {
t.Fatal(err)
}
for _, f := range []string{"top.txt", "internal/mid.txt", "internal/deep/leaf.txt"} {
if err := os.WriteFile(filepath.Join(dir, f), []byte(f+"\n"), 0o644); err != nil {
t.Fatal(err)
}
sh("add", "--", f)
}
sh("commit", "-m", "a tree with directories")
got, err := New(dir).LsUnder(context.Background(), "trunk", "internal")
if err != nil {
t.Fatalf("FAIL: LsUnder a directory: %v", err)
}
if len(got) == 0 {
t.Fatal("FAIL: the directory listed nothing")
}
t.Logf("ok: %d entries under internal/", len(got))
// The joined form is allowed by its FLAG NAME, never by the value.
if err := checkArgs([]string{"ls", "-R", "x", "--under=internal"}); err != nil {
t.Errorf("FAIL: a joined value of quarry's own flag was refused: %v", err)
}
if err := checkArgs([]string{"ls", "-R", "x", "--help=internal"}); err == nil {
t.Error("FAIL: a joined value smuggled an unknown flag past the guard")
}
}