fix: case-insensitive RawPath comparison to prevent infinite redirect

This commit is contained in:
KIbeom Lee 2024-11-21 00:42:51 +09:00
parent c8a3adc657
commit 01c7c95fc1
2 changed files with 64 additions and 1 deletions

49
gin.go
View File

@ -9,6 +9,7 @@ import (
"html/template" "html/template"
"net" "net"
"net/http" "net/http"
"net/url"
"os" "os"
"path" "path"
"regexp" "regexp"
@ -643,11 +644,57 @@ func (engine *Engine) HandleContext(c *Context) {
c.index = oldIndexValue c.index = oldIndexValue
} }
// compareRawWithEscaped compares the RawPath with the EscapedPath of a URL.
// It returns true if they are equal, false otherwise.
func compareRawWithEscaped(url *url.URL) bool {
rawPath := url.RawPath
escapedPath := url.EscapedPath()
if len(rawPath) != len(escapedPath) {
return false
}
for i := 0; i < len(rawPath); i++ {
if rawPath[i] != escapedPath[i] {
return false
}
if rawPath[i] == '%' && i+2 < len(rawPath) {
if rawPath[i+1] == '2' && (rawPath[i+2] == 'F' || rawPath[i+2] == 'f') {
return false
}
var diff byte
if rawPath[i+1] > escapedPath[i+1] {
diff = rawPath[i+1] - escapedPath[i+1]
} else {
diff = escapedPath[i+1] - rawPath[i+1]
}
if diff != 0 && diff != 32 {
return false
}
if rawPath[i+2] > escapedPath[i+2] {
diff = rawPath[i+2] - escapedPath[i+2]
} else {
diff = escapedPath[i+2] - rawPath[i+2]
}
if diff != 0 && diff != 32 {
return false
}
i += 2
}
}
return true
}
func (engine *Engine) handleHTTPRequest(c *Context) { func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method httpMethod := c.Request.Method
rPath := c.Request.URL.Path rPath := c.Request.URL.Path
unescape := false unescape := false
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 && !compareRawWithEscaped(c.Request.URL) {
rPath = c.Request.URL.RawPath rPath = c.Request.URL.RawPath
unescape = engine.UnescapePathValues unescape = engine.UnescapePathValues
} }

View File

@ -789,3 +789,19 @@ func TestEngineHandleMethodNotAllowedCornerCase(t *testing.T) {
w := PerformRequest(r, "GET", "/base/v1/user/groups") w := PerformRequest(r, "GET", "/base/v1/user/groups")
assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
} }
// Test the fix for https://github.com/gin-gonic/gin/issues/4034
func TestLowercasePercentEncodePath(t *testing.T) {
route := Default()
route.UnescapePathValues = false
route.UseRawPath = true
route.RedirectFixedPath = true
route.GET("/핫", func(ctx *Context) {
ctx.JSON(200, H{})
})
req := httptest.NewRequest("GET", "/%ed%95%ab", nil)
w := httptest.NewRecorder()
route.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
assert.Equal(t, "{}", w.Body.String())
}