Files

46 lines
1.8 KiB
Go
Raw Permalink Normal View History

package store
import (
"os"
"path/filepath"
"strings"
)
// UnderRoot reports whether p resolves inside root ON DISK — following every
// symlink on the way, including one planted in a directory that already
// exists. p need not exist: the deepest existing ancestor is what gets
// resolved, so the answer is available before anything is created.
//
// A lexical check (filepath.Rel, path.Clean, HasPrefix) answers a question
// about the STRING. os.MkdirAll, os.CreateTemp, os.Rename and os.Open all
// answer the question about the FILESYSTEM, and that is the one that decides
// where the bytes land: "docs/x.md" is a clean relative path by every lexical
// test and lands outside the root the moment "docs" is a symlink. It lives in
// this package because the two callers that need it — the syncer materializing
// a peer's op into a mount, and the file:// backend resolving a key under the
// hub's storage root — are the same question about different roots.
func UnderRoot(root, p string) bool {
rootReal, err := filepath.EvalSymlinks(root)
if err != nil {
return false
}
for dir := p; ; {
if real, err := filepath.EvalSymlinks(dir); err == nil {
rel, err := filepath.Rel(rootReal, real)
return err == nil && rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator))
}
// A component that EXISTS but does not resolve is a dangling symlink,
// and stepping past it answered "inside the root" for a path that
// lands wherever the link points the moment anything creates the
// target. The guard must answer about where the bytes would go.
if fi, err := os.Lstat(dir); err == nil && fi.Mode()&os.ModeSymlink != 0 {
return false
}
parent := filepath.Dir(dir)
if parent == dir {
return false // walked off the top without finding anything that exists
}
dir = parent
}
}