diff --git a/router.go b/router.go index bb173300..c930ec3a 100644 --- a/router.go +++ b/router.go @@ -159,6 +159,13 @@ type Router struct { // The handler can be used to keep your server from crashing because of // unrecovered panics. PanicHandler func(http.ResponseWriter, *http.Request, interface{}) + + // Go 1.5 introduced the RawPath field in net/url to hold the encoded form of Path. + // The Parse function sets both Path and RawPath in the URL it returns, + // and URL's String method uses RawPath if it is a valid encoding of Path, + // by calling the EncodedPath method. + // This tells the router to use the request.URL.RawPath when parsing the path. + UseRawPath bool } // Make sure the Router conforms with the http.Handler interface @@ -172,6 +179,7 @@ func New() *Router { RedirectFixedPath: true, HandleMethodNotAllowed: true, HandleOPTIONS: true, + UseRawPath: false, } } @@ -339,6 +347,10 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { path := req.URL.Path + if r.UseRawPath { + path = req.URL.String() + } + if root := r.trees[req.Method]; root != nil { if handle, ps, tsr := root.getValue(path); handle != nil { handle(w, req, ps) diff --git a/router_test.go b/router_test.go index fe58c8a9..48c2634f 100644 --- a/router_test.go +++ b/router_test.go @@ -528,3 +528,48 @@ func TestRouterServeFiles(t *testing.T) { t.Error("serving file failed") } } + +func TestRouterUseRawPathSuccess(t *testing.T) { + router := New() + router.UseRawPath = true + + routed := false + router.Handle("GET", "/user/:name", func(w http.ResponseWriter, r *http.Request, ps Params) { + routed = true + want := Params{Param{"name", "abc/123"}} + if !reflect.DeepEqual(ps, want) { + t.Fatalf("wrong wildcard values: want %v, got %v", want, ps) + } + }) + + w := new(mockResponseWriter) + + req, _ := http.NewRequest("GET", "/user/abc%2F123", nil) + router.ServeHTTP(w, req) + + if !routed { + t.Fatal("routing failed") + } +} + +func TestRouterUseRawPathFailure(t *testing.T) { + router := New() + + routed := false + router.Handle("GET", "/user/:name", func(w http.ResponseWriter, r *http.Request, ps Params) { + routed = true + want := Params{Param{"name", "abc/123"}} + if !reflect.DeepEqual(ps, want) { + t.Fatalf("wrong wildcard values: want %v, got %v", want, ps) + } + }) + + w := new(mockResponseWriter) + + req, _ := http.NewRequest("GET", "/user/abc%2F123", nil) + router.ServeHTTP(w, req) + + if routed { + t.Fatal("routing unexpectedly succeeded") + } +} diff --git a/tree.go b/tree.go index a8fa98b0..c067ed70 100644 --- a/tree.go +++ b/tree.go @@ -5,6 +5,7 @@ package httprouter import ( + "net/url" "strings" "unicode" "unicode/utf8" @@ -371,7 +372,12 @@ walk: // outer loop for walking the tree i := len(p) p = p[:i+1] // expand slice within preallocated capacity p[i].Key = n.path[1:] - p[i].Value = path[:end] + + value, err := url.PathUnescape(path[:end]) + if err != nil { + return + } + p[i].Value = value // we need to go deeper! if end < len(path) { diff --git a/tree_test.go b/tree_test.go index e89d9452..489b4909 100644 --- a/tree_test.go +++ b/tree_test.go @@ -193,6 +193,7 @@ func TestTreeWildcard(t *testing.T) { {"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{"dir", "js"}, Param{"filepath", "/inc/framework.js"}}}, {"/info/gordon/public", false, "/info/:user/public", Params{Param{"user", "gordon"}}}, {"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{"user", "gordon"}, Param{"project", "go"}}}, + {"/search/something%2Fencoded", false, "/search/:query", Params{Param{"query", "something/encoded"}}}, }) checkPriorities(t, tree)