diff --git a/glide.lock b/glide.lock index e51b31984..3c13cfee5 100644 --- a/glide.lock +++ b/glide.lock @@ -89,7 +89,7 @@ imports: - name: github.com/containous/flaeg version: b5d2dc5878df07c2d74413348186982e7b865871 - name: github.com/containous/mux - version: af6ea922f7683d9706834157e6b0610e22ccb2db + version: 06ccd3e75091eb659b1d720cda0e16bc7057954c - name: github.com/containous/staert version: 1e26a71803e428fd933f5f9c8e50a26878f53147 - name: github.com/coreos/etcd diff --git a/server/rules_test.go b/server/rules_test.go index 73e0617dd..f0ad65edd 100644 --- a/server/rules_test.go +++ b/server/rules_test.go @@ -136,6 +136,57 @@ func TestPriorites(t *testing.T) { assert.NotEqual(t, foobarMatcher.Handler, fooHandler, "Error matching priority") } +func TestHostRegexp(t *testing.T) { + testCases := []struct { + desc string + hostExp string + urls map[string]bool + }{ + { + desc: "capturing group", + hostExp: "{subdomain:(foo\\.)?bar\\.com}", + urls: map[string]bool{ + "http://foo.bar.com": true, + "http://bar.com": true, + "http://fooubar.com": false, + "http://barucom": false, + "http://barcom": false, + }, + }, + { + desc: "non capturing group", + hostExp: "{subdomain:(?:foo\\.)?bar\\.com}", + urls: map[string]bool{ + "http://foo.bar.com": true, + "http://bar.com": true, + "http://fooubar.com": false, + "http://barucom": false, + "http://barcom": false, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + rls := &Rules{ + route: &serverRoute{ + route: &mux.Route{}, + }, + } + + rt := rls.hostRegexp(test.hostExp) + + for testURL, match := range test.urls { + req := testhelpers.MustNewRequest(http.MethodGet, testURL, nil) + assert.Equal(t, match, rt.Match(req, &mux.RouteMatch{})) + } + }) + } +} + type fakeHandler struct { name string } diff --git a/vendor/github.com/containous/mux/doc.go b/vendor/github.com/containous/mux/doc.go index cce30b2f0..3fab182ea 100644 --- a/vendor/github.com/containous/mux/doc.go +++ b/vendor/github.com/containous/mux/doc.go @@ -57,11 +57,6 @@ calling mux.Vars(): vars := mux.Vars(request) category := vars["category"] -Note that if any capturing groups are present, mux will panic() during parsing. To prevent -this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to -"/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably -when capturing groups were present. - And this is all you need to know about the basic usage. More advanced options are explained below. diff --git a/vendor/github.com/containous/mux/mux.go b/vendor/github.com/containous/mux/mux.go index 93f7a1576..91faff120 100644 --- a/vendor/github.com/containous/mux/mux.go +++ b/vendor/github.com/containous/mux/mux.go @@ -11,7 +11,10 @@ import ( "path" "regexp" "sort" - "strings" +) + +var ( + ErrMethodMismatch = errors.New("method is not allowed") ) // NewRouter returns a new router instance. @@ -40,6 +43,10 @@ func NewRouter() *Router { type Router struct { // Configurable Handler to be used when no route matches. NotFoundHandler http.Handler + + // Configurable Handler to be used when the request method does not match the route. + MethodNotAllowedHandler http.Handler + // Parent route, if this is a subrouter. parent parentRoute // Routes to be matched, in order. @@ -66,6 +73,11 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool { } } + if match.MatchErr == ErrMethodMismatch && r.MethodNotAllowedHandler != nil { + match.Handler = r.MethodNotAllowedHandler + return true + } + // Closest match for a router (includes sub-routers) if r.NotFoundHandler != nil { match.Handler = r.NotFoundHandler @@ -82,7 +94,7 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { if !r.skipClean { path := req.URL.Path if r.useEncodedPath { - path = getPath(req) + path = req.URL.EscapedPath() } // Clean path to canonical form and redirect. if p := cleanPath(path); p != path { @@ -106,9 +118,15 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { req = setVars(req, match.Vars) req = setCurrentRoute(req, match.Route) } + + if handler == nil && match.MatchErr == ErrMethodMismatch { + handler = methodNotAllowedHandler() + } + if handler == nil { handler = http.NotFoundHandler() } + if !r.KeepContext { defer contextClear(req) } @@ -356,6 +374,11 @@ type RouteMatch struct { Route *Route Handler http.Handler Vars map[string]string + + // MatchErr is set to appropriate matching error + // It is set to ErrMethodMismatch if there is a mismatch in + // the request method and route method + MatchErr error } type contextKey int @@ -397,28 +420,6 @@ func setCurrentRoute(r *http.Request, val interface{}) *http.Request { // Helpers // ---------------------------------------------------------------------------- -// getPath returns the escaped path if possible; doing what URL.EscapedPath() -// which was added in go1.5 does -func getPath(req *http.Request) string { - if req.RequestURI != "" { - // Extract the path from RequestURI (which is escaped unlike URL.Path) - // as detailed here as detailed in https://golang.org/pkg/net/url/#URL - // for < 1.5 server side workaround - // http://localhost/path/here?v=1 -> /path/here - path := req.RequestURI - path = strings.TrimPrefix(path, req.URL.Scheme+`://`) - path = strings.TrimPrefix(path, req.URL.Host) - if i := strings.LastIndex(path, "?"); i > -1 { - path = path[:i] - } - if i := strings.LastIndex(path, "#"); i > -1 { - path = path[:i] - } - return path - } - return req.URL.Path -} - // cleanPath returns the canonical path for p, eliminating . and .. elements. // Borrowed from the net/http package. func cleanPath(p string) string { @@ -557,3 +558,12 @@ func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]s } return true } + +// methodNotAllowed replies to the request with an HTTP status code 405. +func methodNotAllowed(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusMethodNotAllowed) +} + +// methodNotAllowedHandler returns a simple request handler +// that replies to each request with a status code 405. +func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) } diff --git a/vendor/github.com/containous/mux/regexp.go b/vendor/github.com/containous/mux/regexp.go index 80d1f7858..da8114ca4 100644 --- a/vendor/github.com/containous/mux/regexp.go +++ b/vendor/github.com/containous/mux/regexp.go @@ -109,13 +109,6 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash, if errCompile != nil { return nil, errCompile } - - // Check for capturing groups which used to work in older versions - if reg.NumSubexp() != len(idxs)/2 { - panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) + - "Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)") - } - // Done! return &routeRegexp{ template: template, @@ -141,7 +134,7 @@ type routeRegexp struct { matchQuery bool // The strictSlash value defined on the route, but disabled if PathPrefix was used. strictSlash bool - // Determines whether to use encoded path from getPath function or unencoded + // Determines whether to use encoded req.URL.EnscapedPath() or unencoded // req.URL.Path for path matching useEncodedPath bool // Expanded regexp. @@ -162,7 +155,7 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { } path := req.URL.Path if r.useEncodedPath { - path = getPath(req) + path = req.URL.EscapedPath() } return r.regexp.MatchString(path) } @@ -272,7 +265,7 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) } path := req.URL.Path if r.useEncodedPath { - path = getPath(req) + path = req.URL.EscapedPath() } // Store path variables. if v.path != nil { @@ -320,7 +313,14 @@ func getHost(r *http.Request) string { } func extractVars(input string, matches []int, names []string, output map[string]string) { - for i, name := range names { - output[name] = input[matches[2*i+2]:matches[2*i+3]] + matchesCount := 0 + prevEnd := -1 + for i := 2; i < len(matches) && matchesCount < len(names); i += 2 { + if prevEnd < matches[i+1] { + value := input[matches[i]:matches[i+1]] + output[names[matchesCount]] = value + prevEnd = matches[i+1] + matchesCount++ + } } } diff --git a/vendor/github.com/containous/mux/route.go b/vendor/github.com/containous/mux/route.go index 1199a7f47..69c3893ff 100644 --- a/vendor/github.com/containous/mux/route.go +++ b/vendor/github.com/containous/mux/route.go @@ -54,12 +54,27 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool { if r.buildOnly || r.err != nil { return false } + + var matchErr error + // Match everything. for _, m := range r.matchers { if matched := m.Match(req, match); !matched { + if _, ok := m.(methodMatcher); ok { + matchErr = ErrMethodMismatch + continue + } + matchErr = nil return false } } + + if matchErr != nil { + match.MatchErr = matchErr + return false + } + + match.MatchErr = nil // Yay, we have a match. Let's collect some info about it. if match.Route == nil { match.Route = r @@ -70,6 +85,7 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool { if match.Vars == nil { match.Vars = make(map[string]string) } + // Set variables. if r.regexp != nil { r.regexp.setMatch(req, match, r) @@ -607,6 +623,44 @@ func (r *Route) GetPathRegexp() (string, error) { return r.regexp.path.regexp.String(), nil } +// GetQueriesRegexp returns the expanded regular expressions used to match the +// route queries. +// This is useful for building simple REST API documentation and for instrumentation +// against third-party services. +// An empty list will be returned if the route does not have queries. +func (r *Route) GetQueriesRegexp() ([]string, error) { + if r.err != nil { + return nil, r.err + } + if r.regexp == nil || r.regexp.queries == nil { + return nil, errors.New("mux: route doesn't have queries") + } + var queries []string + for _, query := range r.regexp.queries { + queries = append(queries, query.regexp.String()) + } + return queries, nil +} + +// GetQueriesTemplates returns the templates used to build the +// query matching. +// This is useful for building simple REST API documentation and for instrumentation +// against third-party services. +// An empty list will be returned if the route does not define queries. +func (r *Route) GetQueriesTemplates() ([]string, error) { + if r.err != nil { + return nil, r.err + } + if r.regexp == nil || r.regexp.queries == nil { + return nil, errors.New("mux: route doesn't have queries") + } + var queries []string + for _, query := range r.regexp.queries { + queries = append(queries, query.template) + } + return queries, nil +} + // GetMethods returns the methods the route matches against // This is useful for building simple REST API documentation and for instrumentation // against third-party services.