mirror of https://github.com/Xhofe/alist
383 lines
10 KiB
Go
383 lines
10 KiB
Go
package handles
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
stdpath "path"
|
|
"strings"
|
|
|
|
"github.com/alist-org/alist/v3/internal/archive/tool"
|
|
"github.com/alist-org/alist/v3/internal/conf"
|
|
"github.com/alist-org/alist/v3/internal/errs"
|
|
"github.com/alist-org/alist/v3/internal/fs"
|
|
"github.com/alist-org/alist/v3/internal/model"
|
|
"github.com/alist-org/alist/v3/internal/op"
|
|
"github.com/alist-org/alist/v3/internal/setting"
|
|
"github.com/alist-org/alist/v3/internal/sign"
|
|
"github.com/alist-org/alist/v3/pkg/utils"
|
|
"github.com/alist-org/alist/v3/server/common"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/pkg/errors"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type ArchiveMetaReq struct {
|
|
Path string `json:"path" form:"path"`
|
|
Password string `json:"password" form:"password"`
|
|
Refresh bool `json:"refresh" form:"refresh"`
|
|
ArchivePass string `json:"archive_pass" form:"archive_pass"`
|
|
}
|
|
|
|
type ArchiveMetaResp struct {
|
|
Comment string `json:"comment"`
|
|
IsEncrypted bool `json:"encrypted"`
|
|
Content []ArchiveContentResp `json:"content"`
|
|
Sort *model.Sort `json:"sort,omitempty"`
|
|
RawURL string `json:"raw_url"`
|
|
Sign string `json:"sign"`
|
|
}
|
|
|
|
type ArchiveContentResp struct {
|
|
ObjResp
|
|
Children []ArchiveContentResp `json:"children"`
|
|
}
|
|
|
|
func toObjsRespWithoutSignAndThumb(obj model.Obj) ObjResp {
|
|
return ObjResp{
|
|
Name: obj.GetName(),
|
|
Size: obj.GetSize(),
|
|
IsDir: obj.IsDir(),
|
|
Modified: obj.ModTime(),
|
|
Created: obj.CreateTime(),
|
|
HashInfoStr: obj.GetHash().String(),
|
|
HashInfo: obj.GetHash().Export(),
|
|
Sign: "",
|
|
Thumb: "",
|
|
Type: utils.GetObjType(obj.GetName(), obj.IsDir()),
|
|
}
|
|
}
|
|
|
|
func toContentResp(objs []model.ObjTree) []ArchiveContentResp {
|
|
if objs == nil {
|
|
return nil
|
|
}
|
|
ret, _ := utils.SliceConvert(objs, func(src model.ObjTree) (ArchiveContentResp, error) {
|
|
return ArchiveContentResp{
|
|
ObjResp: toObjsRespWithoutSignAndThumb(src),
|
|
Children: toContentResp(src.GetChildren()),
|
|
}, nil
|
|
})
|
|
return ret
|
|
}
|
|
|
|
func FsArchiveMeta(c *gin.Context) {
|
|
var req ArchiveMetaReq
|
|
if err := c.ShouldBind(&req); err != nil {
|
|
common.ErrorResp(c, err, 400)
|
|
return
|
|
}
|
|
user := c.MustGet("user").(*model.User)
|
|
if !user.CanReadArchives() {
|
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
|
return
|
|
}
|
|
reqPath, err := user.JoinPath(req.Path)
|
|
if err != nil {
|
|
common.ErrorResp(c, err, 403)
|
|
return
|
|
}
|
|
meta, err := op.GetNearestMeta(reqPath)
|
|
if err != nil {
|
|
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
|
common.ErrorResp(c, err, 500, true)
|
|
return
|
|
}
|
|
}
|
|
c.Set("meta", meta)
|
|
if !common.CanAccess(user, meta, reqPath, req.Password) {
|
|
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
|
return
|
|
}
|
|
archiveArgs := model.ArchiveArgs{
|
|
LinkArgs: model.LinkArgs{
|
|
Header: c.Request.Header,
|
|
Type: c.Query("type"),
|
|
HttpReq: c.Request,
|
|
},
|
|
Password: req.ArchivePass,
|
|
}
|
|
ret, err := fs.ArchiveMeta(c, reqPath, model.ArchiveMetaArgs{
|
|
ArchiveArgs: archiveArgs,
|
|
Refresh: req.Refresh,
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, errs.WrongArchivePassword) {
|
|
common.ErrorResp(c, err, 202)
|
|
} else {
|
|
common.ErrorResp(c, err, 500)
|
|
}
|
|
return
|
|
}
|
|
s := ""
|
|
if isEncrypt(meta, reqPath) || setting.GetBool(conf.SignAll) {
|
|
s = sign.Sign(reqPath)
|
|
}
|
|
api := "/ae"
|
|
if ret.DriverProviding {
|
|
api = "/ad"
|
|
}
|
|
common.SuccessResp(c, ArchiveMetaResp{
|
|
Comment: ret.GetComment(),
|
|
IsEncrypted: ret.IsEncrypted(),
|
|
Content: toContentResp(ret.GetTree()),
|
|
Sort: ret.Sort,
|
|
RawURL: fmt.Sprintf("%s%s%s", common.GetApiUrl(c.Request), api, utils.EncodePath(reqPath, true)),
|
|
Sign: s,
|
|
})
|
|
}
|
|
|
|
type ArchiveListReq struct {
|
|
ArchiveMetaReq
|
|
model.PageReq
|
|
InnerPath string `json:"inner_path" form:"inner_path"`
|
|
}
|
|
|
|
type ArchiveListResp struct {
|
|
Content []ObjResp `json:"content"`
|
|
Total int64 `json:"total"`
|
|
}
|
|
|
|
func FsArchiveList(c *gin.Context) {
|
|
var req ArchiveListReq
|
|
if err := c.ShouldBind(&req); err != nil {
|
|
common.ErrorResp(c, err, 400)
|
|
return
|
|
}
|
|
req.Validate()
|
|
user := c.MustGet("user").(*model.User)
|
|
if !user.CanReadArchives() {
|
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
|
return
|
|
}
|
|
reqPath, err := user.JoinPath(req.Path)
|
|
if err != nil {
|
|
common.ErrorResp(c, err, 403)
|
|
return
|
|
}
|
|
meta, err := op.GetNearestMeta(reqPath)
|
|
if err != nil {
|
|
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
|
common.ErrorResp(c, err, 500, true)
|
|
return
|
|
}
|
|
}
|
|
c.Set("meta", meta)
|
|
if !common.CanAccess(user, meta, reqPath, req.Password) {
|
|
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
|
return
|
|
}
|
|
objs, err := fs.ArchiveList(c, reqPath, model.ArchiveListArgs{
|
|
ArchiveInnerArgs: model.ArchiveInnerArgs{
|
|
ArchiveArgs: model.ArchiveArgs{
|
|
LinkArgs: model.LinkArgs{
|
|
Header: c.Request.Header,
|
|
Type: c.Query("type"),
|
|
HttpReq: c.Request,
|
|
},
|
|
Password: req.ArchivePass,
|
|
},
|
|
InnerPath: utils.FixAndCleanPath(req.InnerPath),
|
|
},
|
|
Refresh: req.Refresh,
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, errs.WrongArchivePassword) {
|
|
common.ErrorResp(c, err, 202)
|
|
} else {
|
|
common.ErrorResp(c, err, 500)
|
|
}
|
|
return
|
|
}
|
|
total, objs := pagination(objs, &req.PageReq)
|
|
ret, _ := utils.SliceConvert(objs, func(src model.Obj) (ObjResp, error) {
|
|
return toObjsRespWithoutSignAndThumb(src), nil
|
|
})
|
|
common.SuccessResp(c, ArchiveListResp{
|
|
Content: ret,
|
|
Total: int64(total),
|
|
})
|
|
}
|
|
|
|
type ArchiveDecompressReq struct {
|
|
SrcDir string `json:"src_dir" form:"src_dir"`
|
|
DstDir string `json:"dst_dir" form:"dst_dir"`
|
|
Name string `json:"name" form:"name"`
|
|
ArchivePass string `json:"archive_pass" form:"archive_pass"`
|
|
InnerPath string `json:"inner_path" form:"inner_path"`
|
|
CacheFull bool `json:"cache_full" form:"cache_full"`
|
|
PutIntoNewDir bool `json:"put_into_new_dir" form:"put_into_new_dir"`
|
|
}
|
|
|
|
func FsArchiveDecompress(c *gin.Context) {
|
|
var req ArchiveDecompressReq
|
|
if err := c.ShouldBind(&req); err != nil {
|
|
common.ErrorResp(c, err, 400)
|
|
return
|
|
}
|
|
user := c.MustGet("user").(*model.User)
|
|
if !user.CanDecompress() {
|
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
|
return
|
|
}
|
|
srcPath, err := user.JoinPath(stdpath.Join(req.SrcDir, req.Name))
|
|
if err != nil {
|
|
common.ErrorResp(c, err, 403)
|
|
return
|
|
}
|
|
dstDir, err := user.JoinPath(req.DstDir)
|
|
if err != nil {
|
|
common.ErrorResp(c, err, 403)
|
|
return
|
|
}
|
|
t, err := fs.ArchiveDecompress(c, srcPath, dstDir, model.ArchiveDecompressArgs{
|
|
ArchiveInnerArgs: model.ArchiveInnerArgs{
|
|
ArchiveArgs: model.ArchiveArgs{
|
|
LinkArgs: model.LinkArgs{
|
|
Header: c.Request.Header,
|
|
Type: c.Query("type"),
|
|
HttpReq: c.Request,
|
|
},
|
|
Password: req.ArchivePass,
|
|
},
|
|
InnerPath: utils.FixAndCleanPath(req.InnerPath),
|
|
},
|
|
CacheFull: req.CacheFull,
|
|
PutIntoNewDir: req.PutIntoNewDir,
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, errs.WrongArchivePassword) {
|
|
common.ErrorResp(c, err, 202)
|
|
} else {
|
|
common.ErrorResp(c, err, 500)
|
|
}
|
|
return
|
|
}
|
|
common.SuccessResp(c, gin.H{
|
|
"task": getTaskInfo(t),
|
|
})
|
|
}
|
|
|
|
func ArchiveDown(c *gin.Context) {
|
|
archiveRawPath := c.MustGet("path").(string)
|
|
innerPath := utils.FixAndCleanPath(c.Query("inner"))
|
|
password := c.Query("pass")
|
|
filename := stdpath.Base(innerPath)
|
|
storage, err := fs.GetStorage(archiveRawPath, &fs.GetStoragesArgs{})
|
|
if err != nil {
|
|
common.ErrorResp(c, err, 500)
|
|
return
|
|
}
|
|
if common.ShouldProxy(storage, filename) {
|
|
ArchiveProxy(c)
|
|
return
|
|
} else {
|
|
link, _, err := fs.ArchiveDriverExtract(c, archiveRawPath, model.ArchiveInnerArgs{
|
|
ArchiveArgs: model.ArchiveArgs{
|
|
LinkArgs: model.LinkArgs{
|
|
IP: c.ClientIP(),
|
|
Header: c.Request.Header,
|
|
Type: c.Query("type"),
|
|
HttpReq: c.Request,
|
|
Redirect: true,
|
|
},
|
|
Password: password,
|
|
},
|
|
InnerPath: innerPath,
|
|
})
|
|
if err != nil {
|
|
common.ErrorResp(c, err, 500)
|
|
return
|
|
}
|
|
down(c, link)
|
|
}
|
|
}
|
|
|
|
func ArchiveProxy(c *gin.Context) {
|
|
archiveRawPath := c.MustGet("path").(string)
|
|
innerPath := utils.FixAndCleanPath(c.Query("inner"))
|
|
password := c.Query("pass")
|
|
filename := stdpath.Base(innerPath)
|
|
storage, err := fs.GetStorage(archiveRawPath, &fs.GetStoragesArgs{})
|
|
if err != nil {
|
|
common.ErrorResp(c, err, 500)
|
|
return
|
|
}
|
|
if canProxy(storage, filename) {
|
|
// TODO: Support external download proxy URL
|
|
link, file, err := fs.ArchiveDriverExtract(c, archiveRawPath, model.ArchiveInnerArgs{
|
|
ArchiveArgs: model.ArchiveArgs{
|
|
LinkArgs: model.LinkArgs{
|
|
Header: c.Request.Header,
|
|
Type: c.Query("type"),
|
|
HttpReq: c.Request,
|
|
},
|
|
Password: password,
|
|
},
|
|
InnerPath: innerPath,
|
|
})
|
|
if err != nil {
|
|
common.ErrorResp(c, err, 500)
|
|
return
|
|
}
|
|
localProxy(c, link, file, storage.GetStorage().ProxyRange)
|
|
} else {
|
|
common.ErrorStrResp(c, "proxy not allowed", 403)
|
|
return
|
|
}
|
|
}
|
|
|
|
func ArchiveInternalExtract(c *gin.Context) {
|
|
archiveRawPath := c.MustGet("path").(string)
|
|
innerPath := utils.FixAndCleanPath(c.Query("inner"))
|
|
password := c.Query("pass")
|
|
rc, size, err := fs.ArchiveInternalExtract(c, archiveRawPath, model.ArchiveInnerArgs{
|
|
ArchiveArgs: model.ArchiveArgs{
|
|
LinkArgs: model.LinkArgs{
|
|
Header: c.Request.Header,
|
|
Type: c.Query("type"),
|
|
HttpReq: c.Request,
|
|
},
|
|
Password: password,
|
|
},
|
|
InnerPath: innerPath,
|
|
})
|
|
if err != nil {
|
|
common.ErrorResp(c, err, 500)
|
|
return
|
|
}
|
|
defer func() {
|
|
if err := rc.Close(); err != nil {
|
|
log.Errorf("failed to close file streamer, %v", err)
|
|
}
|
|
}()
|
|
headers := map[string]string{
|
|
"Referrer-Policy": "no-referrer",
|
|
"Cache-Control": "max-age=0, no-cache, no-store, must-revalidate",
|
|
}
|
|
filename := stdpath.Base(innerPath)
|
|
headers["Content-Disposition"] = fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, filename, url.PathEscape(filename))
|
|
contentType := c.Request.Header.Get("Content-Type")
|
|
if contentType == "" {
|
|
contentType = utils.GetMimeType(filename)
|
|
}
|
|
c.DataFromReader(200, size, contentType, rc, headers)
|
|
}
|
|
|
|
func ArchiveExtensions(c *gin.Context) {
|
|
var ext []string
|
|
for key := range tool.Tools {
|
|
ext = append(ext, strings.TrimPrefix(key, "."))
|
|
}
|
|
common.SuccessResp(c, ext)
|
|
}
|