Skip to content

Commit e94155b

Browse files
committed
rename to PathJoin (instead of SafeXxx), add tests, add comments
1 parent 1fb8a53 commit e94155b

File tree

14 files changed

+34
-27
lines changed

14 files changed

+34
-27
lines changed

models/git/lfs_lock.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func init() {
3434

3535
// BeforeInsert is invoked from XORM before inserting an object of this type.
3636
func (l *LFSLock) BeforeInsert() {
37-
l.Path = util.SafePathRel(l.Path)
37+
l.Path = util.PathJoinRel(l.Path)
3838
}
3939

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

52-
lock.Path = util.SafePathRel(lock.Path)
52+
lock.Path = util.PathJoinRel(lock.Path)
5353
lock.RepoID = repo.ID
5454

5555
l, err := GetLFSLock(dbCtx, repo, lock.Path)
@@ -69,7 +69,7 @@ func CreateLFSLock(ctx context.Context, repo *repo_model.Repository, lock *LFSLo
6969

7070
// GetLFSLock returns release by given path.
7171
func GetLFSLock(ctx context.Context, repo *repo_model.Repository, path string) (*LFSLock, error) {
72-
path = util.SafePathRel(path)
72+
path = util.PathJoinRel(path)
7373
rel := &LFSLock{RepoID: repo.ID}
7474
has, err := db.GetEngine(ctx).Where("lower(path) = ?", strings.ToLower(path)).Get(rel)
7575
if err != nil {

modules/options/base.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func joinLocalPaths(baseDirs []string, subDir string, elems ...string) (paths []
9898
copy(abs[2:], elems)
9999
for _, baseDir := range baseDirs {
100100
abs[0] = mustLocalPathAbs(baseDir)
101-
paths = append(paths, util.SafeFilePathAbs(abs...))
101+
paths = append(paths, util.FilePathJoinAbs(abs...))
102102
}
103103
return paths
104104
}

modules/options/static.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func fileFromOptionsDir(elems ...string) ([]byte, error) {
5858
return data, nil
5959
}
6060

61-
f, err := Assets.Open(util.SafePathRelX(elems...))
61+
f, err := Assets.Open(util.PathJoinRelX(elems...))
6262
if err != nil {
6363
return nil, err
6464
}

modules/public/public.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func setWellKnownContentType(w http.ResponseWriter, file string) {
9393

9494
func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) bool {
9595
// 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
96-
f, err := fs.Open(util.SafePathRelX(file))
96+
f, err := fs.Open(util.PathJoinRelX(file))
9797
if err != nil {
9898
if os.IsNotExist(err) {
9999
return false

modules/storage/local.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func NewLocalStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error
6464
}
6565

6666
func (l *LocalStorage) buildLocalPath(p string) string {
67-
return util.SafeFilePathAbs(l.dir, p)
67+
return util.FilePathJoinAbs(l.dir, p)
6868
}
6969

7070
// Open a file

modules/storage/minio.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ func NewMinioStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error
121121
}
122122

123123
func (m *MinioStorage) buildMinioPath(p string) string {
124-
return util.SafePathRelX(m.basePath, p)
124+
return util.PathJoinRelX(m.basePath, p)
125125
}
126126

127127
// Open open a file

modules/util/path.go

+12-9
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ import (
1515
"strings"
1616
)
1717

18-
// SafePathRel joins the path elements into a single path, each element is cleaned by path.Clean separately.
18+
// PathJoinRel joins the path elements into a single path, each element is cleaned by path.Clean separately.
1919
// It only returns the following values (like path.Join), any redundant part (empty, relative dots, slashes) is removed.
20+
// It's caller's duty to make every element not bypass its own directly level, to avoid security issues.
2021
//
2122
// empty => ``
2223
// `` => ``
@@ -26,7 +27,7 @@ import (
2627
// `foo\..\bar` => `foo\..\bar`
2728
// {`foo`, ``, `bar`} => `foo/bar`
2829
// {`foo`, `..`, `bar`} => `foo/bar`
29-
func SafePathRel(elem ...string) string {
30+
func PathJoinRel(elem ...string) string {
3031
elems := make([]string, len(elem))
3132
for i, e := range elem {
3233
if e == "" {
@@ -44,34 +45,36 @@ func SafePathRel(elem ...string) string {
4445
}
4546
}
4647

47-
// SafePathRelX joins the path elements into a single path like SafePathRel,
48+
// PathJoinRelX joins the path elements into a single path like PathJoinRel,
4849
// and covert all backslashes to slashes. (X means "extended", also means the combination of `\` and `/`).
49-
// It returns similar results as SafePathRel except:
50+
// It's caller's duty to make every element not bypass its own directly level, to avoid security issues.
51+
// It returns similar results as PathJoinRel except:
5052
//
5153
// `foo\..\bar` => `bar` (because it's processed as `foo/../bar`)
5254
//
5355
// All backslashes are handled as slashes, the result only contains slashes.
54-
func SafePathRelX(elem ...string) string {
56+
func PathJoinRelX(elem ...string) string {
5557
elems := make([]string, len(elem))
5658
for i, e := range elem {
5759
if e == "" {
5860
continue
5961
}
6062
elems[i] = path.Clean("/" + strings.ReplaceAll(e, "\\", "/"))
6163
}
62-
return SafePathRel(elems...)
64+
return PathJoinRel(elems...)
6365
}
6466

6567
const pathSeparator = string(os.PathSeparator)
6668

67-
// SafeFilePathAbs joins the path elements into a single file path, each element is cleaned by filepath.Clean separately.
69+
// FilePathJoinAbs joins the path elements into a single file path, each element is cleaned by filepath.Clean separately.
6870
// All slashes/backslashes are converted to path separators before cleaning, the result only contains path separators.
6971
// The first element must be an absolute path, caller should prepare the base path.
70-
// Like SafePathRel, any redundant part (empty, relative dots, slashes) is removed.
72+
// It's caller's duty to make every element not bypass its own directly level, to avoid security issues.
73+
// Like PathJoinRel, any redundant part (empty, relative dots, slashes) is removed.
7174
//
7275
// {`/foo`, ``, `bar`} => `/foo/bar`
7376
// {`/foo`, `..`, `bar`} => `/foo/bar`
74-
func SafeFilePathAbs(elem ...string) string {
77+
func FilePathJoinAbs(elem ...string) string {
7578
elems := make([]string, len(elem))
7679

7780
// POISX filesystem can have `\` in file names. Windows: `\` and `/` are both used for path separators

modules/util/path_test.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,10 @@ func TestCleanPath(t *testing.T) {
151151
{[]string{`a\..\b`}, `a\..\b`},
152152
{[]string{`a`, ``, `b`}, `a/b`},
153153
{[]string{`a`, `..`, `b`}, `a/b`},
154+
{[]string{`lfs`, `repo/..`, `user/../path`}, `lfs/path`},
154155
}
155156
for _, c := range cases {
156-
assert.Equal(t, c.expected, SafePathRel(c.elems...), "case: %v", c.elems)
157+
assert.Equal(t, c.expected, PathJoinRel(c.elems...), "case: %v", c.elems)
157158
}
158159

159160
cases = []struct {
@@ -169,9 +170,10 @@ func TestCleanPath(t *testing.T) {
169170
{[]string{`a\..\b`}, `b`},
170171
{[]string{`a`, ``, `b`}, `a/b`},
171172
{[]string{`a`, `..`, `b`}, `a/b`},
173+
{[]string{`lfs`, `repo/..`, `user/../path`}, `lfs/path`},
172174
}
173175
for _, c := range cases {
174-
assert.Equal(t, c.expected, SafePathRelX(c.elems...), "case: %v", c.elems)
176+
assert.Equal(t, c.expected, PathJoinRelX(c.elems...), "case: %v", c.elems)
175177
}
176178

177179
// for POSIX only, but the result is similar on Windows, because the first element must be an absolute path
@@ -187,6 +189,7 @@ func TestCleanPath(t *testing.T) {
187189
{[]string{`C:\a/..\b`}, `C:\b`},
188190
{[]string{`C:\a`, ``, `b`}, `C:\a\b`},
189191
{[]string{`C:\a`, `..`, `b`}, `C:\a\b`},
192+
{[]string{`C:\lfs`, `repo/..`, `user/../path`}, `C:\lfs\path`},
190193
}
191194
} else {
192195
cases = []struct {
@@ -200,9 +203,10 @@ func TestCleanPath(t *testing.T) {
200203
{[]string{`/a\..\b`}, `/b`},
201204
{[]string{`/a`, ``, `b`}, `/a/b`},
202205
{[]string{`/a`, `..`, `b`}, `/a/b`},
206+
{[]string{`/lfs`, `repo/..`, `user/../path`}, `/lfs/path`},
203207
}
204208
}
205209
for _, c := range cases {
206-
assert.Equal(t, c.expected, SafeFilePathAbs(c.elems...), "case: %v", c.elems)
210+
assert.Equal(t, c.expected, FilePathJoinAbs(c.elems...), "case: %v", c.elems)
207211
}
208212
}

routers/web/base.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor
4545
routing.UpdateFuncInfo(req.Context(), funcInfo)
4646

4747
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
48-
rPath = util.SafePathRelX(rPath)
48+
rPath = util.PathJoinRelX(rPath)
4949

5050
u, err := objStore.URL(rPath, path.Base(rPath))
5151
if err != nil {
@@ -81,7 +81,7 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor
8181
routing.UpdateFuncInfo(req.Context(), funcInfo)
8282

8383
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
84-
rPath = util.SafePathRelX(rPath)
84+
rPath = util.PathJoinRelX(rPath)
8585
if rPath == "" || rPath == "." {
8686
http.Error(w, "file not found", http.StatusNotFound)
8787
return

routers/web/repo/editor.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,7 @@ func UploadFilePost(ctx *context.Context) {
726726

727727
func cleanUploadFileName(name string) string {
728728
// Rebase the filename
729-
name = util.SafePathRel(name)
729+
name = util.PathJoinRel(name)
730730
// Git disallows any filenames to have a .git directory in them.
731731
for _, part := range strings.Split(name, "/") {
732732
if strings.ToLower(part) == ".git" {

routers/web/repo/lfs.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ func LFSLockFile(ctx *context.Context) {
207207
ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks")
208208
return
209209
}
210-
lockPath = util.SafePathRel(lockPath)
210+
lockPath = util.PathJoinRel(lockPath)
211211
if len(lockPath) == 0 {
212212
ctx.Flash.Error(ctx.Tr("repo.settings.lfs_invalid_locking_path", originalPath))
213213
ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks")

services/migrations/gitea_uploader.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,7 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
866866
}
867867

868868
// SECURITY: The TreePath must be cleaned! use relative path
869-
comment.TreePath = util.SafePathRel(comment.TreePath)
869+
comment.TreePath = util.PathJoinRel(comment.TreePath)
870870

871871
var patch string
872872
reader, writer := io.Pipe()

services/packages/container/blob_uploader.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type BlobUploader struct {
3131
}
3232

3333
func buildFilePath(id string) string {
34-
return util.SafeFilePathAbs(setting.Packages.ChunkedUploadPath, id)
34+
return util.FilePathJoinAbs(setting.Packages.ChunkedUploadPath, id)
3535
}
3636

3737
// NewBlobUploader creates a new blob uploader for the given id

services/repository/files/file.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *user_m
129129
// CleanUploadFileName Trims a filename and returns empty string if it is a .git directory
130130
func CleanUploadFileName(name string) string {
131131
// Rebase the filename
132-
name = util.SafePathRel(name)
132+
name = util.PathJoinRel(name)
133133
// Git disallows any filenames to have a .git directory in them.
134134
for _, part := range strings.Split(name, "/") {
135135
if strings.ToLower(part) == ".git" {

0 commit comments

Comments
 (0)