refactor: route WebDAV through storage backend

This commit is contained in:
OpenClaw Agent
2026-09-21 17:27:54 +08:00
parent 09b808042d
commit a7192fd96e
3 changed files with 230 additions and 6 deletions
+191 -6
View File
@@ -606,18 +606,203 @@ func (s *Server) webdav(w http.ResponseWriter, r *http.Request) {
r.URL.Path = "/" + vp
defer func() { r.URL.Path = old }()
inner := s.WebDAVFS
if inner == nil && s.MultiRoot {
if m, ok := s.Store.(storage.Multi); ok {
inner = newWebDAVMultiFS(m)
}
}
if inner == nil {
inner = webdav.LocalFileSystem(s.WebDAVRoot)
inner = &webdavStorageFS{store: s.Store, removeMode: s.RemoveMode}
}
fs := &webdavAuthFS{inner: inner, account: account(r, s)}
(&webdav.Handler{FileSystem: fs}).ServeHTTP(w, r)
}
type webdavStorageFS struct {
store storage.Storage
removeMode int
}
func davStoragePath(name string) string {
p := strings.Trim(strings.ReplaceAll(name, "\\", "/"), "/")
p = strings.TrimPrefix(p, "webdav/")
p = strings.TrimPrefix(p, "webdav")
return strings.Trim(p, "/")
}
func (f *webdavStorageFS) info(name string) (*webdav.FileInfo, error) {
p := davStoragePath(name)
fi, err := f.store.Stat(p)
if err != nil {
return nil, err
}
return &webdav.FileInfo{Path: "/" + p, Size: fi.Size(), ModTime: fi.ModTime(), IsDir: fi.IsDir()}, nil
}
func (f *webdavStorageFS) Open(ctx context.Context, name string) (io.ReadCloser, error) {
p := davStoragePath(name)
fi, err := f.store.Stat(p)
if err != nil {
return nil, err
}
if fi.IsDir() {
return nil, fmt.Errorf("is a directory")
}
return f.store.Open(p)
}
func (f *webdavStorageFS) Stat(ctx context.Context, name string) (*webdav.FileInfo, error) {
return f.info(name)
}
func (f *webdavStorageFS) ReadDir(ctx context.Context, name string, recursive bool) ([]webdav.FileInfo, error) {
root := davStoragePath(name)
var out []webdav.FileInfo
var walk func(string) error
walk = func(dir string) error {
items, err := f.store.List(dir)
if err != nil {
return err
}
for _, item := range items {
p := path.Join(dir, item.Name())
out = append(out, webdav.FileInfo{Path: "/" + p, Size: item.Size(), ModTime: item.ModTime(), IsDir: item.IsDir()})
if recursive && item.IsDir() {
if err := walk(p); err != nil {
return err
}
}
}
return nil
}
if err := walk(root); err != nil {
return nil, err
}
return out, nil
}
func (f *webdavStorageFS) Create(ctx context.Context, name string, body io.ReadCloser, opts *webdav.CreateOptions) (*webdav.FileInfo, bool, error) {
p := davStoragePath(name)
_, statErr := f.store.Stat(p)
if statErr != nil && !os.IsNotExist(statErr) {
return nil, false, statErr
}
created := os.IsNotExist(statErr)
file, err := f.store.Create(p)
if err != nil {
return nil, false, err
}
_, copyErr := io.Copy(file, body)
closeErr := file.Close()
body.Close()
if copyErr != nil {
return nil, false, copyErr
}
if closeErr != nil {
return nil, false, closeErr
}
fi, err := f.info(p)
return fi, created, err
}
func (f *webdavStorageFS) RemoveAll(ctx context.Context, name string, opts *webdav.RemoveAllOptions) error {
p := davStoragePath(name)
if p == "" {
return fmt.Errorf("cannot remove storage root")
}
if f.removeMode == 1 {
if t, ok := f.store.(storage.Trashable); ok {
return t.Trash(p)
}
}
if f.removeMode == 2 {
if t, ok := f.store.(storage.Trashable); ok {
home, err := os.UserHomeDir()
if err != nil {
return err
}
return t.MoveToTrash(p, filepath.Join(home, ".chfs_trashbin"))
}
}
return f.store.Remove(p)
}
func (f *webdavStorageFS) Mkdir(ctx context.Context, name string) error {
p := davStoragePath(name)
if p == "" {
return fmt.Errorf("cannot create storage root")
}
return f.store.MkdirAll(p)
}
func (f *webdavStorageFS) Copy(ctx context.Context, name, dest string, opts *webdav.CopyOptions) (bool, error) {
src := davStoragePath(name)
dst := davStoragePath(dest)
if m, ok := f.store.(storage.Multi); ok {
srcRoot, _, srcOK := m.FindRoot(src)
dstRoot, _, dstOK := m.FindRoot(dst)
if srcOK && dstOK && srcRoot != dstRoot {
return false, webdav.NewHTTPError(http.StatusForbidden, fmt.Errorf("cross-root copy is not supported"))
}
}
info, err := f.store.Stat(src)
if err != nil {
return false, err
}
if info.IsDir() {
if err := f.store.MkdirAll(dst); err != nil {
return false, err
}
items, err := f.store.List(src)
if err != nil {
return false, err
}
for _, item := range items {
created, err := f.Copy(ctx, path.Join(src, item.Name()), path.Join(dst, item.Name()), opts)
if err != nil {
return false, err
}
_ = created
}
return true, nil
}
in, err := f.store.Open(src)
if err != nil {
return false, err
}
out, err := f.store.Create(dst)
if err != nil {
in.Close()
return false, err
}
_, copyErr := io.Copy(out, in)
in.Close()
closeErr := out.Close()
if copyErr != nil {
return false, copyErr
}
if closeErr != nil {
return false, closeErr
}
return true, nil
}
func (f *webdavStorageFS) Move(ctx context.Context, name, dest string, opts *webdav.MoveOptions) (bool, error) {
src := davStoragePath(name)
dst := davStoragePath(dest)
if opts != nil && opts.NoOverwrite {
if _, err := f.store.Stat(dst); err == nil {
return false, fmt.Errorf("destination exists")
}
}
if m, ok := f.store.(storage.Multi); ok {
srcRoot, _, srcOK := m.FindRoot(src)
dstRoot, _, dstOK := m.FindRoot(dst)
if srcOK && dstOK && srcRoot != dstRoot {
return false, webdav.NewHTTPError(http.StatusForbidden, fmt.Errorf("cross-root move is not supported"))
}
}
if err := f.store.Rename(src, dst); err != nil {
return false, webdav.NewHTTPError(http.StatusForbidden, err)
}
return true, nil
}
type webdavMultiFS struct {
roots map[string]webdav.FileSystem
names []string