From 01c7c95fc12197b77fb9e718fec301a2386eeb71 Mon Sep 17 00:00:00 2001 From: KIbeom Lee Date: Thu, 21 Nov 2024 00:42:51 +0900 Subject: [PATCH] fix: case-insensitive RawPath comparison to prevent infinite redirect --- gin.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- routes_test.go | 16 ++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/gin.go b/gin.go index e17596aa..d7110908 100644 --- a/gin.go +++ b/gin.go @@ -9,6 +9,7 @@ import ( "html/template" "net" "net/http" + "net/url" "os" "path" "regexp" @@ -643,11 +644,57 @@ func (engine *Engine) HandleContext(c *Context) { 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) { httpMethod := c.Request.Method rPath := c.Request.URL.Path 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 unescape = engine.UnescapePathValues } diff --git a/routes_test.go b/routes_test.go index d6233b09..d631da84 100644 --- a/routes_test.go +++ b/routes_test.go @@ -789,3 +789,19 @@ func TestEngineHandleMethodNotAllowedCornerCase(t *testing.T) { w := PerformRequest(r, "GET", "/base/v1/user/groups") 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()) +}