feat: add permission-aware file copy and move
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user