current commit
261029.2 · www --public reaches only the open internet and shows nothing of the machine
package main

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
	"path"
	"path/filepath"
	"strings"
)

// followMarker marks a directory that links out of a tree may reach. lord
// keeps csi and .claude in a store outside the repo; one marker in the
// store root opens them all.
var followMarker = "." + progName + "-follow"

// treeFS opens a tree's files through its os.Root, which follows a link
// only while it stays inside the tree and never an absolute one. What the
// root refuses is resolved again: an absolute link back into the tree is
// served from the tree, and a link out of it is served when a marker lies
// at or above its target and other users could read the target. A shared
// tree also refuses whatever other users could not read inside it.
type treeFS struct{ t *tree }

func (f treeFS) Open(name string) (fs.File, error) {
	// A shared tree hides its locks, which name the tree's path, and its
	// dot entries, since .git holds the history and deleted files.
	if f.t.shared && (isLockName(path.Base(name)) || f.t.dotted(name)) {
		return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
	}
	// A public tree hides the worktree's .git, which names the cache's
	// path.
	if f.t.public && f.t.inGit(name) {
		return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
	}
	file, err := f.open(name)
	if err != nil || !f.t.shared {
		return file, err
	}
	if ref := f.t.private(name); ref != nil {
		file.Close()
		return nil, ref
	}
	return file, nil
}

// unlisted reports whether a listing of dir leaves out its entry n: a
// lock, or on a share a dot entry. The listing and its version agree, so
// a hidden file never reloads the page.
func (t *tree) unlisted(dir, n string) bool {
	return isLockName(n) || t.shared && t.dotted(path.Join(dir, n)) || t.public && dir == "." && n == ".git"
}

// dotted reports whether name, or where it resolves inside the tree,
// passes a name that begins with a dot.
func (t *tree) dotted(name string) bool {
	if hasDotName(name) {
		return true
	}
	real, err := filepath.EvalSymlinks(filepath.Join(t.dir, filepath.FromSlash(name)))
	if err != nil || !within(real, t.realDir) {
		return false
	}
	rel, err := filepath.Rel(t.realDir, real)
	return err == nil && hasDotName(filepath.ToSlash(rel))
}

// inGit reports whether name, or where it resolves inside the tree, is
// the worktree's .git or below it.
func (t *tree) inGit(name string) bool {
	isGit := func(n string) bool { return n == ".git" || strings.HasPrefix(n, ".git/") }
	if isGit(name) {
		return true
	}
	real, err := filepath.EvalSymlinks(filepath.Join(t.dir, filepath.FromSlash(name)))
	if err != nil || !within(real, t.realDir) {
		return false
	}
	rel, err := filepath.Rel(t.realDir, real)
	return err == nil && isGit(filepath.ToSlash(rel))
}

// hasDotName reports whether a name on the way to name begins with a dot.
func hasDotName(name string) bool {
	if name == "." {
		return false
	}
	for _, part := range strings.Split(name, "/") {
		if strings.HasPrefix(part, ".") {
			return true
		}
	}
	return false
}

func (f treeFS) open(name string) (fs.File, error) {
	file, err := f.t.root.FS().Open(name)
	if err == nil || !fs.ValidPath(name) {
		return file, err
	}
	real, rerr := filepath.EvalSymlinks(filepath.Join(f.t.dir, filepath.FromSlash(name)))
	if rerr != nil {
		return nil, err
	}
	if within(real, f.t.realDir) {
		rel, rerr := filepath.Rel(f.t.realDir, real)
		if rerr != nil || filepath.ToSlash(rel) == name {
			return nil, err
		}
		return f.t.root.FS().Open(filepath.ToSlash(rel))
	}
	// A stranger's repo never links out: a marker on the machine is
	// the owner's, not the repo's, to grant.
	if f.t.public {
		ref := &refusal{}
		ref.link, ref.target = f.t.outLink(name)
		return nil, ref
	}
	marker, ref := f.t.follow(name, real)
	if ref != nil {
		return nil, ref
	}
	root, err := os.OpenRoot(marker)
	if err != nil {
		return nil, err
	}
	defer root.Close()
	rel, err := filepath.Rel(marker, real)
	if err != nil {
		return nil, err
	}
	return root.Open(rel)
}

// refusal says why a link out of the tree is not followed: no marker lies
// at or above the target, or other users cannot read path.
type refusal struct {
	link    string // the link in the tree, relative to its root
	target  string // where the link leads
	dir     bool   // the target is a directory
	private bool
	path    string      // what other users cannot read
	mode    fs.FileMode // its permission bits
}

func (r *refusal) Error() string {
	msg := r.link + " links outside the tree to\n  " + r.target + "\n"
	if !r.private {
		where := "the target or a directory above it"
		if !r.dir {
			where = "the target's directory or one above it"
		}
		return msg + progName + " follows such links only into a directory that holds " +
			followMarker + ", or below one. Create it in " + where + "."
	}
	what := "it"
	if r.path != r.target {
		what = r.path
	}
	return msg + fmt.Sprintf("Other users cannot read %s (mode %03o), so "+progName+" does not serve it.", what, r.mode.Perm())
}

// follow decides whether name, which resolves to real outside the tree,
// may be served. It returns the directory holding the marker, or why not.
func (t *tree) follow(name, real string) (string, *refusal) {
	ref := &refusal{}
	ref.link, ref.target = t.outLink(name)
	if info, err := os.Stat(ref.target); err == nil {
		ref.dir = info.IsDir()
	}
	marker := findMarker(real)
	if marker == "" {
		return "", ref
	}
	if p, mode, ok := othersRead(marker, real); !ok {
		ref.private, ref.path, ref.mode = true, p, mode
		return "", ref
	}
	return marker, nil
}

// outLink finds the first link on the way to name that leads out of the
// tree, and where it leads.
func (t *tree) outLink(name string) (link, target string) {
	parts := strings.Split(name, "/")
	for i := range parts {
		p := path.Join(parts[:i+1]...)
		real, err := filepath.EvalSymlinks(filepath.Join(t.dir, filepath.FromSlash(p)))
		if err == nil && !within(real, t.realDir) {
			return p, real
		}
	}
	return name, ""
}

// findMarker returns the nearest directory at or above real that holds the
// marker, or "".
func findMarker(real string) string {
	dir := real
	if info, err := os.Stat(real); err != nil || !info.IsDir() {
		dir = filepath.Dir(real)
	}
	for {
		if info, err := os.Lstat(filepath.Join(dir, followMarker)); err == nil && info.Mode().IsRegular() {
			return dir
		}
		up := filepath.Dir(dir)
		if up == dir {
			return ""
		}
		dir = up
	}
}

// othersRead reports whether other users could read real, and every
// directory from the marker down to it: a file needs the others-read bit, a
// directory others-read and others-execute. Otherwise it returns the first
// path from the marker down that fails, with its mode.
func othersRead(marker, real string) (string, fs.FileMode, bool) {
	rel, err := filepath.Rel(marker, real)
	if err != nil {
		return real, 0, false
	}
	p := marker
	steps := []string{p}
	if rel != "." {
		for _, s := range strings.Split(rel, string(filepath.Separator)) {
			p = filepath.Join(p, s)
			steps = append(steps, p)
		}
	}
	for _, p := range steps {
		info, err := os.Stat(p)
		if err != nil {
			return p, 0, false
		}
		need := fs.FileMode(0o004)
		if info.IsDir() {
			need = 0o005
		}
		if info.Mode().Perm()&need != need {
			return p, info.Mode(), false
		}
	}
	return "", 0, true
}

// private refuses name in a shared tree when other users could not read
// it, or could not pass a directory on the way. Each step of name is
// checked where it resolves, from the tree's root or from the marker
// above a link's target.
func (t *tree) private(name string) *refusal {
	parts := []string{"."}
	if name != "." {
		parts = strings.Split(name, "/")
	}
	for i := range parts {
		p := path.Join(parts[:i+1]...)
		real, err := filepath.EvalSymlinks(filepath.Join(t.dir, filepath.FromSlash(p)))
		if err != nil {
			return &refusal{link: name, private: true}
		}
		base := t.realDir
		if !within(real, t.realDir) {
			if base = findMarker(real); base == "" {
				return &refusal{link: name, target: real}
			}
		}
		if bad, mode, ok := othersRead(base, real); !ok {
			ref := &refusal{link: name, target: real, private: true, path: bad, mode: mode}
			if info, err := os.Stat(real); err == nil && p == name {
				ref.dir = info.IsDir()
			}
			return ref
		}
	}
	return nil
}

// refused reports why name cannot be opened, or nil when it can or fails
// for another reason.
func (t *tree) refused(name string) *refusal {
	f, err := t.fsys.Open(name)
	if err == nil {
		f.Close()
		return nil
	}
	var ref *refusal
	if errors.As(err, &ref) {
		return ref
	}
	return nil
}