From 7d54690cdc4be1effb746ee60d950f937aa9e897 Mon Sep 17 00:00:00 2001 From: Mihard Date: Sun, 16 Apr 2023 20:13:47 +0200 Subject: [PATCH] Proper colon support in reverse (#2416) * Adds support of the escaped colon in echo.Reverse --------- Co-authored-by: Mihard --- echo_test.go | 109 ++++++++++++++++++++++++++++++++++++++++++--------- router.go | 7 +++- 2 files changed, 96 insertions(+), 20 deletions(-) diff --git a/echo_test.go b/echo_test.go index 2f66c8c6c..eab25db33 100644 --- a/echo_test.go +++ b/echo_test.go @@ -1517,26 +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/*" - - 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, tc.expect, e.Reverse(tc.whenRouteName, tc.whenParams...)) + }) + } } func TestEchoReverseHandleHostProperly(t *testing.T) { diff --git a/router.go b/router.go index 597660d39..50a6385ab 100644 --- a/router.go +++ b/router.go @@ -159,7 +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] == ':' || route.Path[i] == '*') && n < ln { + 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 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]))