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: "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_head", Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||||
{Key: "customize_body", 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
|
// single settings
|
||||||
{Key: "token", Value: token, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
{Key: "token", Value: token, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,3 +9,7 @@ type Config struct {
|
||||||
NoCache bool
|
NoCache bool
|
||||||
NoUpload bool
|
NoUpload bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Config) MustProxy() bool {
|
||||||
|
return c.OnlyProxy || c.OnlyLocal
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@ package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/operations"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -92,3 +94,11 @@ func PutAsTask(dstDirPath string, file model.FileStreamer) error {
|
||||||
}
|
}
|
||||||
return err
|
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
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
stdpath "path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -26,3 +27,11 @@ func StandardizePath(path string) string {
|
||||||
func PathEqual(path1, path2 string) bool {
|
func PathEqual(path1, path2 string) bool {
|
||||||
return StandardizePath(path1) == StandardizePath(path2)
|
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 (
|
import (
|
||||||
"github.com/alist-org/alist/v3/internal/db"
|
"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/fs"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pkg/errors"
|
||||||
stdpath "path"
|
stdpath "path"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,7 +29,13 @@ func FsGet(c *gin.Context) {
|
||||||
}
|
}
|
||||||
user := c.MustGet("user").(*model.User)
|
user := c.MustGet("user").(*model.User)
|
||||||
req.Path = stdpath.Join(user.BasePath, req.Path)
|
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)
|
c.Set("meta", meta)
|
||||||
if !canAccess(user, meta, req.Path, req.Password) {
|
if !canAccess(user, meta, req.Path, req.Password) {
|
||||||
common.ErrorStrResp(c, "password is incorrect", 401)
|
common.ErrorStrResp(c, "password is incorrect", 401)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/alist-org/alist/v3/internal/db"
|
"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/fs"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/setting"
|
"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/pkg/utils"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pkg/errors"
|
||||||
stdpath "path"
|
stdpath "path"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -41,7 +43,13 @@ func FsList(c *gin.Context) {
|
||||||
req.Validate()
|
req.Validate()
|
||||||
user := c.MustGet("user").(*model.User)
|
user := c.MustGet("user").(*model.User)
|
||||||
req.Path = stdpath.Join(user.BasePath, req.Path)
|
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)
|
c.Set("meta", meta)
|
||||||
if !canAccess(user, meta, req.Path, req.Password) {
|
if !canAccess(user, meta, req.Path, req.Password) {
|
||||||
common.ErrorStrResp(c, "password is incorrect", 401)
|
common.ErrorStrResp(c, "password is incorrect", 401)
|
||||||
|
|
|
@ -13,6 +13,8 @@ func Init(r *gin.Engine) {
|
||||||
common.SecretKey = []byte(conf.Conf.JwtSecret)
|
common.SecretKey = []byte(conf.Conf.JwtSecret)
|
||||||
Cors(r)
|
Cors(r)
|
||||||
|
|
||||||
|
r.GET("/d/*path", controllers.Down)
|
||||||
|
|
||||||
api := r.Group("/api", middlewares.Auth)
|
api := r.Group("/api", middlewares.Auth)
|
||||||
api.POST("/auth/login", controllers.Login)
|
api.POST("/auth/login", controllers.Login)
|
||||||
api.GET("/auth/current", controllers.CurrentUser)
|
api.GET("/auth/current", controllers.CurrentUser)
|
||||||
|
|
Loading…
Reference in New Issue