forked from tonistiigi/fsutil
-
Notifications
You must be signed in to change notification settings - Fork 0
/
validator.go
93 lines (84 loc) · 2.08 KB
/
validator.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package fsutil
import (
"os"
"path"
"runtime"
"sort"
"strings"
"syscall"
"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)
}
if runtime.GOOS == "windows" {
p = strings.Replace(p, "\\", "", -1)
}
if p != path.Clean(p) {
return errors.WithStack(&os.PathError{Path: p, Err: syscall.EINVAL, Op: "unclean path"})
}
if path.IsAbs(p) {
return errors.WithStack(&os.PathError{Path: p, Err: syscall.EINVAL, Op: "absolute path"})
}
dir := path.Dir(p)
base := path.Base(p)
if dir == "." {
dir = ""
}
if dir == ".." || strings.HasPrefix(p, "../") {
return errors.WithStack(&os.PathError{Path: p, Err: syscall.EINVAL, Op: "escape check"})
}
// 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 {
return errors.Errorf("changes out of order: %q %q", p, path.Join(v.parentDirs[i].dir, v.parentDirs[i].last))
}
v.parentDirs[i].last = base
if kind != ChangeKindDelete && fi.IsDir() {
v.parentDirs = append(v.parentDirs, parent{
dir: path.Join(dir, base),
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
}