diff --git a/chfs-ng/internal/api/server.go b/chfs-ng/internal/api/server.go index 7f390f1..deb8edc 100644 --- a/chfs-ng/internal/api/server.go +++ b/chfs-ng/internal/api/server.go @@ -1426,9 +1426,6 @@ func (f *webdavAuthFS) Move(ctx context.Context, name, dest string, opts *webdav if err := f.allow(name, auth.Delete); err != nil { return false, err } - if err := f.checkTree(ctx, name, auth.Delete); err != nil { - return false, err - } parent := path.Dir(webdavAuthPath(dest)) if parent == "." { parent = "" @@ -1436,11 +1433,24 @@ func (f *webdavAuthFS) Move(ctx context.Context, name, dest string, opts *webdav if err := f.allow(parent, auth.Write); err != nil { return false, err } + // CHFS checks an existing destination before resolving a missing source. + // This matters because MOVE to an occupied destination returns 412 even + // when the source has disappeared, while a missing source with a free + // destination is reported as 403. if opts == nil || !opts.NoOverwrite { if err := f.authorizeOverwrite(ctx, dest); err != nil { return false, err } } + if _, err := f.inner.Stat(ctx, name); err != nil { + if os.IsNotExist(err) { + return false, webdav.NewHTTPError(http.StatusForbidden, fmt.Errorf("source not found")) + } + return false, err + } + if err := f.checkTree(ctx, name, auth.Delete); err != nil { + return false, err + } return f.inner.Move(ctx, name, dest, opts) }