2021-11-27 16:12:04 +00:00
|
|
|
// 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"
|
|
|
|
"fmt"
|
|
|
|
"github.com/Xhofe/alist/conf"
|
2021-12-06 07:55:05 +00:00
|
|
|
"github.com/Xhofe/alist/drivers/base"
|
2022-01-03 12:06:36 +00:00
|
|
|
"github.com/Xhofe/alist/drivers/operate"
|
2021-11-27 16:12:04 +00:00
|
|
|
"github.com/Xhofe/alist/model"
|
2022-01-03 12:06:36 +00:00
|
|
|
"github.com/Xhofe/alist/server/common"
|
2021-11-27 16:12:04 +00:00
|
|
|
"github.com/Xhofe/alist/utils"
|
2021-11-28 07:10:48 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
2021-12-19 12:32:47 +00:00
|
|
|
"net"
|
2021-11-27 16:12:04 +00:00
|
|
|
"net/http"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2021-11-28 07:10:48 +00:00
|
|
|
"time"
|
2021-11-27 16:12:04 +00:00
|
|
|
)
|
|
|
|
|
2021-11-28 07:10:48 +00:00
|
|
|
type FileSystem struct{}
|
2021-11-27 16:12:04 +00:00
|
|
|
|
2021-11-28 07:10:48 +00:00
|
|
|
func (fs *FileSystem) File(rawPath string) (*model.File, error) {
|
2021-11-27 16:12:04 +00:00
|
|
|
rawPath = utils.ParsePath(rawPath)
|
|
|
|
if model.AccountsCount() > 1 && rawPath == "/" {
|
2021-11-28 07:10:48 +00:00
|
|
|
now := time.Now()
|
2021-11-27 16:12:04 +00:00
|
|
|
return &model.File{
|
|
|
|
Name: "root",
|
|
|
|
Size: 0,
|
|
|
|
Type: conf.FOLDER,
|
|
|
|
Driver: "root",
|
2021-11-28 07:10:48 +00:00
|
|
|
UpdatedAt: &now,
|
|
|
|
}, nil
|
2021-11-27 16:12:04 +00:00
|
|
|
}
|
2022-01-03 12:06:36 +00:00
|
|
|
account, path_, driver, err := common.ParsePath(rawPath)
|
2021-11-27 16:12:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-11-28 07:10:48 +00:00
|
|
|
return driver.File(path_, account)
|
2021-11-27 16:12:04 +00:00
|
|
|
}
|
|
|
|
|
2021-11-28 07:10:48 +00:00
|
|
|
func (fs *FileSystem) Files(rawPath string) ([]model.File, error) {
|
2021-11-27 16:12:04 +00:00
|
|
|
rawPath = utils.ParsePath(rawPath)
|
|
|
|
if model.AccountsCount() > 1 && rawPath == "/" {
|
|
|
|
files, err := model.GetAccountFiles()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-11-28 07:10:48 +00:00
|
|
|
return files, nil
|
2021-11-27 16:12:04 +00:00
|
|
|
}
|
2022-01-03 12:06:36 +00:00
|
|
|
account, path_, driver, err := common.ParsePath(rawPath)
|
2021-11-27 16:12:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-11-28 07:10:48 +00:00
|
|
|
return driver.Files(path_, account)
|
2021-11-27 16:12:04 +00:00
|
|
|
}
|
|
|
|
|
2021-12-19 12:32:47 +00:00
|
|
|
func ClientIP(r *http.Request) string {
|
|
|
|
xForwardedFor := r.Header.Get("X-Forwarded-For")
|
|
|
|
ip := strings.TrimSpace(strings.Split(xForwardedFor, ",")[0])
|
|
|
|
if ip != "" {
|
|
|
|
return ip
|
|
|
|
}
|
|
|
|
|
|
|
|
ip = strings.TrimSpace(r.Header.Get("X-Real-Ip"))
|
|
|
|
if ip != "" {
|
|
|
|
return ip
|
|
|
|
}
|
|
|
|
|
|
|
|
if ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)); err == nil {
|
|
|
|
return ip
|
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2021-11-29 08:42:46 +00:00
|
|
|
func (fs *FileSystem) Link(r *http.Request, rawPath string) (string, error) {
|
2021-11-27 16:12:04 +00:00
|
|
|
rawPath = utils.ParsePath(rawPath)
|
2021-11-29 08:42:46 +00:00
|
|
|
log.Debugf("get link path: %s", rawPath)
|
2021-11-27 16:12:04 +00:00
|
|
|
if model.AccountsCount() > 1 && rawPath == "/" {
|
|
|
|
// error
|
|
|
|
}
|
2022-01-03 12:06:36 +00:00
|
|
|
account, path_, driver, err := common.ParsePath(rawPath)
|
2021-11-27 16:12:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2021-11-29 08:42:46 +00:00
|
|
|
link := ""
|
|
|
|
protocol := "http"
|
|
|
|
if r.TLS != nil {
|
|
|
|
protocol = "https"
|
|
|
|
}
|
|
|
|
if driver.Config().OnlyProxy || account.WebdavProxy {
|
|
|
|
link = fmt.Sprintf("%s://%s/p%s", protocol, r.Host, rawPath)
|
2022-01-04 13:21:27 +00:00
|
|
|
if conf.GetBool("check down link") {
|
2021-12-09 11:24:34 +00:00
|
|
|
sign := utils.SignWithToken(utils.Base(rawPath), conf.Token)
|
|
|
|
link += "?sign" + sign
|
2021-11-28 07:10:48 +00:00
|
|
|
}
|
2021-11-29 08:42:46 +00:00
|
|
|
} else {
|
2021-12-19 12:32:47 +00:00
|
|
|
link_, err := driver.Link(base.Args{Path: path_, IP: ClientIP(r)}, account)
|
2021-12-09 11:24:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
link = link_.Url
|
2021-11-28 07:10:48 +00:00
|
|
|
}
|
2021-11-29 08:42:46 +00:00
|
|
|
log.Debugf("webdav get link: %s", link)
|
|
|
|
return link, err
|
2021-11-27 16:12:04 +00:00
|
|
|
}
|
|
|
|
|
2021-12-05 08:09:39 +00:00
|
|
|
func (fs *FileSystem) CreateDirectory(ctx context.Context, rawPath string) error {
|
|
|
|
rawPath = utils.ParsePath(rawPath)
|
|
|
|
if rawPath == "/" {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
|
|
|
if model.AccountsCount() > 1 && len(strings.Split(rawPath, "/")) < 2 {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
2022-01-03 12:06:36 +00:00
|
|
|
account, path_, driver, err := common.ParsePath(rawPath)
|
2021-12-05 08:09:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-12-28 13:28:27 +00:00
|
|
|
log.Debugf("mkdir: %s", path_)
|
2022-01-03 12:06:36 +00:00
|
|
|
return operate.MakeDir(driver, account, path_, true)
|
2021-11-27 16:12:04 +00:00
|
|
|
}
|
|
|
|
|
2021-12-05 07:22:19 +00:00
|
|
|
func (fs *FileSystem) Upload(ctx context.Context, r *http.Request, rawPath string) error {
|
|
|
|
rawPath = utils.ParsePath(rawPath)
|
|
|
|
if model.AccountsCount() > 1 && rawPath == "/" {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
2022-01-03 12:06:36 +00:00
|
|
|
account, path_, driver, err := common.ParsePath(rawPath)
|
2021-12-05 07:22:19 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-12-06 14:14:32 +00:00
|
|
|
//fileSize, err := strconv.ParseUint(r.Header.Get("Content-Length"), 10, 64)
|
|
|
|
fileSize := uint64(r.ContentLength)
|
|
|
|
//if err != nil {
|
|
|
|
// return err
|
|
|
|
//}
|
2021-12-05 07:22:19 +00:00
|
|
|
filePath, fileName := filepath.Split(path_)
|
|
|
|
fileData := model.FileStream{
|
2021-12-06 09:49:20 +00:00
|
|
|
MIMEType: r.Header.Get("Content-Type"),
|
|
|
|
File: r.Body,
|
|
|
|
Size: fileSize,
|
|
|
|
Name: fileName,
|
|
|
|
ParentPath: filePath,
|
2021-12-05 07:22:19 +00:00
|
|
|
}
|
2022-01-03 12:06:36 +00:00
|
|
|
return operate.Upload(driver, account, &fileData, true)
|
2021-12-05 07:22:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (fs *FileSystem) Delete(rawPath string) error {
|
|
|
|
rawPath = utils.ParsePath(rawPath)
|
|
|
|
if rawPath == "/" {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
|
|
|
if model.AccountsCount() > 1 && len(strings.Split(rawPath, "/")) < 2 {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
2022-01-03 12:06:36 +00:00
|
|
|
account, path_, driver, err := common.ParsePath(rawPath)
|
2021-12-05 07:22:19 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-01-03 12:06:36 +00:00
|
|
|
return operate.Delete(driver, account, path_, true)
|
2021-12-05 07:22:19 +00:00
|
|
|
}
|
|
|
|
|
2021-11-27 16:12:04 +00:00
|
|
|
// 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.
|
2021-12-05 07:22:19 +00:00
|
|
|
func moveFiles(ctx context.Context, fs *FileSystem, src string, dst string, overwrite bool) (status int, err error) {
|
|
|
|
src = utils.ParsePath(src)
|
|
|
|
dst = utils.ParsePath(dst)
|
2021-12-10 14:24:43 +00:00
|
|
|
log.Debugf("move %s -> %s", src, dst)
|
2021-12-05 07:22:19 +00:00
|
|
|
if src == dst {
|
|
|
|
return http.StatusMethodNotAllowed, errDestinationEqualsSource
|
|
|
|
}
|
2022-01-03 12:06:36 +00:00
|
|
|
srcAccount, srcPath, driver, err := common.ParsePath(src)
|
2021-12-05 07:22:19 +00:00
|
|
|
if err != nil {
|
|
|
|
return http.StatusMethodNotAllowed, err
|
|
|
|
}
|
2022-01-03 12:06:36 +00:00
|
|
|
dstAccount, dstPath, _, err := common.ParsePath(dst)
|
2021-12-05 07:22:19 +00:00
|
|
|
if err != nil {
|
|
|
|
return http.StatusMethodNotAllowed, err
|
|
|
|
}
|
|
|
|
if srcAccount.Name != dstAccount.Name {
|
|
|
|
return http.StatusMethodNotAllowed, errInvalidDestination
|
|
|
|
}
|
2022-01-03 12:06:36 +00:00
|
|
|
err = operate.Move(driver, srcAccount, srcPath, dstPath, true)
|
2021-12-05 07:22:19 +00:00
|
|
|
if err != nil {
|
2021-12-16 14:50:23 +00:00
|
|
|
log.Debug(err)
|
2021-12-05 07:22:19 +00:00
|
|
|
return http.StatusInternalServerError, err
|
|
|
|
}
|
2021-11-27 16:12:04 +00:00
|
|
|
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.
|
2021-12-05 07:22:19 +00:00
|
|
|
func copyFiles(ctx context.Context, fs *FileSystem, src string, dst string, overwrite bool, depth int, recursion int) (status int, err error) {
|
|
|
|
src = utils.ParsePath(src)
|
|
|
|
dst = utils.ParsePath(dst)
|
2021-12-10 14:24:43 +00:00
|
|
|
log.Debugf("move %s -> %s", src, dst)
|
2021-12-05 07:22:19 +00:00
|
|
|
if src == dst {
|
|
|
|
return http.StatusMethodNotAllowed, errDestinationEqualsSource
|
|
|
|
}
|
2022-01-03 12:06:36 +00:00
|
|
|
srcAccount, srcPath, driver, err := common.ParsePath(src)
|
2021-12-05 07:22:19 +00:00
|
|
|
if err != nil {
|
|
|
|
return http.StatusMethodNotAllowed, err
|
|
|
|
}
|
2022-01-03 12:06:36 +00:00
|
|
|
dstAccount, dstPath, _, err := common.ParsePath(dst)
|
2021-12-05 07:22:19 +00:00
|
|
|
if err != nil {
|
|
|
|
return http.StatusMethodNotAllowed, err
|
|
|
|
}
|
|
|
|
if srcAccount.Name != dstAccount.Name {
|
|
|
|
// TODO 跨账号复制
|
|
|
|
return http.StatusMethodNotAllowed, errInvalidDestination
|
|
|
|
}
|
2022-01-03 12:06:36 +00:00
|
|
|
err = operate.Copy(driver, srcAccount, srcPath, dstPath, true)
|
2021-12-05 07:22:19 +00:00
|
|
|
if err != nil {
|
|
|
|
return http.StatusInternalServerError, err
|
|
|
|
}
|
2021-11-27 16:12:04 +00:00
|
|
|
return http.StatusNoContent, 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,
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
files, err := fs.Files(name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|