From 81d61cff6460e10ee3b23f0fbdd4c0d56b9f0966 Mon Sep 17 00:00:00 2001 From: Francesco Mari Date: Fri, 17 Dec 2021 11:46:14 +0100 Subject: [PATCH 1/3] Add a flag to unescape variable values --- mux.go | 10 ++++++++++ mux_test.go | 17 ++++++++++++----- regexp.go | 20 +++++++++++++++++++- route.go | 1 + 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/mux.go b/mux.go index f126a60..bf353ae 100644 --- a/mux.go +++ b/mux.go @@ -84,6 +84,8 @@ type routeConf struct { // will not redirect skipClean bool + unescapeVars bool + // Manager for the variables from host and path. regexp routeRegexpGroup @@ -260,6 +262,14 @@ func (r *Router) SkipClean(value bool) *Router { return r } +// UnescapeVars tells the router to escape the route variables before invoking +// the handler. This is useful when used together with UseEncodedPath to avoid +// un-escaping the variable values in the handler. +func (r *Router) UnescapeVars(value bool) *Router { + r.unescapeVars = value + return r +} + // UseEncodedPath tells the router to match the encoded original path // to the routes. // For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to". diff --git a/mux_test.go b/mux_test.go index 2d8d2b3..0f3d223 100644 --- a/mux_test.go +++ b/mux_test.go @@ -1540,13 +1540,10 @@ func TestStrictSlash(t *testing.T) { } func TestUseEncodedPath(t *testing.T) { - r := NewRouter() - r.UseEncodedPath() - tests := []routeTest{ { title: "Router with useEncodedPath, URL with encoded slash does match", - route: r.NewRoute().Path("/v1/{v1}/v2"), + route: NewRouter().UseEncodedPath().NewRoute().Path("/v1/{v1}/v2"), request: newRequest("GET", "http://localhost/v1/1%2F2/v2"), vars: map[string]string{"v1": "1%2F2"}, host: "", @@ -1554,9 +1551,19 @@ func TestUseEncodedPath(t *testing.T) { pathTemplate: `/v1/{v1}/v2`, shouldMatch: true, }, + { + title: "Router with useEncodedPath, URL with encoded slash does match, variables decoded", + route: NewRouter().UseEncodedPath().UnescapeVars(true).NewRoute().Path("/v1/{v1}/v2"), + request: newRequest("GET", "http://localhost/v1/1%2F2/v2"), + vars: map[string]string{"v1": "1/2"}, + host: "", + path: "/v1/1%2F2/v2", + pathTemplate: `/v1/{v1}/v2`, + shouldMatch: true, + }, { title: "Router with useEncodedPath, URL with encoded slash doesn't match", - route: r.NewRoute().Path("/v1/1/2/v2"), + route: NewRouter().UseEncodedPath().NewRoute().Path("/v1/1/2/v2"), request: newRequest("GET", "http://localhost/v1/1%2F2/v2"), vars: map[string]string{"v1": "1%2F2"}, host: "", diff --git a/regexp.go b/regexp.go index 0144842..e6ddb46 100644 --- a/regexp.go +++ b/regexp.go @@ -17,6 +17,7 @@ import ( type routeRegexpOptions struct { strictSlash bool useEncodedPath bool + unescapeVars bool } type regexpType int @@ -204,7 +205,11 @@ func (r *routeRegexp) url(values map[string]string) (string, error) { if r.regexpType == regexpTypeQuery { value = url.QueryEscape(value) } - urlValues[k] = value + if r.options.unescapeVars { + urlValues[k] = url.PathEscape(value) + } else { + urlValues[k] = value + } } rv := fmt.Sprintf(r.reverse, urlValues...) if !r.regexp.MatchString(rv) { @@ -345,6 +350,9 @@ func (v routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) { matches := v.path.regexp.FindStringSubmatchIndex(path) if len(matches) > 0 { extractVars(path, matches, v.path.varsN, m.Vars) + if r.unescapeVars { + unescapeVars(m.Vars) + } // Check if we should redirect. if v.path.options.strictSlash { p1 := strings.HasSuffix(path, "/") @@ -386,3 +394,13 @@ func extractVars(input string, matches []int, names []string, output map[string] output[name] = input[matches[2*i+2]:matches[2*i+3]] } } + +func unescapeVars(vars map[string]string) { + for k, v := range vars { + if decoded, err := url.PathUnescape(v); err != nil { + vars[k] = v + } else { + vars[k] = decoded + } + } +} diff --git a/route.go b/route.go index 750afe5..834aaa1 100644 --- a/route.go +++ b/route.go @@ -186,6 +186,7 @@ func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error { rr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{ strictSlash: r.strictSlash, useEncodedPath: r.useEncodedPath, + unescapeVars: r.unescapeVars, }) if err != nil { return err From 7885b613b0ef36c8c066d2de8749397155623210 Mon Sep 17 00:00:00 2001 From: Francesco Mari Date: Fri, 17 Dec 2021 12:28:06 +0100 Subject: [PATCH 2/3] Add an example in the godoc --- mux.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mux.go b/mux.go index bf353ae..e7abb47 100644 --- a/mux.go +++ b/mux.go @@ -265,6 +265,12 @@ func (r *Router) SkipClean(value bool) *Router { // UnescapeVars tells the router to escape the route variables before invoking // the handler. This is useful when used together with UseEncodedPath to avoid // un-escaping the variable values in the handler. +// +// For example, if the router is configured only with UseEncodedPath(), and +// "/path/foo%2Fbar/to" matches the path template "/path/{var}/to", your handler +// will receive the value "foo%2Fbar" for the variable "var". Instead, if the +// router is configured with both UseEncodedPath() and UnescapeVars(true), the +// value for the variable "var" will be "foo/bar". func (r *Router) UnescapeVars(value bool) *Router { r.unescapeVars = value return r From 11231c0e9337c22ea679a8a2a9f927cda3bf85c7 Mon Sep 17 00:00:00 2001 From: Francesco Mari Date: Fri, 17 Dec 2021 12:47:15 +0100 Subject: [PATCH 3/3] Remove the flag from UnescapeVars --- mux.go | 8 ++++---- mux_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mux.go b/mux.go index e7abb47..83be585 100644 --- a/mux.go +++ b/mux.go @@ -269,10 +269,10 @@ func (r *Router) SkipClean(value bool) *Router { // For example, if the router is configured only with UseEncodedPath(), and // "/path/foo%2Fbar/to" matches the path template "/path/{var}/to", your handler // will receive the value "foo%2Fbar" for the variable "var". Instead, if the -// router is configured with both UseEncodedPath() and UnescapeVars(true), the -// value for the variable "var" will be "foo/bar". -func (r *Router) UnescapeVars(value bool) *Router { - r.unescapeVars = value +// router is configured with both UseEncodedPath() and UnescapeVars(), the value +// for the variable "var" will be "foo/bar". +func (r *Router) UnescapeVars() *Router { + r.unescapeVars = true return r } diff --git a/mux_test.go b/mux_test.go index 0f3d223..fbee209 100644 --- a/mux_test.go +++ b/mux_test.go @@ -1553,7 +1553,7 @@ func TestUseEncodedPath(t *testing.T) { }, { title: "Router with useEncodedPath, URL with encoded slash does match, variables decoded", - route: NewRouter().UseEncodedPath().UnescapeVars(true).NewRoute().Path("/v1/{v1}/v2"), + route: NewRouter().UseEncodedPath().UnescapeVars().NewRoute().Path("/v1/{v1}/v2"), request: newRequest("GET", "http://localhost/v1/1%2F2/v2"), vars: map[string]string{"v1": "1/2"}, host: "",