feat: expose multi-root storage through WebDAV
This commit is contained in:
@@ -30,6 +30,7 @@ type Server struct {
|
|||||||
SessionMu sync.RWMutex
|
SessionMu sync.RWMutex
|
||||||
SessionTimeout int
|
SessionTimeout int
|
||||||
WebDAVRoot string
|
WebDAVRoot string
|
||||||
|
WebDAVFS webdav.FileSystem
|
||||||
Allow string
|
Allow string
|
||||||
MultiRoot bool
|
MultiRoot bool
|
||||||
RemoveMode int
|
RemoveMode int
|
||||||
@@ -603,10 +604,175 @@ func (s *Server) webdav(w http.ResponseWriter, r *http.Request) {
|
|||||||
old := r.URL.Path
|
old := r.URL.Path
|
||||||
r.URL.Path = "/" + vp
|
r.URL.Path = "/" + vp
|
||||||
defer func() { r.URL.Path = old }()
|
defer func() { r.URL.Path = old }()
|
||||||
fs := &webdavAuthFS{inner: webdav.LocalFileSystem(s.WebDAVRoot), account: account(r, s)}
|
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)
|
||||||
|
}
|
||||||
|
fs := &webdavAuthFS{inner: inner, account: account(r, s)}
|
||||||
(&webdav.Handler{FileSystem: fs}).ServeHTTP(w, r)
|
(&webdav.Handler{FileSystem: fs}).ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type webdavMultiFS struct {
|
||||||
|
roots map[string]webdav.FileSystem
|
||||||
|
names []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWebDAVMultiFS(m storage.Multi) *webdavMultiFS {
|
||||||
|
fs := &webdavMultiFS{roots: make(map[string]webdav.FileSystem), names: append([]string(nil), m.Names...)}
|
||||||
|
for i, name := range m.Names {
|
||||||
|
if i < len(m.Roots) {
|
||||||
|
fs.roots[name] = webdav.LocalFileSystem(m.Roots[i].Root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
func splitDAVPath(name string) (string, string) {
|
||||||
|
p := strings.Trim(strings.ReplaceAll(name, "\\\\", "/"), "/")
|
||||||
|
p = strings.TrimPrefix(p, "webdav/")
|
||||||
|
p = strings.TrimPrefix(p, "webdav")
|
||||||
|
if p == "" {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(p, "/", 2)
|
||||||
|
if len(parts) == 1 {
|
||||||
|
return parts[0], ""
|
||||||
|
}
|
||||||
|
return parts[0], parts[1]
|
||||||
|
}
|
||||||
|
func (f *webdavMultiFS) root(name string) (webdav.FileSystem, string, error) {
|
||||||
|
r, rel := splitDAVPath(name)
|
||||||
|
if r == "" {
|
||||||
|
return nil, "", nil
|
||||||
|
}
|
||||||
|
fs, ok := f.roots[r]
|
||||||
|
if !ok {
|
||||||
|
return nil, "", webdav.NewHTTPError(http.StatusNotFound, fmt.Errorf("root not found"))
|
||||||
|
}
|
||||||
|
return fs, rel, nil
|
||||||
|
}
|
||||||
|
func (f *webdavMultiFS) Open(ctx context.Context, name string) (io.ReadCloser, error) {
|
||||||
|
fs, rel, err := f.root(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if fs == nil {
|
||||||
|
return nil, webdav.NewHTTPError(http.StatusForbidden, fmt.Errorf("virtual root is not a file"))
|
||||||
|
}
|
||||||
|
return fs.Open(ctx, "/"+rel)
|
||||||
|
}
|
||||||
|
func (f *webdavMultiFS) Stat(ctx context.Context, name string) (*webdav.FileInfo, error) {
|
||||||
|
if strings.Trim(strings.ReplaceAll(name, "\\\\", "/"), "/") == "" {
|
||||||
|
return &webdav.FileInfo{Path: "/", IsDir: true}, nil
|
||||||
|
}
|
||||||
|
fs, rel, err := f.root(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if rel == "" {
|
||||||
|
return &webdav.FileInfo{Path: "/" + strings.Trim(strings.SplitN(strings.Trim(name, "/"), "/", 2)[0], "/"), IsDir: true}, nil
|
||||||
|
}
|
||||||
|
fi, err := fs.Stat(ctx, "/"+rel)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fi.Path = "/" + strings.Trim(name, "/")
|
||||||
|
return fi, nil
|
||||||
|
}
|
||||||
|
func (f *webdavMultiFS) ReadDir(ctx context.Context, name string, recursive bool) ([]webdav.FileInfo, error) {
|
||||||
|
p := strings.Trim(strings.ReplaceAll(name, "\\\\", "/"), "/")
|
||||||
|
if p == "" {
|
||||||
|
out := make([]webdav.FileInfo, 0, len(f.names))
|
||||||
|
for _, n := range f.names {
|
||||||
|
out = append(out, webdav.FileInfo{Path: "/" + n, IsDir: true})
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
fs, rel, err := f.root(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items, err := fs.ReadDir(ctx, "/"+rel, recursive)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
root, _ := splitDAVPath(p)
|
||||||
|
for i := range items {
|
||||||
|
itemPath := strings.TrimPrefix(items[i].Path, "/")
|
||||||
|
if itemPath == "" {
|
||||||
|
itemPath = filepath.Base(items[i].Path)
|
||||||
|
}
|
||||||
|
items[i].Path = "/" + path.Join(root, itemPath)
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
func (f *webdavMultiFS) Create(ctx context.Context, name string, body io.ReadCloser, opts *webdav.CreateOptions) (*webdav.FileInfo, bool, error) {
|
||||||
|
fs, rel, err := f.root(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
if fs == nil || rel == "" {
|
||||||
|
return nil, false, webdav.NewHTTPError(http.StatusForbidden, fmt.Errorf("cannot create virtual root"))
|
||||||
|
}
|
||||||
|
fi, created, err := fs.Create(ctx, "/"+rel, body, opts)
|
||||||
|
if err == nil {
|
||||||
|
fi.Path = "/" + strings.Trim(name, "/")
|
||||||
|
}
|
||||||
|
return fi, created, err
|
||||||
|
}
|
||||||
|
func (f *webdavMultiFS) RemoveAll(ctx context.Context, name string, opts *webdav.RemoveAllOptions) error {
|
||||||
|
fs, rel, err := f.root(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if fs == nil || rel == "" {
|
||||||
|
return webdav.NewHTTPError(http.StatusForbidden, fmt.Errorf("cannot remove virtual root"))
|
||||||
|
}
|
||||||
|
return fs.RemoveAll(ctx, "/"+rel, opts)
|
||||||
|
}
|
||||||
|
func (f *webdavMultiFS) Mkdir(ctx context.Context, name string) error {
|
||||||
|
fs, rel, err := f.root(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if fs == nil || rel == "" {
|
||||||
|
return webdav.NewHTTPError(http.StatusForbidden, fmt.Errorf("cannot create virtual root"))
|
||||||
|
}
|
||||||
|
return fs.Mkdir(ctx, "/"+rel)
|
||||||
|
}
|
||||||
|
func (f *webdavMultiFS) Copy(ctx context.Context, name, dest string, opts *webdav.CopyOptions) (bool, error) {
|
||||||
|
fs, rel, err := f.root(name)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
dfs, drel, err := f.root(dest)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if fs == nil || dfs == nil || fs != dfs {
|
||||||
|
return false, webdav.NewHTTPError(http.StatusForbidden, fmt.Errorf("cross-root copy is not supported"))
|
||||||
|
}
|
||||||
|
return fs.Copy(ctx, "/"+rel, "/"+drel, opts)
|
||||||
|
}
|
||||||
|
func (f *webdavMultiFS) Move(ctx context.Context, name, dest string, opts *webdav.MoveOptions) (bool, error) {
|
||||||
|
fs, rel, err := f.root(name)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
dfs, drel, err := f.root(dest)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if fs == nil || dfs == nil || fs != dfs {
|
||||||
|
return false, webdav.NewHTTPError(http.StatusForbidden, fmt.Errorf("cross-root move is not supported"))
|
||||||
|
}
|
||||||
|
return fs.Move(ctx, "/"+rel, "/"+drel, opts)
|
||||||
|
}
|
||||||
|
|
||||||
type webdavAuthFS struct {
|
type webdavAuthFS struct {
|
||||||
inner webdav.FileSystem
|
inner webdav.FileSystem
|
||||||
account *auth.Account
|
account *auth.Account
|
||||||
|
|||||||
Reference in New Issue
Block a user