diff --git a/chfs-ng/cmd/chfs/main.go b/chfs-ng/cmd/chfs/main.go index 90c8021..a83d0d7 100644 --- a/chfs-ng/cmd/chfs/main.go +++ b/chfs-ng/cmd/chfs/main.go @@ -67,16 +67,16 @@ func main() { for _, ac := range cfg.Accounts { a := &auth.Account{Username: ac.Username, Password: ac.Password, Mask: auth.ParseMask(ac.Default)} for _, d := range ac.None { - a.Rules = append(a.Rules, auth.PathRule{Dir: d, Deny: true}) + a.Rules = append(a.Rules, auth.PathRule{Dir: accountRulePath(d, cfg.Path), Deny: true}) } for _, d := range ac.Read { - a.Rules = append(a.Rules, auth.PathRule{Dir: d, Mask: auth.Read, ChildrenOnly: true}) + a.Rules = append(a.Rules, auth.PathRule{Dir: accountRulePath(d, cfg.Path), Mask: auth.Read, ChildrenOnly: true}) } for _, d := range ac.Write { - a.Rules = append(a.Rules, auth.PathRule{Dir: d, Mask: auth.ParseMask("w"), ChildrenOnly: true}) + a.Rules = append(a.Rules, auth.PathRule{Dir: accountRulePath(d, cfg.Path), Mask: auth.ParseMask("w"), ChildrenOnly: true}) } for _, d := range ac.Delete { - a.Rules = append(a.Rules, auth.PathRule{Dir: d, Mask: auth.ParseMask("d"), ChildrenOnly: true}) + a.Rules = append(a.Rules, auth.PathRule{Dir: accountRulePath(d, cfg.Path), Mask: auth.ParseMask("d"), ChildrenOnly: true}) } if ac.Username == "guest" { mgr.Guest = a @@ -103,6 +103,29 @@ type multiFlag []string func (m *multiFlag) String() string { return strings.Join(*m, "|") } func (m *multiFlag) Set(v string) error { *m = append(*m, v); return nil } +func accountRulePath(rule string, roots []string) string { + rule = strings.TrimSpace(strings.Trim(rule, "\\\"")) + if rule == "" { + return rule + } + rule = filepath.ToSlash(rule) + if !filepath.IsAbs(rule) { + return strings.Trim(rule, "/") + } + for _, root := range roots { + r := filepath.Clean(root) + rel, err := filepath.Rel(r, rule) + if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) { + continue + } + if rel == "." { + return "" + } + return filepath.ToSlash(rel) + } + return rule +} + func itoa(n int) string { const d = "0123456789" if n == 0 { diff --git a/chfs-ng/internal/api/server.go b/chfs-ng/internal/api/server.go index c3e8ac1..8ad170e 100644 --- a/chfs-ng/internal/api/server.go +++ b/chfs-ng/internal/api/server.go @@ -190,7 +190,7 @@ func (s *Server) files(w http.ResponseWriter, r *http.Request) { } } } - out = append(out, fileInfo{Name: fi.Name(), Path: fp, Size: fi.Size(), Modified: originalTime(fi.ModTime()), Dir: fi.IsDir(), Mask: permString(a.MaskFor(fp)), GuestMask: permString(s.Auth.Guest.MaskFor(fp)), HasSubDir: sub, Icon: iconFor(fi)}) + out = append(out, fileInfo{Name: fi.Name(), Path: fp, Size: fi.Size(), Modified: originalTime(fi.ModTime()), Dir: fi.IsDir(), Mask: permString(a.MaskForEntry(fp)), GuestMask: permString(s.Auth.Guest.MaskForEntry(fp)), HasSubDir: sub, Icon: iconFor(fi)}) } virtual := p == "" && s.MultiRoot writeJSON(w, map[string]any{"files": out, "currentMask": permString(m), "virtualRoot": virtual}) @@ -483,7 +483,7 @@ func (s *Server) search(w http.ResponseWriter, r *http.Request) { continue } if needle == "" || strings.Contains(strings.ToLower(fi.Name()), needle) { - out = append(out, fileInfo{Name: fi.Name(), Path: "/" + strings.Trim(p, "/"), Size: fi.Size(), Modified: originalTime(fi.ModTime()), Dir: fi.IsDir(), Mask: permString(a.MaskFor(p)), GuestMask: permString(s.Auth.Guest.MaskFor(p)), HasSubDir: false, Icon: iconFor(fi)}) + out = append(out, fileInfo{Name: fi.Name(), Path: "/" + strings.Trim(p, "/"), Size: fi.Size(), Modified: originalTime(fi.ModTime()), Dir: fi.IsDir(), Mask: permString(a.MaskForEntry(p)), GuestMask: permString(s.Auth.Guest.MaskForEntry(p)), HasSubDir: false, Icon: iconFor(fi)}) } if fi.IsDir() { if err := walk(p); err != nil { diff --git a/chfs-ng/internal/auth/auth.go b/chfs-ng/internal/auth/auth.go index 1542262..4c7ea8b 100644 --- a/chfs-ng/internal/auth/auth.go +++ b/chfs-ng/internal/auth/auth.go @@ -89,6 +89,18 @@ func (m *Manager) Authenticate(user, pwd string) *Account { return nil } func (a *Account) MaskFor(path string) PermMask { + return a.maskFor(path, false) +} + +// MaskForEntry returns the permission shown for a directory/file while its +// parent directory is being listed. CHFS section rules rule.r/rule.w/rule.d +// affect the contents of the named directory, but the directory entry itself +// keeps the inherited permission until it is opened. +func (a *Account) MaskForEntry(path string) PermMask { + return a.maskFor(path, true) +} + +func (a *Account) maskFor(path string, entry bool) PermMask { path = strings.Trim(path, "/") best := -1 mask := a.Mask @@ -97,7 +109,7 @@ func (a *Account) MaskFor(path string) PermMask { if !matchPath(d, path) { continue } - if r.ChildrenOnly && matchPathExact(d, path) { + if entry && r.ChildrenOnly && matchPathExact(d, path) { continue } if len(d) > best { diff --git a/chfs-ng/internal/auth/rules_test.go b/chfs-ng/internal/auth/rules_test.go index 1510d7f..127ef30 100644 --- a/chfs-ng/internal/auth/rules_test.go +++ b/chfs-ng/internal/auth/rules_test.go @@ -14,9 +14,9 @@ func TestPathRuleSemantics(t *testing.T) { {"", "R"}, {"private", ""}, {"private/x.txt", ""}, - {"share", "R"}, + {"share", "RW"}, {"share/x.txt", "RW"}, - {"drop", "R"}, + {"drop", "RWD"}, {"drop/x.txt", "RWD"}, {"public/x.txt", "R"}, {"public/sub/x.txt", "R"},