|
|
|
// Copyright 2014 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package webdav
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"net/http"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
|
|
|
|
model "github.com/cloudreve/Cloudreve/v3/models"
|
|
|
|
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
|
|
|
|
)
|
|
|
|
|
|
|
|
// slashClean is equivalent to but slightly more efficient than
|
|
|
|
// path.Clean("/" + name).
|
|
|
|
func slashClean(name string) string {
|
|
|
|
if name == "" || name[0] != '/' {
|
|
|
|
name = "/" + name
|
|
|
|
}
|
|
|
|
return path.Clean(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// moveFiles moves files and/or directories from src to dst.
|
|
|
|
//
|
|
|
|
// See section 9.9.4 for when various HTTP status codes apply.
|
|
|
|
func moveFiles(ctx context.Context, fs *filesystem.FileSystem, src FileInfo, dst string, overwrite bool) (status int, err error) {
|
|
|
|
|
|
|
|
var (
|
|
|
|
fileIDs []uint
|
|
|
|
folderIDs []uint
|
|
|
|
)
|
|
|
|
if src.IsDir() {
|
|
|
|
folderIDs = []uint{src.(*model.Folder).ID}
|
|
|
|
} else {
|
|
|
|
fileIDs = []uint{src.(*model.File).ID}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 判断是否需要移动
|
|
|
|
if src.GetPosition() != path.Dir(dst) {
|
|
|
|
if overwrite {
|
|
|
|
if err := _checkOverwriteFile(ctx, fs, src, dst); err != nil {
|
|
|
|
return http.StatusInternalServerError, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
err = fs.Move(
|
|
|
|
ctx,
|
|
|
|
folderIDs,
|
|
|
|
fileIDs,
|
|
|
|
src.GetPosition(),
|
|
|
|
path.Dir(dst),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 判断是否需要重命名
|
|
|
|
if err == nil && src.GetName() != path.Base(dst) {
|
|
|
|
err = fs.Rename(
|
|
|
|
ctx,
|
|
|
|
folderIDs,
|
|
|
|
fileIDs,
|
|
|
|
path.Base(dst),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return http.StatusInternalServerError, err
|
|
|
|
}
|
|
|
|
return http.StatusNoContent, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// copyFiles copies files and/or directories from src to dst.
|
|
|
|
//
|
|
|
|
// See section 9.8.5 for when various HTTP status codes apply.
|
|
|
|
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 {
|
|
|
|
return http.StatusInternalServerError, errRecursionTooDeep
|
|
|
|
}
|
|
|
|
recursion++
|
|
|
|
|
|
|
|
if overwrite {
|
|
|
|
if err := _checkOverwriteFile(ctx, fs, src, dst); err != nil {
|
|
|
|
return http.StatusInternalServerError, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if src.IsDir() {
|
|
|
|
err := fs.Copy(
|
|
|
|
ctx,
|
|
|
|
[]uint{src.(*model.Folder).ID},
|
|
|
|
[]uint{}, src.(*model.Folder).Position,
|
|
|
|
path.Dir(dst),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return http.StatusInternalServerError, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
err := fs.Copy(ctx, []uint{}, []uint{src.(*model.File).ID}, src.(*model.File).Position, path.Dir(dst))
|
|
|
|
if err != nil {
|
|
|
|
return http.StatusInternalServerError, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return http.StatusNoContent, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 判断目标 文件/夹 是否已经存在,存在则先删除目标文件/夹
|
|
|
|
func _checkOverwriteFile(ctx context.Context, fs *filesystem.FileSystem, src FileInfo, dst string) error {
|
|
|
|
if src.IsDir() {
|
|
|
|
ok, folder := fs.IsPathExist(dst)
|
|
|
|
if ok {
|
|
|
|
return fs.Delete(ctx, []uint{folder.ID}, []uint{}, false, false)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ok, file := fs.IsFileExist(dst)
|
|
|
|
if ok {
|
|
|
|
return fs.Delete(ctx, []uint{}, []uint{file.ID}, false, false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// walkFS traverses filesystem fs starting at name up to depth levels.
|
|
|
|
//
|
|
|
|
// Allowed values for depth are 0, 1 or infiniteDepth. For each visited node,
|
|
|
|
// walkFS calls walkFn. If a visited file system node is a directory and
|
|
|
|
// walkFn returns filepath.SkipDir, walkFS will skip traversal of this node.
|
|
|
|
func walkFS(
|
|
|
|
ctx context.Context,
|
|
|
|
fs *filesystem.FileSystem,
|
|
|
|
depth int,
|
|
|
|
name string,
|
|
|
|
info FileInfo,
|
|
|
|
walkFn func(reqPath string, info FileInfo, err error) error) error {
|
|
|
|
// This implementation is based on Walk's code in the standard path/filepath package.
|
|
|
|
err := walkFn(name, info, nil)
|
|
|
|
if err != nil {
|
|
|
|
if info.IsDir() && err == filepath.SkipDir {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !info.IsDir() || depth == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if depth == 1 {
|
|
|
|
depth = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
dirs, _ := info.(*model.Folder).GetChildFolder()
|
|
|
|
files, _ := info.(*model.Folder).GetChildFiles()
|
|
|
|
|
|
|
|
for _, fileInfo := range files {
|
|
|
|
filename := path.Join(name, fileInfo.Name)
|
|
|
|
err = walkFS(ctx, fs, depth, filename, &fileInfo, walkFn)
|
|
|
|
if err != nil {
|
|
|
|
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, fileInfo := range dirs {
|
|
|
|
filename := path.Join(name, fileInfo.Name)
|
|
|
|
err = walkFS(ctx, fs, depth, filename, &fileInfo, walkFn)
|
|
|
|
if err != nil {
|
|
|
|
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|