From 2076d56f0ffc5e1ffe7eadcec643cf1173aaae1a Mon Sep 17 00:00:00 2001 From: HFO4 <912394456@qq.com> Date: Tue, 19 May 2020 10:24:15 +0800 Subject: [PATCH 1/6] Feat: dynamic writing site title, favicon, description, custom html (#286) --- assets | 2 +- middleware/option.go | 43 +++++++++++++++++ middleware/option_test.go | 98 +++++++++++++++++++++++++++++++++++++++ models/migration.go | 1 + routers/router.go | 1 + 5 files changed, 144 insertions(+), 1 deletion(-) diff --git a/assets b/assets index 63ee122d..b44dc051 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 63ee122d977284e3ccf20f4c1ed228584e705c4d +Subproject commit b44dc0514520580fc284937400743123bbb2c6d4 diff --git a/middleware/option.go b/middleware/option.go index a4c6fcd7..9c954321 100644 --- a/middleware/option.go +++ b/middleware/option.go @@ -1,10 +1,13 @@ package middleware import ( + "github.com/HFO4/cloudreve/bootstrap" model "github.com/HFO4/cloudreve/models" "github.com/HFO4/cloudreve/pkg/hashid" "github.com/HFO4/cloudreve/pkg/serializer" + "github.com/HFO4/cloudreve/pkg/util" "github.com/gin-gonic/gin" + "io/ioutil" ) // HashID 将给定对象的HashID转换为真实ID @@ -38,3 +41,43 @@ func IsFunctionEnabled(key string) gin.HandlerFunc { c.Next() } } + +// InjectSiteInfo 向首页html中插入站点信息 +func InjectSiteInfo() gin.HandlerFunc { + ignoreFunc := func(c *gin.Context) { + c.Next() + } + // 读取index.html + file, err := bootstrap.StaticFS.Open("/index.html") + if err != nil { + util.Log().Warning("静态文件[index.html]不存在,可能会影响首页展示") + return ignoreFunc + } + + fileContentBytes, err := ioutil.ReadAll(file) + if err != nil { + util.Log().Warning("静态文件[index.html]读取失败,可能会影响首页展示") + return ignoreFunc + } + fileContent := string(fileContentBytes) + + return func(c *gin.Context) { + if c.Request.URL.Path == "/" || c.Request.URL.Path == "/index.html" { + // 读取、替换站点设置 + options := model.GetSettingByNames("siteName", "siteKeywords", "siteScript", + "pwa_small_icon") + finalHTML := util.Replace(map[string]string{ + "{siteName}": options["siteName"], + "{siteDes}": options["siteDes"], + "{siteScript}": options["siteScript"], + "{pwa_small_icon}": options["pwa_small_icon"], + }, fileContent) + + c.Header("Content-Type", "text/html") + c.String(200, finalHTML) + c.Abort() + return + } + c.Next() + } +} diff --git a/middleware/option_test.go b/middleware/option_test.go index 97d8d5e7..b5a4852c 100644 --- a/middleware/option_test.go +++ b/middleware/option_test.go @@ -1,12 +1,17 @@ package middleware import ( + "errors" + "github.com/HFO4/cloudreve/bootstrap" "github.com/HFO4/cloudreve/pkg/cache" "github.com/HFO4/cloudreve/pkg/hashid" + "github.com/HFO4/cloudreve/pkg/util" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" + testMock "github.com/stretchr/testify/mock" "net/http" "net/http/httptest" + "os" "testing" ) @@ -75,3 +80,96 @@ func TestIsFunctionEnabled(t *testing.T) { } } + +type StaticMock struct { + testMock.Mock +} + +func (m StaticMock) Open(name string) (http.File, error) { + args := m.Called(name) + return args.Get(0).(http.File), args.Error(1) +} + +func (m StaticMock) Exists(prefix string, filepath string) bool { + args := m.Called(prefix, filepath) + return args.Bool(0) +} + +func TestInjectSiteInfo(t *testing.T) { + asserts := assert.New(t) + rec := httptest.NewRecorder() + + // index.html 不存在 + { + testStatic := &StaticMock{} + bootstrap.StaticFS = testStatic + testStatic.On("Open", "/index.html"). + Return(&os.File{}, errors.New("error")) + TestFunc := InjectSiteInfo() + + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{} + c.Request, _ = http.NewRequest("GET", "/", nil) + TestFunc(c) + asserts.False(c.IsAborted()) + } + + // index.html 读取失败 + { + file, _ := util.CreatNestedFile("tests/index.html") + file.Close() + testStatic := &StaticMock{} + bootstrap.StaticFS = testStatic + testStatic.On("Open", "/index.html"). + Return(file, nil) + TestFunc := InjectSiteInfo() + + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{} + c.Request, _ = http.NewRequest("GET", "/", nil) + TestFunc(c) + asserts.False(c.IsAborted()) + } + + // 成功且命中 + { + file, _ := util.CreatNestedFile("tests/index.html") + defer file.Close() + testStatic := &StaticMock{} + bootstrap.StaticFS = testStatic + testStatic.On("Open", "/index.html"). + Return(file, nil) + TestFunc := InjectSiteInfo() + + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{} + c.Request, _ = http.NewRequest("GET", "/", nil) + + cache.Set("setting_siteName", "cloudreve", 0) + cache.Set("setting_siteKeywords", "cloudreve", 0) + cache.Set("setting_siteScript", "cloudreve", 0) + cache.Set("setting_pwa_small_icon", "cloudreve", 0) + + TestFunc(c) + asserts.True(c.IsAborted()) + } + + // 成功且未命中 + { + file, _ := util.CreatNestedFile("tests/index.html") + defer file.Close() + testStatic := &StaticMock{} + bootstrap.StaticFS = testStatic + testStatic.On("Open", "/index.html"). + Return(file, nil) + TestFunc := InjectSiteInfo() + + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{} + c.Request, _ = http.NewRequest("GET", "/2", nil) + + TestFunc(c) + asserts.False(c.IsAborted()) + } + +} diff --git a/models/migration.go b/models/migration.go index b50041ef..85969e4f 100644 --- a/models/migration.go +++ b/models/migration.go @@ -82,6 +82,7 @@ func addDefaultSettings() { {Name: "siteKeywords", Value: `网盘,网盘`, Type: "basic"}, {Name: "siteDes", Value: `Cloudreve`, Type: "basic"}, {Name: "siteTitle", Value: `平步云端`, Type: "basic"}, + {Name: "siteScript", Value: ``, Type: "basic"}, {Name: "fromName", Value: `Cloudreve`, Type: "mail"}, {Name: "mail_keepalive", Value: `30`, Type: "mail"}, {Name: "fromAdress", Value: `no-reply@acg.blue`, Type: "mail"}, diff --git a/routers/router.go b/routers/router.go index ad0da4b4..083a007c 100644 --- a/routers/router.go +++ b/routers/router.go @@ -82,6 +82,7 @@ func InitMasterRouter() *gin.Engine { 静态资源 */ r.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{"/api/"}))) + r.Use(middleware.InjectSiteInfo()) r.Use(static.Serve("/", bootstrap.StaticFS)) r.GET("manifest.json", controllers.Manifest) From ed684420a2cb91f40af7d1b5f342953d45680bd5 Mon Sep 17 00:00:00 2001 From: HFO4 <912394456@qq.com> Date: Tue, 19 May 2020 10:26:24 +0800 Subject: [PATCH 2/6] Modify: actions should not perform clean before build --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ba8912ad..97f18542 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,6 +46,7 @@ jobs: - name: Check out code into the Go module directory uses: actions/checkout@v2 with: + clean: false submodules: 'recursive' - name: Get dependencies and build From 4c458df666d06f6d1f673b8255daffe6c5c3748b Mon Sep 17 00:00:00 2001 From: HFO4 <912394456@qq.com> Date: Tue, 19 May 2020 10:34:16 +0800 Subject: [PATCH 3/6] Fix: failed tests while static files not loaded --- middleware/option.go | 4 ++++ middleware/option_test.go | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/middleware/option.go b/middleware/option.go index 9c954321..3aaf6191 100644 --- a/middleware/option.go +++ b/middleware/option.go @@ -47,6 +47,10 @@ func InjectSiteInfo() gin.HandlerFunc { ignoreFunc := func(c *gin.Context) { c.Next() } + if bootstrap.StaticFS == nil { + return ignoreFunc + } + // 读取index.html file, err := bootstrap.StaticFS.Open("/index.html") if err != nil { diff --git a/middleware/option_test.go b/middleware/option_test.go index b5a4852c..1a75f61c 100644 --- a/middleware/option_test.go +++ b/middleware/option_test.go @@ -99,6 +99,17 @@ func TestInjectSiteInfo(t *testing.T) { asserts := assert.New(t) rec := httptest.NewRecorder() + // 静态资源未加载 + { + TestFunc := InjectSiteInfo() + + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{} + c.Request, _ = http.NewRequest("GET", "/", nil) + TestFunc(c) + asserts.False(c.IsAborted()) + } + // index.html 不存在 { testStatic := &StaticMock{} From 869c0006c56a42eb316f6ef9fa248a9f5155d14e Mon Sep 17 00:00:00 2001 From: AaronLiu Date: Tue, 19 May 2020 11:11:56 +0800 Subject: [PATCH 4/6] Create FUNDING.yml --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..ac16ce94 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: ["https://cloudreve.org/buy.php", cloudreve.org] From 7ed14c4d8176437189caca1cccc2ce0e429f71ba Mon Sep 17 00:00:00 2001 From: AaronLiu Date: Tue, 19 May 2020 11:12:45 +0800 Subject: [PATCH 5/6] Update FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index ac16ce94..2420bc4f 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -custom: ["https://cloudreve.org/buy.php", cloudreve.org] +custom: ["https://cloudreve.org/buy.php"] From 5af3c4e24467abeed1ad0cebe7d7bf3159a13970 Mon Sep 17 00:00:00 2001 From: HFO4 <912394456@qq.com> Date: Tue, 19 May 2020 11:25:01 +0800 Subject: [PATCH 6/6] Fix: directory renaming should not be limited by file extensions (#395) --- .github/workflows/build.yml | 2 ++ pkg/filesystem/manage.go | 2 +- pkg/filesystem/manage_test.go | 29 ++++++++++++++++++++++++++++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 97f18542..adb64286 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,6 +48,8 @@ jobs: with: clean: false submodules: 'recursive' + - run: | + git fetch --prune --unshallow --tags - name: Get dependencies and build run: | diff --git a/pkg/filesystem/manage.go b/pkg/filesystem/manage.go index 21542f80..9a5a636d 100644 --- a/pkg/filesystem/manage.go +++ b/pkg/filesystem/manage.go @@ -32,7 +32,7 @@ type Object struct { // Rename 重命名对象 func (fs *FileSystem) Rename(ctx context.Context, dir, file []uint, new string) (err error) { // 验证新名字 - if !fs.ValidateLegalName(ctx, new) || !fs.ValidateExtension(ctx, new) { + if !fs.ValidateLegalName(ctx, new) || (len(file) > 0 && !fs.ValidateExtension(ctx, new)) { return ErrIllegalObjectName } diff --git a/pkg/filesystem/manage_test.go b/pkg/filesystem/manage_test.go index 5ef221d8..2d795ce5 100644 --- a/pkg/filesystem/manage_test.go +++ b/pkg/filesystem/manage_test.go @@ -707,12 +707,39 @@ func TestFileSystem_Rename(t *testing.T) { asserts.Equal(ErrPathNotExist, err) } - // 新名字不合法 + // 新名字是目录,不合法 { err := fs.Rename(ctx, []uint{10}, []uint{}, "ne/w") asserts.Error(err) asserts.Equal(ErrIllegalObjectName, err) } + + // 新名字是文件,不合法 + { + err := fs.Rename(ctx, []uint{}, []uint{10}, "ne/w") + asserts.Error(err) + asserts.Equal(ErrIllegalObjectName, err) + } + + // 新名字是文件,扩展名不合法 + { + fs.User.Policy.OptionsSerialized.FileType = []string{"txt"} + err := fs.Rename(ctx, []uint{}, []uint{10}, "1.jpg") + asserts.Error(err) + asserts.Equal(ErrIllegalObjectName, err) + } + + // 新名字是目录,不应该检测扩展名 + { + fs.User.Policy.OptionsSerialized.FileType = []string{"txt"} + mock.ExpectQuery("SELECT(.+)folders(.+)"). + WithArgs(10, 1). + WillReturnRows(sqlmock.NewRows([]string{"id", "name"})) + err := fs.Rename(ctx, []uint{10}, []uint{}, "new") + asserts.NoError(mock.ExpectationsWereMet()) + asserts.Error(err) + asserts.Equal(ErrPathNotExist, err) + } } func TestFileSystem_SaveTo(t *testing.T) {