diff --git a/router.go b/router.go index 1eab403d..d373f8b9 100644 --- a/router.go +++ b/router.go @@ -203,6 +203,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 @@ -216,6 +223,7 @@ func New() *Router { RedirectFixedPath: true, HandleMethodNotAllowed: true, HandleOPTIONS: true, + UseRawPath: false, } } @@ -465,6 +473,10 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { path := req.URL.Path + if r.UseRawPath && req.URL.RawPath != "" { + path = req.URL.RawPath + } + if root := r.trees[req.Method]; root != nil { if handle, ps, tsr := root.getValue(path, r.getParams); handle != nil { if ps != nil { diff --git a/router_test.go b/router_test.go index ae7d2435..092cca90 100644 --- a/router_test.go +++ b/router_test.go @@ -697,3 +697,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 6eb4fe67..f1d8a6b1 100644 --- a/tree.go +++ b/tree.go @@ -5,6 +5,7 @@ package httprouter import ( + "net/url" "strings" "unicode" "unicode/utf8" @@ -368,9 +369,16 @@ walk: // Outer loop for walking the tree // Expand slice within preallocated capacity i := len(*ps) *ps = (*ps)[:i+1] + + key := n.path[1:] + value, err := url.PathUnescape(path[:end]) + if err != nil { + value = path[:end] + } + (*ps)[i] = Param{ - Key: n.path[1:], - Value: path[:end], + Key: key, + Value: value, } } diff --git a/tree_test.go b/tree_test.go index 2209abcd..23646578 100644 --- a/tree_test.go +++ b/tree_test.go @@ -181,6 +181,8 @@ 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"}}}, + {"/search/invalid%encoding", false, "/search/:query", Params{Param{"query", "invalid%encoding"}}}, }) checkPriorities(t, tree)