Prechádzať zdrojové kódy

The url.RawPath used when engine.UseRawPath is set to true. (#810)

Sergey Egorov 8 rokov pred
rodič
commit
b1872ec369
4 zmenil súbory, kde vykonal 130 pridanie a 12 odobranie
  1. 22 3
      gin.go
  2. 39 0
      routes_test.go
  3. 19 3
      tree.go
  4. 50 6
      tree_test.go

+ 22 - 3
gin.go

@@ -83,6 +83,13 @@ type (
 		// #726 #755 If enabled, it will thrust some headers starting with
 		// #726 #755 If enabled, it will thrust some headers starting with
 		// 'X-AppEngine...' for better integration with that PaaS.
 		// 'X-AppEngine...' for better integration with that PaaS.
 		AppEngine bool
 		AppEngine bool
+
+		// If enabled, the url.RawPath will be used to find parameters.
+		UseRawPath bool
+		// If true, the path value will be unescaped.
+		// If UseRawPath is false (by default), the UnescapePathValues effectively is true,
+		// as url.Path gonna be used, which is already unescaped.
+		UnescapePathValues bool
 	}
 	}
 )
 )
 
 
@@ -94,6 +101,8 @@ var _ IRouter = &Engine{}
 // - RedirectFixedPath:      false
 // - RedirectFixedPath:      false
 // - HandleMethodNotAllowed: false
 // - HandleMethodNotAllowed: false
 // - ForwardedByClientIP:    true
 // - ForwardedByClientIP:    true
+// - UseRawPath:             false
+// - UnescapePathValues:     true
 func New() *Engine {
 func New() *Engine {
 	debugPrintWARNINGNew()
 	debugPrintWARNINGNew()
 	engine := &Engine{
 	engine := &Engine{
@@ -107,6 +116,8 @@ func New() *Engine {
 		HandleMethodNotAllowed: false,
 		HandleMethodNotAllowed: false,
 		ForwardedByClientIP:    true,
 		ForwardedByClientIP:    true,
 		AppEngine:              defaultAppEngine,
 		AppEngine:              defaultAppEngine,
+		UseRawPath:             false,
+		UnescapePathValues:     true,
 		trees:                  make(methodTrees, 0, 9),
 		trees:                  make(methodTrees, 0, 9),
 	}
 	}
 	engine.RouterGroup.engine = engine
 	engine.RouterGroup.engine = engine
@@ -284,7 +295,15 @@ func (engine *Engine) HandleContext(c *Context) {
 
 
 func (engine *Engine) handleHTTPRequest(context *Context) {
 func (engine *Engine) handleHTTPRequest(context *Context) {
 	httpMethod := context.Request.Method
 	httpMethod := context.Request.Method
-	path := context.Request.URL.Path
+	var path string
+	var unescape bool
+	if engine.UseRawPath && len(context.Request.URL.RawPath) > 0 {
+		path = context.Request.URL.RawPath
+		unescape = engine.UnescapePathValues
+	} else {
+		path = context.Request.URL.Path
+		unescape = false
+	}
 
 
 	// Find root of the tree for the given HTTP method
 	// Find root of the tree for the given HTTP method
 	t := engine.trees
 	t := engine.trees
@@ -292,7 +311,7 @@ func (engine *Engine) handleHTTPRequest(context *Context) {
 		if t[i].method == httpMethod {
 		if t[i].method == httpMethod {
 			root := t[i].root
 			root := t[i].root
 			// Find route in tree
 			// Find route in tree
-			handlers, params, tsr := root.getValue(path, context.Params)
+			handlers, params, tsr := root.getValue(path, context.Params, unescape)
 			if handlers != nil {
 			if handlers != nil {
 				context.handlers = handlers
 				context.handlers = handlers
 				context.Params = params
 				context.Params = params
@@ -317,7 +336,7 @@ func (engine *Engine) handleHTTPRequest(context *Context) {
 	if engine.HandleMethodNotAllowed {
 	if engine.HandleMethodNotAllowed {
 		for _, tree := range engine.trees {
 		for _, tree := range engine.trees {
 			if tree.method != httpMethod {
 			if tree.method != httpMethod {
-				if handlers, _, _ := tree.root.getValue(path, nil); handlers != nil {
+				if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
 					context.handlers = engine.allNoMethod
 					context.handlers = engine.allNoMethod
 					serveError(context, 405, default405Body)
 					serveError(context, 405, default405Body)
 					return
 					return

+ 39 - 0
routes_test.go

@@ -400,3 +400,42 @@ func TestRouterNotFound(t *testing.T) {
 	w = performRequest(router, "GET", "/")
 	w = performRequest(router, "GET", "/")
 	assert.Equal(t, w.Code, 404)
 	assert.Equal(t, w.Code, 404)
 }
 }
+
+func TestRouteRawPath(t *testing.T) {
+	route := New()
+	route.UseRawPath = true
+
+	route.POST("/project/:name/build/:num", func(c *Context) {
+		name := c.Params.ByName("name")
+		num := c.Params.ByName("num")
+
+		assert.Equal(t, c.Param("name"), name)
+		assert.Equal(t, c.Param("num"), num)
+
+		assert.Equal(t, "Some/Other/Project", name)
+		assert.Equal(t, "222", num)
+	})
+
+	w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222")
+	assert.Equal(t, w.Code, 200)
+}
+
+func TestRouteRawPathNoUnescape(t *testing.T) {
+	route := New()
+	route.UseRawPath = true
+	route.UnescapePathValues = false
+
+	route.POST("/project/:name/build/:num", func(c *Context) {
+		name := c.Params.ByName("name")
+		num := c.Params.ByName("num")
+
+		assert.Equal(t, c.Param("name"), name)
+		assert.Equal(t, c.Param("num"), num)
+
+		assert.Equal(t, "Some%2FOther%2FProject", name)
+		assert.Equal(t, "333", num)
+	})
+
+	w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333")
+	assert.Equal(t, w.Code, 200)
+}

+ 19 - 3
tree.go

@@ -5,6 +5,7 @@
 package gin
 package gin
 
 
 import (
 import (
+	"net/url"
 	"strings"
 	"strings"
 	"unicode"
 	"unicode"
 )
 )
@@ -363,7 +364,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle
 // If no handle can be found, a TSR (trailing slash redirect) recommendation is
 // If no handle can be found, a TSR (trailing slash redirect) recommendation is
 // made if a handle exists with an extra (without the) trailing slash for the
 // made if a handle exists with an extra (without the) trailing slash for the
 // given path.
 // given path.
-func (n *node) getValue(path string, po Params) (handlers HandlersChain, p Params, tsr bool) {
+func (n *node) getValue(path string, po Params, unescape bool) (handlers HandlersChain, p Params, tsr bool) {
 	p = po
 	p = po
 walk: // Outer loop for walking the tree
 walk: // Outer loop for walking the tree
 	for {
 	for {
@@ -406,7 +407,15 @@ walk: // Outer loop for walking the tree
 					i := len(p)
 					i := len(p)
 					p = p[:i+1] // expand slice within preallocated capacity
 					p = p[:i+1] // expand slice within preallocated capacity
 					p[i].Key = n.path[1:]
 					p[i].Key = n.path[1:]
-					p[i].Value = path[:end]
+					val := path[:end]
+					if unescape {
+						var err error
+						if p[i].Value, err = url.QueryUnescape(val); err != nil {
+							p[i].Value = val // fallback, in case of error
+						}
+					} else {
+						p[i].Value = val
+					}
 
 
 					// we need to go deeper!
 					// we need to go deeper!
 					if end < len(path) {
 					if end < len(path) {
@@ -440,7 +449,14 @@ walk: // Outer loop for walking the tree
 					i := len(p)
 					i := len(p)
 					p = p[:i+1] // expand slice within preallocated capacity
 					p = p[:i+1] // expand slice within preallocated capacity
 					p[i].Key = n.path[2:]
 					p[i].Key = n.path[2:]
-					p[i].Value = path
+					if unescape {
+						var err error
+						if p[i].Value, err = url.QueryUnescape(path); err != nil {
+							p[i].Value = path // fallback, in case of error
+						}
+					} else {
+						p[i].Value = path
+					}
 
 
 					handlers = n.handlers
 					handlers = n.handlers
 					return
 					return

+ 50 - 6
tree_test.go

@@ -37,9 +37,14 @@ type testRequests []struct {
 	ps         Params
 	ps         Params
 }
 }
 
 
-func checkRequests(t *testing.T, tree *node, requests testRequests) {
+func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ...bool) {
+	unescape := false
+	if len(unescapes) >= 1 {
+		unescape = unescapes[0]
+	}
+
 	for _, request := range requests {
 	for _, request := range requests {
-		handler, ps, _ := tree.getValue(request.path, nil)
+		handler, ps, _ := tree.getValue(request.path, nil, unescape)
 
 
 		if handler == nil {
 		if handler == nil {
 			if !request.nilHandler {
 			if !request.nilHandler {
@@ -197,6 +202,45 @@ func TestTreeWildcard(t *testing.T) {
 	checkMaxParams(t, tree)
 	checkMaxParams(t, tree)
 }
 }
 
 
+func TestUnescapeParameters(t *testing.T) {
+	tree := &node{}
+
+	routes := [...]string{
+		"/",
+		"/cmd/:tool/:sub",
+		"/cmd/:tool/",
+		"/src/*filepath",
+		"/search/:query",
+		"/files/:dir/*filepath",
+		"/info/:user/project/:project",
+		"/info/:user",
+	}
+	for _, route := range routes {
+		tree.addRoute(route, fakeHandler(route))
+	}
+
+	//printChildren(tree, "")
+	unescape := true
+	checkRequests(t, tree, testRequests{
+		{"/", false, "/", nil},
+		{"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}},
+		{"/cmd/test", true, "", Params{Param{"tool", "test"}}},
+		{"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}},
+		{"/src/some/file+test.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file test.png"}}},
+		{"/src/some/file++++%%%%test.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file++++%%%%test.png"}}},
+		{"/src/some/file%2Ftest.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file/test.png"}}},
+		{"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng in ünìcodé"}}},
+		{"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{"user", "gordon"}, Param{"project", "go"}}},
+		{"/info/slash%2Fgordon", false, "/info/:user", Params{Param{"user", "slash/gordon"}}},
+		{"/info/slash%2Fgordon/project/Project%20%231", false, "/info/:user/project/:project", Params{Param{"user", "slash/gordon"}, Param{"project", "Project #1"}}},
+		{"/info/slash%%%%", false, "/info/:user", Params{Param{"user", "slash%%%%"}}},
+		{"/info/slash%%%%2Fgordon/project/Project%%%%20%231", false, "/info/:user/project/:project", Params{Param{"user", "slash%%%%2Fgordon"}, Param{"project", "Project%%%%20%231"}}},
+	}, unescape)
+
+	checkPriorities(t, tree)
+	checkMaxParams(t, tree)
+}
+
 func catchPanic(testFunc func()) (recv interface{}) {
 func catchPanic(testFunc func()) (recv interface{}) {
 	defer func() {
 	defer func() {
 		recv = recover()
 		recv = recover()
@@ -430,7 +474,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
 		"/doc/",
 		"/doc/",
 	}
 	}
 	for _, route := range tsrRoutes {
 	for _, route := range tsrRoutes {
-		handler, _, tsr := tree.getValue(route, nil)
+		handler, _, tsr := tree.getValue(route, nil, false)
 		if handler != nil {
 		if handler != nil {
 			t.Fatalf("non-nil handler for TSR route '%s", route)
 			t.Fatalf("non-nil handler for TSR route '%s", route)
 		} else if !tsr {
 		} else if !tsr {
@@ -447,7 +491,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
 		"/api/world/abc",
 		"/api/world/abc",
 	}
 	}
 	for _, route := range noTsrRoutes {
 	for _, route := range noTsrRoutes {
-		handler, _, tsr := tree.getValue(route, nil)
+		handler, _, tsr := tree.getValue(route, nil, false)
 		if handler != nil {
 		if handler != nil {
 			t.Fatalf("non-nil handler for No-TSR route '%s", route)
 			t.Fatalf("non-nil handler for No-TSR route '%s", route)
 		} else if tsr {
 		} else if tsr {
@@ -466,7 +510,7 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) {
 		t.Fatalf("panic inserting test route: %v", recv)
 		t.Fatalf("panic inserting test route: %v", recv)
 	}
 	}
 
 
-	handler, _, tsr := tree.getValue("/", nil)
+	handler, _, tsr := tree.getValue("/", nil, false)
 	if handler != nil {
 	if handler != nil {
 		t.Fatalf("non-nil handler")
 		t.Fatalf("non-nil handler")
 	} else if tsr {
 	} else if tsr {
@@ -617,7 +661,7 @@ func TestTreeInvalidNodeType(t *testing.T) {
 
 
 	// normal lookup
 	// normal lookup
 	recv := catchPanic(func() {
 	recv := catchPanic(func() {
-		tree.getValue("/test", nil)
+		tree.getValue("/test", nil, false)
 	})
 	})
 	if rs, ok := recv.(string); !ok || rs != panicMsg {
 	if rs, ok := recv.(string); !ok || rs != panicMsg {
 		t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
 		t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)