diff --git a/chfs-ng/internal/api/server.go b/chfs-ng/internal/api/server.go index da0b001..06612c4 100644 --- a/chfs-ng/internal/api/server.go +++ b/chfs-ng/internal/api/server.go @@ -2,6 +2,7 @@ package api import ( "archive/zip" + "context" "encoding/json" "fmt" "io" @@ -598,21 +599,106 @@ func (s *Server) webdav(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } - vp := strings.TrimPrefix(r.URL.Path, "/webdav") - vp = strings.Trim(vp, "/") - if !account(r, s).MaskFor(vp).HasRead() { - http.Error(w, "forbidden", 403) - return - } - if r.Method != http.MethodGet && r.Method != http.MethodHead && !account(r, s).MaskFor(vp).HasWrite() { - http.Error(w, "forbidden", 403) - return - } + vp := strings.Trim(strings.TrimPrefix(r.URL.Path, "/webdav"), "/") old := r.URL.Path r.URL.Path = "/" + vp defer func() { r.URL.Path = old }() - h := &webdav.Handler{FileSystem: webdav.LocalFileSystem(s.WebDAVRoot)} - h.ServeHTTP(w, r) + fs := &webdavAuthFS{inner: webdav.LocalFileSystem(s.WebDAVRoot), account: account(r, s)} + (&webdav.Handler{FileSystem: fs}).ServeHTTP(w, r) +} + +type webdavAuthFS struct { + inner webdav.FileSystem + account *auth.Account +} + +func (f *webdavAuthFS) allow(path string, p auth.PermMask) error { + path = strings.Trim(path, "/") + if f.account == nil || f.account.MaskFor(path)&p != p { + return webdav.NewHTTPError(http.StatusForbidden, fmt.Errorf("forbidden")) + } + return nil +} +func (f *webdavAuthFS) Open(ctx context.Context, name string) (io.ReadCloser, error) { + if err := f.allow(name, auth.Read); err != nil { + return nil, err + } + return f.inner.Open(ctx, name) +} +func (f *webdavAuthFS) Stat(ctx context.Context, name string) (*webdav.FileInfo, error) { + if err := f.allow(name, auth.Read); err != nil { + return nil, err + } + return f.inner.Stat(ctx, name) +} +func (f *webdavAuthFS) ReadDir(ctx context.Context, name string, recursive bool) ([]webdav.FileInfo, error) { + if err := f.allow(name, auth.Read); err != nil { + return nil, err + } + items, err := f.inner.ReadDir(ctx, name, recursive) + if err != nil { + return nil, err + } + out := make([]webdav.FileInfo, 0, len(items)) + for _, item := range items { + p := strings.Trim(item.Path, "/") + if f.account.MaskFor(p).HasRead() { + out = append(out, item) + } + } + return out, nil +} +func (f *webdavAuthFS) Create(ctx context.Context, name string, body io.ReadCloser, opts *webdav.CreateOptions) (*webdav.FileInfo, bool, error) { + parent := path.Dir(strings.Trim(name, "/")) + if parent == "." { + parent = "" + } + if err := f.allow(parent, auth.Write); err != nil { + return nil, false, err + } + return f.inner.Create(ctx, name, body, opts) +} +func (f *webdavAuthFS) RemoveAll(ctx context.Context, name string, opts *webdav.RemoveAllOptions) error { + if err := f.allow(name, auth.Delete); err != nil { + return err + } + return f.inner.RemoveAll(ctx, name, opts) +} +func (f *webdavAuthFS) Mkdir(ctx context.Context, name string) error { + parent := path.Dir(strings.Trim(name, "/")) + if parent == "." { + parent = "" + } + if err := f.allow(parent, auth.Write); err != nil { + return err + } + return f.inner.Mkdir(ctx, name) +} +func (f *webdavAuthFS) Copy(ctx context.Context, name, dest string, opts *webdav.CopyOptions) (bool, error) { + if err := f.allow(name, auth.Read); err != nil { + return false, err + } + parent := path.Dir(strings.Trim(dest, "/")) + if parent == "." { + parent = "" + } + if err := f.allow(parent, auth.Write); err != nil { + return false, err + } + return f.inner.Copy(ctx, name, dest, opts) +} +func (f *webdavAuthFS) Move(ctx context.Context, name, dest string, opts *webdav.MoveOptions) (bool, error) { + if err := f.allow(name, auth.Write); err != nil { + return false, err + } + parent := path.Dir(strings.Trim(dest, "/")) + if parent == "." { + parent = "" + } + if err := f.allow(parent, auth.Write); err != nil { + return false, err + } + return f.inner.Move(ctx, name, dest, opts) } func (s *Server) ipFilter(next http.Handler) http.Handler {