mirror of https://github.com/Xhofe/alist
feat: file down handle
parent
d89ec89d51
commit
67bc66fedf
|
@ -88,7 +88,7 @@ func initialSettings() {
|
|||
{Key: "global_readme", Value: "This is global readme", Type: conf.TypeText, Group: model.GLOBAL},
|
||||
{Key: "customize_head", Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||
{Key: "customize_body", Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||
{Key: "link_expiration", Value: "4", Type: conf.TypeNumber, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||
{Key: "link_expiration", Value: "0", Type: conf.TypeNumber, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||
// single settings
|
||||
{Key: "token", Value: token, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||
}
|
||||
|
|
|
@ -9,3 +9,7 @@ type Config struct {
|
|||
NoCache bool
|
||||
NoUpload bool
|
||||
}
|
||||
|
||||
func (c Config) MustProxy() bool {
|
||||
return c.OnlyProxy || c.OnlyLocal
|
||||
}
|
||||
|
|
|
@ -2,7 +2,9 @@ package fs
|
|||
|
||||
import (
|
||||
"context"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/operations"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@ -92,3 +94,11 @@ func PutAsTask(dstDirPath string, file model.FileStreamer) error {
|
|||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func GetAccount(path string) (driver.Driver, error) {
|
||||
accountDriver, _, err := operations.GetAccountAndActualPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return accountDriver, nil
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
stdpath "path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
@ -26,3 +27,11 @@ func StandardizePath(path string) string {
|
|||
func PathEqual(path1, path2 string) bool {
|
||||
return StandardizePath(path1) == StandardizePath(path2)
|
||||
}
|
||||
|
||||
func Ext(path string) string {
|
||||
ext := stdpath.Ext(path)
|
||||
if strings.HasPrefix(ext, ".") {
|
||||
return ext[1:]
|
||||
}
|
||||
return ext
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var HttpClient = &http.Client{}
|
||||
|
||||
func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.Obj) error {
|
||||
// read data with native
|
||||
var err error
|
||||
if link.Data != nil {
|
||||
defer func() {
|
||||
_ = link.Data.Close()
|
||||
}()
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.GetName(), url.QueryEscape(file.GetName())))
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(file.GetSize(), 10))
|
||||
if link.Header != nil {
|
||||
for h, val := range link.Header {
|
||||
w.Header()[h] = val
|
||||
}
|
||||
}
|
||||
if link.Status == 0 {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
w.WriteHeader(link.Status)
|
||||
}
|
||||
_, err = io.Copy(w, link.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// local file
|
||||
if link.FilePath != nil && *link.FilePath != "" {
|
||||
f, err := os.Open(*link.FilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
fileStat, err := os.Stat(*link.FilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.GetName(), url.QueryEscape(file.GetName())))
|
||||
http.ServeContent(w, r, file.GetName(), fileStat.ModTime(), f)
|
||||
return nil
|
||||
} else {
|
||||
req, err := http.NewRequest(r.Method, link.URL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for h, val := range r.Header {
|
||||
if strings.ToLower(h) == "authorization" {
|
||||
continue
|
||||
}
|
||||
req.Header[h] = val
|
||||
}
|
||||
for h, val := range link.Header {
|
||||
req.Header[h] = val
|
||||
}
|
||||
res, err := HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = res.Body.Close()
|
||||
}()
|
||||
log.Debugf("proxy status: %d", res.StatusCode)
|
||||
for h, v := range res.Header {
|
||||
w.Header()[h] = v
|
||||
}
|
||||
w.WriteHeader(res.StatusCode)
|
||||
if res.StatusCode >= 400 {
|
||||
all, _ := ioutil.ReadAll(res.Body)
|
||||
msg := string(all)
|
||||
log.Debugln(msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
_, err = io.Copy(w, res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"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/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"
|
||||
stdpath "path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Down(c *gin.Context) {
|
||||
rawPath := parsePath(c.Param("path"))
|
||||
filename := stdpath.Base(rawPath)
|
||||
meta, err := db.GetNearestMeta(rawPath)
|
||||
if err != nil {
|
||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
// verify sign
|
||||
if needSign(meta, rawPath) {
|
||||
s := c.Param("sign")
|
||||
err = sign.Verify(filename, s)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 401, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
account, err := fs.GetAccount(rawPath)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
return
|
||||
}
|
||||
if needProxy(account, filename) {
|
||||
link, err := fs.Link(c, rawPath, model.LinkArgs{
|
||||
Header: c.Request.Header,
|
||||
})
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
return
|
||||
}
|
||||
obj, err := fs.Get(c, rawPath)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
return
|
||||
}
|
||||
err = common.Proxy(c.Writer, c.Request, link, obj)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
link, err := fs.Link(c, rawPath, model.LinkArgs{
|
||||
IP: c.ClientIP(),
|
||||
Header: c.Request.Header,
|
||||
})
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
return
|
||||
}
|
||||
c.Redirect(302, link.URL)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: implement
|
||||
// path maybe contains # ? etc.
|
||||
func parsePath(path string) string {
|
||||
return utils.StandardizePath(path)
|
||||
}
|
||||
|
||||
func needSign(meta *model.Meta, path string) bool {
|
||||
if meta == nil || meta.Password == "" {
|
||||
return false
|
||||
}
|
||||
if !meta.SubFolder && path != meta.Path {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func needProxy(account driver.Driver, filename string) bool {
|
||||
config := account.Config()
|
||||
if config.MustProxy() {
|
||||
return true
|
||||
}
|
||||
proxyTypes := setting.GetByKey("proxy_types")
|
||||
if strings.Contains(proxyTypes, utils.Ext(filename)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -2,10 +2,12 @@ package controllers
|
|||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"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/server/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pkg/errors"
|
||||
stdpath "path"
|
||||
)
|
||||
|
||||
|
@ -27,7 +29,13 @@ func FsGet(c *gin.Context) {
|
|||
}
|
||||
user := c.MustGet("user").(*model.User)
|
||||
req.Path = stdpath.Join(user.BasePath, req.Path)
|
||||
meta, _ := db.GetNearestMeta(req.Path)
|
||||
meta, err := db.GetNearestMeta(req.Path)
|
||||
if err != nil {
|
||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Set("meta", meta)
|
||||
if !canAccess(user, meta, req.Path, req.Password) {
|
||||
common.ErrorStrResp(c, "password is incorrect", 401)
|
||||
|
|
|
@ -2,6 +2,7 @@ package controllers
|
|||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"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/setting"
|
||||
|
@ -9,6 +10,7 @@ import (
|
|||
"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"
|
||||
stdpath "path"
|
||||
"time"
|
||||
)
|
||||
|
@ -41,7 +43,13 @@ func FsList(c *gin.Context) {
|
|||
req.Validate()
|
||||
user := c.MustGet("user").(*model.User)
|
||||
req.Path = stdpath.Join(user.BasePath, req.Path)
|
||||
meta, _ := db.GetNearestMeta(req.Path)
|
||||
meta, err := db.GetNearestMeta(req.Path)
|
||||
if err != nil {
|
||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Set("meta", meta)
|
||||
if !canAccess(user, meta, req.Path, req.Password) {
|
||||
common.ErrorStrResp(c, "password is incorrect", 401)
|
||||
|
|
|
@ -13,6 +13,8 @@ func Init(r *gin.Engine) {
|
|||
common.SecretKey = []byte(conf.Conf.JwtSecret)
|
||||
Cors(r)
|
||||
|
||||
r.GET("/d/*path", controllers.Down)
|
||||
|
||||
api := r.Group("/api", middlewares.Auth)
|
||||
api.POST("/auth/login", controllers.Login)
|
||||
api.GET("/auth/current", controllers.CurrentUser)
|
||||
|
|
Loading…
Reference in New Issue