Skip to content

Commit 451d0d0

Browse files
committed
feat(router): add literal colon support (#1432)
1 parent 24d6764 commit 451d0d0

File tree

4 files changed

+83
-1
lines changed

4 files changed

+83
-1
lines changed

gin.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ import (
2424
)
2525

2626
const defaultMultipartMemory = 32 << 20 // 32 MB
27+
const escapedColon = "\\:"
28+
const colon = ":"
29+
const backslash = "\\"
2730

2831
var (
2932
default404Body = []byte("404 page not found")
@@ -474,6 +477,26 @@ func (engine *Engine) validateHeader(header string) (clientIP string, valid bool
474477
return "", false
475478
}
476479

480+
// updateRouteTree do update to the route tree recursively
481+
func updateRouteTree(n *node) {
482+
n.path = strings.ReplaceAll(n.path, escapedColon, colon)
483+
n.fullPath = strings.ReplaceAll(n.fullPath, escapedColon, colon)
484+
n.indices = strings.ReplaceAll(n.indices, backslash, colon)
485+
if n.children == nil {
486+
return
487+
}
488+
for _, child := range n.children {
489+
updateRouteTree(child)
490+
}
491+
}
492+
493+
// updateRouteTrees do update to the route trees
494+
func (engine *Engine) updateRouteTrees() {
495+
for _, tree := range engine.trees {
496+
updateRouteTree(tree.root)
497+
}
498+
}
499+
477500
// parseIP parse a string representation of an IP and returns a net.IP with the
478501
// minimum byte representation or nil if input is invalid.
479502
func parseIP(ip string) net.IP {
@@ -498,7 +521,7 @@ func (engine *Engine) Run(addr ...string) (err error) {
498521
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
499522
"Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
500523
}
501-
524+
engine.updateRouteTrees()
502525
address := resolveAddress(addr)
503526
debugPrint("Listening and serving HTTP on %s\n", address)
504527
err = http.ListenAndServe(address, engine.Handler())

gin_integration_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,3 +577,28 @@ func TestTreeRunDynamicRouting(t *testing.T) {
577577
func isWindows() bool {
578578
return runtime.GOOS == "windows"
579579
}
580+
581+
func TestEscapedColon(t *testing.T) {
582+
router := New()
583+
f := func(u string) {
584+
router.GET(u, func(c *Context) { c.String(http.StatusOK, u) })
585+
}
586+
f("/r/r\\:r")
587+
f("/r/r:r")
588+
f("/r/r/:r")
589+
f("/r/r/\\:r")
590+
f("/r/r/r\\:r")
591+
assert.Panics(t, func() {
592+
f("\\foo:")
593+
})
594+
595+
router.updateRouteTrees()
596+
ts := httptest.NewServer(router)
597+
defer ts.Close()
598+
599+
testRequest(t, ts.URL+"/r/r123", "", "/r/r:r")
600+
testRequest(t, ts.URL+"/r/r:r", "", "/r/r\\:r")
601+
testRequest(t, ts.URL+"/r/r/r123", "", "/r/r/:r")
602+
testRequest(t, ts.URL+"/r/r/:r", "", "/r/r/\\:r")
603+
testRequest(t, ts.URL+"/r/r/r:r", "", "/r/r/r\\:r")
604+
}

tree.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,19 @@ walk:
269269
// Returns -1 as index, if no wildcard was found.
270270
func findWildcard(path string) (wildcard string, i int, valid bool) {
271271
// Find start
272+
escapeColon := false
272273
for start, c := range []byte(path) {
274+
if escapeColon {
275+
escapeColon = false
276+
if c == ':' {
277+
continue
278+
}
279+
panic("invalid escape string in path '" + path + "'")
280+
}
281+
if c == '\\' {
282+
escapeColon = true
283+
continue
284+
}
273285
// A wildcard starts with ':' (param) or '*' (catch-all)
274286
if c != ':' && c != '*' {
275287
continue

tree_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ func TestTreeWildcard(t *testing.T) {
192192
"/get/abc/123abg/:param",
193193
"/get/abc/123abf/:param",
194194
"/get/abc/123abfff/:param",
195+
"/get/abc/escaped_colon/test\\:param",
195196
}
196197
for _, route := range routes {
197198
tree.addRoute(route, fakeHandler(route))
@@ -315,6 +316,7 @@ func TestTreeWildcard(t *testing.T) {
315316
{"/get/abc/123abg/test", false, "/get/abc/123abg/:param", Params{Param{Key: "param", Value: "test"}}},
316317
{"/get/abc/123abf/testss", false, "/get/abc/123abf/:param", Params{Param{Key: "param", Value: "testss"}}},
317318
{"/get/abc/123abfff/te", false, "/get/abc/123abfff/:param", Params{Param{Key: "param", Value: "te"}}},
319+
{"/get/abc/escaped_colon/test\\:param", false, "/get/abc/escaped_colon/test\\:param", nil},
318320
})
319321

320322
checkPriorities(t, tree)
@@ -419,6 +421,9 @@ func TestTreeWildcardConflict(t *testing.T) {
419421
{"/id/:id", false},
420422
{"/static/*file", false},
421423
{"/static/", true},
424+
{"/escape/test\\:d1", false},
425+
{"/escape/test\\:d2", false},
426+
{"/escape/test:param", false},
422427
}
423428
testRoutes(t, routes)
424429
}
@@ -971,3 +976,20 @@ func TestTreeWildcardConflictEx(t *testing.T) {
971976
}
972977
}
973978
}
979+
980+
func TestTreeInvalidEscape(t *testing.T) {
981+
routes := map[string]bool{
982+
"/r1/r": true,
983+
"/r2/:r": true,
984+
"/r3/\\:r": true,
985+
}
986+
tree := &node{}
987+
for route, valid := range routes {
988+
recv := catchPanic(func() {
989+
tree.addRoute(route, fakeHandler(route))
990+
})
991+
if recv == nil != valid {
992+
t.Fatalf("%s should be %t but got %v", route, valid, recv)
993+
}
994+
}
995+
}

0 commit comments

Comments
 (0)