github.com/labstack/echo/v4
Advanced tools
@@ -17,3 +17,3 @@ name: Run checks | ||
| # run static analysis only with the latest Go version | ||
| LATEST_GO_VERSION: "1.25" | ||
| LATEST_GO_VERSION: "1.26" | ||
@@ -48,3 +48,1 @@ jobs: | ||
| govulncheck ./... | ||
@@ -17,3 +17,3 @@ name: Run Tests | ||
| # run coverage and benchmarks only with the latest Go version | ||
| LATEST_GO_VERSION: "1.25" | ||
| LATEST_GO_VERSION: "1.26" | ||
@@ -29,3 +29,3 @@ jobs: | ||
| # we derive from last four major releases promise. | ||
| go: ["1.22", "1.23", "1.24", "1.25"] | ||
| go: ["1.25", "1.26"] | ||
| name: ${{ matrix.os }} @ Go ${{ matrix.go }} | ||
@@ -32,0 +32,0 @@ runs-on: ${{ matrix.os }} |
+16
-0
| # Changelog | ||
| ## v4.15.2 - 2026-05-01 | ||
| **Security** | ||
| * `Context.Scheme()` should validate values taken from header by @aldas in https://github.com/labstack/echo/pull/2962 | ||
| Thanks to @shblue21 for reporting this [issue](https://github.com/labstack/echo/issues/2952). | ||
| ## v4.15.1 - 2026-02-22 | ||
| **Enhancements** | ||
| * CSRF: support older token-based CSRF protection handler that want to render token into template by @aldas in https://github.com/labstack/echo/pull/2905 | ||
| ## v4.15.0 - 2026-01-01 | ||
@@ -4,0 +20,0 @@ |
+149
-33
@@ -907,56 +907,172 @@ // SPDX-License-Identifier: MIT | ||
| func TestContext_Scheme(t *testing.T) { | ||
| tests := []struct { | ||
| c Context | ||
| s string | ||
| var testCases = []struct { | ||
| name string | ||
| givenIsTLS bool | ||
| givenHeaders http.Header | ||
| expect string | ||
| }{ | ||
| { | ||
| &context{ | ||
| request: &http.Request{ | ||
| TLS: &tls.ConnectionState{}, | ||
| }, | ||
| name: "defaults to http without TLS or headers", | ||
| givenIsTLS: false, | ||
| givenHeaders: nil, | ||
| expect: "http", | ||
| }, | ||
| { | ||
| name: "returns https when TLS is enabled", | ||
| givenIsTLS: true, | ||
| givenHeaders: nil, | ||
| expect: "https", | ||
| }, | ||
| { | ||
| name: "TLS takes precedence over forwarded proto", | ||
| givenIsTLS: true, | ||
| givenHeaders: http.Header{ | ||
| HeaderXForwardedProto: []string{"http"}, | ||
| }, | ||
| "https", | ||
| expect: "https", | ||
| }, | ||
| { | ||
| &context{ | ||
| request: &http.Request{ | ||
| Header: http.Header{HeaderXForwardedProto: []string{"https"}}, | ||
| }, | ||
| name: "uses X-Forwarded-Proto http", | ||
| givenIsTLS: false, | ||
| givenHeaders: http.Header{ | ||
| HeaderXForwardedProto: []string{"http"}, | ||
| }, | ||
| "https", | ||
| expect: "http", | ||
| }, | ||
| { | ||
| &context{ | ||
| request: &http.Request{ | ||
| Header: http.Header{HeaderXForwardedProtocol: []string{"http"}}, | ||
| }, | ||
| name: "uses X-Forwarded-Proto https", | ||
| givenIsTLS: false, | ||
| givenHeaders: http.Header{ | ||
| HeaderXForwardedProto: []string{"https"}, | ||
| }, | ||
| "http", | ||
| expect: "https", | ||
| }, | ||
| { | ||
| &context{ | ||
| request: &http.Request{ | ||
| Header: http.Header{HeaderXForwardedSsl: []string{"on"}}, | ||
| }, | ||
| name: "X-Forwarded-Proto is case insensitive", | ||
| givenIsTLS: false, | ||
| givenHeaders: http.Header{ | ||
| HeaderXForwardedProto: []string{"HTTPS"}, | ||
| }, | ||
| "https", | ||
| expect: "HTTPS", | ||
| }, | ||
| { | ||
| &context{ | ||
| request: &http.Request{ | ||
| Header: http.Header{HeaderXUrlScheme: []string{"https"}}, | ||
| }, | ||
| name: "uses X-Forwarded-Proto ws", | ||
| givenIsTLS: false, | ||
| givenHeaders: http.Header{ | ||
| HeaderXForwardedProto: []string{"ws"}, | ||
| }, | ||
| "https", | ||
| expect: "ws", | ||
| }, | ||
| { | ||
| &context{ | ||
| request: &http.Request{}, | ||
| name: "uses X-Forwarded-Proto wss", | ||
| givenIsTLS: false, | ||
| givenHeaders: http.Header{ | ||
| HeaderXForwardedProto: []string{"wss"}, | ||
| }, | ||
| "http", | ||
| expect: "wss", | ||
| }, | ||
| { | ||
| name: "ignores invalid X-Forwarded-Proto and uses X-Forwarded-Protocol", | ||
| givenIsTLS: false, | ||
| givenHeaders: http.Header{ | ||
| HeaderXForwardedProto: []string{"ftp"}, | ||
| HeaderXForwardedProtocol: []string{"https"}, | ||
| }, | ||
| expect: "https", | ||
| }, | ||
| { | ||
| name: "uses X-Forwarded-Protocol", | ||
| givenIsTLS: false, | ||
| givenHeaders: http.Header{ | ||
| HeaderXForwardedProtocol: []string{"https"}, | ||
| }, | ||
| expect: "https", | ||
| }, | ||
| { | ||
| name: "X-Forwarded-Proto takes precedence over X-Forwarded-Protocol", | ||
| givenIsTLS: false, | ||
| givenHeaders: http.Header{ | ||
| HeaderXForwardedProto: []string{"http"}, | ||
| HeaderXForwardedProtocol: []string{"https"}, | ||
| }, | ||
| expect: "http", | ||
| }, | ||
| { | ||
| name: "uses X-Forwarded-Ssl on", | ||
| givenIsTLS: false, | ||
| givenHeaders: http.Header{ | ||
| HeaderXForwardedSsl: []string{"on"}, | ||
| }, | ||
| expect: "https", | ||
| }, | ||
| { | ||
| name: "X-Forwarded-Ssl on is case sensitive", | ||
| givenIsTLS: false, | ||
| givenHeaders: http.Header{ | ||
| HeaderXForwardedSsl: []string{"ON"}, | ||
| }, | ||
| expect: "http", | ||
| }, | ||
| { | ||
| name: "X-Forwarded-Protocol takes precedence over X-Forwarded-Ssl", | ||
| givenIsTLS: false, | ||
| givenHeaders: http.Header{ | ||
| HeaderXForwardedProtocol: []string{"http"}, | ||
| HeaderXForwardedSsl: []string{"on"}, | ||
| }, | ||
| expect: "http", | ||
| }, | ||
| { | ||
| name: "uses X-Url-Scheme", | ||
| givenIsTLS: false, | ||
| givenHeaders: http.Header{ | ||
| HeaderXUrlScheme: []string{"https"}, | ||
| }, | ||
| expect: "https", | ||
| }, | ||
| { | ||
| name: "X-Forwarded-Ssl takes precedence over X-Url-Scheme", | ||
| givenIsTLS: false, | ||
| givenHeaders: http.Header{ | ||
| HeaderXForwardedSsl: []string{"on"}, | ||
| HeaderXUrlScheme: []string{"http"}, | ||
| }, | ||
| expect: "https", | ||
| }, | ||
| { | ||
| name: "ignores invalid forwarded headers and falls back to http", | ||
| givenIsTLS: false, | ||
| givenHeaders: http.Header{ | ||
| HeaderXForwardedProto: []string{"ftp"}, | ||
| HeaderXForwardedProtocol: []string{"smtp"}, | ||
| HeaderXForwardedSsl: []string{"off"}, | ||
| HeaderXUrlScheme: []string{"file"}, | ||
| }, | ||
| expect: "http", | ||
| }, | ||
| { | ||
| name: "ignores empty forwarded proto and uses X-Url-Scheme", | ||
| givenIsTLS: false, | ||
| givenHeaders: http.Header{ | ||
| HeaderXForwardedProto: []string{""}, | ||
| HeaderXUrlScheme: []string{"https"}, | ||
| }, | ||
| expect: "https", | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| assert.Equal(t, tt.s, tt.c.Scheme()) | ||
| for _, tc := range testCases { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| req := httptest.NewRequest(http.MethodGet, "/", nil) | ||
| if tc.givenHeaders != nil { | ||
| req.Header = tc.givenHeaders | ||
| } | ||
| e := New() | ||
| c := e.NewContext(req, nil) | ||
| if tc.givenIsTLS { | ||
| c.Request().TLS = &tls.ConnectionState{} | ||
| } | ||
| assert.Equal(t, tc.expect, c.Scheme()) | ||
| }) | ||
| } | ||
@@ -963,0 +1079,0 @@ } |
+16
-3
@@ -275,2 +275,15 @@ // SPDX-License-Identifier: MIT | ||
| func isValidProto(proto string) bool { | ||
| if proto == "" { | ||
| return false | ||
| } | ||
| for _, p := range []string{"http", "https", "ws", "wss"} { | ||
| if strings.EqualFold(proto, p) { | ||
| return true | ||
| } | ||
| } | ||
| return false | ||
| } | ||
| // Scheme returns the HTTP protocol scheme, `http` or `https`. | ||
| func (c *context) Scheme() string { | ||
@@ -282,6 +295,6 @@ // Can't use `r.Request.URL.Scheme` | ||
| } | ||
| if scheme := c.request.Header.Get(HeaderXForwardedProto); scheme != "" { | ||
| if scheme := c.request.Header.Get(HeaderXForwardedProto); isValidProto(scheme) { | ||
| return scheme | ||
| } | ||
| if scheme := c.request.Header.Get(HeaderXForwardedProtocol); scheme != "" { | ||
| if scheme := c.request.Header.Get(HeaderXForwardedProtocol); isValidProto(scheme) { | ||
| return scheme | ||
@@ -292,3 +305,3 @@ } | ||
| } | ||
| if scheme := c.request.Header.Get(HeaderXUrlScheme); scheme != "" { | ||
| if scheme := c.request.Header.Get(HeaderXUrlScheme); isValidProto(scheme) { | ||
| return scheme | ||
@@ -295,0 +308,0 @@ } |
+1
-1
@@ -270,3 +270,3 @@ // SPDX-License-Identifier: MIT | ||
| // Version of Echo | ||
| Version = "4.15.0" | ||
| Version = "4.15.2" | ||
| website = "https://echo.labstack.com" | ||
@@ -273,0 +273,0 @@ // http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo |
+8
-8
| module github.com/labstack/echo/v4 | ||
| go 1.24.0 | ||
| go 1.25.0 | ||
| require ( | ||
| github.com/labstack/gommon v0.4.2 | ||
| github.com/labstack/gommon v0.5.0 | ||
| github.com/stretchr/testify v1.11.1 | ||
| github.com/valyala/fasttemplate v1.2.2 | ||
| golang.org/x/crypto v0.46.0 | ||
| golang.org/x/net v0.48.0 | ||
| golang.org/x/time v0.14.0 | ||
| golang.org/x/crypto v0.50.0 | ||
| golang.org/x/net v0.53.0 | ||
| golang.org/x/time v0.15.0 | ||
| ) | ||
@@ -17,8 +17,8 @@ | ||
| github.com/mattn/go-colorable v0.1.14 // indirect | ||
| github.com/mattn/go-isatty v0.0.20 // indirect | ||
| github.com/mattn/go-isatty v0.0.22 // indirect | ||
| github.com/pmezard/go-difflib v1.0.0 // indirect | ||
| github.com/valyala/bytebufferpool v1.0.0 // indirect | ||
| golang.org/x/sys v0.39.0 // indirect | ||
| golang.org/x/text v0.32.0 // indirect | ||
| golang.org/x/sys v0.43.0 // indirect | ||
| golang.org/x/text v0.36.0 // indirect | ||
| gopkg.in/yaml.v3 v3.0.1 // indirect | ||
| ) |
+14
-15
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
| github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= | ||
| github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= | ||
| github.com/labstack/gommon v0.5.0 h1:6VSQ2NOzsnEJ5W6+84E0RbcaDDmgB6NIAzWCczTEe6c= | ||
| github.com/labstack/gommon v0.5.0/go.mod h1:Rzlg7HHy1maLfzBYGg9NZcVuz1sA68HHhLjhcEllYE0= | ||
| github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= | ||
| github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= | ||
| github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | ||
| github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||
| github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= | ||
| github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= | ||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
@@ -17,13 +17,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
| github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= | ||
| golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= | ||
| golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= | ||
| golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= | ||
| golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= | ||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
| golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= | ||
| golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= | ||
| golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= | ||
| golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= | ||
| golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= | ||
| golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= | ||
| golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= | ||
| golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= | ||
| golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= | ||
| golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= | ||
| golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= | ||
| golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= | ||
| golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= | ||
| golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= | ||
| golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= | ||
| golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= | ||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
@@ -30,0 +29,0 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |