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
}