mirror of https://github.com/cloudreve/Cloudreve
Feat: adapt copy method for WebDAV
parent
9fdf2fe7ab
commit
c1b02380ac
|
@ -15,6 +15,15 @@ func FillSlash(path string) string {
|
||||||
return path + "/"
|
return path + "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveSlash 移除路径最后的`/`
|
||||||
|
// TODO 测试
|
||||||
|
func RemoveSlash(path string) string {
|
||||||
|
if len(path) > 1 {
|
||||||
|
return strings.TrimSuffix(path, "/")
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
// SplitPath 分割路径为列表
|
// SplitPath 分割路径为列表
|
||||||
func SplitPath(path string) []string {
|
func SplitPath(path string) []string {
|
||||||
if len(path) == 0 || path[0] != '/' {
|
if len(path) == 0 || path[0] != '/' {
|
||||||
|
|
|
@ -180,95 +180,29 @@ func copyProps(dst, src File) error {
|
||||||
// copyFiles copies files and/or directories from src to dst.
|
// copyFiles copies files and/or directories from src to dst.
|
||||||
//
|
//
|
||||||
// See section 9.8.5 for when various HTTP status codes apply.
|
// See section 9.8.5 for when various HTTP status codes apply.
|
||||||
func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bool, depth int, recursion int) (status int, err error) {
|
func copyFiles(ctx context.Context, fs *filesystem.FileSystem, src FileInfo, dst string, overwrite bool, depth int, recursion int) (status int, err error) {
|
||||||
if recursion == 1000 {
|
if recursion == 1000 {
|
||||||
return http.StatusInternalServerError, errRecursionTooDeep
|
return http.StatusInternalServerError, errRecursionTooDeep
|
||||||
}
|
}
|
||||||
recursion++
|
recursion++
|
||||||
|
|
||||||
// TODO: section 9.8.3 says that "Note that an infinite-depth COPY of /A/
|
if src.IsDir() {
|
||||||
// into /A/B/ could lead to infinite recursion if not handled correctly."
|
err := fs.Copy(
|
||||||
|
ctx,
|
||||||
srcFile, err := fs.OpenFile(ctx, src, os.O_RDONLY, 0)
|
[]uint{src.(*model.Folder).ID},
|
||||||
if err != nil {
|
[]uint{}, src.(*model.Folder).Position,
|
||||||
if os.IsNotExist(err) {
|
path.Dir(dst),
|
||||||
return http.StatusNotFound, err
|
)
|
||||||
}
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
defer srcFile.Close()
|
|
||||||
srcStat, err := srcFile.Stat()
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return http.StatusNotFound, err
|
|
||||||
}
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
srcPerm := srcStat.Mode() & os.ModePerm
|
|
||||||
|
|
||||||
created := false
|
|
||||||
if _, err := fs.Stat(ctx, dst); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
created = true
|
|
||||||
} else {
|
|
||||||
return http.StatusForbidden, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !overwrite {
|
|
||||||
return http.StatusPreconditionFailed, os.ErrExist
|
|
||||||
}
|
|
||||||
if err := fs.RemoveAll(ctx, dst); err != nil && !os.IsNotExist(err) {
|
|
||||||
return http.StatusForbidden, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if srcStat.IsDir() {
|
|
||||||
if err := fs.Mkdir(ctx, dst, srcPerm); err != nil {
|
|
||||||
return http.StatusForbidden, err
|
|
||||||
}
|
|
||||||
if depth == infiniteDepth {
|
|
||||||
children, err := srcFile.Readdir(-1)
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusForbidden, err
|
|
||||||
}
|
|
||||||
for _, c := range children {
|
|
||||||
name := c.Name()
|
|
||||||
s := path.Join(src, name)
|
|
||||||
d := path.Join(dst, name)
|
|
||||||
cStatus, cErr := copyFiles(ctx, fs, s, d, overwrite, depth, recursion)
|
|
||||||
if cErr != nil {
|
|
||||||
// TODO: MultiStatus.
|
|
||||||
return cStatus, cErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
dstFile, err := fs.OpenFile(ctx, dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, srcPerm)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
return http.StatusInternalServerError, err
|
||||||
return http.StatusConflict, err
|
|
||||||
}
|
|
||||||
return http.StatusForbidden, err
|
|
||||||
|
|
||||||
}
|
}
|
||||||
_, copyErr := io.Copy(dstFile, srcFile)
|
} else {
|
||||||
propsErr := copyProps(dstFile, srcFile)
|
err := fs.Copy(ctx, []uint{src.(*model.File).ID}, []uint{}, src.(*model.File).Position, dst)
|
||||||
closeErr := dstFile.Close()
|
if err != nil {
|
||||||
if copyErr != nil {
|
return http.StatusInternalServerError, err
|
||||||
return http.StatusInternalServerError, copyErr
|
|
||||||
}
|
|
||||||
if propsErr != nil {
|
|
||||||
return http.StatusInternalServerError, propsErr
|
|
||||||
}
|
|
||||||
if closeErr != nil {
|
|
||||||
return http.StatusInternalServerError, closeErr
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if created {
|
|
||||||
return http.StatusCreated, nil
|
|
||||||
}
|
|
||||||
return http.StatusNoContent, nil
|
return http.StatusNoContent, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/HFO4/cloudreve/pkg/filesystem"
|
"github.com/HFO4/cloudreve/pkg/filesystem"
|
||||||
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
|
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
|
||||||
"github.com/HFO4/cloudreve/pkg/filesystem/local"
|
"github.com/HFO4/cloudreve/pkg/filesystem/local"
|
||||||
|
"github.com/HFO4/cloudreve/pkg/util"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
@ -38,7 +39,7 @@ func (h *Handler) stripPrefix(p string, uid uint) (string, int, error) {
|
||||||
}
|
}
|
||||||
prefix := h.Prefix + strconv.FormatUint(uint64(uid), 10)
|
prefix := h.Prefix + strconv.FormatUint(uint64(uid), 10)
|
||||||
if r := strings.TrimPrefix(p, prefix); len(r) < len(p) {
|
if r := strings.TrimPrefix(p, prefix); len(r) < len(p) {
|
||||||
return r, http.StatusOK, nil
|
return util.RemoveSlash(r), http.StatusOK, nil
|
||||||
}
|
}
|
||||||
return p, http.StatusNotFound, errPrefixMismatch
|
return p, http.StatusNotFound, errPrefixMismatch
|
||||||
}
|
}
|
||||||
|
@ -406,6 +407,12 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *fil
|
||||||
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
|
isExist, target := isPathExist(ctx, fs, src)
|
||||||
|
|
||||||
|
if !isExist {
|
||||||
|
return http.StatusNotFound, nil
|
||||||
|
}
|
||||||
|
|
||||||
if r.Method == "COPY" {
|
if r.Method == "COPY" {
|
||||||
// Section 7.5.1 says that a COPY only needs to lock the destination,
|
// Section 7.5.1 says that a COPY only needs to lock the destination,
|
||||||
// not both destination and source. Strictly speaking, this is racy,
|
// not both destination and source. Strictly speaking, this is racy,
|
||||||
|
@ -429,7 +436,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *fil
|
||||||
return http.StatusBadRequest, errInvalidDepth
|
return http.StatusBadRequest, errInvalidDepth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return copyFiles(ctx, h.FileSystem, src, dst, r.Header.Get("Overwrite") != "F", depth, 0)
|
return copyFiles(ctx, fs, target, dst, r.Header.Get("Overwrite") != "F", depth, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
release, status, err := h.confirmLocks(r, src, dst, fs)
|
release, status, err := h.confirmLocks(r, src, dst, fs)
|
||||||
|
|
|
@ -24,6 +24,7 @@ func initWebDAV(group *gin.RouterGroup) {
|
||||||
group.Handle("LOCK", ":uid/*path", controllers.ServeWebDAV)
|
group.Handle("LOCK", ":uid/*path", controllers.ServeWebDAV)
|
||||||
group.Handle("UNLOCK", ":uid/*path", controllers.ServeWebDAV)
|
group.Handle("UNLOCK", ":uid/*path", controllers.ServeWebDAV)
|
||||||
group.Handle("PROPPATCH", ":uid/*path", controllers.ServeWebDAV)
|
group.Handle("PROPPATCH", ":uid/*path", controllers.ServeWebDAV)
|
||||||
|
group.Handle("COPY", ":uid/*path", controllers.ServeWebDAV)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue