From fa25f03885e68990af694fcc394539152126b1c8 Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Wed, 1 Jun 2022 15:04:14 +0530 Subject: [PATCH 01/22] add test case negative and positive for `SPA` filepath.Abs err on windows --- mux_httpserver_test.go | 158 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/mux_httpserver_test.go b/mux_httpserver_test.go index 907ab91d..8faf75bd 100644 --- a/mux_httpserver_test.go +++ b/mux_httpserver_test.go @@ -8,9 +8,92 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "os" + "path/filepath" + "runtime" + "strings" "testing" ) +// spaHandler implements the http.Handler interface, so we can use it +// to respond to HTTP requests. The path to the static directory and +// path to the index file within that static directory are used to +// serve the SPA in the given static directory. +type spaHandler struct { + staticPath string + indexPath string +} + +// FilepathAbsServeHTTP inspects the URL path to locate a file within the static dir +// on the SPA handler. If a file is found, it will be served. If not, the +// file located at the index path on the SPA handler will be served. This +// is suitable behavior for serving an SPA (single page application). +// This is a negative test case where `filepath.Abs` will return path value like `D:\` +// if our route is `/`. As per docs: Abs returns an absolute representation of path. If the path is not absolute it will be joined with the current working directory to turn it into an absolute path. The absolute path name for a given file is not guaranteed to be unique. Abs calls Clean on the result. +func (h spaHandler) FilepathAbsServeHTTP(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) + + // check whether a file exists at the given 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)) + return + } else if err != nil { + // if we got an error (that wasn't that the file doesn't exist) stating the + // file, return a 500 internal server error and stop + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // otherwise, use http.FileServer to serve the static dir + http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r) +} + +// ServeHTTP inspects the URL path to locate a file within the static dir +// on the SPA handler. If a file is found, it will be served. If not, the +// 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 + var err error + path := filepath.Join(h.staticPath, 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 + } + + // check whether a file exists at the given 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)) + return + } else if err != nil { + // if we got an error (that wasn't that the file doesn't exist) stating the + // file, return a 500 internal server error and stop + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // otherwise, use http.FileServer to serve the static dir + http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r) +} + func TestSchemeMatchers(t *testing.T) { router := NewRouter() router.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { @@ -48,3 +131,78 @@ func TestSchemeMatchers(t *testing.T) { assertResponseBody(t, s, "hello https world") }) } + +func TestServeHttpFilepathAbs(t *testing.T) { + // create a diretory name `build` + os.Mkdir("build", 0700) + + // create a file `index.html` and write below content + htmlContent := []byte(`helloworld`) + err := os.WriteFile("./build/index.html", htmlContent, 0700) + if err != nil { + t.Fatal(err) + } + + // make new request + req, err := http.NewRequest("GET", "/", nil) + if err != nil { + t.Fatal(err) + } + + // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. + rr := httptest.NewRecorder() + spa := spaHandler{staticPath: "./build", indexPath: "index.html"} + spa.FilepathAbsServeHTTP(rr, req) + + status := rr.Code + if runtime.GOOS != "windows" && status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } else if runtime.GOOS == "windows" && rr.Code != http.StatusInternalServerError { + t.Errorf("handler returned wrong status code in case of windows: got %v want %v", + status, http.StatusOK) + } + + // Check the response body is what we expect. + if runtime.GOOS != "windows" && rr.Body.String() != string(htmlContent) { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), string(htmlContent)) + } else if runtime.GOOS == "windows" && strings.Contains(rr.Body.String(), "syntax is incorrect.") { + t.Errorf("handler returned unexpected body in case of windows: got %v want %v", + rr.Body.String(), string(htmlContent)) + } +} + +func TestServeHttpFilepathJoin(t *testing.T) { + // create a diretory name `build` + os.Mkdir("build", 0700) + + // create a file `index.html` and write below content + htmlContent := []byte(`helloworld`) + err := os.WriteFile("./build/index.html", htmlContent, 0700) + if err != nil { + t.Fatal(err) + } + + // make new request + req, err := http.NewRequest("GET", "/", nil) + if err != nil { + t.Fatal(err) + } + + // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. + rr := httptest.NewRecorder() + spa := spaHandler{staticPath: "./build", indexPath: "index.html"} + spa.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + // Check the response body is what we expect. + if rr.Body.String() != string(htmlContent) { + t.Errorf("handler returned unexpected body: got %v want %v", + rr.Body.String(), string(htmlContent)) + } +} From eee21669a12a642e6ece7ba5f4172acc9f6dd44b Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Wed, 1 Jun 2022 23:23:31 +0530 Subject: [PATCH 02/22] fix comments wrap-lines and uncomment code --- mux_httpserver_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mux_httpserver_test.go b/mux_httpserver_test.go index 8faf75bd..95cccf3e 100644 --- a/mux_httpserver_test.go +++ b/mux_httpserver_test.go @@ -29,7 +29,10 @@ 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). // This is a negative test case where `filepath.Abs` will return path value like `D:\` -// if our route is `/`. As per docs: Abs returns an absolute representation of path. If the path is not absolute it will be joined with the current working directory to turn it into an absolute path. The absolute path name for a given file is not guaranteed to be unique. Abs calls Clean on the result. +// if our route is `/`. As per docs: Abs returns an absolute representation of path. +// If the path is not absolute it will be joined with the current working directory to turn +// it into an absolute path. The absolute path name for a given file is not guaranteed to +// be unique. Abs calls Clean on the result. func (h spaHandler) FilepathAbsServeHTTP(w http.ResponseWriter, r *http.Request) { // get the absolute path to prevent directory traversal path, err := filepath.Abs(r.URL.Path) @@ -41,7 +44,7 @@ func (h spaHandler) FilepathAbsServeHTTP(w http.ResponseWriter, r *http.Request) } // prepend the path with the path to the static directory - // path = filepath.Join(h.staticPath, path) + path = filepath.Join(h.staticPath, path) // check whether a file exists at the given path _, err = os.Stat(path) @@ -66,8 +69,8 @@ func (h spaHandler) FilepathAbsServeHTTP(w http.ResponseWriter, r *http.Request) // 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 var err error + // internally calls path.Clean path to prevent directory traversal path := filepath.Join(h.staticPath, r.URL.Path) if err != nil { // if we failed to get the absolute path respond with a 400 bad request From 4a15a9b1d48421c8f47514995acf315d29e02ea5 Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Wed, 1 Jun 2022 23:37:06 +0530 Subject: [PATCH 03/22] fix condition of test case to check if not contain substring --- mux_httpserver_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mux_httpserver_test.go b/mux_httpserver_test.go index 95cccf3e..662b3337 100644 --- a/mux_httpserver_test.go +++ b/mux_httpserver_test.go @@ -170,7 +170,7 @@ func TestServeHttpFilepathAbs(t *testing.T) { if runtime.GOOS != "windows" && rr.Body.String() != string(htmlContent) { t.Errorf("handler returned unexpected body: got %v want %v", rr.Body.String(), string(htmlContent)) - } else if runtime.GOOS == "windows" && strings.Contains(rr.Body.String(), "syntax is incorrect.") { + } else if runtime.GOOS == "windows" && !strings.Contains(rr.Body.String(), "syntax is incorrect.") { t.Errorf("handler returned unexpected body in case of windows: got %v want %v", rr.Body.String(), string(htmlContent)) } From e5c7656e774499ed62ea29f5328c050f4fe095a7 Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Wed, 1 Jun 2022 23:40:15 +0530 Subject: [PATCH 04/22] fix `README.md` `Single Page Application` section to avoid err in windows --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c7e6872b..8e8b49ad 100644 --- a/README.md +++ b/README.md @@ -251,8 +251,9 @@ 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) + var err error + // Join internally call path.Clean to prevent directory traversal + path = filepath.Join(h.staticPath, path) if err != nil { // if we failed to get the absolute path respond with a 400 bad request // and stop @@ -260,9 +261,6 @@ func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - // prepend the path with the path to the static directory - path = filepath.Join(h.staticPath, path) - // check whether a file exists at the given path _, err = os.Stat(path) if os.IsNotExist(err) { From d0860538d0f5086f614a1f7b0f996774db50dbe6 Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Wed, 1 Jun 2022 23:44:16 +0530 Subject: [PATCH 05/22] fix indentation `README.md` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e8b49ad..121868df 100644 --- a/README.md +++ b/README.md @@ -251,7 +251,7 @@ 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) { - var err error + var err error // Join internally call path.Clean to prevent directory traversal path = filepath.Join(h.staticPath, path) if err != nil { From 36aa408612796078ba8a4870f45c4b693a041c43 Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Wed, 1 Jun 2022 23:56:49 +0530 Subject: [PATCH 06/22] replace WriteFile package to fix test case for older go versions --- mux_httpserver_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mux_httpserver_test.go b/mux_httpserver_test.go index 662b3337..4867415c 100644 --- a/mux_httpserver_test.go +++ b/mux_httpserver_test.go @@ -141,7 +141,7 @@ func TestServeHttpFilepathAbs(t *testing.T) { // create a file `index.html` and write below content htmlContent := []byte(`helloworld`) - err := os.WriteFile("./build/index.html", htmlContent, 0700) + err := ioutil.WriteFile("./build/index.html", htmlContent, 0700) if err != nil { t.Fatal(err) } @@ -182,7 +182,7 @@ func TestServeHttpFilepathJoin(t *testing.T) { // create a file `index.html` and write below content htmlContent := []byte(`helloworld`) - err := os.WriteFile("./build/index.html", htmlContent, 0700) + err := ioutil.WriteFile("./build/index.html", htmlContent, 0700) if err != nil { t.Fatal(err) } From d308dd34c56e9c7f5095538b44d5dbfee3b4be28 Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Fri, 3 Jun 2022 21:01:57 +0530 Subject: [PATCH 07/22] remove err condtion after filepath.Join statement --- README.md | 9 +-- mux_httpserver_test.go | 161 ----------------------------------------- 2 files changed, 1 insertion(+), 169 deletions(-) diff --git a/README.md b/README.md index 121868df..f9d09c31 100644 --- a/README.md +++ b/README.md @@ -251,18 +251,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) { - var err error // Join internally call path.Clean to prevent directory traversal path = filepath.Join(h.staticPath, 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 - } // 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)) diff --git a/mux_httpserver_test.go b/mux_httpserver_test.go index 4867415c..907ab91d 100644 --- a/mux_httpserver_test.go +++ b/mux_httpserver_test.go @@ -8,95 +8,9 @@ import ( "io/ioutil" "net/http" "net/http/httptest" - "os" - "path/filepath" - "runtime" - "strings" "testing" ) -// spaHandler implements the http.Handler interface, so we can use it -// to respond to HTTP requests. The path to the static directory and -// path to the index file within that static directory are used to -// serve the SPA in the given static directory. -type spaHandler struct { - staticPath string - indexPath string -} - -// FilepathAbsServeHTTP inspects the URL path to locate a file within the static dir -// on the SPA handler. If a file is found, it will be served. If not, the -// file located at the index path on the SPA handler will be served. This -// is suitable behavior for serving an SPA (single page application). -// This is a negative test case where `filepath.Abs` will return path value like `D:\` -// if our route is `/`. As per docs: Abs returns an absolute representation of path. -// If the path is not absolute it will be joined with the current working directory to turn -// it into an absolute path. The absolute path name for a given file is not guaranteed to -// be unique. Abs calls Clean on the result. -func (h spaHandler) FilepathAbsServeHTTP(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) - - // check whether a file exists at the given 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)) - return - } else if err != nil { - // if we got an error (that wasn't that the file doesn't exist) stating the - // file, return a 500 internal server error and stop - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // otherwise, use http.FileServer to serve the static dir - http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r) -} - -// ServeHTTP inspects the URL path to locate a file within the static dir -// on the SPA handler. If a file is found, it will be served. If not, the -// 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) { - var err error - // internally calls path.Clean path to prevent directory traversal - path := filepath.Join(h.staticPath, 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 - } - - // check whether a file exists at the given 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)) - return - } else if err != nil { - // if we got an error (that wasn't that the file doesn't exist) stating the - // file, return a 500 internal server error and stop - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // otherwise, use http.FileServer to serve the static dir - http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r) -} - func TestSchemeMatchers(t *testing.T) { router := NewRouter() router.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { @@ -134,78 +48,3 @@ func TestSchemeMatchers(t *testing.T) { assertResponseBody(t, s, "hello https world") }) } - -func TestServeHttpFilepathAbs(t *testing.T) { - // create a diretory name `build` - os.Mkdir("build", 0700) - - // create a file `index.html` and write below content - htmlContent := []byte(`helloworld`) - err := ioutil.WriteFile("./build/index.html", htmlContent, 0700) - if err != nil { - t.Fatal(err) - } - - // make new request - req, err := http.NewRequest("GET", "/", nil) - if err != nil { - t.Fatal(err) - } - - // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. - rr := httptest.NewRecorder() - spa := spaHandler{staticPath: "./build", indexPath: "index.html"} - spa.FilepathAbsServeHTTP(rr, req) - - status := rr.Code - if runtime.GOOS != "windows" && status != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", - status, http.StatusOK) - } else if runtime.GOOS == "windows" && rr.Code != http.StatusInternalServerError { - t.Errorf("handler returned wrong status code in case of windows: got %v want %v", - status, http.StatusOK) - } - - // Check the response body is what we expect. - if runtime.GOOS != "windows" && rr.Body.String() != string(htmlContent) { - t.Errorf("handler returned unexpected body: got %v want %v", - rr.Body.String(), string(htmlContent)) - } else if runtime.GOOS == "windows" && !strings.Contains(rr.Body.String(), "syntax is incorrect.") { - t.Errorf("handler returned unexpected body in case of windows: got %v want %v", - rr.Body.String(), string(htmlContent)) - } -} - -func TestServeHttpFilepathJoin(t *testing.T) { - // create a diretory name `build` - os.Mkdir("build", 0700) - - // create a file `index.html` and write below content - htmlContent := []byte(`helloworld`) - err := ioutil.WriteFile("./build/index.html", htmlContent, 0700) - if err != nil { - t.Fatal(err) - } - - // make new request - req, err := http.NewRequest("GET", "/", nil) - if err != nil { - t.Fatal(err) - } - - // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. - rr := httptest.NewRecorder() - spa := spaHandler{staticPath: "./build", indexPath: "index.html"} - spa.ServeHTTP(rr, req) - - if status := rr.Code; status != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", - status, http.StatusOK) - } - - // Check the response body is what we expect. - if rr.Body.String() != string(htmlContent) { - t.Errorf("handler returned unexpected body: got %v want %v", - rr.Body.String(), string(htmlContent)) - } -} From a3df0f4e4ba0646aa3dc070105abc44d272f5039 Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Wed, 10 Aug 2022 22:09:42 +0530 Subject: [PATCH 08/22] fix `path` var initialisation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f9d09c31..0daef68b 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,7 @@ type spaHandler struct { // is suitable behavior for serving an SPA (single page application). func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Join internally call path.Clean to prevent directory traversal - path = filepath.Join(h.staticPath, path) + path := filepath.Join(h.staticPath, path) // check whether a file exists at the given path _, err := os.Stat(path) From 07eedffb4388b4ed26b86c67aedca1e513e7553b Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Thu, 18 Aug 2022 02:19:02 +0530 Subject: [PATCH 09/22] [docs] `authenticationMiddleware` initialization in the `README.md` file (#693) * initial commit * fix `README.md` - authenticationMiddleware initialization --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c7e6872b..f09a5b35 100644 --- a/README.md +++ b/README.md @@ -576,7 +576,7 @@ func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler r := mux.NewRouter() r.HandleFunc("/", handler) -amw := authenticationMiddleware{} +amw := authenticationMiddleware{tokenUsers: make(map[string]string)} amw.Populate() r.Use(amw.Middleware) From 5e1e8c8d45ad101414d06762b0f7f6200babc929 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Fri, 9 Dec 2022 10:56:37 -0500 Subject: [PATCH 10/22] archive mode --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f09a5b35..35aa29b6 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ --- -⚠️ **[The Gorilla Toolkit is looking for a new maintainer](https://github.com/gorilla/mux/issues/659)** +The Gorilla project has been archived, and is no longer under active maintainenance. You can read more here: https://github.com/gorilla#gorilla-toolkit --- From eb99d7a67714bbab6db27f896a8c8c947b51b610 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Fri, 9 Dec 2022 10:56:57 -0500 Subject: [PATCH 11/22] archive mode --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 35aa29b6..64a20e7d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ --- -The Gorilla project has been archived, and is no longer under active maintainenance. You can read more here: https://github.com/gorilla#gorilla-toolkit +**The Gorilla project has been archived, and is no longer under active maintainenance. You can read more here: https://github.com/gorilla#gorilla-toolkit** --- From 7456b4e9ae7b67e27ffaa2e497941e77d39bc884 Mon Sep 17 00:00:00 2001 From: Corey Daley Date: Sat, 15 Jul 2023 10:48:51 -0400 Subject: [PATCH 12/22] Update README.md Signed-off-by: Corey Daley --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 64a20e7d..f836a4e7 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,6 @@ ![Gorilla Logo](https://cloud-cdn.questionable.services/gorilla-icon-64.png) ---- - -**The Gorilla project has been archived, and is no longer under active maintainenance. You can read more here: https://github.com/gorilla#gorilla-toolkit** - ---- - Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to their respective handler. From 1fa2ee87bf61940bfac616e98513448af97a1813 Mon Sep 17 00:00:00 2001 From: Apoorva Jagtap <35304110+apoorvajagtap@users.noreply.github.com> Date: Mon, 24 Jul 2023 01:45:04 +0530 Subject: [PATCH 13/22] [GPT-95] Update go version, add tools for verification and testing (#718) Fixes # https://gorilla-web-toolkit.atlassian.net/browse/GPT-95 **Summary of Changes** Added `.github/workflows/test.yml` that runs golangci-lint & go tests on any PR created or each push to main branch. --------- Signed-off-by: Corey Daley Signed-off-by: Apoorva Jagtap <35304110+apoorvajagtap@users.noreply.github.com> Co-authored-by: Corey Daley --- .circleci/config.yml | 70 -------------------------- .editorconfig | 20 ++++++++ .github/workflows/issues.yml | 20 ++++++++ .github/workflows/test.yml | 55 +++++++++++++++++++++ .gitignore | 1 + Makefile | 34 +++++++++++++ README.md | 8 +-- go.mod | 2 +- middleware_test.go | 95 ++++++++++++++++++++++++++++-------- mux_httpserver_test.go | 14 ++++-- mux_test.go | 20 ++++++-- regexp.go | 2 +- regexp_test.go | 4 +- route.go | 80 +++++++++++++++--------------- 14 files changed, 280 insertions(+), 145 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .editorconfig create mode 100644 .github/workflows/issues.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 Makefile diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index ead3e1d4..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,70 +0,0 @@ -version: 2.1 - -jobs: - "test": - parameters: - version: - type: string - default: "latest" - golint: - type: boolean - default: true - modules: - type: boolean - default: true - goproxy: - type: string - default: "" - docker: - - image: "circleci/golang:<< parameters.version >>" - working_directory: /go/src/github.com/gorilla/mux - environment: - GO111MODULE: "on" - GOPROXY: "<< parameters.goproxy >>" - steps: - - checkout - - run: - name: "Print the Go version" - command: > - go version - - run: - name: "Fetch dependencies" - command: > - if [[ << parameters.modules >> = true ]]; then - go mod download - export GO111MODULE=on - else - go get -v ./... - fi - # Only run gofmt, vet & lint against the latest Go version - - run: - name: "Run golint" - command: > - if [ << parameters.version >> = "latest" ] && [ << parameters.golint >> = true ]; then - go get -u golang.org/x/lint/golint - golint ./... - fi - - run: - name: "Run gofmt" - command: > - if [[ << parameters.version >> = "latest" ]]; then - diff -u <(echo -n) <(gofmt -d -e .) - fi - - run: - name: "Run go vet" - command: > - if [[ << parameters.version >> = "latest" ]]; then - go vet -v ./... - fi - - run: - name: "Run go test (+ race detector)" - command: > - go test -v -race ./... - -workflows: - tests: - jobs: - - test: - matrix: - parameters: - version: ["latest", "1.15", "1.14", "1.13", "1.12", "1.11"] diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..c6b74c3e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +; https://editorconfig.org/ + +root = true + +[*] +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[{Makefile,go.mod,go.sum,*.go,.gitmodules}] +indent_style = tab +indent_size = 4 + +[*.md] +indent_size = 4 +trim_trailing_whitespace = false + +eclint_indent_style = unset \ No newline at end of file diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml new file mode 100644 index 00000000..055ca822 --- /dev/null +++ b/.github/workflows/issues.yml @@ -0,0 +1,20 @@ +# Add issues or pull-requests created to the project. +name: Add issue or pull request to Project + +on: + issues: + types: + - opened + pull_request: + types: + - opened + +jobs: + add-to-project: + runs-on: ubuntu-latest + steps: + - name: Add issue to project + uses: actions/add-to-project@v0.5.0 + with: + project-url: https://github.com/orgs/gorilla/projects/4 + github-token: ${{ secrets.ADD_TO_PROJECT_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..af48d228 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,55 @@ +name: CI +on: + push: + branches: + - main + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + verify-and-test: + strategy: + matrix: + go: ['1.19','1.20'] + os: [ubuntu-latest, macos-latest, windows-latest] + fail-fast: true + runs-on: ${{ matrix.os }} + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Setup Go ${{ matrix.go }} + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go }} + cache: false + + - name: Run GolangCI-Lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.53 + args: --timeout=5m + + - name: Run GoSec + if: matrix.os == 'ubuntu-latest' + uses: securego/gosec@master + with: + args: ./... + + - name: Run GoVulnCheck + uses: golang/govulncheck-action@v1 + with: + go-version-input: ${{ matrix.go }} + go-package: ./... + + - name: Run Tests + run: go test -race -cover -coverprofile=coverage -covermode=atomic -v ./... + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + files: ./coverage \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..84039fec --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +coverage.coverprofile diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..98f5ab75 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '') +GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest + +GO_SEC=$(shell which gosec 2> /dev/null || echo '') +GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest + +GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '') +GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest + +.PHONY: golangci-lint +golangci-lint: + $(if $(GO_LINT), ,go install $(GO_LINT_URI)) + @echo "##### Running golangci-lint" + golangci-lint run -v + +.PHONY: gosec +gosec: + $(if $(GO_SEC), ,go install $(GO_SEC_URI)) + @echo "##### Running gosec" + gosec ./... + +.PHONY: govulncheck +govulncheck: + $(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI)) + @echo "##### Running govulncheck" + govulncheck ./... + +.PHONY: verify +verify: golangci-lint gosec govulncheck + +.PHONY: test +test: + @echo "##### Running tests" + go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./... \ No newline at end of file diff --git a/README.md b/README.md index f836a4e7..551f3c67 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # gorilla/mux -[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux) -[![CircleCI](https://circleci.com/gh/gorilla/mux.svg?style=svg)](https://circleci.com/gh/gorilla/mux) -[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge) +![testing](https://github.com/gorilla/mux/actions/workflows/test.yml/badge.svg) +[![codecov](https://codecov.io/github/gorilla/mux/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/mux) +[![godoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux) +[![sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge) + ![Gorilla Logo](https://cloud-cdn.questionable.services/gorilla-icon-64.png) diff --git a/go.mod b/go.mod index df170a39..7bcfa026 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/gorilla/mux -go 1.12 +go 1.19 diff --git a/middleware_test.go b/middleware_test.go index e9f0ef55..4963b66f 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -158,7 +158,10 @@ func TestMiddlewareExecution(t *testing.T) { router := NewRouter() router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { - w.Write(handlerStr) + _, err := w.Write(handlerStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) t.Run("responds normally without middleware", func(t *testing.T) { @@ -178,7 +181,10 @@ func TestMiddlewareExecution(t *testing.T) { router.Use(func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(mwStr) + _, err := w.Write(mwStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } h.ServeHTTP(w, r) }) }) @@ -196,11 +202,17 @@ func TestMiddlewareNotFound(t *testing.T) { router := NewRouter() router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { - w.Write(handlerStr) + _, err := w.Write(handlerStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) router.Use(func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(mwStr) + _, err := w.Write(mwStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } h.ServeHTTP(w, r) }) }) @@ -221,7 +233,10 @@ func TestMiddlewareNotFound(t *testing.T) { req := newRequest("GET", "/notfound") router.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - rw.Write([]byte("Custom 404 handler")) + _, err := rw.Write([]byte("Custom 404 handler")) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) router.ServeHTTP(rw, req) @@ -237,12 +252,18 @@ func TestMiddlewareMethodMismatch(t *testing.T) { router := NewRouter() router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { - w.Write(handlerStr) + _, err := w.Write(handlerStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }).Methods("GET") router.Use(func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(mwStr) + _, err := w.Write(mwStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } h.ServeHTTP(w, r) }) }) @@ -262,7 +283,10 @@ func TestMiddlewareMethodMismatch(t *testing.T) { req := newRequest("POST", "/") router.MethodNotAllowedHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - rw.Write([]byte("Method not allowed")) + _, err := rw.Write([]byte("Method not allowed")) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) router.ServeHTTP(rw, req) @@ -278,17 +302,26 @@ func TestMiddlewareNotFoundSubrouter(t *testing.T) { router := NewRouter() router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { - w.Write(handlerStr) + _, err := w.Write(handlerStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) subrouter := router.PathPrefix("/sub/").Subrouter() subrouter.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { - w.Write(handlerStr) + _, err := w.Write(handlerStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) router.Use(func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(mwStr) + _, err := w.Write(mwStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } h.ServeHTTP(w, r) }) }) @@ -308,7 +341,10 @@ func TestMiddlewareNotFoundSubrouter(t *testing.T) { req := newRequest("GET", "/sub/notfound") subrouter.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - rw.Write([]byte("Custom 404 handler")) + _, err := rw.Write([]byte("Custom 404 handler")) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) router.ServeHTTP(rw, req) @@ -324,17 +360,26 @@ func TestMiddlewareMethodMismatchSubrouter(t *testing.T) { router := NewRouter() router.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { - w.Write(handlerStr) + _, err := w.Write(handlerStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) subrouter := router.PathPrefix("/sub/").Subrouter() subrouter.HandleFunc("/", func(w http.ResponseWriter, e *http.Request) { - w.Write(handlerStr) + _, err := w.Write(handlerStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }).Methods("GET") router.Use(func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(mwStr) + _, err := w.Write(mwStr) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } h.ServeHTTP(w, r) }) }) @@ -354,7 +399,10 @@ func TestMiddlewareMethodMismatchSubrouter(t *testing.T) { req := newRequest("POST", "/sub/") router.MethodNotAllowedHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - rw.Write([]byte("Method not allowed")) + _, err := rw.Write([]byte("Method not allowed")) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) router.ServeHTTP(rw, req) @@ -508,7 +556,10 @@ func TestMiddlewareOnMultiSubrouter(t *testing.T) { secondSubRouter := router.PathPrefix("/").Subrouter() router.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - rw.Write([]byte(notFound)) + _, err := rw.Write([]byte(notFound)) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }) firstSubRouter.HandleFunc("/first", func(w http.ResponseWriter, r *http.Request) { @@ -521,14 +572,20 @@ func TestMiddlewareOnMultiSubrouter(t *testing.T) { firstSubRouter.Use(func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(first)) + _, err := w.Write([]byte(first)) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } h.ServeHTTP(w, r) }) }) secondSubRouter.Use(func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(second)) + _, err := w.Write([]byte(second)) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } h.ServeHTTP(w, r) }) }) diff --git a/mux_httpserver_test.go b/mux_httpserver_test.go index 907ab91d..f55a2de3 100644 --- a/mux_httpserver_test.go +++ b/mux_httpserver_test.go @@ -5,7 +5,7 @@ package mux import ( "bytes" - "io/ioutil" + "io" "net/http" "net/http/httptest" "testing" @@ -14,10 +14,16 @@ import ( func TestSchemeMatchers(t *testing.T) { router := NewRouter() router.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { - rw.Write([]byte("hello http world")) + _, err := rw.Write([]byte("hello http world")) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }).Schemes("http") router.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { - rw.Write([]byte("hello https world")) + _, err := rw.Write([]byte("hello https world")) + if err != nil { + t.Fatalf("Failed writing HTTP response: %v", err) + } }).Schemes("https") assertResponseBody := func(t *testing.T, s *httptest.Server, expectedBody string) { @@ -28,7 +34,7 @@ func TestSchemeMatchers(t *testing.T) { if resp.StatusCode != 200 { t.Fatalf("expected a status code of 200, got %v", resp.StatusCode) } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("unexpected error reading body: %v", err) } diff --git a/mux_test.go b/mux_test.go index 2d8d2b3e..4345254b 100644 --- a/mux_test.go +++ b/mux_test.go @@ -10,7 +10,8 @@ import ( "context" "errors" "fmt" - "io/ioutil" + "io" + "log" "net/http" "net/http/httptest" "net/url" @@ -2136,7 +2137,10 @@ type methodsSubrouterTest struct { // methodHandler writes the method string in response. func methodHandler(method string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(method)) + _, err := w.Write([]byte(method)) + if err != nil { + log.Printf("Failed writing HTTP response: %v", err) + } } } @@ -2778,7 +2782,7 @@ func TestSubrouterCustomMethodNotAllowed(t *testing.T) { tt.Errorf("Expected status code 405 (got %d)", w.Code) } - b, err := ioutil.ReadAll(w.Body) + b, err := io.ReadAll(w.Body) if err != nil { tt.Errorf("failed to read body: %v", err) } @@ -2859,7 +2863,10 @@ func stringMapEqual(m1, m2 map[string]string) bool { // http.ResponseWriter. func stringHandler(s string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(s)) + _, err := w.Write([]byte(s)) + if err != nil { + log.Printf("Failed writing HTTP response: %v", err) + } } } @@ -2892,7 +2899,10 @@ func newRequest(method, url string) *http.Request { // Simulate writing to wire var buff bytes.Buffer - req.Write(&buff) + err = req.Write(&buff) + if err != nil { + log.Printf("Failed writing HTTP request: %v", err) + } ioreader := bufio.NewReader(&buff) // Parse request off of 'wire' diff --git a/regexp.go b/regexp.go index 37c11edc..5d05cfa0 100644 --- a/regexp.go +++ b/regexp.go @@ -195,7 +195,7 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { // url builds a URL part using the given values. func (r *routeRegexp) url(values map[string]string) (string, error) { - urlValues := make([]interface{}, len(r.varsN), len(r.varsN)) + urlValues := make([]interface{}, len(r.varsN)) for k, v := range r.varsN { value, ok := values[v] if !ok { diff --git a/regexp_test.go b/regexp_test.go index 0d80e6a5..d7518f3e 100644 --- a/regexp_test.go +++ b/regexp_test.go @@ -54,7 +54,7 @@ func Benchmark_findQueryKey(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - for key, _ := range all { + for key := range all { _, _ = findFirstQueryKey(query, key) } } @@ -79,7 +79,7 @@ func Benchmark_findQueryKeyGoLib(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - for key, _ := range all { + for key := range all { v := u.Query()[key] if len(v) > 0 { _ = v[0] diff --git a/route.go b/route.go index 750afe57..ce1c9bfe 100644 --- a/route.go +++ b/route.go @@ -64,7 +64,7 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool { match.MatchErr = nil } - matchErr = nil + matchErr = nil // nolint:ineffassign return false } } @@ -230,9 +230,9 @@ 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.Headers("Content-Type", "application/json", -// "X-Requested-With", "XMLHttpRequest") +// r := mux.NewRouter() +// r.Headers("Content-Type", "application/json", +// "X-Requested-With", "XMLHttpRequest") // // The above route will only match if both request header values match. // If the value is an empty string, it will match any value if the key is set. @@ -255,9 +255,9 @@ func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool { // HeadersRegexp accepts a sequence of key/value pairs, where the value has regex // support. For example: // -// r := mux.NewRouter() -// r.HeadersRegexp("Content-Type", "application/(text|json)", -// "X-Requested-With", "XMLHttpRequest") +// r := mux.NewRouter() +// r.HeadersRegexp("Content-Type", "application/(text|json)", +// "X-Requested-With", "XMLHttpRequest") // // The above route will only match if both the request header matches both regular expressions. // If the value is an empty string, it will match any value if the key is set. @@ -283,10 +283,10 @@ func (r *Route) HeadersRegexp(pairs ...string) *Route { // // For example: // -// r := mux.NewRouter() -// r.Host("www.example.com") -// r.Host("{subdomain}.domain.com") -// r.Host("{subdomain:[a-z]+}.domain.com") +// r := mux.NewRouter() +// r.Host("www.example.com") +// r.Host("{subdomain}.domain.com") +// r.Host("{subdomain:[a-z]+}.domain.com") // // Variable names must be unique in a given route. They can be retrieved // calling mux.Vars(request). @@ -342,11 +342,11 @@ func (r *Route) Methods(methods ...string) *Route { // // For example: // -// r := mux.NewRouter() -// r.Path("/products/").Handler(ProductsHandler) -// r.Path("/products/{key}").Handler(ProductsHandler) -// r.Path("/articles/{category}/{id:[0-9]+}"). -// Handler(ArticleHandler) +// r := mux.NewRouter() +// r.Path("/products/").Handler(ProductsHandler) +// r.Path("/products/{key}").Handler(ProductsHandler) +// r.Path("/articles/{category}/{id:[0-9]+}"). +// Handler(ArticleHandler) // // Variable names must be unique in a given route. They can be retrieved // calling mux.Vars(request). @@ -377,8 +377,8 @@ 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.Queries("foo", "bar", "id", "{id:[0-9]+}") +// r := mux.NewRouter() +// r.Queries("foo", "bar", "id", "{id:[0-9]+}") // // The above route will only match if the URL contains the defined queries // values, e.g.: ?foo=bar&id=42. @@ -473,11 +473,11 @@ func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route { // // It will test the inner routes only if the parent route matched. For example: // -// r := mux.NewRouter() -// s := r.Host("www.example.com").Subrouter() -// s.HandleFunc("/products/", ProductsHandler) -// s.HandleFunc("/products/{key}", ProductHandler) -// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) +// r := mux.NewRouter() +// s := r.Host("www.example.com").Subrouter() +// s.HandleFunc("/products/", ProductsHandler) +// s.HandleFunc("/products/{key}", ProductHandler) +// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) // // Here, the routes registered in the subrouter won't be tested if the host // doesn't match. @@ -497,36 +497,36 @@ func (r *Route) Subrouter() *Router { // It accepts a sequence of key/value pairs for the route variables. For // example, given this route: // -// r := mux.NewRouter() -// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). -// Name("article") +// r := mux.NewRouter() +// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). +// Name("article") // // ...a URL for it can be built using: // -// url, err := r.Get("article").URL("category", "technology", "id", "42") +// url, err := r.Get("article").URL("category", "technology", "id", "42") // // ...which will return an url.URL with the following path: // -// "/articles/technology/42" +// "/articles/technology/42" // // This also works for host variables: // -// r := mux.NewRouter() -// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). -// Host("{subdomain}.domain.com"). -// Name("article") +// r := mux.NewRouter() +// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). +// Host("{subdomain}.domain.com"). +// Name("article") // -// // url.String() will be "http://news.domain.com/articles/technology/42" -// url, err := r.Get("article").URL("subdomain", "news", -// "category", "technology", -// "id", "42") +// // url.String() will be "http://news.domain.com/articles/technology/42" +// url, err := r.Get("article").URL("subdomain", "news", +// "category", "technology", +// "id", "42") // // 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() -// url, err := r.Host("example.com") -// .Schemes("https", "http").URL() +// // url.String() will be "https://example.com" +// r := mux.NewRouter() +// url, err := r.Host("example.com") +// .Schemes("https", "http").URL() // // All variables defined in the route are required, and their values must // conform to the corresponding patterns. From 96847b8b06f990d7b53c8a5c647ee73d3e70582a Mon Sep 17 00:00:00 2001 From: Corey Daley Date: Sun, 23 Jul 2023 16:27:23 -0400 Subject: [PATCH 14/22] Delete release-drafter.yml (#719) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What type of PR is this? (check all applicable) - [ ] Refactor - [ ] Feature - [ ] Bug Fix - [ ] Optimization - [ ] Documentation Update ## Description ## Related Tickets & Documents - Related Issue # - Closes # ## Added/updated tests? - [ ] Yes - [ ] No, and this is why: _please replace this line with details on why tests have not been included_ - [ ] I need help with writing tests ## Run verifications and test - [ ] `make verify` is passing - [ ] `make test` is passing Signed-off-by: Corey Daley --- .github/release-drafter.yml | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .github/release-drafter.yml diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml deleted file mode 100644 index 2db2e139..00000000 --- a/.github/release-drafter.yml +++ /dev/null @@ -1,8 +0,0 @@ -# Config for https://github.com/apps/release-drafter -template: | - - - - ## CHANGELOG - - $CHANGES From 81b48a39f012296f51d582d3cefaaba1c2f5a01f Mon Sep 17 00:00:00 2001 From: Corey Daley Date: Sun, 23 Jul 2023 16:28:38 -0400 Subject: [PATCH 15/22] Delete stale.yml (#720) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What type of PR is this? (check all applicable) - [ ] Refactor - [ ] Feature - [ ] Bug Fix - [ ] Optimization - [ ] Documentation Update ## Description ## Related Tickets & Documents - Related Issue # - Closes # ## Added/updated tests? - [ ] Yes - [ ] No, and this is why: _please replace this line with details on why tests have not been included_ - [ ] I need help with writing tests ## Run verifications and test - [ ] `make verify` is passing - [ ] `make test` is passing Signed-off-by: Corey Daley --- .github/stale.yml | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index f4b12d30..00000000 --- a/.github/stale.yml +++ /dev/null @@ -1,12 +0,0 @@ -daysUntilStale: 75 -daysUntilClose: 14 -# Issues with these labels will never be considered stale -exemptLabels: - - proposal - - needs review - - build system -staleLabel: stale -markComment: > - This issue has been automatically marked as stale because it hasn't seen - a recent update. It'll be automatically closed in a few days. -closeComment: false From 2392d7d19e8c3b02af216a404709415813467766 Mon Sep 17 00:00:00 2001 From: Corey Daley Date: Sun, 23 Jul 2023 16:29:12 -0400 Subject: [PATCH 16/22] Delete AUTHORS (#721) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What type of PR is this? (check all applicable) - [ ] Refactor - [ ] Feature - [ ] Bug Fix - [ ] Optimization - [ ] Documentation Update ## Description ## Related Tickets & Documents - Related Issue # - Closes # ## Added/updated tests? - [ ] Yes - [ ] No, and this is why: _please replace this line with details on why tests have not been included_ - [ ] I need help with writing tests ## Run verifications and test - [ ] `make verify` is passing - [ ] `make test` is passing Signed-off-by: Corey Daley --- AUTHORS | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 AUTHORS diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index b722392e..00000000 --- a/AUTHORS +++ /dev/null @@ -1,8 +0,0 @@ -# This is the official list of gorilla/mux authors for copyright purposes. -# -# Please keep the list sorted. - -Google LLC (https://opensource.google.com/) -Kamil Kisielk -Matt Silverlock -Rodrigo Moraes (https://github.com/moraes) From ff63d0e2fd6b3634b25a63c85c27a3dc80a3b8c8 Mon Sep 17 00:00:00 2001 From: Corey Daley Date: Sun, 23 Jul 2023 16:32:33 -0400 Subject: [PATCH 17/22] Update LICENSE (#722) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What type of PR is this? (check all applicable) - [ ] Refactor - [ ] Feature - [ ] Bug Fix - [ ] Optimization - [ ] Documentation Update ## Description ## Related Tickets & Documents - Related Issue # - Closes # ## Added/updated tests? - [ ] Yes - [ ] No, and this is why: _please replace this line with details on why tests have not been included_ - [ ] I need help with writing tests ## Run verifications and test - [ ] `make verify` is passing - [ ] `make test` is passing Signed-off-by: Corey Daley --- LICENSE | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/LICENSE b/LICENSE index 6903df63..1cedc459 100644 --- a/LICENSE +++ b/LICENSE @@ -1,27 +1,13 @@ -Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved. +BSD 3-Clause License -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: +Copyright 2023 Gorilla web toolkit contributors - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS β€œAS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 753457f3059d3db9a96f9f4c67d7f3724e97478d Mon Sep 17 00:00:00 2001 From: Sham Karthik S <53367916+shamkarthik@users.noreply.github.com> Date: Tue, 25 Jul 2023 22:05:16 +0530 Subject: [PATCH 18/22] Updated the logo in README.md (#724) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What type of PR is this? (check all applicable) - [ ] Refactor - [ ] Feature - [ ] Bug Fix - [ ] Optimization - [x] Documentation Update ## Description ## Related Tickets & Documents - Related Issue #710 - Closes #710 ## Added/updated tests? - [ ] Yes - [ ] No, and this is why: _please replace this line with details on why tests have not been included_ - [ ] I need help with writing tests ## Run verifications and test - [ ] `make verify` is passing - [ ] `make test` is passing Signed-off-by: Sham Karthik S <53367916+shamkarthik@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 551f3c67..4553c1fe 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge) -![Gorilla Logo](https://cloud-cdn.questionable.services/gorilla-icon-64.png) +![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5) Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to their respective handler. From 809d12850fc170caa4229eb2da58a4701c9837b8 Mon Sep 17 00:00:00 2001 From: Corey Daley Date: Tue, 25 Jul 2023 13:19:44 -0400 Subject: [PATCH 19/22] Update LICENSE (#723) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What type of PR is this? (check all applicable) - [ ] Refactor - [ ] Feature - [ ] Bug Fix - [ ] Optimization - [ ] Documentation Update ## Description ## Related Tickets & Documents - Related Issue # - Closes # ## Added/updated tests? - [ ] Yes - [ ] No, and this is why: _please replace this line with details on why tests have not been included_ - [ ] I need help with writing tests ## Run verifications and test - [ ] `make verify` is passing - [ ] `make test` is passing Signed-off-by: Corey Daley --- LICENSE | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/LICENSE b/LICENSE index 1cedc459..bb9d80bc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,13 +1,27 @@ -BSD 3-Clause License +Copyright (c) 2023 The Gorilla Authors. All rights reserved. -Copyright 2023 Gorilla web toolkit contributors +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS β€œAS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From cfc696d6d239ff68ceb71ee35c9a4e4ef3f30ed9 Mon Sep 17 00:00:00 2001 From: Corey Daley Date: Sun, 30 Jul 2023 12:23:57 -0400 Subject: [PATCH 20/22] Update issues.yml (#726) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What type of PR is this? (check all applicable) - [ ] Refactor - [ ] Feature - [ ] Bug Fix - [ ] Optimization - [ ] Documentation Update ## Description ## Related Tickets & Documents - Related Issue # - Closes # ## Added/updated tests? - [ ] Yes - [ ] No, and this is why: _please replace this line with details on why tests have not been included_ - [ ] I need help with writing tests ## Run verifications and test - [ ] `make verify` is passing - [ ] `make test` is passing Signed-off-by: Corey Daley --- .github/workflows/issues.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml index 055ca822..ff9e3183 100644 --- a/.github/workflows/issues.yml +++ b/.github/workflows/issues.yml @@ -5,7 +5,7 @@ on: issues: types: - opened - pull_request: + pull_request_target: types: - opened @@ -17,4 +17,4 @@ jobs: uses: actions/add-to-project@v0.5.0 with: project-url: https://github.com/orgs/gorilla/projects/4 - github-token: ${{ secrets.ADD_TO_PROJECT_TOKEN }} \ No newline at end of file + github-token: ${{ secrets.ADD_TO_PROJECT_TOKEN }} From 651928c42f7b60c7942e46d5f651dd1ece444246 Mon Sep 17 00:00:00 2001 From: Corey Daley Date: Sun, 30 Jul 2023 12:37:42 -0400 Subject: [PATCH 21/22] Update issues.yml (#727) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What type of PR is this? (check all applicable) - [ ] Refactor - [ ] Feature - [ ] Bug Fix - [ ] Optimization - [ ] Documentation Update ## Description ## Related Tickets & Documents - Related Issue # - Closes # ## Added/updated tests? - [ ] Yes - [ ] No, and this is why: _please replace this line with details on why tests have not been included_ - [ ] I need help with writing tests ## Run verifications and test - [ ] `make verify` is passing - [ ] `make test` is passing Signed-off-by: Corey Daley --- .github/workflows/issues.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml index ff9e3183..8be6cedb 100644 --- a/.github/workflows/issues.yml +++ b/.github/workflows/issues.yml @@ -8,6 +8,7 @@ on: pull_request_target: types: - opened + - reopened jobs: add-to-project: From 546dd0cc9f3ecdef8b065ac6336b4c6ed99887d4 Mon Sep 17 00:00:00 2001 From: ICHINOSE Shogo Date: Mon, 31 Jul 2023 10:44:28 +0900 Subject: [PATCH 22/22] run go fmt with Go 1.20 (#725) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What type of PR is this? (check all applicable) - [ ] Refactor - [ ] Feature - [ ] Bug Fix - [ ] Optimization - [x] Documentation Update ## Description `go fmt` now formats godoc comments. I apply it. ## Related Tickets & Documents - Related Issue # - Closes # ## Added/updated tests? - [ ] Yes - [x] No, and this is why: this pull request updates only comments - [ ] I need help with writing tests ## Run verifications and test - [x] `make verify` is passing - [x] `make test` is passing Co-authored-by: Corey Daley --- doc.go | 25 ++++++++++++------------- mux.go | 14 +++++++------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/doc.go b/doc.go index bd5a38b5..80601351 100644 --- a/doc.go +++ b/doc.go @@ -10,18 +10,18 @@ http.ServeMux, mux.Router matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are: - * Requests can be matched based on URL host, path, path prefix, schemes, - header and query values, HTTP methods or using custom matchers. - * URL hosts, paths and query values can have variables with an optional - regular expression. - * Registered URLs can be built, or "reversed", which helps maintaining - references to resources. - * Routes can be used as subrouters: nested routes are only tested if the - parent route matches. This is useful to define groups of routes that - share common conditions like a host, a path prefix or other repeated - attributes. As a bonus, this optimizes request matching. - * It implements the http.Handler interface so it is compatible with the - standard http.ServeMux. + - Requests can be matched based on URL host, path, path prefix, schemes, + header and query values, HTTP methods or using custom matchers. + - URL hosts, paths and query values can have variables with an optional + regular expression. + - Registered URLs can be built, or "reversed", which helps maintaining + references to resources. + - Routes can be used as subrouters: nested routes are only tested if the + parent route matches. This is useful to define groups of routes that + share common conditions like a host, a path prefix or other repeated + attributes. As a bonus, this optimizes request matching. + - It implements the http.Handler interface so it is compatible with the + standard http.ServeMux. Let's start registering a couple of URL paths and handlers: @@ -301,6 +301,5 @@ A more complex authentication middleware, which maps session token to users, cou r.Use(amw.Middleware) Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. - */ package mux diff --git a/mux.go b/mux.go index f126a602..1e089906 100644 --- a/mux.go +++ b/mux.go @@ -31,17 +31,17 @@ func NewRouter() *Router { // It implements the http.Handler interface, so it can be registered to serve // requests: // -// var router = mux.NewRouter() +// var router = mux.NewRouter() // -// func main() { -// http.Handle("/", router) -// } +// func main() { +// http.Handle("/", router) +// } // // Or, for Google App Engine, register it in a init() function: // -// func init() { -// http.Handle("/", router) -// } +// func init() { +// http.Handle("/", router) +// } // // This will send all incoming requests to the router. type Router struct {