From 795796e5151042b137f411ccf15b1fef3ecbd317 Mon Sep 17 00:00:00 2001 From: Mihard Date: Sun, 12 Mar 2023 23:18:30 +0100 Subject: [PATCH 1/3] Adds support of the escaped colon in echo.Reverse --- echo_test.go | 3 +++ router.go | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/echo_test.go b/echo_test.go index 2f66c8c6c..bc3f3c685 100644 --- a/echo_test.go +++ b/echo_test.go @@ -1525,6 +1525,7 @@ func TestEchoReverse(t *testing.T) { e.GET("/params/:foo", dummyHandler).Name = "/params/:foo" e.GET("/params/:foo/bar/:qux", dummyHandler).Name = "/params/:foo/bar/:qux" e.GET("/params/:foo/bar/:qux/*", dummyHandler).Name = "/params/:foo/bar/:qux/*" + e.GET("/params\\::customVerb", dummyHandler).Name = "/params:customVerb" assert.Equal(t, "/static", e.Reverse("/static")) assert.Equal(t, "/static", e.Reverse("/static", "missing param")) @@ -1537,6 +1538,8 @@ func TestEchoReverse(t *testing.T) { assert.Equal(t, "/params/one/bar/:qux", e.Reverse("/params/:foo/bar/:qux", "one")) assert.Equal(t, "/params/one/bar/two", e.Reverse("/params/:foo/bar/:qux", "one", "two")) assert.Equal(t, "/params/one/bar/two/three", e.Reverse("/params/:foo/bar/:qux/*", "one", "two", "three")) + + assert.Equal(t, "/params:PATCH", e.Reverse("/params:customVerb", "PATCH")) } func TestEchoReverseHandleHostProperly(t *testing.T) { diff --git a/router.go b/router.go index 597660d39..9ab8cb0d7 100644 --- a/router.go +++ b/router.go @@ -159,7 +159,10 @@ func (r *Router) Reverse(name string, params ...interface{}) string { for _, route := range r.routes { if route.Name == name { for i, l := 0, len(route.Path); i < l; i++ { - if (route.Path[i] == ':' || route.Path[i] == '*') && n < ln { + if route.Path[i] == '\\' { + continue + } + if ((route.Path[i] == ':' && route.Path[i-1] != '\\') || route.Path[i] == '*') && n < ln { for ; i < l && route.Path[i] != '/'; i++ { } uri.WriteString(fmt.Sprintf("%v", params[n])) From b25cea6ccfd4186c2ca7d3dbaaaf9fc03516ceaa Mon Sep 17 00:00:00 2001 From: Mihard Date: Sun, 12 Mar 2023 23:22:56 +0100 Subject: [PATCH 2/3] Adds support of the escaped colon in echo.Reverse --- router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router.go b/router.go index 9ab8cb0d7..d38e32c1a 100644 --- a/router.go +++ b/router.go @@ -162,7 +162,7 @@ func (r *Router) Reverse(name string, params ...interface{}) string { if route.Path[i] == '\\' { continue } - if ((route.Path[i] == ':' && route.Path[i-1] != '\\') || route.Path[i] == '*') && n < ln { + if ((route.Path[i] == ':' && (i == 0 || route.Path[i-1] != '\\')) || route.Path[i] == '*') && n < ln { for ; i < l && route.Path[i] != '/'; i++ { } uri.WriteString(fmt.Sprintf("%v", params[n])) From b31bae77de5ecc3f59318319e4bfeaf3ce4adf2b Mon Sep 17 00:00:00 2001 From: Mihard Date: Sun, 16 Apr 2023 17:49:44 +0200 Subject: [PATCH 3/3] CR - Add backslash support and improve `TestEchoReverse` --- echo_test.go | 112 +++++++++++++++++++++++++++++++++++++++++---------- router.go | 8 ++-- 2 files changed, 95 insertions(+), 25 deletions(-) diff --git a/echo_test.go b/echo_test.go index bc3f3c685..eab25db33 100644 --- a/echo_test.go +++ b/echo_test.go @@ -1517,29 +1517,97 @@ func TestEcho_OnAddRouteHandler(t *testing.T) { } func TestEchoReverse(t *testing.T) { - e := New() - dummyHandler := func(Context) error { return nil } + var testCases = []struct { + name string + whenRouteName string + whenParams []interface{} + expect string + }{ + { + name: "ok,static with no params", + whenRouteName: "/static", + expect: "/static", + }, + { + name: "ok,static with non existent param", + whenRouteName: "/static", + whenParams: []interface{}{"missing param"}, + expect: "/static", + }, + { + name: "ok, wildcard with no params", + whenRouteName: "/static/*", + expect: "/static/*", + }, + { + name: "ok, wildcard with params", + whenRouteName: "/static/*", + whenParams: []interface{}{"foo.txt"}, + expect: "/static/foo.txt", + }, + { + name: "ok, single param without param", + whenRouteName: "/params/:foo", + expect: "/params/:foo", + }, + { + name: "ok, single param with param", + whenRouteName: "/params/:foo", + whenParams: []interface{}{"one"}, + expect: "/params/one", + }, + { + name: "ok, multi param without params", + whenRouteName: "/params/:foo/bar/:qux", + expect: "/params/:foo/bar/:qux", + }, + { + name: "ok, multi param with one param", + whenRouteName: "/params/:foo/bar/:qux", + whenParams: []interface{}{"one"}, + expect: "/params/one/bar/:qux", + }, + { + name: "ok, multi param with all params", + whenRouteName: "/params/:foo/bar/:qux", + whenParams: []interface{}{"one", "two"}, + expect: "/params/one/bar/two", + }, + { + name: "ok, multi param + wildcard with all params", + whenRouteName: "/params/:foo/bar/:qux/*", + whenParams: []interface{}{"one", "two", "three"}, + expect: "/params/one/bar/two/three", + }, + { + name: "ok, backslash is not escaped", + whenRouteName: "/backslash", + whenParams: []interface{}{"test"}, + expect: `/a\b/test`, + }, + { + name: "ok, escaped colon verbs", + whenRouteName: "/params:customVerb", + whenParams: []interface{}{"PATCH"}, + expect: `/params:PATCH`, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + e := New() + dummyHandler := func(Context) error { return nil } + + e.GET("/static", dummyHandler).Name = "/static" + e.GET("/static/*", dummyHandler).Name = "/static/*" + e.GET("/params/:foo", dummyHandler).Name = "/params/:foo" + e.GET("/params/:foo/bar/:qux", dummyHandler).Name = "/params/:foo/bar/:qux" + e.GET("/params/:foo/bar/:qux/*", dummyHandler).Name = "/params/:foo/bar/:qux/*" + e.GET("/a\\b/:x", dummyHandler).Name = "/backslash" + e.GET("/params\\::customVerb", dummyHandler).Name = "/params:customVerb" - e.GET("/static", dummyHandler).Name = "/static" - e.GET("/static/*", dummyHandler).Name = "/static/*" - e.GET("/params/:foo", dummyHandler).Name = "/params/:foo" - e.GET("/params/:foo/bar/:qux", dummyHandler).Name = "/params/:foo/bar/:qux" - e.GET("/params/:foo/bar/:qux/*", dummyHandler).Name = "/params/:foo/bar/:qux/*" - e.GET("/params\\::customVerb", dummyHandler).Name = "/params:customVerb" - - assert.Equal(t, "/static", e.Reverse("/static")) - assert.Equal(t, "/static", e.Reverse("/static", "missing param")) - assert.Equal(t, "/static/*", e.Reverse("/static/*")) - assert.Equal(t, "/static/foo.txt", e.Reverse("/static/*", "foo.txt")) - - assert.Equal(t, "/params/:foo", e.Reverse("/params/:foo")) - assert.Equal(t, "/params/one", e.Reverse("/params/:foo", "one")) - assert.Equal(t, "/params/:foo/bar/:qux", e.Reverse("/params/:foo/bar/:qux")) - assert.Equal(t, "/params/one/bar/:qux", e.Reverse("/params/:foo/bar/:qux", "one")) - assert.Equal(t, "/params/one/bar/two", e.Reverse("/params/:foo/bar/:qux", "one", "two")) - assert.Equal(t, "/params/one/bar/two/three", e.Reverse("/params/:foo/bar/:qux/*", "one", "two", "three")) - - assert.Equal(t, "/params:PATCH", e.Reverse("/params:customVerb", "PATCH")) + assert.Equal(t, tc.expect, e.Reverse(tc.whenRouteName, tc.whenParams...)) + }) + } } func TestEchoReverseHandleHostProperly(t *testing.T) { diff --git a/router.go b/router.go index d38e32c1a..50a6385ab 100644 --- a/router.go +++ b/router.go @@ -159,10 +159,12 @@ func (r *Router) Reverse(name string, params ...interface{}) string { for _, route := range r.routes { if route.Name == name { for i, l := 0, len(route.Path); i < l; i++ { - if route.Path[i] == '\\' { - continue + hasBackslash := route.Path[i] == '\\' + if hasBackslash && i+1 < l && route.Path[i+1] == ':' { + i++ // backslash before colon escapes that colon. in that case skip backslash } - if ((route.Path[i] == ':' && (i == 0 || route.Path[i-1] != '\\')) || route.Path[i] == '*') && n < ln { + if n < ln && (route.Path[i] == '*' || (!hasBackslash && route.Path[i] == ':')) { + // in case of `*` wildcard or `:` (unescaped colon) param we replace everything till next slash or end of path for ; i < l && route.Path[i] != '/'; i++ { } uri.WriteString(fmt.Sprintf("%v", params[n]))