alist/server/webdav/file.go

345 lines
8.9 KiB
Go
Raw Normal View History

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"
"mime"
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"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/drivers/operate"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
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
2022-03-17 13:56:50 +00:00
var upFileMap = make(map[string]*model.File)
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)
2022-03-17 13:56:50 +00:00
if f, ok := upFileMap[rawPath]; ok {
return f, nil
}
2022-01-03 12:06:36 +00:00
account, path_, driver, err := common.ParsePath(rawPath)
2022-04-01 12:40:57 +00:00
log.Debugln(account, path_, driver, err)
2021-11-27 16:12:04 +00:00
if err != nil {
2022-04-01 13:57:55 +00:00
if err.Error() == "path not found" {
accountFiles := model.GetAccountFilesByPath(rawPath)
if len(accountFiles) != 0 {
now := time.Now()
return &model.File{
Name: "root",
Size: 0,
Type: conf.FOLDER,
UpdatedAt: &now,
}, nil
}
}
2021-11-27 16:12:04 +00:00
return nil, err
}
file, err := operate.File(driver, account, path_)
if err != nil && err.Error() == "path not found" {
accountFiles := model.GetAccountFilesByPath(rawPath)
if len(accountFiles) != 0 {
now := time.Now()
return &model.File{
Name: "root",
Size: 0,
Type: conf.FOLDER,
UpdatedAt: &now,
}, nil
}
}
return file, err
2021-11-27 16:12:04 +00:00
}
func (fs *FileSystem) Files(ctx context.Context, rawPath string) ([]model.File, error) {
2021-11-27 16:12:04 +00:00
rawPath = utils.ParsePath(rawPath)
2022-03-31 12:43:17 +00:00
//var files []model.File
//var err error
//if model.AccountsCount() > 1 && rawPath == "/" {
// files, err = model.GetAccountFilesByPath("/")
//} else {
// account, path_, driver, err := common.ParsePath(rawPath)
// if err != nil {
// return nil, err
// }
// files, err = operate.Files(driver, account, path_)
//}
_, files, _, _, _, err := common.Path(rawPath)
if err != nil {
return nil, err
}
meta, _ := model.GetMetaByPath(rawPath)
if visitor := ctx.Value("visitor"); visitor != nil {
if visitor.(bool) {
log.Debug("visitor")
files = common.Hide(meta, files)
}
} else {
log.Debug("admin")
}
return files, nil
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 ""
}
2022-02-13 07:57:42 +00:00
func (fs *FileSystem) Link(w http.ResponseWriter, 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"
}
2022-02-13 07:57:42 +00:00
// 直接返回
if account.WebdavDirect {
file, err := fs.File(rawPath)
if err != nil {
return "", err
}
2022-04-23 14:43:02 +00:00
link_, err := driver.Link(base.Args{Path: path_, Header: r.Header}, account)
2022-02-13 07:57:42 +00:00
if err != nil {
return "", err
}
err = common.Proxy(w, r, link_, file)
return "", err
}
2021-11-29 08:42:46 +00:00
if driver.Config().OnlyProxy || account.WebdavProxy {
link = fmt.Sprintf("%s://%s/p%s", protocol, r.Host, rawPath)
2022-03-29 08:34:22 +00:00
//if conf.GetBool("check down link") {
sign := utils.SignWithToken(utils.Base(rawPath), conf.Token)
link += "?sign=" + sign
//}
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
}
func (fs *FileSystem) Upload(ctx context.Context, r *http.Request, rawPath string) (FileInfo, error) {
2021-12-05 07:22:19 +00:00
rawPath = utils.ParsePath(rawPath)
if model.AccountsCount() > 1 && rawPath == "/" {
return nil, ErrNotImplemented
2021-12-05 07:22:19 +00:00
}
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 nil, err
2021-12-05 07:22:19 +00:00
}
fileSize := uint64(r.ContentLength)
2021-12-05 07:22:19 +00:00
filePath, fileName := filepath.Split(path_)
2022-02-02 10:48:34 +00:00
now := time.Now()
fi := &model.File{
Name: fileName,
Size: 0,
UpdatedAt: &now,
}
2022-02-02 10:48:34 +00:00
if fileSize == 0 {
// 如果文件大小为0默认成功
2022-03-17 13:56:50 +00:00
upFileMap[rawPath] = fi
return fi, nil
2022-03-17 13:56:50 +00:00
} else {
delete(upFileMap, rawPath)
2022-02-02 10:48:34 +00:00
}
mimeType := r.Header.Get("Content-Type")
if mimeType == "" || strings.ToLower(mimeType) == "application/octet-stream" {
mimeTypeTmp := mime.TypeByExtension(path.Ext(fileName))
if mimeTypeTmp != "" {
mimeType = mimeTypeTmp
} else {
mimeType = "application/octet-stream"
}
}
2021-12-05 07:22:19 +00:00
fileData := model.FileStream{
MIMEType: mimeType,
File: r.Body,
Size: fileSize,
Name: fileName,
ParentPath: filePath,
2021-12-05 07:22:19 +00:00
}
return fi, 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(ctx, name)
2021-11-27 16:12:04 +00:00
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
}