From 0469372acdb71bc96fb2d2de7b7a0bd18efe9430 Mon Sep 17 00:00:00 2001 From: Nathan Castelein Date: Thu, 29 Dec 2016 15:45:12 +0100 Subject: [PATCH] Allow to use RawPath instead of Path, and give the possibility to unescape path parameters --- gin.go | 9 ++++- tree.go | 9 ++++- tree_test.go | 106 ++++++++++++++++++++++++++++++++++----------------- 3 files changed, 86 insertions(+), 38 deletions(-) diff --git a/gin.go b/gin.go index 60f4ab29..ac6b1431 100644 --- a/gin.go +++ b/gin.go @@ -78,6 +78,8 @@ type ( // handler. HandleMethodNotAllowed bool ForwardedByClientIP bool + UseRawPath bool + UnescapePathValues bool } ) @@ -270,6 +272,9 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (engine *Engine) handleHTTPRequest(context *Context) { httpMethod := context.Request.Method path := context.Request.URL.Path + if engine.UseRawPath && len(context.Request.URL.RawPath) > 0 { + path = context.Request.URL.RawPath + } // Find root of the tree for the given HTTP method t := engine.trees @@ -277,7 +282,7 @@ func (engine *Engine) handleHTTPRequest(context *Context) { if t[i].method == httpMethod { root := t[i].root // Find route in tree - handlers, params, tsr := root.getValue(path, context.Params) + handlers, params, tsr := root.getValue(path, context.Params, engine.UnescapePathValues) if handlers != nil { context.handlers = handlers context.Params = params @@ -302,7 +307,7 @@ func (engine *Engine) handleHTTPRequest(context *Context) { if engine.HandleMethodNotAllowed { for _, tree := range engine.trees { if tree.method != httpMethod { - if handlers, _, _ := tree.root.getValue(path, nil); handlers != nil { + if handlers, _, _ := tree.root.getValue(path, nil, engine.UnescapePathValues); handlers != nil { context.handlers = engine.allNoMethod serveError(context, 405, default405Body) return diff --git a/tree.go b/tree.go index 4f1da271..0054b9b6 100644 --- a/tree.go +++ b/tree.go @@ -5,6 +5,7 @@ package gin import ( + "net/url" "strings" "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 // made if a handle exists with an extra (without the) trailing slash for the // given path. -func (n *node) getValue(path string, po Params) (handlers HandlersChain, p Params, tsr bool) { +func (n *node) getValue(path string, po Params, unescapeValues bool) (handlers HandlersChain, p Params, tsr bool) { p = po walk: // Outer loop for walking the tree for { @@ -407,6 +408,12 @@ walk: // Outer loop for walking the tree p = p[:i+1] // expand slice within preallocated capacity p[i].Key = n.path[1:] p[i].Value = path[:end] + // as it is a path parameter, unescape value + if unescapeValues { + if val, err := url.QueryUnescape(path[:end]); err == nil { + p[i].Value = val + } + } // we need to go deeper! if end < len(path) { diff --git a/tree_test.go b/tree_test.go index ed21783c..8b75355b 100644 --- a/tree_test.go +++ b/tree_test.go @@ -35,11 +35,12 @@ type testRequests []struct { nilHandler bool route string ps Params + unescape bool } func checkRequests(t *testing.T, tree *node, requests testRequests) { for _, request := range requests { - handler, ps, _ := tree.getValue(request.path, nil) + handler, ps, _ := tree.getValue(request.path, nil, request.unescape) if handler == nil { if !request.nilHandler { @@ -134,17 +135,17 @@ func TestTreeAddAndGet(t *testing.T) { //printChildren(tree, "") checkRequests(t, tree, testRequests{ - {"/a", false, "/a", nil}, - {"/", true, "", nil}, - {"/hi", false, "/hi", nil}, - {"/contact", false, "/contact", nil}, - {"/co", false, "/co", nil}, - {"/con", true, "", nil}, // key mismatch - {"/cona", true, "", nil}, // key mismatch - {"/no", true, "", nil}, // no matching child - {"/ab", false, "/ab", nil}, - {"/α", false, "/α", nil}, - {"/β", false, "/β", nil}, + {"/a", false, "/a", nil, false}, + {"/", true, "", nil, false}, + {"/hi", false, "/hi", nil, false}, + {"/contact", false, "/contact", nil, false}, + {"/co", false, "/co", nil, false}, + {"/con", true, "", nil, false}, // key mismatch + {"/cona", true, "", nil, false}, // key mismatch + {"/no", true, "", nil, false}, // no matching child + {"/ab", false, "/ab", nil, false}, + {"/α", false, "/α", nil, false}, + {"/β", false, "/β", nil, false}, }) checkPriorities(t, tree) @@ -177,20 +178,55 @@ func TestTreeWildcard(t *testing.T) { //printChildren(tree, "") checkRequests(t, tree, testRequests{ - {"/", false, "/", nil}, - {"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}}, - {"/cmd/test", true, "", Params{Param{"tool", "test"}}}, - {"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{"tool", "test"}, Param{"sub", "3"}}}, - {"/src/", false, "/src/*filepath", Params{Param{"filepath", "/"}}}, - {"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}}, - {"/search/", false, "/search/", nil}, - {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}}, - {"/search/someth!ng+in+ünìcodé/", true, "", Params{Param{"query", "someth!ng+in+ünìcodé"}}}, - {"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}}, - {"/user_gopher/about", false, "/user_:name/about", Params{Param{"name", "gopher"}}}, - {"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{"dir", "js"}, Param{"filepath", "/inc/framework.js"}}}, - {"/info/gordon/public", false, "/info/:user/public", Params{Param{"user", "gordon"}}}, - {"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{"user", "gordon"}, Param{"project", "go"}}}, + {"/", false, "/", nil, false}, + {"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}, false}, + {"/cmd/test", true, "", Params{Param{"tool", "test"}}, false}, + {"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{"tool", "test"}, Param{"sub", "3"}}, false}, + {"/src/", false, "/src/*filepath", Params{Param{"filepath", "/"}}, false}, + {"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}, false}, + {"/search/", false, "/search/", nil, false}, + {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}, false}, + {"/search/someth!ng+in+ünìcodé/", true, "", Params{Param{"query", "someth!ng+in+ünìcodé"}}, false}, + {"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}, false}, + {"/user_gopher/about", false, "/user_:name/about", Params{Param{"name", "gopher"}}, false}, + {"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{"dir", "js"}, Param{"filepath", "/inc/framework.js"}}, false}, + {"/info/gordon/public", false, "/info/:user/public", Params{Param{"user", "gordon"}}, false}, + {"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{"user", "gordon"}, Param{"project", "go"}}, false}, + }) + + checkPriorities(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, "") + + checkRequests(t, tree, testRequests{ + {"/", false, "/", nil, true}, + {"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}, true}, + {"/cmd/test", true, "", Params{Param{"tool", "test"}}, true}, + {"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}, true}, + {"/src/some/file+test.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file+test.png"}}, true}, + {"/src/some/file%2Ftest.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file%2Ftest.png"}}, true}, + {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng in ünìcodé"}}, true}, + {"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{"user", "gordon"}, Param{"project", "go"}}, true}, + {"/info/slash%2Fgordon", false, "/info/:user", Params{Param{"user", "slash/gordon"}}, true}, }) checkPriorities(t, tree) @@ -298,11 +334,11 @@ func TestTreeDupliatePath(t *testing.T) { //printChildren(tree, "") checkRequests(t, tree, testRequests{ - {"/", false, "/", nil}, - {"/doc/", false, "/doc/", nil}, - {"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}}, - {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}}, - {"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}}, + {"/", false, "/", nil, false}, + {"/doc/", false, "/doc/", nil, false}, + {"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}, false}, + {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}, false}, + {"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}, false}, }) } @@ -430,7 +466,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { "/doc/", } for _, route := range tsrRoutes { - handler, _, tsr := tree.getValue(route, nil) + handler, _, tsr := tree.getValue(route, nil, false) if handler != nil { t.Fatalf("non-nil handler for TSR route '%s", route) } else if !tsr { @@ -447,7 +483,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { "/api/world/abc", } for _, route := range noTsrRoutes { - handler, _, tsr := tree.getValue(route, nil) + handler, _, tsr := tree.getValue(route, nil, false) if handler != nil { t.Fatalf("non-nil handler for No-TSR route '%s", route) } else if tsr { @@ -466,7 +502,7 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) { t.Fatalf("panic inserting test route: %v", recv) } - handler, _, tsr := tree.getValue("/", nil) + handler, _, tsr := tree.getValue("/", nil, false) if handler != nil { t.Fatalf("non-nil handler") } else if tsr { @@ -617,7 +653,7 @@ func TestTreeInvalidNodeType(t *testing.T) { // normal lookup recv := catchPanic(func() { - tree.getValue("/test", nil) + tree.getValue("/test", nil, false) }) if rs, ok := recv.(string); !ok || rs != panicMsg { t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)