Skip to content

Commit

Permalink
Merge branch 'main' into bug-fix-headers-regexp-doc
Browse files Browse the repository at this point in the history
  • Loading branch information
coreydaley authored Aug 17, 2023
2 parents c37390b + 79f2f45 commit 5e94bf4
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 18 deletions.
15 changes: 3 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,20 +247,11 @@ type spaHandler struct {
// file located at the index path on the SPA handler will be served. This
// is suitable behavior for serving an SPA (single page application).
func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// get the absolute path to prevent directory traversal
path, err := filepath.Abs(r.URL.Path)
if err != nil {
// if we failed to get the absolute path respond with a 400 bad request
// and stop
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

// prepend the path with the path to the static directory
path = filepath.Join(h.staticPath, path)
// Join internally call path.Clean to prevent directory traversal
path := filepath.Join(h.staticPath, path)

// check whether a file exists at the given path
_, err = os.Stat(path)
_, err := os.Stat(path)
if os.IsNotExist(err) {
// file does not exist, serve index.html
http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath))
Expand Down
47 changes: 47 additions & 0 deletions mux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2069,6 +2069,53 @@ func TestNoMatchMethodErrorHandler(t *testing.T) {
}
}

func TestMultipleDefinitionOfSamePathWithDifferentMethods(t *testing.T) {
emptyHandler := func(w http.ResponseWriter, r *http.Request) {}

r := NewRouter()
r.HandleFunc("/api", emptyHandler).Methods("POST")
r.HandleFunc("/api", emptyHandler).Queries("time", "{time:[0-9]+}").Methods("GET")

t.Run("Post Method should be matched properly", func(t *testing.T) {
req, _ := http.NewRequest("POST", "http://localhost/api", nil)
match := new(RouteMatch)
matched := r.Match(req, match)
if !matched {
t.Error("Should have matched route for methods")
}
if match.MatchErr != nil {
t.Error("Should not have any matching error. Found:", match.MatchErr)
}
})

t.Run("Get Method with invalid query value should not match", func(t *testing.T) {
req, _ := http.NewRequest("GET", "http://localhost/api?time=-4", nil)
match := new(RouteMatch)
matched := r.Match(req, match)
if matched {
t.Error("Should not have matched route for methods")
}
if match.MatchErr != ErrNotFound {
t.Error("Should have ErrNotFound error. Found:", match.MatchErr)
}
})

t.Run("A mismach method of a valid path should return ErrMethodMismatch", func(t *testing.T) {
r := NewRouter()
r.HandleFunc("/api2", emptyHandler).Methods("POST")
req, _ := http.NewRequest("GET", "http://localhost/api2", nil)
match := new(RouteMatch)
matched := r.Match(req, match)
if matched {
t.Error("Should not have matched route for methods")
}
if match.MatchErr != ErrMethodMismatch {
t.Error("Should have ErrMethodMismatch error. Found:", match.MatchErr)
}
})

}

func TestErrMatchNotFound(t *testing.T) {
emptyHandler := func(w http.ResponseWriter, r *http.Request) {}

Expand Down
22 changes: 16 additions & 6 deletions route.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool {

matchErr = nil // nolint:ineffassign
return false
} else {
// Multiple routes may share the same path but use different HTTP methods. For instance:
// Route 1: POST "/users/{id}".
// Route 2: GET "/users/{id}", parameters: "id": "[0-9]+".
//
// The router must handle these cases correctly. For a GET request to "/users/abc" with "id" as "-2",
// The router should return a "Not Found" error as no route fully matches this request.
if match.MatchErr == ErrMethodMismatch {
match.MatchErr = nil
}
}
}

Expand Down Expand Up @@ -230,7 +240,7 @@ func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
// Headers adds a matcher for request header values.
// It accepts a sequence of key/value pairs to be matched. For example:
//
// r := mux.NewRouter()
// r := mux.NewRouter().NewRoute()
// r.Headers("Content-Type", "application/json",
// "X-Requested-With", "XMLHttpRequest")
//
Expand Down Expand Up @@ -284,7 +294,7 @@ func (r *Route) HeadersRegexp(pairs ...string) *Route {
//
// For example:
//
// r := mux.NewRouter()
// r := mux.NewRouter().NewRoute()
// r.Host("www.example.com")
// r.Host("{subdomain}.domain.com")
// r.Host("{subdomain:[a-z]+}.domain.com")
Expand Down Expand Up @@ -343,7 +353,7 @@ func (r *Route) Methods(methods ...string) *Route {
//
// For example:
//
// r := mux.NewRouter()
// r := mux.NewRouter().NewRoute()
// r.Path("/products/").Handler(ProductsHandler)
// r.Path("/products/{key}").Handler(ProductsHandler)
// r.Path("/articles/{category}/{id:[0-9]+}").
Expand Down Expand Up @@ -378,7 +388,7 @@ func (r *Route) PathPrefix(tpl string) *Route {
// It accepts a sequence of key/value pairs. Values may define variables.
// For example:
//
// r := mux.NewRouter()
// r := mux.NewRouter().NewRoute()
// r.Queries("foo", "bar", "id", "{id:[0-9]+}")
//
// The above route will only match if the URL contains the defined queries
Expand Down Expand Up @@ -474,7 +484,7 @@ func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
//
// It will test the inner routes only if the parent route matched. For example:
//
// r := mux.NewRouter()
// r := mux.NewRouter().NewRoute()
// s := r.Host("www.example.com").Subrouter()
// s.HandleFunc("/products/", ProductsHandler)
// s.HandleFunc("/products/{key}", ProductHandler)
Expand Down Expand Up @@ -525,7 +535,7 @@ func (r *Route) Subrouter() *Router {
// The scheme of the resulting url will be the first argument that was passed to Schemes:
//
// // url.String() will be "https://example.com"
// r := mux.NewRouter()
// r := mux.NewRouter().NewRoute()
// url, err := r.Host("example.com")
// .Schemes("https", "http").URL()
//
Expand Down

0 comments on commit 5e94bf4

Please sign in to comment.