alist/server/webdav/file.go
WntFlm 658fd5ad6e fix(webdav): empty mimeType
Now mimeType will always be a non-empty string, by defaulting it to "application/octet-stream".
2022-05-01 09:42:25 +08:00

345 lines
8.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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"
"net"
"net/http"
"path"
"path/filepath"
"strings"
"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"
)
type FileSystem struct{}
var upFileMap = make(map[string]*model.File)
func (fs *FileSystem) File(rawPath string) (*model.File, error) {
rawPath = utils.ParsePath(rawPath)
if f, ok := upFileMap[rawPath]; ok {
return f, nil
}
account, path_, driver, err := common.ParsePath(rawPath)
log.Debugln(account, path_, driver, err)
if err != nil {
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
}
}
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
}
func (fs *FileSystem) Files(ctx context.Context, rawPath string) ([]model.File, error) {
rawPath = utils.ParsePath(rawPath)
//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
}
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 ""
}
func (fs *FileSystem) Link(w http.ResponseWriter, r *http.Request, rawPath string) (string, error) {
rawPath = utils.ParsePath(rawPath)
log.Debugf("get link path: %s", rawPath)
if model.AccountsCount() > 1 && rawPath == "/" {
// error
}
account, path_, driver, err := common.ParsePath(rawPath)
if err != nil {
return "", err
}
link := ""
protocol := "http"
if r.TLS != nil {
protocol = "https"
}
// 直接返回
if account.WebdavDirect {
file, err := fs.File(rawPath)
if err != nil {
return "", err
}
link_, err := driver.Link(base.Args{Path: path_, Header: r.Header}, account)
if err != nil {
return "", err
}
err = common.Proxy(w, r, link_, file)
return "", err
}
if driver.Config().OnlyProxy || account.WebdavProxy {
link = fmt.Sprintf("%s://%s/p%s", protocol, r.Host, rawPath)
//if conf.GetBool("check down link") {
sign := utils.SignWithToken(utils.Base(rawPath), conf.Token)
link += "?sign=" + sign
//}
} else {
link_, err := driver.Link(base.Args{Path: path_, IP: ClientIP(r)}, account)
if err != nil {
return "", err
}
link = link_.Url
}
log.Debugf("webdav get link: %s", link)
return link, err
}
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
}
account, path_, driver, err := common.ParsePath(rawPath)
if err != nil {
return err
}
log.Debugf("mkdir: %s", path_)
return operate.MakeDir(driver, account, path_, true)
}
func (fs *FileSystem) Upload(ctx context.Context, r *http.Request, rawPath string) (FileInfo, error) {
rawPath = utils.ParsePath(rawPath)
if model.AccountsCount() > 1 && rawPath == "/" {
return nil, ErrNotImplemented
}
account, path_, driver, err := common.ParsePath(rawPath)
if err != nil {
return nil, err
}
fileSize := uint64(r.ContentLength)
filePath, fileName := filepath.Split(path_)
now := time.Now()
fi := &model.File{
Name: fileName,
Size: 0,
UpdatedAt: &now,
}
if fileSize == 0 {
// 如果文件大小为0默认成功
upFileMap[rawPath] = fi
return fi, nil
} else {
delete(upFileMap, rawPath)
}
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"
}
}
fileData := model.FileStream{
MIMEType: mimeType,
File: r.Body,
Size: fileSize,
Name: fileName,
ParentPath: filePath,
}
return fi, operate.Upload(driver, account, &fileData, true)
}
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
}
account, path_, driver, err := common.ParsePath(rawPath)
if err != nil {
return err
}
return operate.Delete(driver, account, path_, true)
}
// 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, src string, dst string, overwrite bool) (status int, err error) {
src = utils.ParsePath(src)
dst = utils.ParsePath(dst)
log.Debugf("move %s -> %s", src, dst)
if src == dst {
return http.StatusMethodNotAllowed, errDestinationEqualsSource
}
srcAccount, srcPath, driver, err := common.ParsePath(src)
if err != nil {
return http.StatusMethodNotAllowed, err
}
dstAccount, dstPath, _, err := common.ParsePath(dst)
if err != nil {
return http.StatusMethodNotAllowed, err
}
if srcAccount.Name != dstAccount.Name {
return http.StatusMethodNotAllowed, errInvalidDestination
}
err = operate.Move(driver, srcAccount, srcPath, dstPath, true)
if err != nil {
log.Debug(err)
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, src string, dst string, overwrite bool, depth int, recursion int) (status int, err error) {
src = utils.ParsePath(src)
dst = utils.ParsePath(dst)
log.Debugf("move %s -> %s", src, dst)
if src == dst {
return http.StatusMethodNotAllowed, errDestinationEqualsSource
}
srcAccount, srcPath, driver, err := common.ParsePath(src)
if err != nil {
return http.StatusMethodNotAllowed, err
}
dstAccount, dstPath, _, err := common.ParsePath(dst)
if err != nil {
return http.StatusMethodNotAllowed, err
}
if srcAccount.Name != dstAccount.Name {
// TODO 跨账号复制
return http.StatusMethodNotAllowed, errInvalidDestination
}
err = operate.Copy(driver, srcAccount, srcPath, dstPath, true)
if err != nil {
return http.StatusInternalServerError, err
}
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)
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
}