Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce path Clean/Join helper functions #23495

Merged
merged 14 commits into from
Mar 21, 2023
Merged
6 changes: 3 additions & 3 deletions models/git/lfs_lock.go
Original file line number Diff line number Diff line change
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.SafePathRel(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.SafePathRel(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.SafePathRel(path)
rel := &LFSLock{RepoID: repo.ID}
has, err := db.GetEngine(ctx).Where("lower(path) = ?", strings.ToLower(path)).Get(rel)
if err != nil {
Expand Down
66 changes: 52 additions & 14 deletions modules/options/base.go
Original file line number Diff line number Diff line change
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,53 @@ 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)
log.Fatal("Unable to get absolute path for %q: %v", s, err)
delvh marked this conversation as resolved.
Show resolved Hide resolved
}
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.SafeFilePathAbs(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 static directory %s is a directory. %w", localPath, err)
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
} 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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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.SafePathRelX(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
Original file line number Diff line number Diff line change
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
}
delvh marked this conversation as resolved.
Show resolved Hide resolved
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.SafePathRelX(file))
if err != nil {
if os.IsNotExist(err) {
return false
Expand Down
17 changes: 10 additions & 7 deletions modules/storage/local.go
Original file line number Diff line number Diff line change
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.SafeFilePathAbs(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
Loading