perf: replace regex with custom functions in redirectTrailingSlash

This commit is contained in:
1911860538 2025-10-30 22:22:09 +08:00
parent ecb3f7b5e2
commit 95504d9ec7
2 changed files with 67 additions and 8 deletions

44
gin.go
View File

@ -11,9 +11,9 @@ import (
"net/http"
"os"
"path"
"regexp"
"strings"
"sync"
"unicode"
"github.com/gin-gonic/gin/internal/bytesconv"
filesystem "github.com/gin-gonic/gin/internal/fs"
@ -48,11 +48,6 @@ var defaultTrustedCIDRs = []*net.IPNet{
},
}
var (
regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+")
regRemoveRepeatedChar = regexp.MustCompile("/{2,}")
)
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
@ -776,8 +771,8 @@ func redirectTrailingSlash(c *Context) {
req := c.Request
p := req.URL.Path
if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." {
prefix = regSafePrefix.ReplaceAllString(prefix, "")
prefix = regRemoveRepeatedChar.ReplaceAllString(prefix, "/")
prefix = sanitizePathChars(prefix)
prefix = removeRepeatedSlash(prefix)
p = prefix + "/" + req.URL.Path
}
@ -788,6 +783,39 @@ func redirectTrailingSlash(c *Context) {
redirectRequest(c)
}
// sanitizePathChars removes unsafe characters from path strings,
// keeping only letters, numbers, forward slashes, and hyphens.
func sanitizePathChars(s string) string {
return strings.Map(func(r rune) rune {
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '/' || r == '-' {
return r
}
return -1
}, s)
}
// removeRepeatedSlash removes consecutive forward slashes from a string,
// replacing sequences of multiple slashes with a single slash.
func removeRepeatedSlash(s string) string {
if !strings.Contains(s, "//") {
return s
}
var sb strings.Builder
sb.Grow(len(s) - 1)
prevChar := rune(0)
for _, r := range s {
if r == '/' && prevChar == '/' {
continue
}
sb.WriteRune(r)
prevChar = r
}
return sb.String()
}
func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
req := c.Request
rPath := req.URL.Path

View File

@ -1012,3 +1012,34 @@ func TestUpdateRouteTreesCalledOnce(t *testing.T) {
assert.Equal(t, "ok", w.Body.String())
}
}
func TestRemoveRepeatedSlash(t *testing.T) {
testCases := []struct {
name string
str string
want string
}{
{
name: "noSlash",
str: "abc",
want: "abc",
},
{
name: "withSlash",
str: "/a/b/c/",
want: "/a/b/c/",
},
{
name: "withRepeatedSlash",
str: "/a//b///c////",
want: "/a/b/c/",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
res := removeRepeatedSlash(tc.str)
assert.Equal(t, tc.want, res)
})
}
}