mirror of
https://github.com/gin-gonic/gin.git
synced 2026-09-04 14:49:27 +08:00
Merge 85534a93f8ddc09fb0608a594e40eac61836ebef into dcaa4296d111981ffb31ac3eba90bb63e1eb5ab9
This commit is contained in:
commit
1b383c54ec
83
path.go
83
path.go
@ -26,6 +26,22 @@ func cleanPath(p string) string {
|
|||||||
return "/"
|
return "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(p) > 1 {
|
||||||
|
if p[0] != '/' && isSingleCleanPathSegment(p) {
|
||||||
|
return "/" + p
|
||||||
|
}
|
||||||
|
if p[0] == '/' {
|
||||||
|
if p[1] == '/' {
|
||||||
|
if cleaned, ok := cleanRepeatedLeadingSlash(p); ok {
|
||||||
|
return cleaned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cleaned, ok := cleanTrailingParentPath(p); ok {
|
||||||
|
return cleaned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reasonably sized buffer on stack to avoid allocations in the common case.
|
// Reasonably sized buffer on stack to avoid allocations in the common case.
|
||||||
// If a larger buffer is required, it gets allocated dynamically.
|
// If a larger buffer is required, it gets allocated dynamically.
|
||||||
buf := make([]byte, 0, stackBufSize)
|
buf := make([]byte, 0, stackBufSize)
|
||||||
@ -123,6 +139,73 @@ func cleanPath(p string) string {
|
|||||||
return string(buf[:w])
|
return string(buf[:w])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isSingleCleanPathSegment(s string) bool {
|
||||||
|
if s == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s == "." || s == ".." {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if s[i] == '/' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanRepeatedLeadingSlash(p string) (string, bool) {
|
||||||
|
i := 1
|
||||||
|
for i < len(p) && p[i] == '/' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i == len(p) || p[i:] == "." || p[i:] == ".." {
|
||||||
|
return "/", true
|
||||||
|
}
|
||||||
|
if isSingleCleanPathSegment(p[i:]) {
|
||||||
|
return p[i-1:], true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanTrailingParentPath(p string) (string, bool) {
|
||||||
|
parentEnd := len(p) - 3
|
||||||
|
if parentEnd < 1 || p[parentEnd:] != "/.." || !isCleanAbsolutePath(p[:parentEnd]) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentStart := parentEnd - 1
|
||||||
|
for segmentStart > 0 && p[segmentStart] != '/' {
|
||||||
|
segmentStart--
|
||||||
|
}
|
||||||
|
if segmentStart == 0 {
|
||||||
|
return "/", true
|
||||||
|
}
|
||||||
|
return p[:segmentStart], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCleanAbsolutePath(p string) bool {
|
||||||
|
if p == "" || p[0] != '/' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if p == "/" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentStart := 1
|
||||||
|
for i := 1; i <= len(p); i++ {
|
||||||
|
if i < len(p) && p[i] != '/' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch p[segmentStart:i] {
|
||||||
|
case "", ".", "..":
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
segmentStart = i + 1
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Internal helper to lazily create a buffer if necessary.
|
// Internal helper to lazily create a buffer if necessary.
|
||||||
// Calls to this function get inlined.
|
// Calls to this function get inlined.
|
||||||
func bufApp(buf *[]byte, s string, w int, c byte) {
|
func bufApp(buf *[]byte, s string, w int, c byte) {
|
||||||
|
|||||||
79
path_test.go
79
path_test.go
@ -132,6 +132,85 @@ func TestPathCleanLong(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPathCleanFastPathHelpers(t *testing.T) {
|
||||||
|
cleanSegmentTests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"empty", "", false},
|
||||||
|
{"dot", ".", false},
|
||||||
|
{"dotdot", "..", false},
|
||||||
|
{"nested", "abc/def", false},
|
||||||
|
{"clean", "abc", true},
|
||||||
|
}
|
||||||
|
for _, test := range cleanSegmentTests {
|
||||||
|
t.Run("single segment "+test.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, test.want, isSingleCleanPathSegment(test.path))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
repeatedSlashTests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
want string
|
||||||
|
wantHit bool
|
||||||
|
}{
|
||||||
|
{"only slashes", "///", "/", true},
|
||||||
|
{"dot segment", "//.", "/", true},
|
||||||
|
{"dotdot segment", "//..", "/", true},
|
||||||
|
{"single segment", "//abc", "/abc", true},
|
||||||
|
{"nested segment", "//abc/def", "", false},
|
||||||
|
}
|
||||||
|
for _, test := range repeatedSlashTests {
|
||||||
|
t.Run("leading slash "+test.name, func(t *testing.T) {
|
||||||
|
got, ok := cleanRepeatedLeadingSlash(test.path)
|
||||||
|
assert.Equal(t, test.wantHit, ok)
|
||||||
|
assert.Equal(t, test.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
parentPathTests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
want string
|
||||||
|
wantHit bool
|
||||||
|
}{
|
||||||
|
{"root parent", "/abc/..", "/", true},
|
||||||
|
{"clean parent", "/abc/b/..", "/abc", true},
|
||||||
|
{"not trailing parent", "/abc/b", "", false},
|
||||||
|
{"unclean prefix", "/abc/./b/..", "", false},
|
||||||
|
}
|
||||||
|
for _, test := range parentPathTests {
|
||||||
|
t.Run("trailing parent "+test.name, func(t *testing.T) {
|
||||||
|
got, ok := cleanTrailingParentPath(test.path)
|
||||||
|
assert.Equal(t, test.wantHit, ok)
|
||||||
|
assert.Equal(t, test.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
absolutePathTests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"empty", "", false},
|
||||||
|
{"relative", "abc", false},
|
||||||
|
{"root", "/", true},
|
||||||
|
{"single segment", "/abc", true},
|
||||||
|
{"nested", "/abc/def", true},
|
||||||
|
{"trailing slash", "/abc/", false},
|
||||||
|
{"dot segment", "/abc/./def", false},
|
||||||
|
{"dotdot segment", "/abc/../def", false},
|
||||||
|
{"empty segment", "/abc//def", false},
|
||||||
|
}
|
||||||
|
for _, test := range absolutePathTests {
|
||||||
|
t.Run("absolute path "+test.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, test.want, isCleanAbsolutePath(test.path))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkPathCleanLong(b *testing.B) {
|
func BenchmarkPathCleanLong(b *testing.B) {
|
||||||
cleanTests := genLongPaths()
|
cleanTests := genLongPaths()
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user