fileserver: Improve file hiding logic for directories and prefixes

Now, a filename to hide that is specified without a path separator will
count as hidden if it appears in any component of the file path (not
only the last component); semantically, this means hiding a file by only
its name (without any part of a path) will hide both files and folders,
e.g. hiding ".git" will hide "/.git" and also "/.git/foo".

We also do prefix matching so that hiding "/.git" will hide "/.git"
and "/.git/foo" but not "/.gitignore".

The remaining logic is a globular match like before.
This commit is contained in:
Matthew Holt 2020-09-11 12:20:39 -06:00
parent 9859ab8148
commit 0ee4378227
No known key found for this signature in database
GPG key ID: 2A349DD577D586A5
2 changed files with 95 additions and 17 deletions

View file

@ -329,28 +329,37 @@ func sanitizedPathJoin(root, reqPath string) string {
// fileHidden returns true if filename is hidden
// according to the hide list.
func fileHidden(filename string, hide []string) bool {
nameOnly := filepath.Base(filename)
sep := string(filepath.Separator)
var components []string
for _, h := range hide {
// assuming h is a glob/shell-like pattern,
// use it to compare the whole file path;
// but if there is no separator in h, then
// just compare against the file's name
compare := filename
if !strings.Contains(h, sep) {
compare = nameOnly
}
hidden, err := filepath.Match(h, compare)
if err != nil {
// malformed pattern; fallback by checking prefix
if strings.HasPrefix(filename, h) {
// if there is no separator in h, then we assume the user
// wants to hide any files or folders that match that
// name; thus we have to compare against each component
// of the filename, e.g. hiding "bar" would hide "/bar"
// as well as "/foo/bar/baz" but not "/barstool".
if len(components) == 0 {
components = strings.Split(filename, sep)
}
for _, c := range components {
if c == h {
return true
}
}
} else if strings.HasPrefix(filename, h) {
// otherwise, if there is a separator in h, and
// filename is exactly prefixed with h, then we
// can do a prefix match so that "/foo" matches
// "/foo/bar" but not "/foobar".
withoutPrefix := strings.TrimPrefix(filename, h)
if strings.HasPrefix(withoutPrefix, sep) {
return true
}
}
if hidden {
// file name or path matches hide pattern
// in the general case, a glob match will suffice
if hidden, _ := filepath.Match(h, filename); hidden {
return true
}
}

View file

@ -93,9 +93,78 @@ func TestSanitizedPathJoin(t *testing.T) {
}
actual := sanitizedPathJoin(tc.inputRoot, u.Path)
if actual != tc.expect {
t.Errorf("Test %d: [%s %s] => %s (expected %s)", i, tc.inputRoot, tc.inputPath, actual, tc.expect)
t.Errorf("Test %d: [%s %s] => %s (expected %s)",
i, tc.inputRoot, tc.inputPath, actual, tc.expect)
}
}
}
// TODO: test fileHidden
func TestFileHidden(t *testing.T) {
for i, tc := range []struct {
inputHide []string
inputPath string
expect bool
}{
{
inputHide: nil,
inputPath: "",
expect: false,
},
{
inputHide: []string{".gitignore"},
inputPath: "/.gitignore",
expect: true,
},
{
inputHide: []string{".git"},
inputPath: "/.gitignore",
expect: false,
},
{
inputHide: []string{"/.git"},
inputPath: "/.gitignore",
expect: false,
},
{
inputHide: []string{".git"},
inputPath: "/.git",
expect: true,
},
{
inputHide: []string{".git"},
inputPath: "/.git/foo",
expect: true,
},
{
inputHide: []string{".git"},
inputPath: "/foo/.git/bar",
expect: true,
},
{
inputHide: []string{"/prefix"},
inputPath: "/prefix/foo",
expect: true,
},
{
inputHide: []string{"/foo/*/bar"},
inputPath: "/foo/asdf/bar",
expect: true,
},
{
inputHide: []string{"/foo"},
inputPath: "/foo",
expect: true,
},
{
inputHide: []string{"/foo"},
inputPath: "/foobar",
expect: false,
},
} {
actual := fileHidden(tc.inputPath, tc.inputHide)
if actual != tc.expect {
t.Errorf("Test %d: Is %s hidden in %v? Got %t but expected %t",
i, tc.inputPath, tc.inputHide, actual, tc.expect)
}
}
}