From b51def0bb82322f8993582b2edb1a94b6429c4bb Mon Sep 17 00:00:00 2001
From: ReneWerner87 <rene@gofiber.io>
Date: Wed, 6 Jan 2021 13:21:54 +0100
Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20UnescapePath=20not=20working=20#?=
 =?UTF-8?q?1102?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Fenny <fenny@gofiber.io>
---
 app_test.go | 22 ++++++++++++++++
 ctx.go      | 73 +++++++++++++++++++++++++++++------------------------
 helpers.go  |  2 +-
 path.go     | 16 ++++++------
 router.go   | 26 +++++++++----------
 5 files changed, 84 insertions(+), 55 deletions(-)

diff --git a/app_test.go b/app_test.go
index bf1c1999..5fb3ca4a 100644
--- a/app_test.go
+++ b/app_test.go
@@ -314,6 +314,28 @@ func Test_App_Use_Params(t *testing.T) {
 	})
 }
 
+func Test_App_Use_UnescapedPath(t *testing.T) {
+	app := New(Config{UnescapePath: true, CaseSensitive: true})
+
+	app.Use("/cRéeR/:param", func(c *Ctx) error {
+		return c.SendString(c.Params("param"))
+	})
+
+	resp, err := app.Test(httptest.NewRequest(MethodGet, "/cR%C3%A9eR/%D9%85%D8%AD%D9%85%D8%AF", nil))
+	utils.AssertEqual(t, nil, err, "app.Test(req)")
+	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
+
+	body, err := ioutil.ReadAll(resp.Body)
+	utils.AssertEqual(t, nil, err, "app.Test(req)")
+	// check the param result
+	utils.AssertEqual(t, "محمد", getString(body))
+
+	// with lowercase letters
+	resp, err = app.Test(httptest.NewRequest(MethodGet, "/cr%C3%A9er/%D9%85%D8%AD%D9%85%D8%AF", nil))
+	utils.AssertEqual(t, nil, err, "app.Test(req)")
+	utils.AssertEqual(t, StatusNotFound, resp.StatusCode, "Status code")
+}
+
 func Test_App_Add_Method_Test(t *testing.T) {
 	app := New()
 	defer func() {
diff --git a/ctx.go b/ctx.go
index 961e0b7e..649e1b80 100644
--- a/ctx.go
+++ b/ctx.go
@@ -33,20 +33,22 @@ const maxParams = 30
 // Ctx represents the Context which hold the HTTP request and response.
 // It has methods for the request query string, parameters, body, HTTP headers and so on.
 type Ctx struct {
-	app          *App                 // Reference to *App
-	route        *Route               // Reference to *Route
-	indexRoute   int                  // Index of the current route
-	indexHandler int                  // Index of the current handler
-	method       string               // HTTP method
-	methodINT    int                  // HTTP method INT equivalent
-	baseURI      string               // HTTP base uri
-	path         string               // Prettified HTTP path -> string copy from pathBuffer
-	pathBuffer   []byte               // Prettified HTTP path buffer
-	treePath     string               // Path for the search in the tree
-	pathOriginal string               // Original HTTP path
-	values       [maxParams]string    // Route parameter values
-	fasthttp     *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
-	matched      bool                 // Non use route matched
+	app                 *App                 // Reference to *App
+	route               *Route               // Reference to *Route
+	indexRoute          int                  // Index of the current route
+	indexHandler        int                  // Index of the current handler
+	method              string               // HTTP method
+	methodINT           int                  // HTTP method INT equivalent
+	baseURI             string               // HTTP base uri
+	path                string               // Prettified HTTP path for the user -> string copy from pathBuffer
+	pathBuffer          []byte               // HTTP path buffer
+	detectionPath       string               // Route detection path              -> string copy from pathBuffer
+	detectionPathBuffer []byte               // HTTP path detectionPath
+	treePath            string               // Path for the search in the tree
+	pathOriginal        string               // Original HTTP path
+	values              [maxParams]string    // Route parameter values
+	fasthttp            *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
+	matched             bool                 // Non use route matched
 }
 
 // Range data for c.Range
@@ -88,7 +90,6 @@ func (app *App) AcquireCtx(fctx *fasthttp.RequestCtx) *Ctx {
 	// Reset matched flag
 	c.matched = false
 	// Set paths
-	c.pathBuffer = append(c.pathBuffer[0:0], fctx.URI().PathOriginal()...)
 	c.pathOriginal = getString(fctx.URI().PathOriginal())
 	// Set method
 	c.method = getString(fctx.Request.Header.Method())
@@ -98,7 +99,7 @@ func (app *App) AcquireCtx(fctx *fasthttp.RequestCtx) *Ctx {
 	// reset base uri
 	c.baseURI = ""
 	// Prettify path
-	c.prettifyPath()
+	c.configDependentPaths()
 	return c
 }
 
@@ -674,15 +675,12 @@ func (c *Ctx) Params(key string, defaultValue ...string) string {
 func (c *Ctx) Path(override ...string) string {
 	if len(override) != 0 && c.path != override[0] {
 		// Set new path to context
-		c.pathBuffer = append(c.pathBuffer[0:0], override[0]...)
 		c.pathOriginal = override[0]
-		// c.path = override[0]
-		// c.pathOriginal = c.path
 
 		// Set new path to request context
 		c.fasthttp.Request.URI().SetPath(c.pathOriginal)
 		// Prettify path
-		c.prettifyPath()
+		c.configDependentPaths()
 	}
 	return c.pathOriginal
 }
@@ -1105,24 +1103,33 @@ func (c *Ctx) XHR() bool {
 	return utils.EqualFoldBytes(utils.UnsafeBytes(c.Get(HeaderXRequestedWith)), []byte("xmlhttprequest"))
 }
 
-// prettifyPath ...
-func (c *Ctx) prettifyPath() {
-	// If UnescapePath enabled, we decode the path
+// configDependentPaths set paths for route recognition and prepared paths for the user,
+// here the features for caseSensitive, decoded paths, strict paths are evaluated
+func (c *Ctx) configDependentPaths() {
+	c.pathBuffer = append(c.pathBuffer[0:0], c.pathOriginal...)
+	// If UnescapePath enabled, we decode the path and save it for the framework user
 	if c.app.config.UnescapePath {
 		c.pathBuffer = fasthttp.AppendUnquotedArg(c.pathBuffer[:0], c.pathBuffer)
 	}
-	// If CaseSensitive is disabled, we lowercase the original path
-	if !c.app.config.CaseSensitive {
-		c.pathBuffer = utils.ToLowerBytes(c.pathBuffer)
-	}
-	// If StrictRouting is disabled, we strip all trailing slashes
-	if !c.app.config.StrictRouting && len(c.pathBuffer) > 1 && c.pathBuffer[len(c.pathBuffer)-1] == '/' {
-		c.pathBuffer = utils.TrimRightBytes(c.pathBuffer, '/')
-	}
 	c.path = getString(c.pathBuffer)
 
+	// another path is specified which is for routing recognition only
+	// use the path that was changed by the previous configuration flags
+	c.detectionPathBuffer = append(c.detectionPathBuffer[0:0], c.pathBuffer...)
+	// If CaseSensitive is disabled, we lowercase the original path
+	if !c.app.config.CaseSensitive {
+		c.detectionPathBuffer = utils.ToLowerBytes(c.detectionPathBuffer)
+	}
+	// If StrictRouting is disabled, we strip all trailing slashes
+	if !c.app.config.StrictRouting && len(c.detectionPathBuffer) > 1 && c.detectionPathBuffer[len(c.detectionPathBuffer)-1] == '/' {
+		c.detectionPathBuffer = utils.TrimRightBytes(c.detectionPathBuffer, '/')
+	}
+	c.detectionPath = getString(c.detectionPathBuffer)
+
+	// Define the path for dividing routes into areas for fast tree detection, so that fewer routes need to be traversed,
+	// since the first three characters area select a list of routes
 	c.treePath = c.treePath[0:0]
-	if len(c.path) >= 3 {
-		c.treePath = c.path[:3]
+	if len(c.detectionPath) >= 3 {
+		c.treePath = c.detectionPath[:3]
 	}
 }
diff --git a/helpers.go b/helpers.go
index ee973f36..392f9e6b 100644
--- a/helpers.go
+++ b/helpers.go
@@ -120,7 +120,7 @@ func methodExist(ctx *Ctx) (exist bool) {
 				continue
 			}
 			// Check if it matches the request path
-			match := route.match(ctx.path, ctx.pathOriginal, &ctx.values)
+			match := route.match(ctx.detectionPath, ctx.path, &ctx.values)
 			// No match, next route
 			if match {
 				// We matched
diff --git a/path.go b/path.go
index c44f058a..3a5fdd46 100644
--- a/path.go
+++ b/path.go
@@ -227,37 +227,37 @@ func findNextCharsetPosition(search string, charset []byte) int {
 }
 
 // getMatch parses the passed url and tries to match it against the route segments and determine the parameter positions
-func (routeParser *routeParser) getMatch(s, original string, params *[maxParams]string, partialCheck bool) bool {
+func (routeParser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool) bool {
 	var i, paramsIterator, partLen int
 	for _, segment := range routeParser.segs {
-		partLen = len(s)
+		partLen = len(detectionPath)
 		// check const segment
 		if !segment.IsParam {
 			i = segment.Length
 			// is optional part or the const part must match with the given string
 			// check if the end of the segment is a optional slash
-			if segment.HasOptionalSlash && partLen == i-1 && s == segment.Const[:i-1] {
+			if segment.HasOptionalSlash && partLen == i-1 && detectionPath == segment.Const[:i-1] {
 				i--
-			} else if !(i <= partLen && s[:i] == segment.Const) {
+			} else if !(i <= partLen && detectionPath[:i] == segment.Const) {
 				return false
 			}
 		} else {
 			// determine parameter length
-			i = findParamLen(s, segment)
+			i = findParamLen(detectionPath, segment)
 			if !segment.IsOptional && i == 0 {
 				return false
 			}
 			// take over the params positions
-			params[paramsIterator] = original[:i]
+			params[paramsIterator] = path[:i]
 			paramsIterator++
 		}
 
 		// reduce founded part from the string
 		if partLen > 0 {
-			s, original = s[i:], original[i:]
+			detectionPath, path = detectionPath[i:], path[i:]
 		}
 	}
-	if len(s) != 0 && !partialCheck {
+	if len(detectionPath) != 0 && !partialCheck {
 		return false
 	}
 
diff --git a/router.go b/router.go
index 5b7614f2..a1f2ad1b 100644
--- a/router.go
+++ b/router.go
@@ -55,14 +55,14 @@ type Route struct {
 	Handlers []Handler `json:"-"`      // Ctx handlers
 }
 
-func (r *Route) match(path, original string, params *[maxParams]string) (match bool) {
-	// root path check
-	if r.root && path == "/" {
+func (r *Route) match(detectionPath, path string, params *[maxParams]string) (match bool) {
+	// root detectionPath check
+	if r.root && detectionPath == "/" {
 		return true
-		// '*' wildcard matches any path
+		// '*' wildcard matches any detectionPath
 	} else if r.star {
-		if len(original) > 1 {
-			params[0] = original[1:]
+		if len(path) > 1 {
+			params[0] = path[1:]
 		} else {
 			params[0] = ""
 		}
@@ -71,19 +71,19 @@ func (r *Route) match(path, original string, params *[maxParams]string) (match b
 	// Does this route have parameters
 	if len(r.Params) > 0 {
 		// Match params
-		if match := r.routeParser.getMatch(path, original, params, r.use); match {
-			// Get params from the original path
+		if match := r.routeParser.getMatch(detectionPath, path, params, r.use); match {
+			// Get params from the path detectionPath
 			return match
 		}
 	}
 	// Is this route a Middleware?
 	if r.use {
-		// Single slash will match or path prefix
-		if r.root || strings.HasPrefix(path, r.path) {
+		// Single slash will match or detectionPath prefix
+		if r.root || strings.HasPrefix(detectionPath, r.path) {
 			return true
 		}
-		// Check for a simple path match
-	} else if len(r.path) == len(path) && r.path == path {
+		// Check for a simple detectionPath match
+	} else if len(r.path) == len(detectionPath) && r.path == detectionPath {
 		return true
 	}
 	// No match
@@ -107,7 +107,7 @@ func (app *App) next(c *Ctx) (match bool, err error) {
 		route := tree[c.indexRoute]
 
 		// Check if it matches the request path
-		match = route.match(c.path, c.pathOriginal, &c.values)
+		match = route.match(c.detectionPath, c.path, &c.values)
 
 		// No match, next route
 		if !match {