From 28d84fc0beee64b705f0826e8cfbd266d4eb3198 Mon Sep 17 00:00:00 2001 From: pwli Date: Thu, 17 Dec 2020 12:46:31 +0800 Subject: [PATCH 1/8] Update echo.go Fix Static files route not working --- echo.go | 1 + 1 file changed, 1 insertion(+) diff --git a/echo.go b/echo.go index 64e64c2c3..3b55d4bf0 100644 --- a/echo.go +++ b/echo.go @@ -503,6 +503,7 @@ func (common) static(prefix, root string, get func(string, HandlerFunc, ...Middl } return c.File(name) } + get(prefix, h) if prefix == "/" { return get(prefix+"*", h) } From 98f78a0234fb469f21a761e29952763159d7afce Mon Sep 17 00:00:00 2001 From: pwli Date: Thu, 17 Dec 2020 12:49:08 +0800 Subject: [PATCH 2/8] Update echo_test.go add tests --- echo_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/echo_test.go b/echo_test.go index 7f3597428..cd2e7888f 100644 --- a/echo_test.go +++ b/echo_test.go @@ -105,6 +105,15 @@ func TestEchoStatic(t *testing.T) { expectHeaderLocation: "/folder/", expectBodyStartsWith: "", }, + { + name: "Directory Redirect with non-root path", + givenPrefix: "/static", + givenRoot: "_fixture", + whenURL: "/static", + expectStatus: http.StatusMovedPermanently, + expectHeaderLocation: "/static/", + expectBodyStartsWith: "", + }, { name: "Directory with index.html", givenPrefix: "/", @@ -164,6 +173,40 @@ func TestEchoStatic(t *testing.T) { } } +func TestEchoStaticRedirectIndex(t *testing.T) { + assert := assert.New(t) + e := New() + + // HandlerFunc + e.Static("/static", "_fixture") + + errCh := make(chan error) + + go func() { + errCh <- e.Start("127.0.0.1:1323") + }() + + time.Sleep(200 * time.Millisecond) + + if resp, err := http.Get("http://127.0.0.1:1323/static"); err == nil { + defer resp.Body.Close() + assert.Equal(http.StatusOK, resp.StatusCode) + + if body, err := ioutil.ReadAll(resp.Body); err == nil { + assert.Equal(true, strings.HasPrefix(string(body), "")) + } else { + assert.Fail(err.Error()) + } + + } else { + assert.Fail(err.Error()) + } + + if err := e.Close(); err != nil { + t.Fatal(err) + } +} + func TestEchoFile(t *testing.T) { e := New() e.File("/walle", "_fixture/images/walle.png") From a143892d6ab07981d2f92adce7f5f81a243bd59b Mon Sep 17 00:00:00 2001 From: pwli Date: Thu, 17 Dec 2020 13:17:55 +0800 Subject: [PATCH 3/8] Fix Static files route not working --- echo.go | 1 + 1 file changed, 1 insertion(+) diff --git a/echo.go b/echo.go index 3b55d4bf0..28e092327 100644 --- a/echo.go +++ b/echo.go @@ -965,3 +965,4 @@ func applyMiddleware(h HandlerFunc, middleware ...MiddlewareFunc) HandlerFunc { } return h } + From fd7db74694835a5e631083fd520de0d51fbe4050 Mon Sep 17 00:00:00 2001 From: pwli Date: Thu, 17 Dec 2020 13:18:12 +0800 Subject: [PATCH 4/8] Fix Static files route not working --- echo.go | 1 - 1 file changed, 1 deletion(-) diff --git a/echo.go b/echo.go index 28e092327..3b55d4bf0 100644 --- a/echo.go +++ b/echo.go @@ -965,4 +965,3 @@ func applyMiddleware(h HandlerFunc, middleware ...MiddlewareFunc) HandlerFunc { } return h } - From cc6a6a7c4fdc22fb87d11d07061963ef2b04be87 Mon Sep 17 00:00:00 2001 From: Roland Lammel Date: Sat, 2 Jan 2021 21:22:17 +0100 Subject: [PATCH 5/8] Fix handling of static routes with/without trailing slash with added tests --- echo.go | 12 ++- echo_test.go | 33 +++++++++ middleware/static_test.go | 150 +++++++++++++++++++++++++++++++++++++- 3 files changed, 190 insertions(+), 5 deletions(-) diff --git a/echo.go b/echo.go index 3b55d4bf0..6db485d10 100644 --- a/echo.go +++ b/echo.go @@ -503,9 +503,15 @@ func (common) static(prefix, root string, get func(string, HandlerFunc, ...Middl } return c.File(name) } - get(prefix, h) - if prefix == "/" { - return get(prefix+"*", h) + // Handle added routes based on trailing slash: + // /prefix => exact route "/prefix" + any route "/prefix/*" + // /prefix/ => only any route "/prefix/*" + if prefix != "" { + if prefix[len(prefix)-1] == '/' { + // Only add any route for intentional trailing slash + return get(prefix+"*", h) + } + get(prefix, h) } return get(prefix+"/*", h) } diff --git a/echo_test.go b/echo_test.go index cd2e7888f..781b901fa 100644 --- a/echo_test.go +++ b/echo_test.go @@ -114,6 +114,23 @@ func TestEchoStatic(t *testing.T) { expectHeaderLocation: "/static/", expectBodyStartsWith: "", }, + { + name: "Prefixed directory 404 (request URL without slash)", + givenPrefix: "/folder/", // trailing slash will intentionally not match "/folder" + givenRoot: "_fixture", + whenURL: "/folder", // no trailing slash + expectStatus: http.StatusNotFound, + expectBodyStartsWith: "{\"message\":\"Not Found\"}\n", + }, + { + name: "Prefixed directory redirect (without slash redirect to slash)", + givenPrefix: "/folder", // no trailing slash shall match /folder and /folder/* + givenRoot: "_fixture", + whenURL: "/folder", // no trailing slash + expectStatus: http.StatusMovedPermanently, + expectHeaderLocation: "/folder/", + expectBodyStartsWith: "", + }, { name: "Directory with index.html", givenPrefix: "/", @@ -122,6 +139,22 @@ func TestEchoStatic(t *testing.T) { expectStatus: http.StatusOK, expectBodyStartsWith: "", }, + { + name: "Prefixed directory with index.html (prefix ending with slash)", + givenPrefix: "/assets/", + givenRoot: "_fixture", + whenURL: "/assets/", + expectStatus: http.StatusOK, + expectBodyStartsWith: "", + }, + { + name: "Prefixed directory with index.html (prefix ending without slash)", + givenPrefix: "/assets", + givenRoot: "_fixture", + whenURL: "/assets/", + expectStatus: http.StatusOK, + expectBodyStartsWith: "", + }, { name: "Sub-directory with index.html", givenPrefix: "/", diff --git a/middleware/static_test.go b/middleware/static_test.go index 3e6ca5601..8c0c97ded 100644 --- a/middleware/static_test.go +++ b/middleware/static_test.go @@ -1,11 +1,13 @@ package middleware import ( - "github.com/labstack/echo/v4" - "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" + "strings" "testing" + + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" ) func TestStatic(t *testing.T) { @@ -131,3 +133,147 @@ func TestStatic(t *testing.T) { }) } } + +func TestStatic_GroupWithStatic(t *testing.T) { + var testCases = []struct { + name string + givenGroup string + givenPrefix string + givenRoot string + whenURL string + expectStatus int + expectHeaderLocation string + expectBodyStartsWith string + }{ + { + name: "ok", + givenPrefix: "/images", + givenRoot: "../_fixture/images", + whenURL: "/group/images/walle.png", + expectStatus: http.StatusOK, + expectBodyStartsWith: string([]byte{0x89, 0x50, 0x4e, 0x47}), + }, + { + name: "No file", + givenPrefix: "/images", + givenRoot: "../_fixture/scripts", + whenURL: "/group/images/bolt.png", + expectStatus: http.StatusNotFound, + expectBodyStartsWith: "{\"message\":\"Not Found\"}\n", + }, + { + name: "Directory not found (no trailing slash)", + givenPrefix: "/images", + givenRoot: "../_fixture/images", + whenURL: "/group/images/", + expectStatus: http.StatusNotFound, + expectBodyStartsWith: "{\"message\":\"Not Found\"}\n", + }, + { + name: "Directory redirect", + givenPrefix: "/", + givenRoot: "../_fixture", + whenURL: "/group/folder", + expectStatus: http.StatusMovedPermanently, + expectHeaderLocation: "/group/folder/", + expectBodyStartsWith: "", + }, + { + name: "Prefixed directory 404 (request URL without slash)", + givenGroup: "_fixture", + givenPrefix: "/folder/", // trailing slash will intentionally not match "/folder" + givenRoot: "../_fixture", + whenURL: "/_fixture/folder", // no trailing slash + expectStatus: http.StatusNotFound, + expectBodyStartsWith: "{\"message\":\"Not Found\"}\n", + }, + { + name: "Prefixed directory redirect (without slash redirect to slash)", + givenGroup: "_fixture", + givenPrefix: "/folder", // no trailing slash shall match /folder and /folder/* + givenRoot: "../_fixture", + whenURL: "/_fixture/folder", // no trailing slash + expectStatus: http.StatusMovedPermanently, + expectHeaderLocation: "/_fixture/folder/", + expectBodyStartsWith: "", + }, + { + name: "Directory with index.html", + givenPrefix: "/", + givenRoot: "../_fixture", + whenURL: "/group/", + expectStatus: http.StatusOK, + expectBodyStartsWith: "", + }, + { + name: "Prefixed directory with index.html (prefix ending with slash)", + givenPrefix: "/assets/", + givenRoot: "../_fixture", + whenURL: "/group/assets/", + expectStatus: http.StatusOK, + expectBodyStartsWith: "", + }, + { + name: "Prefixed directory with index.html (prefix ending without slash)", + givenPrefix: "/assets", + givenRoot: "../_fixture", + whenURL: "/group/assets/", + expectStatus: http.StatusOK, + expectBodyStartsWith: "", + }, + { + name: "Sub-directory with index.html", + givenPrefix: "/", + givenRoot: "../_fixture", + whenURL: "/group/folder/", + expectStatus: http.StatusOK, + expectBodyStartsWith: "", + }, + { + name: "do not allow directory traversal (backslash - windows separator)", + givenPrefix: "/", + givenRoot: "../_fixture/", + whenURL: `/group/..\\middleware/basic_auth.go`, + expectStatus: http.StatusNotFound, + expectBodyStartsWith: "{\"message\":\"Not Found\"}\n", + }, + { + name: "do not allow directory traversal (slash - unix separator)", + givenPrefix: "/", + givenRoot: "../_fixture/", + whenURL: `/group/../middleware/basic_auth.go`, + expectStatus: http.StatusNotFound, + expectBodyStartsWith: "{\"message\":\"Not Found\"}\n", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + e := echo.New() + group := "/group" + if tc.givenGroup != "" { + group = tc.givenGroup + } + g := e.Group(group) + g.Static(tc.givenPrefix, tc.givenRoot) + + req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil) + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + assert.Equal(t, tc.expectStatus, rec.Code) + body := rec.Body.String() + if tc.expectBodyStartsWith != "" { + assert.True(t, strings.HasPrefix(body, tc.expectBodyStartsWith)) + } else { + assert.Equal(t, "", body) + } + + if tc.expectHeaderLocation != "" { + assert.Equal(t, tc.expectHeaderLocation, rec.Header().Get(echo.HeaderLocation)) + } else { + _, ok := rec.Result().Header[echo.HeaderLocation] + assert.False(t, ok) + } + }) + } +} From b374c97e5ef4282525c4b04db281a0935dd2a62f Mon Sep 17 00:00:00 2001 From: Roland Lammel Date: Thu, 4 Feb 2021 19:28:05 +0100 Subject: [PATCH 6/8] Add a CHANGELOG.md for historic tracking of changes --- CHANGELOG.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..6a665d5d7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,68 @@ +# Changelog + +## v4.2.0 - 2020-02-04 + +**Important notes** + +The behaviour for binding data has been reworked for compatibility with echo before v4.1.11 by +enforcing `explicit tagging` for processing parameters. This **may break** your code if you +expect combined handling of query/path/form params. +Please see the updated documentation for [request](https://echo.labstack.com/guide/request) and [binding](https://echo.labstack.com/guide/request) + +**Security** + +* Fix directory traversal vulnerability for Windows (#1718, little-cui) + +**Enhancements** + +* Add Echo#ListenerNetwork as configuration (#1667, pafuent) +* Add ability to change the status code using response beforeFuncs (#1706, RashadAnsari) +* Echo server startup to allow data race free access to listener address +* Binder: Restore pre v4.1.11 behaviour for c.Bind() to use query params only for GET or DELETE methods (#1727, aldas) +* Binder: Add separate methods to bind only query params, path params or request body (#1681, aldas) +* Binder: New fluent binder for query/path/form parameter binding (#1717, #1736, aldas) +* Router: Performance improvements for missed routes (#1689, pafuent) +* Router: Improve performance for Real-IP detection using IndexByte instead of Split (#1640, imxyb) +* Middleware: New rate limiting middleware (#1724, iambenkay) +* Middleware: New timeout middleware implementation for go1.13+ (#1743, ) +* Middleware: Allow regex pattern for CORS middleware (#1623, KlotzAndrew) +* Middleware: Add IgnoreBase parameter to static middleware (#1701, lnenad, iambenkay) +* Middleware: Add an optional custom function to CORS middleware to validate origin (#1651, curvegrid) +* Middleware: Support form fields in JWT middleware (#1704, rkfg) +* Middleware: Use sync.Pool for (de)compress middleware to improve performance (#1699, #1672, pafuent) +* Middleware: Add decompress middleware to support gzip compressed requests (#1687, arun0009) +* Middleware: Add ErrJWTInvalid for JWT middleware (#1627, juanbelieni) +* Middleware: Add SameSite mode for CSRF cookies to support iframes (#1524, pr0head) + +**Fixes** + +* Fix handling of special trailing slash case for partial prefix (#1741, stffabi) +* Fix handling of static routes with trailing slash (#1747) +* Fix Static files route not working (#1671, pwli0755, lammel) +* Fix use of caret(^) in regex for rewrite middleware (#1588, chotow) +* Fix Echo#Reverse for Any type routes (#1695, pafuent) +* Fix Router#Find panic with infinite loop (#1661, pafuent) +* Fix Router#Find panic fails on Param paths (#1659, pafuent) +* Fix DefaultHTTPErrorHandler with Debug=true (#1477, lammel) +* Fix incorrect CORS headers (#1669, ulasakdeniz) +* Fix proxy middleware rewritePath to use url with updated tests (#1630, arun0009) +* Fix rewritePath for proxy middleware to use escaped path in (#1628, arun0009) +* Remove unless defer (#1656, imxyb) + +**General** + +* New maintainers for Echo: Roland Lammel (@lammel) and Pablo Andres Fuente (@pafuent) +* Add GitHub action to compare benchmarks (#1702, pafuent) +* Binding query/path params and form fields to struct only works for explicit tags (#1729,#1734, aldas) +* Add support for Go 1.15 in CI (#1683, asahasrabuddhe) +* Add test for request id to remain unchanged if provided (#1719, iambenkay) +* Refactor echo instance listener access and startup to speed up testing (#1735, aldas) +* Refactor and improve various tests for binding and routing +* Run test workflow only for relevant changes (#1637, #1636, pofl) +* Update .travis.yml (#1662, santosh653) +* Update README.md with an recents framework benchmark (#1679, pafuent) + +This release was made possible by **over 100 commits** from more than **20 contributors**: +asahasrabuddhe, aldas, AndrewKlotz, arun0009, chotow, curvegrid, iambenkay, imxyb, +juanbelieni, lammel, little-cui, lnenad, pafuent, pofl, pr0head, pwli, RashadAnsari, +rkfg, santosh653, segfiner, stffabi, ulasakdeniz From 813838f5f4c44559abc73d2c7afe8aef7b43a5c8 Mon Sep 17 00:00:00 2001 From: Roland Lammel Date: Mon, 8 Feb 2021 16:07:52 +0100 Subject: [PATCH 7/8] Add latest regex rules for rewrite/proxy middleware --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a665d5d7..bc3283016 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## v4.2.0 - 2020-02-04 +## v4.2.0 - 2020-02-08 **Important notes** @@ -9,6 +9,9 @@ enforcing `explicit tagging` for processing parameters. This **may break** your expect combined handling of query/path/form params. Please see the updated documentation for [request](https://echo.labstack.com/guide/request) and [binding](https://echo.labstack.com/guide/request) +The handling for rewrite rules has been slightly adjusted to expand `*` to a non-greedy `(.*?)` capture group. This is only relevant if multiple asterisks are used in your rules. +Please see [rewrite](https://echo.labstack.com/middleware/rewrite) and [proxy](https://echo.labstack.com/middleware/proxy) for details. + **Security** * Fix directory traversal vulnerability for Windows (#1718, little-cui) @@ -23,6 +26,7 @@ Please see the updated documentation for [request](https://echo.labstack.com/gui * Binder: New fluent binder for query/path/form parameter binding (#1717, #1736, aldas) * Router: Performance improvements for missed routes (#1689, pafuent) * Router: Improve performance for Real-IP detection using IndexByte instead of Split (#1640, imxyb) +* Middleware: Support real regex rules for rewrite and proxy middleware (#1767) * Middleware: New rate limiting middleware (#1724, iambenkay) * Middleware: New timeout middleware implementation for go1.13+ (#1743, ) * Middleware: Allow regex pattern for CORS middleware (#1623, KlotzAndrew) From 313ff75a175b62b432ee694dba90d2d51f4514d6 Mon Sep 17 00:00:00 2001 From: Roland Lammel Date: Thu, 11 Feb 2021 13:46:00 +0100 Subject: [PATCH 8/8] Add upcoming security fix for open redirect, adjust date --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc3283016..33f5587f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## v4.2.0 - 2020-02-08 +## v4.2.0 - 2020-02-11 **Important notes** @@ -15,6 +15,7 @@ Please see [rewrite](https://echo.labstack.com/middleware/rewrite) and [proxy](h **Security** * Fix directory traversal vulnerability for Windows (#1718, little-cui) +* Fix open redirect vulnerability with trailing slash (#1771,#1775 aldas,GeoffreyFrogeye) **Enhancements**