diff --git a/internal/bootstrap/data/setting.go b/internal/bootstrap/data/setting.go index cd36647c..10466f07 100644 --- a/internal/bootstrap/data/setting.go +++ b/internal/bootstrap/data/setting.go @@ -67,29 +67,29 @@ func initialSettings() { } initialSettingItems = []model.SettingItem{ // site settings - {Key: "version", Value: conf.Version, Type: conf.TypeString, Group: model.SITE, Flag: model.READONLY}, - {Key: "base_url", Value: "", Type: conf.TypeString, Group: model.SITE}, - {Key: "site_title", Value: "AList", Type: conf.TypeString, Group: model.SITE}, - {Key: "site_logo", Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg", Type: conf.TypeString, Group: model.SITE}, - {Key: "favicon", Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg", Type: conf.TypeString, Group: model.SITE}, - {Key: "announcement", Value: "https://github.com/alist-org/alist", Type: conf.TypeString, Group: model.SITE}, + {Key: conf.VERSION, Value: conf.Version, Type: conf.TypeString, Group: model.SITE, Flag: model.READONLY}, + {Key: conf.BaseUrl, Value: "", Type: conf.TypeString, Group: model.SITE}, + {Key: conf.SiteTitle, Value: "AList", Type: conf.TypeString, Group: model.SITE}, + {Key: conf.SiteLogo, Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg", Type: conf.TypeString, Group: model.SITE}, + {Key: conf.Favicon, Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg", Type: conf.TypeString, Group: model.SITE}, + {Key: conf.Announcement, Value: "https://github.com/alist-org/alist", Type: conf.TypeString, Group: model.SITE}, // style settings - {Key: "icon_color", Value: "#1890ff", Type: conf.TypeString, Group: model.STYLE}, + {Key: conf.IconColor, Value: "#1890ff", Type: conf.TypeString, Group: model.STYLE}, // preview settings - {Key: "text_types", Value: "txt,htm,html,xml,java,properties,sql,js,md,json,conf,ini,vue,php,py,bat,gitignore,yml,go,sh,c,cpp,h,hpp,tsx,vtt,srt,ass,rs,lrc", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE}, - {Key: "audio_types", Value: "mp3,flac,ogg,m4a,wav,opus", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE}, - {Key: "video_types", Value: "mp4,mkv,avi,mov,rmvb,webm,flv", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE}, - {Key: "proxy_types", Value: "m3u8", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE}, - {Key: "pdf_viewer_url", Value: "https://alist-org.github.io/pdf.js/web/viewer.html?file=$url", Type: conf.TypeString, Group: model.PREVIEW}, - {Key: "audio_autoplay", Value: "true", Type: conf.TypeBool, Group: model.PREVIEW}, - {Key: "video_autoplay", Value: "true", Type: conf.TypeBool, Group: model.PREVIEW}, + {Key: conf.TextTypes, Value: "txt,htm,html,xml,java,properties,sql,js,md,json,conf,ini,vue,php,py,bat,gitignore,yml,go,sh,c,cpp,h,hpp,tsx,vtt,srt,ass,rs,lrc", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE}, + {Key: conf.AudioTypes, Value: "mp3,flac,ogg,m4a,wav,opus", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE}, + {Key: conf.VideoTypes, Value: "mp4,mkv,avi,mov,rmvb,webm,flv", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE}, + {Key: conf.ProxyTypes, Value: "m3u8", Type: conf.TypeText, Group: model.PREVIEW, Flag: model.PRIVATE}, + {Key: conf.PdfViewerUrl, Value: "https://alist-org.github.io/pdf.js/web/viewer.html?file=$url", Type: conf.TypeString, Group: model.PREVIEW}, + {Key: conf.AudioAutoplay, Value: "true", Type: conf.TypeBool, Group: model.PREVIEW}, + {Key: conf.VideoAutoplay, Value: "true", Type: conf.TypeBool, Group: model.PREVIEW}, // global settings - {Key: "hide_files", Value: "/\\/README.md/i", 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_body", Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE}, - {Key: "link_expiration", Value: "0", Type: conf.TypeNumber, Group: model.GLOBAL, Flag: model.PRIVATE}, + {Key: conf.HideFiles, Value: "/\\/README.md/i", Type: conf.TypeText, Group: model.GLOBAL}, + {Key: conf.GlobalReadme, Value: "This is global readme", Type: conf.TypeText, Group: model.GLOBAL}, + {Key: conf.CustomizeHead, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE}, + {Key: conf.CustomizeBody, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE}, + {Key: conf.LinkExpiration, 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}, + {Key: conf.Token, Value: token, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE}, } } diff --git a/internal/conf/const.go b/internal/conf/const.go index 7230cfb9..d7341200 100644 --- a/internal/conf/const.go +++ b/internal/conf/const.go @@ -7,3 +7,26 @@ const ( TypeText = "text" TypeNumber = "number" ) + +const ( + VERSION = "version" + BaseUrl = "base_url" + SiteTitle = "site_title" + SiteLogo = "site_logo" + Favicon = "favicon" + Announcement = "announcement" + IconColor = "icon_color" + TextTypes = "text_types" + AudioTypes = "audio_types" + VideoTypes = "video_types" + ProxyTypes = "proxy_types" + PdfViewerUrl = "pdf_viewer_url" + AudioAutoplay = "audio_autoplay" + VideoAutoplay = "video_autoplay" + HideFiles = "hide_files" + GlobalReadme = "global_readme" + CustomizeHead = "customize_head" + CustomizeBody = "customize_body" + LinkExpiration = "link_expiration" + Token = "token" +) diff --git a/internal/model/link.go b/internal/model/link.go index c660c718..38953285 100644 --- a/internal/model/link.go +++ b/internal/model/link.go @@ -12,8 +12,8 @@ type LinkArgs struct { } type Link struct { - URL string - Header http.Header // needed header + URL string `json:"url"` + Header http.Header `json:"header"` // needed header Data io.ReadCloser // return file reader directly Status int // status maybe 200 or 206, etc FilePath *string // local file, return the filepath diff --git a/internal/sign/sign.go b/internal/sign/sign.go index 4e2d62f3..de1b451f 100644 --- a/internal/sign/sign.go +++ b/internal/sign/sign.go @@ -1,6 +1,7 @@ package sign import ( + "github.com/alist-org/alist/v3/internal/conf" "github.com/alist-org/alist/v3/internal/setting" "github.com/alist-org/alist/v3/pkg/sign" "sync" @@ -11,7 +12,7 @@ var once sync.Once var instance sign.Sign func Sign(data string) string { - expire := setting.GetIntSetting("link_expiration", 0) + expire := setting.GetIntSetting(conf.LinkExpiration, 0) if expire == 0 { return NotExpired(data) } else { @@ -35,5 +36,5 @@ func Verify(data string, sign string) error { } func Instance() { - instance = sign.NewHMACSign([]byte(setting.GetByKey("token"))) + instance = sign.NewHMACSign([]byte(setting.GetByKey(conf.Token))) } diff --git a/server/common/base.go b/server/common/base.go new file mode 100644 index 00000000..1e5f35c6 --- /dev/null +++ b/server/common/base.go @@ -0,0 +1,18 @@ +package common + +import ( + "fmt" + "github.com/alist-org/alist/v3/internal/conf" + "github.com/alist-org/alist/v3/internal/setting" + "net/http" + "strings" +) + +func GetBaseUrl(r *http.Request) string { + baseUrl := setting.GetByKey(conf.BaseUrl) + if baseUrl == "" { + baseUrl = fmt.Sprintf("//%s", r.Host) + } + strings.TrimSuffix(baseUrl, "/") + return baseUrl +} diff --git a/server/controllers/down.go b/server/controllers/down.go index 14a82b00..3ef0bc15 100644 --- a/server/controllers/down.go +++ b/server/controllers/down.go @@ -2,6 +2,7 @@ package controllers import ( "fmt" + "github.com/alist-org/alist/v3/internal/conf" "github.com/alist-org/alist/v3/internal/sign" stdpath "path" "strings" @@ -84,7 +85,7 @@ func shouldProxy(account driver.Driver, filename string) bool { if account.Config().MustProxy() || account.GetAccount().WebProxy { return true } - proxyTypes := setting.GetByKey("proxy_types") + proxyTypes := setting.GetByKey(conf.ProxyTypes) if strings.Contains(proxyTypes, utils.Ext(filename)) { return true } @@ -102,11 +103,11 @@ func canProxy(account driver.Driver, filename string) bool { if account.Config().MustProxy() || account.GetAccount().WebProxy { return true } - proxyTypes := setting.GetByKey("proxy_types") + proxyTypes := setting.GetByKey(conf.ProxyTypes) if strings.Contains(proxyTypes, utils.Ext(filename)) { return true } - textTypes := setting.GetByKey("text_types") + textTypes := setting.GetByKey(conf.TextTypes) if strings.Contains(textTypes, utils.Ext(filename)) { return true } diff --git a/server/controllers/fsget.go b/server/controllers/fsget.go deleted file mode 100644 index 35f6c935..00000000 --- a/server/controllers/fsget.go +++ /dev/null @@ -1,60 +0,0 @@ -package controllers - -import ( - stdpath "path" - - "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" -) - -type FsGetReq struct { - Path string `json:"path" form:"path"` - Password string `json:"password" form:"password"` -} - -type FsGetResp struct { - ObjResp - RawURL string `json:"raw_url"` -} - -func FsGet(c *gin.Context) { - var req FsGetReq - if err := c.ShouldBind(&req); err != nil { - common.ErrorResp(c, err, 400) - return - } - user := c.MustGet("user").(*model.User) - req.Path = stdpath.Join(user.BasePath, req.Path) - meta, err := db.GetNearestMeta(req.Path) - if err != nil { - if !errors.Is(errors.Cause(err), errs.MetaNotFound) { - common.ErrorResp(c, err, 500) - return - } - } - c.Set("meta", meta) - if !canAccess(user, meta, req.Path, req.Password) { - common.ErrorStrResp(c, "password is incorrect", 401) - return - } - obj, err := fs.Get(c, req.Path) - if err != nil { - common.ErrorResp(c, err, 500) - return - } - common.SuccessResp(c, FsGetResp{ - ObjResp: ObjResp{ - Name: obj.GetName(), - Size: obj.GetSize(), - IsDir: obj.IsDir(), - Modified: obj.ModTime(), - Sign: common.Sign(obj), - }, - // TODO: set raw url - }) -} diff --git a/server/controllers/fsmanage.go b/server/controllers/fsmanage.go index c94f7b4a..f50344fd 100644 --- a/server/controllers/fsmanage.go +++ b/server/controllers/fsmanage.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/alist-org/alist/v3/internal/fs" "github.com/alist-org/alist/v3/internal/model" + "github.com/alist-org/alist/v3/internal/sign" "github.com/alist-org/alist/v3/server/common" "github.com/gin-gonic/gin" stdpath "path" @@ -11,12 +12,12 @@ import ( "time" ) -type MkdirReq struct { - Path string `json:"path"` +type MkdirOrLinkReq struct { + Path string `json:"path" form:"path"` } func FsMkdir(c *gin.Context) { - var req MkdirReq + var req MkdirOrLinkReq if err := c.ShouldBind(&req); err != nil { common.ErrorResp(c, err, 400) return @@ -162,3 +163,32 @@ func FsPut(c *gin.Context) { } common.SuccessResp(c) } + +// Link return real link, just for proxy program +func Link(c *gin.Context) { + var req MkdirOrLinkReq + if err := c.ShouldBind(&req); err != nil { + common.ErrorResp(c, err, 400) + return + } + user := c.MustGet("user").(*model.User) + rawPath := stdpath.Join(user.BasePath, req.Path) + account, err := fs.GetAccount(rawPath) + if err != nil { + common.ErrorResp(c, err, 500) + return + } + if account.Config().OnlyLocal { + common.SuccessResp(c, model.Link{ + URL: fmt.Sprintf("%s/p%s?d&sign=%s", common.GetBaseUrl(c.Request), req.Path, sign.Sign(stdpath.Base(rawPath))), + }) + return + } + link, _, err := fs.Link(c, rawPath, model.LinkArgs{IP: c.ClientIP()}) + if err != nil { + common.ErrorResp(c, err, 500) + return + } + common.SuccessResp(c, link) + return +} diff --git a/server/controllers/fslist.go b/server/controllers/fsread.go similarity index 70% rename from server/controllers/fslist.go rename to server/controllers/fsread.go index fcb0ba9e..afb6725c 100644 --- a/server/controllers/fslist.go +++ b/server/controllers/fsread.go @@ -8,7 +8,6 @@ import ( "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/pkg/utils" "github.com/alist-org/alist/v3/server/common" "github.com/gin-gonic/gin" @@ -61,9 +60,8 @@ func FsList(c *gin.Context) { return } total, objs := pagination(objs, &req.PageReq) - baseURL := setting.GetByKey("base_url", c.Request.Host) common.SuccessResp(c, FsListResp{ - Content: toObjResp(objs, req.Path, baseURL), + Content: toObjResp(objs), Total: int64(total), }) } @@ -99,7 +97,7 @@ func pagination(objs []model.Obj, req *common.PageReq) (int, []model.Obj) { return total, objs[start:end] } -func toObjResp(objs []model.Obj, path string, baseURL string) []ObjResp { +func toObjResp(objs []model.Obj) []ObjResp { var resp []ObjResp for _, obj := range objs { resp = append(resp, ObjResp{ @@ -112,3 +110,50 @@ func toObjResp(objs []model.Obj, path string, baseURL string) []ObjResp { } return resp } + +type FsGetReq struct { + Path string `json:"path" form:"path"` + Password string `json:"password" form:"password"` +} + +type FsGetResp struct { + ObjResp + RawURL string `json:"raw_url"` +} + +func FsGet(c *gin.Context) { + var req FsGetReq + if err := c.ShouldBind(&req); err != nil { + common.ErrorResp(c, err, 400) + return + } + user := c.MustGet("user").(*model.User) + req.Path = stdpath.Join(user.BasePath, req.Path) + meta, err := db.GetNearestMeta(req.Path) + if err != nil { + if !errors.Is(errors.Cause(err), errs.MetaNotFound) { + common.ErrorResp(c, err, 500) + return + } + } + c.Set("meta", meta) + if !canAccess(user, meta, req.Path, req.Password) { + common.ErrorStrResp(c, "password is incorrect", 401) + return + } + obj, err := fs.Get(c, req.Path) + if err != nil { + common.ErrorResp(c, err, 500) + return + } + common.SuccessResp(c, FsGetResp{ + ObjResp: ObjResp{ + Name: obj.GetName(), + Size: obj.GetSize(), + IsDir: obj.IsDir(), + Modified: obj.ModTime(), + Sign: common.Sign(obj), + }, + // TODO: set raw url + }) +} diff --git a/server/middlewares/auth.go b/server/middlewares/auth.go index d458998e..093c5273 100644 --- a/server/middlewares/auth.go +++ b/server/middlewares/auth.go @@ -1,6 +1,7 @@ package middlewares import ( + "github.com/alist-org/alist/v3/internal/conf" "github.com/alist-org/alist/v3/internal/db" "github.com/alist-org/alist/v3/internal/model" "github.com/alist-org/alist/v3/internal/setting" @@ -12,7 +13,7 @@ import ( // if token is empty, set user to guest func Auth(c *gin.Context) { token := c.GetHeader("Authorization") - if token == setting.GetByKey("token") { + if token == setting.GetByKey(conf.Token) { admin, err := db.GetAdmin() if err != nil { common.ErrorResp(c, err, 500) @@ -59,3 +60,13 @@ func AuthAdmin(c *gin.Context) { c.Next() } } + +func AuthWrite(c *gin.Context) { + user := c.MustGet("user").(*model.User) + if !user.IsAdmin() && user.ReadOnly { + common.ErrorStrResp(c, "You have no write access", 403) + c.Abort() + } else { + c.Next() + } +} diff --git a/server/router.go b/server/router.go index c46584c1..c0fcfc80 100644 --- a/server/router.go +++ b/server/router.go @@ -56,11 +56,20 @@ func Init(r *gin.Engine) { public.GET("/settings", controllers.PublicSettings) public.Any("/list", controllers.FsList) public.Any("/get", controllers.FsGet) + + fs := api.Group("/fs", middlewares.AuthWrite) + fs.POST("/mkdir", controllers.FsMkdir) + fs.POST("/rename", controllers.FsRename) + fs.POST("/move", controllers.FsMove) + fs.POST("/copy", controllers.FsCopy) + fs.POST("/remove", controllers.FsRemove) + fs.POST("/put", controllers.FsPut) + fs.POST("/link", controllers.Link) } func Cors(r *gin.Engine) { config := cors.DefaultConfig() config.AllowAllOrigins = true - config.AllowHeaders = append(config.AllowHeaders, "Authorization", "range") + config.AllowHeaders = append(config.AllowHeaders, "Authorization", "range", "File-Path") r.Use(cors.New(config)) }