Skip to content

Commit 674ef1c

Browse files
kushmansinghelithrar
authored andcommitted
Add mechanism to route based on the escaped path (#184)
* Add mechanism to route based on the escaped path, correct request mocking in tests * Remove unneccessary regex matching, substitute with string slicing * Add test case and handling for requests with no host/scheme
1 parent cf79e51 commit 674ef1c

File tree

3 files changed

+89
-7
lines changed

3 files changed

+89
-7
lines changed

mux.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net/http"
1111
"path"
1212
"regexp"
13+
"strings"
1314
)
1415

1516
// NewRouter returns a new router instance.
@@ -76,8 +77,9 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
7677
// mux.Vars(request).
7778
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
7879
if !r.skipClean {
80+
path := getPath(req)
7981
// Clean path to canonical form and redirect.
80-
if p := cleanPath(req.URL.Path); p != req.URL.Path {
82+
if p := cleanPath(path); p != path {
8183

8284
// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
8385
// This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
@@ -358,6 +360,32 @@ func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
358360
// Helpers
359361
// ----------------------------------------------------------------------------
360362

363+
// getPath returns the escaped path if possible; doing what URL.EscapedPath()
364+
// which was added in go1.5 does
365+
func getPath(req *http.Request) string {
366+
if req.RequestURI != "" {
367+
// Extract the path from RequestURI (which is escaped unlike URL.Path)
368+
// as detailed here as detailed in https://golang.org/pkg/net/url/#URL
369+
// for < 1.5 server side workaround
370+
// http://localhost/path/here?v=1 -> /path/here
371+
path := req.RequestURI
372+
if i := len(req.URL.Scheme); i > 0 {
373+
path = path[i+len(`://`):]
374+
}
375+
if i := len(req.URL.Host); i > 0 {
376+
path = path[i:]
377+
}
378+
if i := strings.LastIndex(path, "?"); i > -1 {
379+
path = path[:i]
380+
}
381+
if i := strings.LastIndex(path, "#"); i > -1 {
382+
path = path[:i]
383+
}
384+
return path
385+
}
386+
return req.URL.Path
387+
}
388+
361389
// cleanPath returns the canonical path for p, eliminating . and .. elements.
362390
// Borrowed from the net/http package.
363391
func cleanPath(p string) string {

mux_test.go

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
package mux
66

77
import (
8+
"bufio"
9+
"bytes"
810
"errors"
911
"fmt"
1012
"net/http"
@@ -280,6 +282,16 @@ func TestPath(t *testing.T) {
280282
pathTemplate: `/111`,
281283
shouldMatch: false,
282284
},
285+
{
286+
title: "Path route, match root with no host",
287+
route: new(Route).Path("/"),
288+
request: newRequest("GET", "/"),
289+
vars: map[string]string{},
290+
host: "",
291+
path: "/",
292+
pathTemplate: `/`,
293+
shouldMatch: true,
294+
},
283295
{
284296
title: "Path route, wrong path in request in request URL",
285297
route: new(Route).Path("/111/222/333"),
@@ -309,6 +321,16 @@ func TestPath(t *testing.T) {
309321
pathTemplate: `/111/{v1:[0-9]{3}}/333`,
310322
shouldMatch: false,
311323
},
324+
{
325+
title: "Path route, URL with encoded slash does match",
326+
route: new(Route).Path("/v1/{v1}/v2"),
327+
request: newRequest("GET", "http://localhost/v1/1%2F2/v2"),
328+
vars: map[string]string{"v1": "1%2F2"},
329+
host: "",
330+
path: "/v1/1%2F2/v2",
331+
pathTemplate: `/v1/{v1}/v2`,
332+
shouldMatch: true,
333+
},
312334
{
313335
title: "Path route with multiple patterns, match",
314336
route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
@@ -1466,11 +1488,42 @@ func stringMapEqual(m1, m2 map[string]string) bool {
14661488
return true
14671489
}
14681490

1469-
// newRequest is a helper function to create a new request with a method and url
1491+
// newRequest is a helper function to create a new request with a method and url.
1492+
// The request returned is a 'server' request as opposed to a 'client' one through
1493+
// simulated write onto the wire and read off of the wire.
1494+
// The differences between requests are detailed in the net/http package.
14701495
func newRequest(method, url string) *http.Request {
14711496
req, err := http.NewRequest(method, url, nil)
14721497
if err != nil {
14731498
panic(err)
14741499
}
1500+
// extract the escaped original host+path from url
1501+
// http://localhost/path/here?v=1#frag -> //localhost/path/here
1502+
opaque := ""
1503+
if i := len(req.URL.Scheme); i > 0 {
1504+
opaque = url[i+1:]
1505+
}
1506+
1507+
if i := strings.LastIndex(opaque, "?"); i > -1 {
1508+
opaque = opaque[:i]
1509+
}
1510+
if i := strings.LastIndex(opaque, "#"); i > -1 {
1511+
opaque = opaque[:i]
1512+
}
1513+
1514+
// Escaped host+path workaround as detailed in https://golang.org/pkg/net/url/#URL
1515+
// for < 1.5 client side workaround
1516+
req.URL.Opaque = opaque
1517+
1518+
// Simulate writing to wire
1519+
var buff bytes.Buffer
1520+
req.Write(&buff)
1521+
ioreader := bufio.NewReader(&buff)
1522+
1523+
// Parse request off of 'wire'
1524+
req, err = http.ReadRequest(ioreader)
1525+
if err != nil {
1526+
panic(err)
1527+
}
14751528
return req
14761529
}

regexp.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
149149
if r.matchQuery {
150150
return r.matchQueryString(req)
151151
}
152-
153-
return r.regexp.MatchString(req.URL.Path)
152+
path := getPath(req)
153+
return r.regexp.MatchString(path)
154154
}
155155

156156
return r.regexp.MatchString(getHost(req))
@@ -253,14 +253,15 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
253253
extractVars(host, matches, v.host.varsN, m.Vars)
254254
}
255255
}
256+
path := getPath(req)
256257
// Store path variables.
257258
if v.path != nil {
258-
matches := v.path.regexp.FindStringSubmatchIndex(req.URL.Path)
259+
matches := v.path.regexp.FindStringSubmatchIndex(path)
259260
if len(matches) > 0 {
260-
extractVars(req.URL.Path, matches, v.path.varsN, m.Vars)
261+
extractVars(path, matches, v.path.varsN, m.Vars)
261262
// Check if we should redirect.
262263
if v.path.strictSlash {
263-
p1 := strings.HasSuffix(req.URL.Path, "/")
264+
p1 := strings.HasSuffix(path, "/")
264265
p2 := strings.HasSuffix(v.path.template, "/")
265266
if p1 != p2 {
266267
u, _ := url.Parse(req.URL.String())

0 commit comments

Comments
 (0)