diff --git a/chfs-ng/internal/api/server.go b/chfs-ng/internal/api/server.go index fb8b9a9..9db8e81 100644 --- a/chfs-ng/internal/api/server.go +++ b/chfs-ng/internal/api/server.go @@ -73,6 +73,8 @@ func (s *Server) Handler() http.Handler { mux.HandleFunc("/chfs/files", s.files) mux.HandleFunc("/chfs/newdir", s.newdir) mux.HandleFunc("/chfs/rename", s.rename) + mux.HandleFunc("/chfs/move", s.move) + mux.HandleFunc("/chfs/copy", s.copy) mux.HandleFunc("/chfs/rmfiles", s.remove) mux.HandleFunc("/chfs/exist", s.exist) mux.HandleFunc("/chfs/session", s.session) @@ -345,6 +347,85 @@ func (s *Server) rename(w http.ResponseWriter, r *http.Request) { s.logOp(r, fmt.Sprintf("renamed '%s' to '%s'", old, neu)) writeCreated(w) } +func (s *Server) transfer(w http.ResponseWriter, r *http.Request, copyOnly bool) { + a := account(r, s) + rawSrc, rawDst := r.FormValue("source"), r.FormValue("destination") + if unsafePath(rawSrc) || unsafePath(rawDst) { + writeJSONError(w, http.StatusBadRequest, "invalid parameter") + return + } + src, dst := strings.Trim(rawSrc, "/"), strings.Trim(rawDst, "/") + if src == "" || dst == "" || src == dst { + writeJSONError(w, http.StatusBadRequest, "invalid parameter") + return + } + if !a.MaskFor(src).HasRead() || !a.MaskFor(dst).HasWrite() || !a.MaskFor(path.Dir(dst)).HasWrite() { + writeJSONError(w, http.StatusForbidden, "has no privilege") + return + } + info, err := s.Store.Stat(src) + if err != nil { + http.Error(w, err.Error(), 404) + return + } + if info.IsDir() && (dst == src || strings.HasPrefix(dst, src+"/")) { + writeJSONError(w, http.StatusBadRequest, "invalid parameter") + return + } + if _, err = s.Store.Stat(dst); err == nil { + writeJSONError(w, http.StatusBadRequest, "invalid parameter") + return + } else if !os.IsNotExist(err) { + http.Error(w, err.Error(), 500) + return + } + if info.IsDir() { + if err = checkReadTree(s.Store, a, src); err != nil { + writeJSONError(w, http.StatusForbidden, err.Error()) + return + } + } + if copyOnly { + err = s.Store.Copy(src, dst) + } else { + err = s.Store.Rename(src, dst) + } + if err != nil { + http.Error(w, err.Error(), 500) + return + } + action := "moved" + if copyOnly { + action = "copied" + } + s.logOp(r, fmt.Sprintf("%s '%s' to '%s'", action, src, dst)) + writeCreated(w) +} +func checkReadTree(store storage.Storage, a *auth.Account, root string) error { + if !a.MaskFor(root).HasRead() { + return fmt.Errorf("read permission denied") + } + info, err := store.Stat(root) + if err != nil { + return err + } + if !info.IsDir() { + return nil + } + items, err := store.List(root) + if err != nil { + return err + } + for _, item := range items { + if err := checkReadTree(store, a, path.Join(root, item.Name())); err != nil { + return err + } + } + return nil +} +func (s *Server) move(w http.ResponseWriter, r *http.Request) { s.transfer(w, r, false) } +func (s *Server) copy(w http.ResponseWriter, r *http.Request) { s.transfer(w, r, true) } + func (s *Server) remove(w http.ResponseWriter, r *http.Request) { a := account(r, s) _ = r.ParseMultipartForm(16 << 20) diff --git a/chfs-ng/internal/storage/multipath.go b/chfs-ng/internal/storage/multipath.go index 6c6dce0..f7a3ce2 100644 --- a/chfs-ng/internal/storage/multipath.go +++ b/chfs-ng/internal/storage/multipath.go @@ -147,6 +147,14 @@ func (m Multi) RootName(r Local) string { } return "root" } +func (m Multi) Copy(a, b string) error { + ra, aa, oka := m.find(a) + rb, bb, okb := m.find(b) + if !oka || !okb || aa == "" || bb == "" || ra.Root != rb.Root { + return os.ErrInvalid + } + return ra.Copy(aa, bb) +} func (m Multi) Rename(a, b string) error { ra, aa, oka := m.find(a) rb, bb, okb := m.find(b) diff --git a/chfs-ng/internal/storage/storage.go b/chfs-ng/internal/storage/storage.go index 51975cf..649b703 100644 --- a/chfs-ng/internal/storage/storage.go +++ b/chfs-ng/internal/storage/storage.go @@ -26,6 +26,7 @@ type Storage interface { Create(path string) (*os.File, error) Remove(path string) error Rename(oldPath, newPath string) error + Copy(oldPath, newPath string) error MkdirAll(path string) error List(path string) ([]os.FileInfo, error) } @@ -275,6 +276,23 @@ func copyPath(src, dst string) error { return os.Chtimes(dst, info.ModTime(), info.ModTime()) } +func (s Local) Copy(a, b string) error { + pa, e := s.real(a) + if e != nil { + return e + } + pb, e := s.real(b) + if e != nil { + return e + } + if _, e := os.Lstat(pb); e == nil { + return os.ErrExist + } else if !os.IsNotExist(e) { + return e + } + return copyPath(pa, pb) +} + func (s Local) Rename(a, b string) error { pa, e := s.real(a) if e != nil {