Skip to content
quarry Artifact [40d61dd285]
Login

Artifact [40d61dd285]

Artifact 40d61dd285587bceb1f3c7ff9bafeeaea51395aab28e4175ee39fa2ead119dec:


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")
	}
}

// EVERY call site that spawns zit, against a real zit, in one test.
//
// The marker work was verified verb by verb by hand and shipped with
// tarball and zip unexercised — they count "--" as an OPERAND, so the
// archive routes answered 503 fleet-wide until jva downloaded one. The
// lesson is not "test archives": it is that a change to how arguments
// are SPELLED touches every verb at once, so the test has to be a
// sweep rather than a sample. A new call site belongs in this list.
func TestEveryCallSiteSpawnsZitSuccessfully(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, "sub"), 0o755); err != nil {
		t.Fatal(err)
	}
	for _, f := range []string{"a.txt", "sub/b.txt"} {
		if err := os.WriteFile(filepath.Join(dir, f), []byte(f+"\n"), 0o644); err != nil {
			t.Fatal(err)
		}
		sh("add", "--", f)
	}
	sh("commit", "-m", "base")

	c, ctx := New(dir), context.Background()
	hash, _, err := c.Info(ctx, "trunk")
	if err != nil {
		t.Fatalf("FAIL: Info: %v", err)
	}

	for _, tc := range []struct {
		name string
		run  func() error
	}{
		{"Ls", func() error { _, e := c.Ls(ctx, "trunk"); return e }},
		{"LsNames", func() error { _, e := c.LsNames(ctx, "trunk"); return e }},
		{"LsUnder", func() error { _, e := c.LsUnder(ctx, "trunk", "sub"); return e }},
		{"Cat", func() error { _, e := c.Cat(ctx, "a.txt", "trunk"); return e }},
		{"Info", func() error { _, _, e := c.Info(ctx, "trunk"); return e }},
		{"FileHistory", func() error { _, e := c.FileHistory(ctx, "trunk", "a.txt", 5); return e }},
		{"ArchiveTo tar", func() error {
			return c.ArchiveTo(ctx, "tar", "trunk", "", filepath.Join(dir, "o.tar.gz"), 1<<20)
		}},
		{"ArchiveTo zip", func() error {
			return c.ArchiveTo(ctx, "zip", "trunk", "", filepath.Join(dir, "o.zip"), 1<<20)
		}},
		{"ArtifactRaw", func() error { _, e := c.ArtifactRaw(ctx, hash, 1<<20); return e }},
	} {
		if err := tc.run(); err != nil {
			t.Errorf("FAIL: %s: %v", tc.name, err)
			continue
		}
		t.Logf("ok: %s", tc.name)
	}
}