fix: hide denied entries in CHFS listings
This commit is contained in:
@@ -168,7 +168,9 @@ func (s *Server) files(w http.ResponseWriter, r *http.Request) {
|
|||||||
p := qpath(r)
|
p := qpath(r)
|
||||||
m := a.MaskFor(p)
|
m := a.MaskFor(p)
|
||||||
if !m.HasRead() {
|
if !m.HasRead() {
|
||||||
http.Error(w, "forbidden", 403)
|
// CHFS keeps the files endpoint HTTP-successful for a denied directory
|
||||||
|
// and returns an empty listing with currentMask="".
|
||||||
|
writeJSON(w, map[string]any{"files": []fileInfo{}, "currentMask": "", "virtualRoot": false})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
list, e := s.Store.List(p)
|
list, e := s.Store.List(p)
|
||||||
@@ -179,6 +181,11 @@ func (s *Server) files(w http.ResponseWriter, r *http.Request) {
|
|||||||
out := make([]fileInfo, 0, len(list))
|
out := make([]fileInfo, 0, len(list))
|
||||||
for _, fi := range list {
|
for _, fi := range list {
|
||||||
fp := path.Join(p, fi.Name())
|
fp := path.Join(p, fi.Name())
|
||||||
|
// A rule.none entry is not merely read-less metadata: CHFS hides the
|
||||||
|
// entry from directory listings because the directory itself is denied.
|
||||||
|
if !a.MaskForEntry(fp).HasRead() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
sub := false
|
sub := false
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
if xs, e := s.Store.List(fp); e == nil {
|
if xs, e := s.Store.List(fp); e == nil {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ func TestPathRuleSemantics(t *testing.T) {
|
|||||||
{Dir: "share", Mask: ParseMask("w"), ChildrenOnly: true},
|
{Dir: "share", Mask: ParseMask("w"), ChildrenOnly: true},
|
||||||
{Dir: "drop", Mask: ParseMask("d"), ChildrenOnly: true},
|
{Dir: "drop", Mask: ParseMask("d"), ChildrenOnly: true},
|
||||||
{Dir: "pub*", Mask: ParseMask("r")},
|
{Dir: "pub*", Mask: ParseMask("r")},
|
||||||
|
{Dir: "team/?", Mask: ParseMask("w")},
|
||||||
}
|
}
|
||||||
cases := []struct{ path, want string }{
|
cases := []struct{ path, want string }{
|
||||||
{"", "R"},
|
{"", "R"},
|
||||||
@@ -20,6 +21,42 @@ func TestPathRuleSemantics(t *testing.T) {
|
|||||||
{"drop/x.txt", "RWD"},
|
{"drop/x.txt", "RWD"},
|
||||||
{"public/x.txt", "R"},
|
{"public/x.txt", "R"},
|
||||||
{"public/sub/x.txt", "R"},
|
{"public/sub/x.txt", "R"},
|
||||||
|
{"team/a", "RW"},
|
||||||
|
{"team/ab", "R"},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
if got := permStringForTest(a.MaskFor(tc.path)); got != tc.want {
|
||||||
|
t.Errorf("MaskFor(%q)=%q want %q", tc.path, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entryCases := []struct{ path, want string }{
|
||||||
|
{"private", ""},
|
||||||
|
{"share", "R"},
|
||||||
|
{"drop", "R"},
|
||||||
|
{"public", "R"},
|
||||||
|
{"team/a", "RW"},
|
||||||
|
}
|
||||||
|
for _, tc := range entryCases {
|
||||||
|
if got := permStringForTest(a.MaskForEntry(tc.path)); got != tc.want {
|
||||||
|
t.Errorf("MaskForEntry(%q)=%q want %q", tc.path, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoreSpecificRuleWins(t *testing.T) {
|
||||||
|
a := &Account{Mask: ParseMask("r")}
|
||||||
|
a.Rules = []PathRule{
|
||||||
|
{Dir: "share", Mask: ParseMask("w"), ChildrenOnly: true},
|
||||||
|
{Dir: "share/private", Deny: true},
|
||||||
|
{Dir: "share/public*", Mask: ParseMask("r")},
|
||||||
|
}
|
||||||
|
cases := []struct{ path, want string }{
|
||||||
|
{"share", "RW"},
|
||||||
|
{"share/file.txt", "RW"},
|
||||||
|
{"share/private", ""},
|
||||||
|
{"share/private/file.txt", ""},
|
||||||
|
{"share/public", "R"},
|
||||||
|
{"share/public/file.txt", "R"},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
if got := permStringForTest(a.MaskFor(tc.path)); got != tc.want {
|
if got := permStringForTest(a.MaskFor(tc.path)); got != tc.want {
|
||||||
|
|||||||
Reference in New Issue
Block a user