Skip to content

Commit

Permalink
Introduce path Clean/Join helper functions (#23495)
Browse files Browse the repository at this point in the history
Since #23493 has conflicts with latest commits, this PR is my proposal
for fixing #23371

Details are in the comments

And refactor the `modules/options` module, to make it always use
"filepath" to access local files.

Benefits:

* No need to do `util.CleanPath(strings.ReplaceAll(p, "\\", "/"))),
"/")` any more (not only one before)
* The function behaviors are clearly defined
  • Loading branch information
wxiaoguang committed Mar 21, 2023
1 parent 253a00a commit ce9dee5
Show file tree
Hide file tree
Showing 16 changed files with 261 additions and 152 deletions.
6 changes: 3 additions & 3 deletions models/git/lfs_lock.go
Expand Up @@ -34,7 +34,7 @@ func init() {

// BeforeInsert is invoked from XORM before inserting an object of this type.
func (l *LFSLock) BeforeInsert() {
l.Path = util.CleanPath(l.Path)
l.Path = util.PathJoinRel(l.Path)
}

// CreateLFSLock creates a new lock.
Expand All @@ -49,7 +49,7 @@ func CreateLFSLock(ctx context.Context, repo *repo_model.Repository, lock *LFSLo
return nil, err
}

lock.Path = util.CleanPath(lock.Path)
lock.Path = util.PathJoinRel(lock.Path)
lock.RepoID = repo.ID

l, err := GetLFSLock(dbCtx, repo, lock.Path)
Expand All @@ -69,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 = util.CleanPath(path)
path = util.PathJoinRel(path)
rel := &LFSLock{RepoID: repo.ID}
has, err := db.GetEngine(ctx).Where("lower(path) = ?", strings.ToLower(path)).Get(rel)
if err != nil {
Expand Down
67 changes: 53 additions & 14 deletions modules/options/base.go
Expand Up @@ -7,36 +7,38 @@ import (
"fmt"
"io/fs"
"os"
"path"
"path/filepath"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)

var directories = make(directorySet)

// 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", util.CleanPath(name)))
return fileFromOptionsDir("locale", 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", util.CleanPath(name)))
return fileFromOptionsDir("readme", 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", util.CleanPath(name)))
return fileFromOptionsDir("gitignore", 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", util.CleanPath(name)))
return fileFromOptionsDir("license", 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", util.CleanPath(name)))
return fileFromOptionsDir("label", name)
}

// WalkLocales reads the content of a specific locale
Expand Down Expand Up @@ -79,17 +81,54 @@ 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)
// mustLocalPathAbs coverts a path to absolute path
// FIXME: the old behavior (StaticRootPath might not be absolute), not ideal, just keep the same as before
func mustLocalPathAbs(s string) string {
abs, err := filepath.Abs(s)
if err != nil {
return nil, fmt.Errorf("unable to check if static directory %s is a directory. %w", dir, err)
// This should never happen in a real system. If it happens, the user must have already been in trouble: the system is not able to resolve its own paths.
log.Fatal("Unable to get absolute path for %q: %v", s, err)
}
if !isDir {
return nil, nil
return abs
}

func joinLocalPaths(baseDirs []string, subDir string, elems ...string) (paths []string) {
abs := make([]string, len(elems)+2)
abs[1] = subDir
copy(abs[2:], elems)
for _, baseDir := range baseDirs {
abs[0] = mustLocalPathAbs(baseDir)
paths = append(paths, util.FilePathJoinAbs(abs...))
}
files, err := util.StatDir(dir, true)
if err != nil {
return nil, fmt.Errorf("unable to read directory %q. %w", dir, err)
return paths
}

func listLocalDirIfExist(baseDirs []string, subDir string, elems ...string) (files []string, err error) {
for _, localPath := range joinLocalPaths(baseDirs, subDir, elems...) {
isDir, err := util.IsDir(localPath)
if err != nil {
return nil, fmt.Errorf("unable to check if path %q is a directory. %w", localPath, err)
} else if !isDir {
continue
}

dirFiles, err := util.StatDir(localPath, true)
if err != nil {
return nil, fmt.Errorf("unable to read directory %q. %w", localPath, err)
}
files = append(files, dirFiles...)
}
return files, nil
}

func readLocalFile(baseDirs []string, subDir string, elems ...string) ([]byte, error) {
for _, localPath := range joinLocalPaths(baseDirs, subDir, elems...) {
data, err := os.ReadFile(localPath)
if err == nil {
return data, nil
} else if !os.IsNotExist(err) {
log.Error("Unable to read file %q. Error: %v", localPath, err)
}
}
return nil, os.ErrNotExist
}
48 changes: 6 additions & 42 deletions modules/options/dynamic.go
Expand Up @@ -6,62 +6,26 @@
package options

import (
"fmt"
"os"
"path"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)

var directories = make(directorySet)

// Dir returns all files from static or custom directory.
func Dir(name string) ([]string, error) {
if directories.Filled(name) {
return directories.Get(name), nil
}

var result []string

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 nil, err
}
result = append(result, files...)
result, err := listLocalDirIfExist([]string{setting.CustomPath, setting.StaticRootPath}, "options", name)
if err != nil {
return nil, err
}

return directories.AddAndGet(name, result), nil
}

// 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)

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)
}

staticPath := path.Join(setting.StaticRootPath, "options", name)

isFile, err = util.IsFile(staticPath)
if err != nil {
log.Error("Unable to check if %s is a file. Error: %v", staticPath, err)
}
if isFile {
return os.ReadFile(staticPath)
}

return []byte{}, fmt.Errorf("Asset file does not exist: %s", name)
// fileFromOptionsDir is a helper to read files from custom or static path.
func fileFromOptionsDir(elems ...string) ([]byte, error) {
return readLocalFile([]string{setting.CustomPath, setting.StaticRootPath}, "options", elems...)
}

// IsDynamic will return false when using embedded data (-tags bindata)
Expand Down
39 changes: 10 additions & 29 deletions modules/options/static.go
Expand Up @@ -8,33 +8,20 @@ package options
import (
"fmt"
"io"
"os"
"path"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)

var directories = make(directorySet)

// Dir returns all files from bindata or custom directory.
// Dir returns all files from custom directory or bindata.
func Dir(name string) ([]string, error) {
if directories.Filled(name) {
return directories.Get(name), nil
}

var result []string

for _, dir := range []string{
path.Join(setting.CustomPath, "options", name), // custom dir
// no static dir
} {
files, err := statDirIfExist(dir)
if err != nil {
return nil, err
}
result = append(result, files...)
result, err := listLocalDirIfExist([]string{setting.CustomPath}, "options", name)
if err != nil {
return nil, err
}

files, err := AssetDir(name)
Expand Down Expand Up @@ -64,24 +51,18 @@ func AssetDir(dirName string) ([]string, error) {
return results, nil
}

// 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)

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)
// fileFromOptionsDir is a helper to read files from custom path or bindata.
func fileFromOptionsDir(elems ...string) ([]byte, error) {
// only try custom dir, no static dir
if data, err := readLocalFile([]string{setting.CustomPath}, "options", elems...); err == nil {
return data, nil
}

f, err := Assets.Open(name)
f, err := Assets.Open(util.PathJoinRelX(elems...))
if err != nil {
return nil, err
}
defer f.Close()

return io.ReadAll(f)
}

Expand Down
26 changes: 8 additions & 18 deletions modules/public/public.go
Expand Up @@ -45,29 +45,19 @@ func AssetsHandlerFunc(opts *Options) http.HandlerFunc {
return
}

file := req.URL.Path
file = file[len(opts.Prefix):]
if len(file) == 0 {
resp.WriteHeader(http.StatusNotFound)
return
}
if strings.Contains(file, "\\") {
resp.WriteHeader(http.StatusBadRequest)
return
}
file = "/" + file

var written bool
var corsSent bool
if opts.CorsHandler != nil {
written = true
opts.CorsHandler(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
written = false
corsSent = true
})).ServeHTTP(resp, req)
}
if written {
// If CORS is not sent, the response must have been written by other handlers
if !corsSent {
return
}

file := req.URL.Path[len(opts.Prefix):]

// custom files
if opts.handle(resp, req, http.Dir(custPath), file) {
return
Expand Down Expand Up @@ -102,8 +92,8 @@ 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(util.CleanPath(file))
// actually, fs (http.FileSystem) is designed to be a safe interface, relative paths won't bypass its parent directory, it's also fine to do a clean here
f, err := fs.Open(util.PathJoinRelX(file))
if err != nil {
if os.IsNotExist(err) {
return false
Expand Down
17 changes: 10 additions & 7 deletions modules/storage/local.go
Expand Up @@ -5,11 +5,11 @@ package storage

import (
"context"
"fmt"
"io"
"net/url"
"os"
"path/filepath"
"strings"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
Expand Down Expand Up @@ -41,13 +41,19 @@ func NewLocalStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error
}
config := configInterface.(LocalStorageConfig)

if !filepath.IsAbs(config.Path) {
return nil, fmt.Errorf("LocalStorageConfig.Path should have been prepared by setting/storage.go and should be an absolute path, but not: %q", config.Path)
}
log.Info("Creating new Local Storage at %s", config.Path)
if err := os.MkdirAll(config.Path, os.ModePerm); err != nil {
return nil, err
}

if config.TemporaryPath == "" {
config.TemporaryPath = config.Path + "/tmp"
config.TemporaryPath = filepath.Join(config.Path, "tmp")
}
if !filepath.IsAbs(config.TemporaryPath) {
return nil, fmt.Errorf("LocalStorageConfig.TemporaryPath should be an absolute path, but not: %q", config.TemporaryPath)
}

return &LocalStorage{
Expand All @@ -58,7 +64,7 @@ func NewLocalStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error
}

func (l *LocalStorage) buildLocalPath(p string) string {
return filepath.Join(l.dir, util.CleanPath(strings.ReplaceAll(p, "\\", "/")))
return util.FilePathJoinAbs(l.dir, p)
}

// Open a file
Expand Down Expand Up @@ -128,10 +134,7 @@ func (l *LocalStorage) URL(path, name string) (*url.URL, error) {

// IterateObjects iterates across the objects in the local storage
func (l *LocalStorage) IterateObjects(prefix string, fn func(path string, obj Object) error) error {
dir := l.dir
if prefix != "" {
dir = filepath.Join(l.dir, util.CleanPath(prefix))
}
dir := l.buildLocalPath(prefix)
return filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
Expand Down

0 comments on commit ce9dee5

Please sign in to comment.