Skip to content

Commit 0a192a1

Browse files
kushmansinghelithrar
authored andcommitted
Add useEncodedPath option to router and routes (#190)
- Resolves a breaking change in #184
1 parent 0b13a92 commit 0a192a1

File tree

5 files changed

+91
-25
lines changed

5 files changed

+91
-25
lines changed

mux.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ type Router struct {
5353
// This has no effect when go1.7+ is used, since the context is stored
5454
// on the request itself.
5555
KeepContext bool
56+
// see Router.UseEncodedPath(). This defines a flag for all routes.
57+
useEncodedPath bool
5658
}
5759

5860
// Match matches registered routes against the request.
@@ -77,7 +79,10 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
7779
// mux.Vars(request).
7880
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
7981
if !r.skipClean {
80-
path := getPath(req)
82+
path := req.URL.Path
83+
if r.useEncodedPath {
84+
path = getPath(req)
85+
}
8186
// Clean path to canonical form and redirect.
8287
if p := cleanPath(path); p != path {
8388

@@ -152,6 +157,21 @@ func (r *Router) SkipClean(value bool) *Router {
152157
return r
153158
}
154159

160+
// UseEncodedPath tells the router to match the encoded original path
161+
// to the routes.
162+
// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
163+
// This behavior has the drawback of needing to match routes against
164+
// r.RequestURI instead of r.URL.Path. Any modifications (such as http.StripPrefix)
165+
// to r.URL.Path will not affect routing when this flag is on and thus may
166+
// induce unintended behavior.
167+
//
168+
// If not called, the router will match the unencoded path to the routes.
169+
// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
170+
func (r *Router) UseEncodedPath() *Router {
171+
r.useEncodedPath = true
172+
return r
173+
}
174+
155175
// ----------------------------------------------------------------------------
156176
// parentRoute
157177
// ----------------------------------------------------------------------------
@@ -189,7 +209,7 @@ func (r *Router) buildVars(m map[string]string) map[string]string {
189209

190210
// NewRoute registers an empty route.
191211
func (r *Router) NewRoute() *Route {
192-
route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean}
212+
route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath}
193213
r.routes = append(r.routes, route)
194214
return route
195215
}

mux_test.go

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -335,16 +335,6 @@ func TestPath(t *testing.T) {
335335
pathTemplate: `/111/{v1:[0-9]{3}}/333`,
336336
shouldMatch: false,
337337
},
338-
{
339-
title: "Path route, URL with encoded slash does match",
340-
route: new(Route).Path("/v1/{v1}/v2"),
341-
request: newRequest("GET", "http://localhost/v1/1%2F2/v2"),
342-
vars: map[string]string{"v1": "1%2F2"},
343-
host: "",
344-
path: "/v1/1%2F2/v2",
345-
pathTemplate: `/v1/{v1}/v2`,
346-
shouldMatch: true,
347-
},
348338
{
349339
title: "Path route with multiple patterns, match",
350340
route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
@@ -430,6 +420,7 @@ func TestPath(t *testing.T) {
430420
for _, test := range tests {
431421
testRoute(t, test)
432422
testTemplate(t, test)
423+
testUseEscapedRoute(t, test)
433424
}
434425
}
435426

@@ -507,6 +498,7 @@ func TestPathPrefix(t *testing.T) {
507498
for _, test := range tests {
508499
testRoute(t, test)
509500
testTemplate(t, test)
501+
testUseEscapedRoute(t, test)
510502
}
511503
}
512504

@@ -583,6 +575,7 @@ func TestHostPath(t *testing.T) {
583575
for _, test := range tests {
584576
testRoute(t, test)
585577
testTemplate(t, test)
578+
testUseEscapedRoute(t, test)
586579
}
587580
}
588581

@@ -909,6 +902,7 @@ func TestQueries(t *testing.T) {
909902
for _, test := range tests {
910903
testRoute(t, test)
911904
testTemplate(t, test)
905+
testUseEscapedRoute(t, test)
912906
}
913907
}
914908

@@ -1068,6 +1062,7 @@ func TestSubRouter(t *testing.T) {
10681062
for _, test := range tests {
10691063
testRoute(t, test)
10701064
testTemplate(t, test)
1065+
testUseEscapedRoute(t, test)
10711066
}
10721067
}
10731068

@@ -1161,6 +1156,40 @@ func TestStrictSlash(t *testing.T) {
11611156
},
11621157
}
11631158

1159+
for _, test := range tests {
1160+
testRoute(t, test)
1161+
testTemplate(t, test)
1162+
testUseEscapedRoute(t, test)
1163+
}
1164+
}
1165+
1166+
func TestUseEncodedPath(t *testing.T) {
1167+
r := NewRouter()
1168+
r.UseEncodedPath()
1169+
1170+
tests := []routeTest{
1171+
{
1172+
title: "Router with useEncodedPath, URL with encoded slash does match",
1173+
route: r.NewRoute().Path("/v1/{v1}/v2"),
1174+
request: newRequest("GET", "http://localhost/v1/1%2F2/v2"),
1175+
vars: map[string]string{"v1": "1%2F2"},
1176+
host: "",
1177+
path: "/v1/1%2F2/v2",
1178+
pathTemplate: `/v1/{v1}/v2`,
1179+
shouldMatch: true,
1180+
},
1181+
{
1182+
title: "Router with useEncodedPath, URL with encoded slash doesn't match",
1183+
route: r.NewRoute().Path("/v1/1/2/v2"),
1184+
request: newRequest("GET", "http://localhost/v1/1%2F2/v2"),
1185+
vars: map[string]string{"v1": "1%2F2"},
1186+
host: "",
1187+
path: "/v1/1%2F2/v2",
1188+
pathTemplate: `/v1/1/2/v2`,
1189+
shouldMatch: false,
1190+
},
1191+
}
1192+
11641193
for _, test := range tests {
11651194
testRoute(t, test)
11661195
testTemplate(t, test)
@@ -1375,6 +1404,11 @@ func testRoute(t *testing.T, test routeTest) {
13751404
}
13761405
}
13771406

1407+
func testUseEscapedRoute(t *testing.T, test routeTest) {
1408+
test.route.useEncodedPath = true
1409+
testRoute(t, test)
1410+
}
1411+
13781412
func testTemplate(t *testing.T, test routeTest) {
13791413
route := test.route
13801414
pathTemplate := test.pathTemplate

old_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,7 @@ func TestNewRegexp(t *testing.T) {
687687
}
688688

689689
for pattern, paths := range tests {
690-
p, _ = newRouteRegexp(pattern, false, false, false, false)
690+
p, _ = newRouteRegexp(pattern, false, false, false, false, false)
691691
for path, result := range paths {
692692
matches = p.regexp.FindStringSubmatch(path)
693693
if result == nil {

regexp.go

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
// Previously we accepted only Python-like identifiers for variable
2525
// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
2626
// name and pattern can't be empty, and names can't contain a colon.
27-
func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) {
27+
func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash, useEncodedPath bool) (*routeRegexp, error) {
2828
// Check if it is well-formed.
2929
idxs, errBraces := braceIndices(tpl)
3030
if errBraces != nil {
@@ -111,14 +111,15 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash
111111
}
112112
// Done!
113113
return &routeRegexp{
114-
template: template,
115-
matchHost: matchHost,
116-
matchQuery: matchQuery,
117-
strictSlash: strictSlash,
118-
regexp: reg,
119-
reverse: reverse.String(),
120-
varsN: varsN,
121-
varsR: varsR,
114+
template: template,
115+
matchHost: matchHost,
116+
matchQuery: matchQuery,
117+
strictSlash: strictSlash,
118+
useEncodedPath: useEncodedPath,
119+
regexp: reg,
120+
reverse: reverse.String(),
121+
varsN: varsN,
122+
varsR: varsR,
122123
}, nil
123124
}
124125

@@ -133,6 +134,9 @@ type routeRegexp struct {
133134
matchQuery bool
134135
// The strictSlash value defined on the route, but disabled if PathPrefix was used.
135136
strictSlash bool
137+
// Determines whether to use encoded path from getPath function or unencoded
138+
// req.URL.Path for path matching
139+
useEncodedPath bool
136140
// Expanded regexp.
137141
regexp *regexp.Regexp
138142
// Reverse template.
@@ -149,7 +153,10 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
149153
if r.matchQuery {
150154
return r.matchQueryString(req)
151155
}
152-
path := getPath(req)
156+
path := req.URL.Path
157+
if r.useEncodedPath {
158+
path = getPath(req)
159+
}
153160
return r.regexp.MatchString(path)
154161
}
155162

@@ -253,7 +260,10 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
253260
extractVars(host, matches, v.host.varsN, m.Vars)
254261
}
255262
}
256-
path := getPath(req)
263+
path := req.URL.Path
264+
if r.useEncodedPath {
265+
path = getPath(req)
266+
}
257267
// Store path variables.
258268
if v.path != nil {
259269
matches := v.path.regexp.FindStringSubmatchIndex(path)

route.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ type Route struct {
2929
// If true, when the path pattern is "/path//to", accessing "/path//to"
3030
// will not redirect
3131
skipClean bool
32+
// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
33+
useEncodedPath bool
3234
// If true, this route never matches: it is only used to build URLs.
3335
buildOnly bool
3436
// The name used to build URLs.
@@ -158,7 +160,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery
158160
tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
159161
}
160162
}
161-
rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash)
163+
rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash, r.useEncodedPath)
162164
if err != nil {
163165
return err
164166
}

0 commit comments

Comments
 (0)