diff --git a/internal/bootstrap/data/setting.go b/internal/bootstrap/data/setting.go index 10de9e68..cd36647c 100644 --- a/internal/bootstrap/data/setting.go +++ b/internal/bootstrap/data/setting.go @@ -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}, } diff --git a/internal/driver/config.go b/internal/driver/config.go index 7baf821a..a87b1caa 100644 --- a/internal/driver/config.go +++ b/internal/driver/config.go @@ -9,3 +9,7 @@ type Config struct { NoCache bool NoUpload bool } + +func (c Config) MustProxy() bool { + return c.OnlyProxy || c.OnlyLocal +} diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 2f53f079..56a8a322 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -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 +} diff --git a/pkg/utils/path.go b/pkg/utils/path.go index 790cc23e..c6a5292d 100644 --- a/pkg/utils/path.go +++ b/pkg/utils/path.go @@ -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 +} diff --git a/server/common/proxy.go b/server/common/proxy.go new file mode 100644 index 00000000..f1f42493 --- /dev/null +++ b/server/common/proxy.go @@ -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 + } +} diff --git a/server/controllers/down.go b/server/controllers/down.go new file mode 100644 index 00000000..9a6553ef --- /dev/null +++ b/server/controllers/down.go @@ -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 +} diff --git a/server/controllers/fsget.go b/server/controllers/fsget.go index e90f28e6..6657f169 100644 --- a/server/controllers/fsget.go +++ b/server/controllers/fsget.go @@ -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) diff --git a/server/controllers/fslist.go b/server/controllers/fslist.go index 0683a35e..05f65120 100644 --- a/server/controllers/fslist.go +++ b/server/controllers/fslist.go @@ -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) diff --git a/server/router.go b/server/router.go index 1403a6bc..23066e37 100644 --- a/server/router.go +++ b/server/router.go @@ -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)