2017-05-04 00:49:12 -04:00
|
|
|
package fsutil
|
|
|
|
|
|
|
|
import (
|
|
|
|
"os"
|
2017-09-21 20:54:27 -04:00
|
|
|
"path"
|
|
|
|
"runtime"
|
2017-05-04 00:49:12 -04:00
|
|
|
"sort"
|
|
|
|
"strings"
|
2020-11-12 21:14:57 -05:00
|
|
|
"syscall"
|
2017-05-04 00:49:12 -04:00
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
type parent struct {
|
|
|
|
dir string
|
|
|
|
last string
|
|
|
|
}
|
|
|
|
|
|
|
|
type Validator struct {
|
|
|
|
parentDirs []parent
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *Validator) HandleChange(kind ChangeKind, p string, fi os.FileInfo, err error) (retErr error) {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// test that all paths are in order and all parent dirs were present
|
|
|
|
if v.parentDirs == nil {
|
|
|
|
v.parentDirs = make([]parent, 1, 10)
|
|
|
|
}
|
2017-09-21 20:54:27 -04:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
p = strings.Replace(p, "\\", "", -1)
|
|
|
|
}
|
|
|
|
if p != path.Clean(p) {
|
2020-11-12 21:14:57 -05:00
|
|
|
return errors.WithStack(&os.PathError{Path: p, Err: syscall.EINVAL, Op: "unclean path"})
|
2017-05-04 00:49:12 -04:00
|
|
|
}
|
2017-09-21 20:54:27 -04:00
|
|
|
if path.IsAbs(p) {
|
2020-11-12 21:14:57 -05:00
|
|
|
return errors.WithStack(&os.PathError{Path: p, Err: syscall.EINVAL, Op: "absolute path"})
|
2017-05-04 00:49:12 -04:00
|
|
|
}
|
2017-09-21 20:54:27 -04:00
|
|
|
dir := path.Dir(p)
|
|
|
|
base := path.Base(p)
|
2017-05-04 00:49:12 -04:00
|
|
|
if dir == "." {
|
|
|
|
dir = ""
|
|
|
|
}
|
|
|
|
if dir == ".." || strings.HasPrefix(p, "../") {
|
2020-11-12 21:14:57 -05:00
|
|
|
return errors.WithStack(&os.PathError{Path: p, Err: syscall.EINVAL, Op: "escape check"})
|
2017-05-04 00:49:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// find a parent dir from saved records
|
|
|
|
i := sort.Search(len(v.parentDirs), func(i int) bool {
|
|
|
|
return ComparePath(v.parentDirs[len(v.parentDirs)-1-i].dir, dir) <= 0
|
|
|
|
})
|
|
|
|
i = len(v.parentDirs) - 1 - i
|
|
|
|
if i != len(v.parentDirs)-1 { // skipping back to grandparent
|
|
|
|
v.parentDirs = v.parentDirs[:i+1]
|
|
|
|
}
|
|
|
|
|
|
|
|
if dir != v.parentDirs[len(v.parentDirs)-1].dir || v.parentDirs[i].last >= base {
|
2017-09-21 20:54:27 -04:00
|
|
|
return errors.Errorf("changes out of order: %q %q", p, path.Join(v.parentDirs[i].dir, v.parentDirs[i].last))
|
2017-05-04 00:49:12 -04:00
|
|
|
}
|
|
|
|
v.parentDirs[i].last = base
|
|
|
|
if kind != ChangeKindDelete && fi.IsDir() {
|
|
|
|
v.parentDirs = append(v.parentDirs, parent{
|
2017-09-21 20:54:27 -04:00
|
|
|
dir: path.Join(dir, base),
|
2017-05-04 00:49:12 -04:00
|
|
|
last: "",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
// todo: validate invalid mode combinations
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func ComparePath(p1, p2 string) int {
|
|
|
|
// byte-by-byte comparison to be compatible with str<>str
|
|
|
|
min := min(len(p1), len(p2))
|
|
|
|
for i := 0; i < min; i++ {
|
|
|
|
switch {
|
|
|
|
case p1[i] == p2[i]:
|
|
|
|
continue
|
|
|
|
case p2[i] != '/' && p1[i] < p2[i] || p1[i] == '/':
|
|
|
|
return -1
|
|
|
|
default:
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return len(p1) - len(p2)
|
|
|
|
}
|
|
|
|
|
|
|
|
func min(x, y int) int {
|
|
|
|
if x < y {
|
|
|
|
return x
|
|
|
|
}
|
|
|
|
return y
|
|
|
|
}
|