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

This commit is contained in:
Sergey Egorov 2017-02-28 11:29:41 +01:00 committed by Bo-Yi Wu
parent f4dec22c50
commit b1872ec369
4 changed files with 130 additions and 12 deletions

25
gin.go
View File

@ -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

View File

@ -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)
}

22
tree.go
View File

@ -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

View File

@ -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)