From b70c7f86604185ed18a9d5e3b25327805e2b6a52 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 8 Mar 2023 11:26:37 +0800 Subject: [PATCH 01/11] Fix incorrect display for comment context menu (#23343) Replace #23342 Fix a regression of #23014: the `a` couldn't be used here because Fomantic UI has style conflicts: `.ui.comments .comment .actions a { display: inline-block; }` And complete one more of my TODOs: "in the future there could be a special CSS class for it" --- templates/repo/issue/view_content/context_menu.tmpl | 10 +++++----- web_src/js/features/aria.js | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/templates/repo/issue/view_content/context_menu.tmpl b/templates/repo/issue/view_content/context_menu.tmpl index f836271b65b5..c073c74ea32f 100644 --- a/templates/repo/issue/view_content/context_menu.tmpl +++ b/templates/repo/issue/view_content/context_menu.tmpl @@ -10,16 +10,16 @@ {{else}} {{$referenceUrl = Printf "%s/files#%s" .ctxData.Issue.Link .item.HashTag}} {{end}} - {{.ctxData.locale.Tr "repo.issues.context.copy_link"}} - {{.ctxData.locale.Tr "repo.issues.context.quote_reply"}} +
{{.ctxData.locale.Tr "repo.issues.context.copy_link"}}
+
{{.ctxData.locale.Tr "repo.issues.context.quote_reply"}}
{{if not .ctxData.UnitIssuesGlobalDisabled}} - {{.ctxData.locale.Tr "repo.issues.context.reference_issue"}} +
{{.ctxData.locale.Tr "repo.issues.context.reference_issue"}}
{{end}} {{if or .ctxData.Permission.IsAdmin .IsCommentPoster .ctxData.HasIssuesOrPullsWritePermission}}
- {{.ctxData.locale.Tr "repo.issues.context.edit"}} +
{{.ctxData.locale.Tr "repo.issues.context.edit"}}
{{if .delete}} - {{.ctxData.locale.Tr "repo.issues.context.delete"}} +
{{.ctxData.locale.Tr "repo.issues.context.delete"}}
{{end}} {{end}} diff --git a/web_src/js/features/aria.js b/web_src/js/features/aria.js index 373d667c5f73..46944336adc1 100644 --- a/web_src/js/features/aria.js +++ b/web_src/js/features/aria.js @@ -83,8 +83,9 @@ function attachOneDropdownAria($dropdown) { if (e.key === 'Enter') { let $item = $dropdown.dropdown('get item', $dropdown.dropdown('get value')); if (!$item) $item = $menu.find('> .item.selected'); // when dropdown filters items by input, there is no "value", so query the "selected" item - // if the selected item is clickable, then trigger the click event. in the future there could be a special CSS class for it. - if ($item && $item.is('a')) $item[0].click(); + // if the selected item is clickable, then trigger the click event. + // we can not click any item without check, because Fomantic code might also handle the Enter event. that would result in double click. + if ($item && ($item.is('a') || $item.is('.js-aria-clickable'))) $item[0].click(); } }); From 7e3b7c23463bf71c4e2ab93f184a675f1e30df0e Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 8 Mar 2023 11:40:41 +0800 Subject: [PATCH 02/11] Do not recognize text files as audio (#23355) Close #17108 This PR uses a trick (removing the ID3 tag) to detect the content again to to see whether the content is text type. --------- Co-authored-by: delvh Co-authored-by: techknowlogick Co-authored-by: Lunny Xiao --- modules/typesniffer/typesniffer.go | 10 ++++++++++ modules/typesniffer/typesniffer_test.go | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/modules/typesniffer/typesniffer.go b/modules/typesniffer/typesniffer.go index 5b215496b80c..7887fd42b72e 100644 --- a/modules/typesniffer/typesniffer.go +++ b/modules/typesniffer/typesniffer.go @@ -106,6 +106,16 @@ func DetectContentType(data []byte) SniffedType { } } + if strings.HasPrefix(ct, "audio/") && bytes.HasPrefix(data, []byte("ID3")) { + // The MP3 detection is quite inaccurate, any content with "ID3" prefix will result in "audio/mpeg". + // So remove the "ID3" prefix and detect again, if result is text, then it must be text content. + // This works especially because audio files contain many unprintable/invalid characters like `0x00` + ct2 := http.DetectContentType(data[3:]) + if strings.HasPrefix(ct2, "text/") { + ct = ct2 + } + } + return SniffedType{ct} } diff --git a/modules/typesniffer/typesniffer_test.go b/modules/typesniffer/typesniffer_test.go index 2bafdffd141c..6c6da34aa006 100644 --- a/modules/typesniffer/typesniffer_test.go +++ b/modules/typesniffer/typesniffer_test.go @@ -109,6 +109,10 @@ func TestIsAudio(t *testing.T) { mp3, _ := base64.StdEncoding.DecodeString("SUQzBAAAAAABAFRYWFgAAAASAAADbWFqb3JfYnJhbmQAbXA0MgBUWFhYAAAAEQAAA21pbm9yX3Zl") assert.True(t, DetectContentType(mp3).IsAudio()) assert.False(t, DetectContentType([]byte("plain text")).IsAudio()) + + assert.True(t, DetectContentType([]byte("ID3Toy\000")).IsAudio()) + assert.True(t, DetectContentType([]byte("ID3Toy\n====\t* hi 🌞, ...")).IsText()) // test ID3 tag for plain text + assert.True(t, DetectContentType([]byte("ID3Toy\n====\t* hi 🌞, ..."+"🌛"[0:2])).IsText()) // test ID3 tag with incomplete UTF8 char } func TestDetectContentTypeFromReader(t *testing.T) { From a12f5757372f751d25f9e5ca1f168f6920ded894 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Wed, 8 Mar 2023 08:07:58 +0100 Subject: [PATCH 03/11] Clean Path in Options (#23006) At the Moment it is possible to read files in another Directory as supposed using the Options functions. e.g. `options.Gitignore("../label/Default) `. This was discovered while working on #22783, which exposes `options.Gitignore()` through the public API. At the moment, this is not a security problem, as this function is only used internal, but I thought it would be a good idea to make a PR to fix this for all types of Options files, not only Gitignore, to make it safe for the further. This PR should be merged before the linked PR. --------- Co-authored-by: Jason Song --- modules/options/dynamic.go | 8 ++++---- modules/options/static.go | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/options/dynamic.go b/modules/options/dynamic.go index a20253676e67..f9b3714b8fb9 100644 --- a/modules/options/dynamic.go +++ b/modules/options/dynamic.go @@ -79,22 +79,22 @@ func WalkLocales(callback func(path, name string, d fs.DirEntry, err error) erro // Readme reads the content of a specific readme from static or custom path. func Readme(name string) ([]byte, error) { - return fileFromDir(path.Join("readme", name)) + return fileFromDir(path.Join("readme", path.Clean("/"+name))) } // Gitignore reads the content of a specific gitignore from static or custom path. func Gitignore(name string) ([]byte, error) { - return fileFromDir(path.Join("gitignore", name)) + return fileFromDir(path.Join("gitignore", path.Clean("/"+name))) } // License reads the content of a specific license from static or custom path. func License(name string) ([]byte, error) { - return fileFromDir(path.Join("license", name)) + return fileFromDir(path.Join("license", path.Clean("/"+name))) } // Labels reads the content of a specific labels from static or custom path. func Labels(name string) ([]byte, error) { - return fileFromDir(path.Join("label", name)) + return fileFromDir(path.Join("label", path.Clean("/"+name))) } // fileFromDir is a helper to read files from static or custom path. diff --git a/modules/options/static.go b/modules/options/static.go index ff3c86d3f84f..2405d658bfb1 100644 --- a/modules/options/static.go +++ b/modules/options/static.go @@ -84,22 +84,22 @@ func WalkLocales(callback func(path, name string, d fs.DirEntry, err error) erro // Readme reads the content of a specific readme from bindata or custom path. func Readme(name string) ([]byte, error) { - return fileFromDir(path.Join("readme", name)) + return fileFromDir(path.Join("readme", path.Clean("/"+name))) } // Gitignore reads the content of a gitignore locale from bindata or custom path. func Gitignore(name string) ([]byte, error) { - return fileFromDir(path.Join("gitignore", name)) + return fileFromDir(path.Join("gitignore", path.Clean("/"+name))) } // License reads the content of a specific license from bindata or custom path. func License(name string) ([]byte, error) { - return fileFromDir(path.Join("license", name)) + return fileFromDir(path.Join("license", path.Clean("/"+name))) } // Labels reads the content of a specific labels from static or custom path. func Labels(name string) ([]byte, error) { - return fileFromDir(path.Join("label", name)) + return fileFromDir(path.Join("label", path.Clean("/"+name))) } // fileFromDir is a helper to read files from bindata or custom path. From 090e75392385041b3abb30d02564962a3ff687f6 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Wed, 8 Mar 2023 17:31:27 +0800 Subject: [PATCH 04/11] Reduce duplicate and useless code in options (#23369) Avoid maintaining two copies of code, some functions can be used with both `bindata` and `no bindata`. And removed `GetRepoInitFile`, it's useless now. `Readme`/`Gitignore`/`License`/`Labels` will clean the name and use custom files when available. --- modules/label/parser.go | 6 ++-- modules/options/base.go | 56 ++++++++++++++++++++++++++++++ modules/options/dynamic.go | 70 ++++---------------------------------- modules/options/repo.go | 44 ------------------------ modules/options/static.go | 50 ++++----------------------- modules/repository/init.go | 6 ++-- 6 files changed, 74 insertions(+), 158 deletions(-) delete mode 100644 modules/options/repo.go diff --git a/modules/label/parser.go b/modules/label/parser.go index 768c72a61b01..55bf570de6b9 100644 --- a/modules/label/parser.go +++ b/modules/label/parser.go @@ -36,17 +36,17 @@ func (err ErrTemplateLoad) Error() string { // GetTemplateFile loads the label template file by given name, // then parses and returns a list of name-color pairs and optionally description. func GetTemplateFile(name string) ([]*Label, error) { - data, err := options.GetRepoInitFile("label", name+".yaml") + data, err := options.Labels(name + ".yaml") if err == nil && len(data) > 0 { return parseYamlFormat(name+".yaml", data) } - data, err = options.GetRepoInitFile("label", name+".yml") + data, err = options.Labels(name + ".yml") if err == nil && len(data) > 0 { return parseYamlFormat(name+".yml", data) } - data, err = options.GetRepoInitFile("label", name) + data, err = options.Labels(name) if err != nil { return nil, ErrTemplateLoad{name, fmt.Errorf("GetRepoInitFile: %w", err)} } diff --git a/modules/options/base.go b/modules/options/base.go index 039e934b3a4d..3c140f64326b 100644 --- a/modules/options/base.go +++ b/modules/options/base.go @@ -7,11 +7,52 @@ import ( "fmt" "io/fs" "os" + "path" "path/filepath" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" ) +// Locale reads the content of a specific locale from static/bindata or custom path. +func Locale(name string) ([]byte, error) { + return fileFromDir(path.Join("locale", path.Clean("/"+name))) +} + +// Readme reads the content of a specific readme from static/bindata or custom path. +func Readme(name string) ([]byte, error) { + return fileFromDir(path.Join("readme", path.Clean("/"+name))) +} + +// Gitignore reads the content of a gitignore locale from static/bindata or custom path. +func Gitignore(name string) ([]byte, error) { + return fileFromDir(path.Join("gitignore", path.Clean("/"+name))) +} + +// License reads the content of a specific license from static/bindata or custom path. +func License(name string) ([]byte, error) { + return fileFromDir(path.Join("license", path.Clean("/"+name))) +} + +// Labels reads the content of a specific labels from static/bindata or custom path. +func Labels(name string) ([]byte, error) { + return fileFromDir(path.Join("label", path.Clean("/"+name))) +} + +// WalkLocales reads the content of a specific locale +func WalkLocales(callback func(path, name string, d fs.DirEntry, err error) error) error { + if IsDynamic() { + if err := walkAssetDir(filepath.Join(setting.StaticRootPath, "options", "locale"), callback); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to walk locales. Error: %w", err) + } + } + + if err := walkAssetDir(filepath.Join(setting.CustomPath, "options", "locale"), callback); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to walk locales. Error: %w", err) + } + return nil +} + func walkAssetDir(root string, callback func(path, name string, d fs.DirEntry, err error) error) error { if err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { // name is the path relative to the root @@ -37,3 +78,18 @@ func walkAssetDir(root string, callback func(path, name string, d fs.DirEntry, e } return nil } + +func statDirIfExist(dir string) ([]string, error) { + isDir, err := util.IsDir(dir) + if err != nil { + return nil, fmt.Errorf("unable to check if static directory %s is a directory. %w", dir, err) + } + if !isDir { + return nil, nil + } + files, err := util.StatDir(dir, true) + if err != nil { + return nil, fmt.Errorf("unable to read directory %q. %w", dir, err) + } + return files, nil +} diff --git a/modules/options/dynamic.go b/modules/options/dynamic.go index f9b3714b8fb9..8c954492ae51 100644 --- a/modules/options/dynamic.go +++ b/modules/options/dynamic.go @@ -7,10 +7,8 @@ package options import ( "fmt" - "io/fs" "os" "path" - "path/filepath" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -27,76 +25,20 @@ func Dir(name string) ([]string, error) { var result []string - customDir := path.Join(setting.CustomPath, "options", name) - - isDir, err := util.IsDir(customDir) - if err != nil { - return []string{}, fmt.Errorf("Unabe to check if custom directory %s is a directory. %w", customDir, err) - } - if isDir { - files, err := util.StatDir(customDir, true) - if err != nil { - return []string{}, fmt.Errorf("Failed to read custom directory. %w", err) - } - - result = append(result, files...) - } - - staticDir := path.Join(setting.StaticRootPath, "options", name) - - isDir, err = util.IsDir(staticDir) - if err != nil { - return []string{}, fmt.Errorf("unable to check if static directory %s is a directory. %w", staticDir, err) - } - if isDir { - files, err := util.StatDir(staticDir, true) + for _, dir := range []string{ + path.Join(setting.CustomPath, "options", name), // custom dir + path.Join(setting.StaticRootPath, "options", name), // static dir + } { + files, err := statDirIfExist(dir) if err != nil { - return []string{}, fmt.Errorf("Failed to read static directory. %w", err) + return nil, err } - result = append(result, files...) } return directories.AddAndGet(name, result), nil } -// Locale reads the content of a specific locale from static or custom path. -func Locale(name string) ([]byte, error) { - return fileFromDir(path.Join("locale", name)) -} - -// WalkLocales reads the content of a specific locale from static or custom path. -func WalkLocales(callback func(path, name string, d fs.DirEntry, err error) error) error { - if err := walkAssetDir(filepath.Join(setting.StaticRootPath, "options", "locale"), callback); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("failed to walk locales. Error: %w", err) - } - - if err := walkAssetDir(filepath.Join(setting.CustomPath, "options", "locale"), callback); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("failed to walk locales. Error: %w", err) - } - return nil -} - -// Readme reads the content of a specific readme from static or custom path. -func Readme(name string) ([]byte, error) { - return fileFromDir(path.Join("readme", path.Clean("/"+name))) -} - -// Gitignore reads the content of a specific gitignore from static or custom path. -func Gitignore(name string) ([]byte, error) { - return fileFromDir(path.Join("gitignore", path.Clean("/"+name))) -} - -// License reads the content of a specific license from static or custom path. -func License(name string) ([]byte, error) { - return fileFromDir(path.Join("license", path.Clean("/"+name))) -} - -// Labels reads the content of a specific labels from static or custom path. -func Labels(name string) ([]byte, error) { - return fileFromDir(path.Join("label", path.Clean("/"+name))) -} - // fileFromDir is a helper to read files from static or custom path. func fileFromDir(name string) ([]byte, error) { customPath := path.Join(setting.CustomPath, "options", name) diff --git a/modules/options/repo.go b/modules/options/repo.go deleted file mode 100644 index 1480f7808176..000000000000 --- a/modules/options/repo.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package options - -import ( - "fmt" - "os" - "path" - "strings" - - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" -) - -// GetRepoInitFile returns repository init files -func GetRepoInitFile(tp, name string) ([]byte, error) { - cleanedName := strings.TrimLeft(path.Clean("/"+name), "/") - relPath := path.Join("options", tp, cleanedName) - - // Use custom file when available. - customPath := path.Join(setting.CustomPath, relPath) - isFile, err := util.IsFile(customPath) - if err != nil { - log.Error("Unable to check if %s is a file. Error: %v", customPath, err) - } - if isFile { - return os.ReadFile(customPath) - } - - switch tp { - case "readme": - return Readme(cleanedName) - case "gitignore": - return Gitignore(cleanedName) - case "license": - return License(cleanedName) - case "label": - return Labels(cleanedName) - default: - return []byte{}, fmt.Errorf("Invalid init file type") - } -} diff --git a/modules/options/static.go b/modules/options/static.go index 2405d658bfb1..549f4e25b11a 100644 --- a/modules/options/static.go +++ b/modules/options/static.go @@ -8,10 +8,8 @@ package options import ( "fmt" "io" - "io/fs" "os" "path" - "path/filepath" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -28,17 +26,14 @@ func Dir(name string) ([]string, error) { var result []string - customDir := path.Join(setting.CustomPath, "options", name) - isDir, err := util.IsDir(customDir) - if err != nil { - return []string{}, fmt.Errorf("unable to check if custom directory %q is a directory. %w", customDir, err) - } - if isDir { - files, err := util.StatDir(customDir, true) + for _, dir := range []string{ + path.Join(setting.CustomPath, "options", name), // custom dir + // no static dir + } { + files, err := statDirIfExist(dir) if err != nil { - return []string{}, fmt.Errorf("unable to read custom directory %q. %w", customDir, err) + return nil, err } - result = append(result, files...) } @@ -69,39 +64,6 @@ func AssetDir(dirName string) ([]string, error) { return results, nil } -// Locale reads the content of a specific locale from bindata or custom path. -func Locale(name string) ([]byte, error) { - return fileFromDir(path.Join("locale", name)) -} - -// WalkLocales reads the content of a specific locale from static or custom path. -func WalkLocales(callback func(path, name string, d fs.DirEntry, err error) error) error { - if err := walkAssetDir(filepath.Join(setting.CustomPath, "options", "locale"), callback); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("failed to walk locales. Error: %w", err) - } - return nil -} - -// Readme reads the content of a specific readme from bindata or custom path. -func Readme(name string) ([]byte, error) { - return fileFromDir(path.Join("readme", path.Clean("/"+name))) -} - -// Gitignore reads the content of a gitignore locale from bindata or custom path. -func Gitignore(name string) ([]byte, error) { - return fileFromDir(path.Join("gitignore", path.Clean("/"+name))) -} - -// License reads the content of a specific license from bindata or custom path. -func License(name string) ([]byte, error) { - return fileFromDir(path.Join("license", path.Clean("/"+name))) -} - -// Labels reads the content of a specific labels from static or custom path. -func Labels(name string) ([]byte, error) { - return fileFromDir(path.Join("label", path.Clean("/"+name))) -} - // fileFromDir is a helper to read files from bindata or custom path. func fileFromDir(name string) ([]byte, error) { customPath := path.Join(setting.CustomPath, "options", name) diff --git a/modules/repository/init.go b/modules/repository/init.go index 49c8d2a904d1..f9a33cd4f68c 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -136,7 +136,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, } // README - data, err := options.GetRepoInitFile("readme", opts.Readme) + data, err := options.Readme(opts.Readme) if err != nil { return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.Readme, err) } @@ -164,7 +164,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, var buf bytes.Buffer names := strings.Split(opts.Gitignores, ",") for _, name := range names { - data, err = options.GetRepoInitFile("gitignore", name) + data, err = options.Gitignore(name) if err != nil { return fmt.Errorf("GetRepoInitFile[%s]: %w", name, err) } @@ -182,7 +182,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, // LICENSE if len(opts.License) > 0 { - data, err = options.GetRepoInitFile("license", opts.License) + data, err = options.License(opts.License) if err != nil { return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.License, err) } From b116418f05b822481bba3613873eef876da73814 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 8 Mar 2023 20:17:39 +0800 Subject: [PATCH 05/11] Use CleanPath instead of path.Clean (#23371) As title. --- models/git/lfs_lock.go | 12 ++++-------- modules/options/base.go | 10 +++++----- modules/public/public.go | 4 ++-- modules/storage/local.go | 3 +-- modules/storage/minio.go | 3 ++- modules/util/path.go | 8 ++++++++ modules/util/path_test.go | 12 ++++++++++++ routers/web/base.go | 5 +++-- routers/web/repo/editor.go | 2 +- routers/web/repo/lfs.go | 2 +- services/migrations/gitea_uploader.go | 4 ++-- services/packages/container/blob_uploader.go | 4 ++-- services/repository/files/file.go | 4 ++-- 13 files changed, 45 insertions(+), 28 deletions(-) diff --git a/models/git/lfs_lock.go b/models/git/lfs_lock.go index 25480f3f9654..178fa72f09bf 100644 --- a/models/git/lfs_lock.go +++ b/models/git/lfs_lock.go @@ -6,7 +6,6 @@ package git import ( "context" "fmt" - "path" "strings" "time" @@ -17,6 +16,7 @@ import ( "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" ) // LFSLock represents a git lfs lock of repository. @@ -34,11 +34,7 @@ func init() { // BeforeInsert is invoked from XORM before inserting an object of this type. func (l *LFSLock) BeforeInsert() { - l.Path = cleanPath(l.Path) -} - -func cleanPath(p string) string { - return path.Clean("/" + p)[1:] + l.Path = util.CleanPath(l.Path) } // CreateLFSLock creates a new lock. @@ -53,7 +49,7 @@ func CreateLFSLock(ctx context.Context, repo *repo_model.Repository, lock *LFSLo return nil, err } - lock.Path = cleanPath(lock.Path) + lock.Path = util.CleanPath(lock.Path) lock.RepoID = repo.ID l, err := GetLFSLock(dbCtx, repo, lock.Path) @@ -73,7 +69,7 @@ func CreateLFSLock(ctx context.Context, repo *repo_model.Repository, lock *LFSLo // GetLFSLock returns release by given path. func GetLFSLock(ctx context.Context, repo *repo_model.Repository, path string) (*LFSLock, error) { - path = cleanPath(path) + path = util.CleanPath(path) rel := &LFSLock{RepoID: repo.ID} has, err := db.GetEngine(ctx).Where("lower(path) = ?", strings.ToLower(path)).Get(rel) if err != nil { diff --git a/modules/options/base.go b/modules/options/base.go index 3c140f64326b..e83e8df5d094 100644 --- a/modules/options/base.go +++ b/modules/options/base.go @@ -16,27 +16,27 @@ import ( // Locale reads the content of a specific locale from static/bindata or custom path. func Locale(name string) ([]byte, error) { - return fileFromDir(path.Join("locale", path.Clean("/"+name))) + return fileFromDir(path.Join("locale", util.CleanPath(name))) } // Readme reads the content of a specific readme from static/bindata or custom path. func Readme(name string) ([]byte, error) { - return fileFromDir(path.Join("readme", path.Clean("/"+name))) + return fileFromDir(path.Join("readme", util.CleanPath(name))) } // Gitignore reads the content of a gitignore locale from static/bindata or custom path. func Gitignore(name string) ([]byte, error) { - return fileFromDir(path.Join("gitignore", path.Clean("/"+name))) + return fileFromDir(path.Join("gitignore", util.CleanPath(name))) } // License reads the content of a specific license from static/bindata or custom path. func License(name string) ([]byte, error) { - return fileFromDir(path.Join("license", path.Clean("/"+name))) + return fileFromDir(path.Join("license", util.CleanPath(name))) } // Labels reads the content of a specific labels from static/bindata or custom path. func Labels(name string) ([]byte, error) { - return fileFromDir(path.Join("label", path.Clean("/"+name))) + return fileFromDir(path.Join("label", util.CleanPath(name))) } // WalkLocales reads the content of a specific locale diff --git a/modules/public/public.go b/modules/public/public.go index 42026f9b1054..e1d60d89eb9f 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -6,7 +6,6 @@ package public import ( "net/http" "os" - "path" "path/filepath" "strings" @@ -14,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" ) // Options represents the available options to configure the handler. @@ -103,7 +103,7 @@ func setWellKnownContentType(w http.ResponseWriter, file string) { func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) bool { // use clean to keep the file is a valid path with no . or .. - f, err := fs.Open(path.Clean(file)) + f, err := fs.Open(util.CleanPath(file)) if err != nil { if os.IsNotExist(err) { return false diff --git a/modules/storage/local.go b/modules/storage/local.go index a6a9d54a8ca3..05bf1fb28a56 100644 --- a/modules/storage/local.go +++ b/modules/storage/local.go @@ -8,7 +8,6 @@ import ( "io" "net/url" "os" - "path" "path/filepath" "strings" @@ -59,7 +58,7 @@ func NewLocalStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error } func (l *LocalStorage) buildLocalPath(p string) string { - return filepath.Join(l.dir, path.Clean("/" + strings.ReplaceAll(p, "\\", "/"))[1:]) + return filepath.Join(l.dir, util.CleanPath(strings.ReplaceAll(p, "\\", "/"))) } // Open a file diff --git a/modules/storage/minio.go b/modules/storage/minio.go index c427d8d7e312..24da14b63463 100644 --- a/modules/storage/minio.go +++ b/modules/storage/minio.go @@ -15,6 +15,7 @@ import ( "time" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" @@ -120,7 +121,7 @@ func NewMinioStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error } func (m *MinioStorage) buildMinioPath(p string) string { - return strings.TrimPrefix(path.Join(m.basePath, path.Clean("/" + strings.ReplaceAll(p, "\\", "/"))[1:]), "/") + return strings.TrimPrefix(path.Join(m.basePath, util.CleanPath(strings.ReplaceAll(p, "\\", "/"))), "/") } // Open open a file diff --git a/modules/util/path.go b/modules/util/path.go index 74acb7a85fd8..5aa9e15f5c3e 100644 --- a/modules/util/path.go +++ b/modules/util/path.go @@ -14,6 +14,14 @@ import ( "strings" ) +// CleanPath ensure to clean the path +func CleanPath(p string) string { + if strings.HasPrefix(p, "/") { + return path.Clean(p) + } + return path.Clean("/" + p)[1:] +} + // EnsureAbsolutePath ensure that a path is absolute, making it // relative to absoluteBase if necessary func EnsureAbsolutePath(path, absoluteBase string) string { diff --git a/modules/util/path_test.go b/modules/util/path_test.go index 93f4f67cf648..2f020f924dd2 100644 --- a/modules/util/path_test.go +++ b/modules/util/path_test.go @@ -136,3 +136,15 @@ func TestMisc_IsReadmeFileName(t *testing.T) { assert.Equal(t, testCase.idx, idx) } } + +func TestCleanPath(t *testing.T) { + cases := map[string]string{ + "../../test": "test", + "/test": "/test", + "/../test": "/test", + } + + for k, v := range cases { + assert.Equal(t, v, CleanPath(k)) + } +} diff --git a/routers/web/base.go b/routers/web/base.go index b0d8a7c3f1e6..d0135eac7a96 100644 --- a/routers/web/base.go +++ b/routers/web/base.go @@ -19,6 +19,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/modules/web/routing" "code.gitea.io/gitea/services/auth" @@ -44,7 +45,7 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor routing.UpdateFuncInfo(req.Context(), funcInfo) rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/") - rPath = path.Clean("/" + strings.ReplaceAll(rPath, "\\", "/"))[1:] + rPath = util.CleanPath(strings.ReplaceAll(rPath, "\\", "/")) u, err := objStore.URL(rPath, path.Base(rPath)) if err != nil { @@ -80,7 +81,7 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor routing.UpdateFuncInfo(req.Context(), funcInfo) rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/") - rPath = path.Clean("/" + strings.ReplaceAll(rPath, "\\", "/"))[1:] + rPath = util.CleanPath(strings.ReplaceAll(rPath, "\\", "/")) if rPath == "" { http.Error(w, "file not found", http.StatusNotFound) return diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index e5ba4ad2c139..4f208098e476 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -726,7 +726,7 @@ func UploadFilePost(ctx *context.Context) { func cleanUploadFileName(name string) string { // Rebase the filename - name = strings.Trim(path.Clean("/"+name), "/") + name = strings.Trim(util.CleanPath(name), "/") // Git disallows any filenames to have a .git directory in them. for _, part := range strings.Split(name, "/") { if strings.ToLower(part) == ".git" { diff --git a/routers/web/repo/lfs.go b/routers/web/repo/lfs.go index 869a69c37721..43f5527986b9 100644 --- a/routers/web/repo/lfs.go +++ b/routers/web/repo/lfs.go @@ -207,7 +207,7 @@ func LFSLockFile(ctx *context.Context) { ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks") return } - lockPath = path.Clean("/" + lockPath)[1:] + lockPath = util.CleanPath(lockPath) if len(lockPath) == 0 { ctx.Flash.Error(ctx.Tr("repo.settings.lfs_invalid_locking_path", originalPath)) ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks") diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index 8b259a362b1e..ca961524d12c 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -9,7 +9,6 @@ import ( "fmt" "io" "os" - "path" "path/filepath" "strconv" "strings" @@ -30,6 +29,7 @@ import ( "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/uri" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/pull" "github.com/google/uuid" @@ -866,7 +866,7 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error { } // SECURITY: The TreePath must be cleaned! - comment.TreePath = path.Clean("/" + comment.TreePath)[1:] + comment.TreePath = util.CleanPath(comment.TreePath) var patch string reader, writer := io.Pipe() diff --git a/services/packages/container/blob_uploader.go b/services/packages/container/blob_uploader.go index ba92b0507343..860672587d2b 100644 --- a/services/packages/container/blob_uploader.go +++ b/services/packages/container/blob_uploader.go @@ -8,13 +8,13 @@ import ( "errors" "io" "os" - "path" "path/filepath" "strings" packages_model "code.gitea.io/gitea/models/packages" packages_module "code.gitea.io/gitea/modules/packages" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" ) var ( @@ -33,7 +33,7 @@ type BlobUploader struct { } func buildFilePath(id string) string { - return filepath.Join(setting.Packages.ChunkedUploadPath, path.Clean("/" + strings.ReplaceAll(id, "\\", "/"))[1:]) + return filepath.Join(setting.Packages.ChunkedUploadPath, util.CleanPath(strings.ReplaceAll(id, "\\", "/"))) } // NewBlobUploader creates a new blob uploader for the given id diff --git a/services/repository/files/file.go b/services/repository/files/file.go index 2bac4372d378..7939491aec62 100644 --- a/services/repository/files/file.go +++ b/services/repository/files/file.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "net/url" - "path" "strings" "time" @@ -15,6 +14,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" ) // GetFileResponseFromCommit Constructs a FileResponse from a Commit object @@ -129,7 +129,7 @@ func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *user_m // CleanUploadFileName Trims a filename and returns empty string if it is a .git directory func CleanUploadFileName(name string) string { // Rebase the filename - name = strings.Trim(path.Clean("/"+name), "/") + name = strings.Trim(util.CleanPath(name), "/") // Git disallows any filenames to have a .git directory in them. for _, part := range strings.Split(name, "/") { if strings.ToLower(part) == ".git" { From 15a1c2d7efa2c835fb9bcff439446579f08c5e32 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Wed, 8 Mar 2023 20:21:23 +0800 Subject: [PATCH 06/11] Fix panic when getting notes by ref (#23372) Fix #23357 . Now the `/repos/{owner}/{repo}/git/notes/{sha}` API supports getting notes by a ref or sha (https://try.gitea.io/api/swagger#/repository/repoGetNote). But the `GetNote` func can only accept commit ID. https://github.com/go-gitea/gitea/blob/a12f5757372f751d25f9e5ca1f168f6920ded894/modules/git/notes_nogogit.go#L18 So we need to convert the query parameter to commit ID before calling `GetNote`. --- routers/api/v1/repo/notes.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/routers/api/v1/repo/notes.go b/routers/api/v1/repo/notes.go index 2d1f3291f8b9..74969f2cad7c 100644 --- a/routers/api/v1/repo/notes.go +++ b/routers/api/v1/repo/notes.go @@ -58,8 +58,18 @@ func getNote(ctx *context.APIContext, identifier string) { return } + commitSHA, err := ctx.Repo.GitRepo.ConvertToSHA1(identifier) + if err != nil { + if git.IsErrNotExist(err) { + ctx.NotFound(err) + } else { + ctx.Error(http.StatusInternalServerError, "ConvertToSHA1", err) + } + return + } + var note git.Note - if err := git.GetNote(ctx, ctx.Repo.GitRepo, identifier, ¬e); err != nil { + if err := git.GetNote(ctx, ctx.Repo.GitRepo, commitSHA.String(), ¬e); err != nil { if git.IsErrNotExist(err) { ctx.NotFound(identifier) return From d949d8e074407a96dbcfa98a71ccd80527b5ad78 Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Thu, 9 Mar 2023 00:18:10 +0900 Subject: [PATCH 07/11] add user visibility in dashboard navbar (#22747) Add private/limited tag to dashboard user/org list dropdown menu ![image](https://user-images.githubusercontent.com/18380374/216752207-5beb5281-1b0b-4e2b-adfc-b39c192c5032.png) Co-authored-by: Lunny Xiao --- templates/user/dashboard/navbar.tmpl | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/templates/user/dashboard/navbar.tmpl b/templates/user/dashboard/navbar.tmpl index 719d5b06b9f5..ab6f1bc5841b 100644 --- a/templates/user/dashboard/navbar.tmpl +++ b/templates/user/dashboard/navbar.tmpl @@ -5,12 +5,10 @@ {{avatar $.Context .ContextUser}} {{.ContextUser.ShortName 40}} - {{if .ContextUser.IsOrganization}} - - {{if .ContextUser.Visibility.IsLimited}}
{{.locale.Tr "org.settings.visibility.limited_shortname"}}
{{end}} - {{if .ContextUser.Visibility.IsPrivate}}
{{.locale.Tr "org.settings.visibility.private_shortname"}}
{{end}} -
- {{end}} + + {{if .ContextUser.Visibility.IsLimited}}
{{.locale.Tr "org.settings.visibility.limited_shortname"}}
{{end}} + {{if .ContextUser.Visibility.IsPrivate}}
{{.locale.Tr "org.settings.visibility.private_shortname"}}
{{end}} +
{{svg "octicon-triangle-down" 14 "dropdown icon"}}