Skip to content

Commit 3f71ab9

Browse files
zeripathlafriks
andauthored
Clean paths when looking in Storage (#19124)
* Clean paths when looking in Storage Ensure paths are clean for minio aswell as local storage. Use url.Path not RequestURI/EscapedPath in storageHandler. Signed-off-by: Andrew Thornton <[email protected]> * Apply suggestions from code review Co-authored-by: Lauris BH <[email protected]>
1 parent d2c1658 commit 3f71ab9

File tree

4 files changed

+40
-51
lines changed

4 files changed

+40
-51
lines changed

modules/storage/local.go

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package storage
66

77
import (
88
"context"
9-
"errors"
109
"io"
1110
"net/url"
1211
"os"
@@ -18,8 +17,6 @@ import (
1817
"code.gitea.io/gitea/modules/util"
1918
)
2019

21-
// ErrLocalPathNotSupported represents an error that path is not supported
22-
var ErrLocalPathNotSupported = errors.New("local path is not supported")
2320
var _ ObjectStorage = &LocalStorage{}
2421

2522
// LocalStorageType is the type descriptor for local storage
@@ -62,21 +59,18 @@ func NewLocalStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error
6259
}, nil
6360
}
6461

62+
func (l *LocalStorage) buildLocalPath(p string) string {
63+
return filepath.Join(l.dir, path.Clean("/" + strings.ReplaceAll(p, "\\", "/"))[1:])
64+
}
65+
6566
// Open a file
6667
func (l *LocalStorage) Open(path string) (Object, error) {
67-
if !isLocalPathValid(path) {
68-
return nil, ErrLocalPathNotSupported
69-
}
70-
return os.Open(filepath.Join(l.dir, path))
68+
return os.Open(l.buildLocalPath(path))
7169
}
7270

7371
// Save a file
7472
func (l *LocalStorage) Save(path string, r io.Reader, size int64) (int64, error) {
75-
if !isLocalPathValid(path) {
76-
return 0, ErrLocalPathNotSupported
77-
}
78-
79-
p := filepath.Join(l.dir, path)
73+
p := l.buildLocalPath(path)
8074
if err := os.MkdirAll(filepath.Dir(p), os.ModePerm); err != nil {
8175
return 0, err
8276
}
@@ -116,24 +110,12 @@ func (l *LocalStorage) Save(path string, r io.Reader, size int64) (int64, error)
116110

117111
// Stat returns the info of the file
118112
func (l *LocalStorage) Stat(path string) (os.FileInfo, error) {
119-
return os.Stat(filepath.Join(l.dir, path))
120-
}
121-
122-
func isLocalPathValid(p string) bool {
123-
a := path.Clean(p)
124-
if strings.HasPrefix(a, "../") || strings.HasPrefix(a, "..\\") {
125-
return false
126-
}
127-
return a == p
113+
return os.Stat(l.buildLocalPath(path))
128114
}
129115

130116
// Delete delete a file
131117
func (l *LocalStorage) Delete(path string) error {
132-
if !isLocalPathValid(path) {
133-
return ErrLocalPathNotSupported
134-
}
135-
p := filepath.Join(l.dir, path)
136-
return util.Remove(p)
118+
return util.Remove(l.buildLocalPath(path))
137119
}
138120

139121
// URL gets the redirect URL to a file

modules/storage/local_test.go

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,44 @@ import (
1010
"github.com/stretchr/testify/assert"
1111
)
1212

13-
func TestLocalPathIsValid(t *testing.T) {
13+
func TestBuildLocalPath(t *testing.T) {
1414
kases := []struct {
15-
path string
16-
valid bool
15+
localDir string
16+
path string
17+
expected string
1718
}{
1819
{
20+
"a",
21+
"0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
1922
"a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
20-
true,
2123
},
2224
{
23-
"../a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
24-
false,
25+
"a",
26+
"../0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
27+
"a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
2528
},
2629
{
27-
"a\\0\\a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
28-
true,
30+
"a",
31+
"0\\a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
32+
"a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
2933
},
3034
{
31-
"b/../a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
32-
false,
35+
"b",
36+
"a/../0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
37+
"b/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
3338
},
3439
{
35-
"..\\a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
36-
false,
40+
"b",
41+
"a\\..\\0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
42+
"b/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
3743
},
3844
}
3945

4046
for _, k := range kases {
4147
t.Run(k.path, func(t *testing.T) {
42-
assert.EqualValues(t, k.valid, isLocalPathValid(k.path))
48+
l := LocalStorage{dir: k.localDir}
49+
50+
assert.EqualValues(t, k.expected, l.buildLocalPath(k.path))
4351
})
4452
}
4553
}

modules/storage/minio.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func NewMinioStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error
117117
}
118118

119119
func (m *MinioStorage) buildMinioPath(p string) string {
120-
return strings.TrimPrefix(path.Join(m.basePath, p), "/")
120+
return strings.TrimPrefix(path.Join(m.basePath, path.Clean("/" + strings.ReplaceAll(p, "\\", "/"))[1:]), "/")
121121
}
122122

123123
// Open open a file

routers/web/base.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"net/http"
1212
"os"
1313
"path"
14-
"path/filepath"
1514
"strings"
1615

1716
"code.gitea.io/gitea/modules/context"
@@ -28,6 +27,7 @@ import (
2827
)
2928

3029
func storageHandler(storageSetting setting.Storage, prefix string, objStore storage.ObjectStorage) func(next http.Handler) http.Handler {
30+
prefix = strings.Trim(prefix, "/")
3131
funcInfo := routing.GetFuncInfo(storageHandler, prefix)
3232
return func(next http.Handler) http.Handler {
3333
if storageSetting.ServeDirect {
@@ -37,13 +37,15 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor
3737
return
3838
}
3939

40-
if !strings.HasPrefix(req.URL.RequestURI(), "/"+prefix) {
40+
if !strings.HasPrefix(req.URL.Path, "/"+prefix+"/") {
4141
next.ServeHTTP(w, req)
4242
return
4343
}
4444
routing.UpdateFuncInfo(req.Context(), funcInfo)
4545

46-
rPath := strings.TrimPrefix(req.URL.RequestURI(), "/"+prefix)
46+
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
47+
rPath = path.Clean("/" + strings.ReplaceAll(rPath, "\\", "/"))[1:]
48+
4749
u, err := objStore.URL(rPath, path.Base(rPath))
4850
if err != nil {
4951
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
@@ -55,11 +57,12 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor
5557
http.Error(w, fmt.Sprintf("Error whilst getting URL for %s %s", prefix, rPath), 500)
5658
return
5759
}
60+
5861
http.Redirect(
5962
w,
6063
req,
6164
u.String(),
62-
301,
65+
http.StatusMovedPermanently,
6366
)
6467
})
6568
}
@@ -70,22 +73,18 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor
7073
return
7174
}
7275

73-
prefix := strings.Trim(prefix, "/")
74-
75-
if !strings.HasPrefix(req.URL.EscapedPath(), "/"+prefix+"/") {
76+
if !strings.HasPrefix(req.URL.Path, "/"+prefix+"/") {
7677
next.ServeHTTP(w, req)
7778
return
7879
}
7980
routing.UpdateFuncInfo(req.Context(), funcInfo)
8081

81-
rPath := strings.TrimPrefix(req.URL.EscapedPath(), "/"+prefix+"/")
82-
rPath = strings.TrimPrefix(rPath, "/")
82+
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
83+
rPath = path.Clean("/" + strings.ReplaceAll(rPath, "\\", "/"))[1:]
8384
if rPath == "" {
8485
http.Error(w, "file not found", 404)
8586
return
8687
}
87-
rPath = path.Clean("/" + filepath.ToSlash(rPath))
88-
rPath = rPath[1:]
8988

9089
fi, err := objStore.Stat(rPath)
9190
if err == nil && httpcache.HandleTimeCache(req, w, fi) {

0 commit comments

Comments
 (0)