From 69a0de84158fd7cad326599d145c2248bcc15a69 Mon Sep 17 00:00:00 2001 From: Martti T Date: Tue, 24 Oct 2023 21:12:13 +0300 Subject: [PATCH 01/74] Mark unmarshallable yaml struct tags as ignored (#2536) --- middleware/cors.go | 2 +- middleware/rewrite.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/middleware/cors.go b/middleware/cors.go index 10504359f..7ace2f224 100644 --- a/middleware/cors.go +++ b/middleware/cors.go @@ -39,7 +39,7 @@ type ( // See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html // // Optional. - AllowOriginFunc func(origin string) (bool, error) `yaml:"allow_origin_func"` + AllowOriginFunc func(origin string) (bool, error) `yaml:"-"` // AllowMethods determines the value of the Access-Control-Allow-Methods // response header. This header specified the list of methods allowed when diff --git a/middleware/rewrite.go b/middleware/rewrite.go index e5b0a6b56..2090eac04 100644 --- a/middleware/rewrite.go +++ b/middleware/rewrite.go @@ -27,7 +27,7 @@ type ( // Example: // "^/old/[0.9]+/": "/new", // "^/api/.+?/(.*)": "/v2/$1", - RegexRules map[*regexp.Regexp]string `yaml:"regex_rules"` + RegexRules map[*regexp.Regexp]string `yaml:"-"` } ) From c7d6d4373fdfbef5d6f44df0a8ef410c198420ee Mon Sep 17 00:00:00 2001 From: Kai Ratzeburg Date: Sun, 5 Nov 2023 17:01:01 +0100 Subject: [PATCH 02/74] proxy middleware: reuse echo request context (#2537) --- middleware/proxy.go | 4 +++ middleware/proxy_test.go | 60 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/middleware/proxy.go b/middleware/proxy.go index e4f98d9ed..16b00d645 100644 --- a/middleware/proxy.go +++ b/middleware/proxy.go @@ -359,6 +359,10 @@ func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc { c.Set("_error", nil) } + // This is needed for ProxyConfig.ModifyResponse and/or ProxyConfig.Transport to be able to process the Request + // that Balancer may have replaced with c.SetRequest. + req = c.Request() + // Proxy switch { case c.IsWebSocket(): diff --git a/middleware/proxy_test.go b/middleware/proxy_test.go index 415d68e77..1c93ba031 100644 --- a/middleware/proxy_test.go +++ b/middleware/proxy_test.go @@ -747,3 +747,63 @@ func TestProxyBalancerWithNoTargets(t *testing.T) { rrb := NewRoundRobinBalancer([]*ProxyTarget{}) assert.Nil(t, rrb.Next(nil)) } + +type testContextKey string + +type customBalancer struct { + target *ProxyTarget +} + +func (b *customBalancer) AddTarget(target *ProxyTarget) bool { + return false +} + +func (b *customBalancer) RemoveTarget(name string) bool { + return false +} + +func (b *customBalancer) Next(c echo.Context) *ProxyTarget { + ctx := context.WithValue(c.Request().Context(), testContextKey("FROM_BALANCER"), "CUSTOM_BALANCER") + c.SetRequest(c.Request().WithContext(ctx)) + return b.target +} + +func TestModifyResponseUseContext(t *testing.T) { + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) + }), + ) + defer server.Close() + + targetURL, _ := url.Parse(server.URL) + e := echo.New() + e.Use(ProxyWithConfig( + ProxyConfig{ + Balancer: &customBalancer{ + target: &ProxyTarget{ + Name: "tst", + URL: targetURL, + }, + }, + RetryCount: 1, + ModifyResponse: func(res *http.Response) error { + val := res.Request.Context().Value(testContextKey("FROM_BALANCER")) + if valStr, ok := val.(string); ok { + res.Header.Set("FROM_BALANCER", valStr) + } + return nil + }, + }, + )) + + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + + e.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, "OK", rec.Body.String()) + assert.Equal(t, "CUSTOM_BALANCER", rec.Header().Get("FROM_BALANCER")) +} From 50ebcd8d7c17457489df7bcbbcaa3745c687fd32 Mon Sep 17 00:00:00 2001 From: Martti T Date: Tue, 7 Nov 2023 13:40:22 +0200 Subject: [PATCH 03/74] refactor context tests to be separate functions (#2540) --- binder.go | 2 +- context_test.go | 770 +++++++++++++++++++++++++++--------------------- json_test.go | 38 ++- 3 files changed, 448 insertions(+), 362 deletions(-) diff --git a/binder.go b/binder.go index 29cceca0b..8e7b81413 100644 --- a/binder.go +++ b/binder.go @@ -1323,7 +1323,7 @@ func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExi case time.Second: *dest = time.Unix(n, 0) case time.Millisecond: - *dest = time.Unix(n/1e3, (n%1e3)*1e6) // TODO: time.UnixMilli(n) exists since Go1.17 switch to that when min version allows + *dest = time.UnixMilli(n) case time.Nanosecond: *dest = time.Unix(0, n) } diff --git a/context_test.go b/context_test.go index 11a63cfce..85b221446 100644 --- a/context_test.go +++ b/context_test.go @@ -19,7 +19,7 @@ import ( "time" "github.com/labstack/gommon/log" - testify "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) type ( @@ -85,303 +85,401 @@ func (t *Template) Render(w io.Writer, name string, data interface{}, c Context) return t.templates.ExecuteTemplate(w, name, data) } -type responseWriterErr struct { -} - -func (responseWriterErr) Header() http.Header { - return http.Header{} -} +func TestContextEcho(t *testing.T) { + e := New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON)) + rec := httptest.NewRecorder() -func (responseWriterErr) Write([]byte) (int, error) { - return 0, errors.New("err") -} + c := e.NewContext(req, rec).(*context) -func (responseWriterErr) WriteHeader(statusCode int) { + assert.Equal(t, e, c.Echo()) } -func TestContext(t *testing.T) { +func TestContextRequest(t *testing.T) { e := New() req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON)) rec := httptest.NewRecorder() + c := e.NewContext(req, rec).(*context) - assert := testify.New(t) + assert.NotNil(t, c.Request()) + assert.Equal(t, req, c.Request()) +} - // Echo - assert.Equal(e, c.Echo()) +func TestContextResponse(t *testing.T) { + e := New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON)) + rec := httptest.NewRecorder() - // Request - assert.NotNil(c.Request()) + c := e.NewContext(req, rec).(*context) - // Response - assert.NotNil(c.Response()) + assert.NotNil(t, c.Response()) +} - //-------- - // Render - //-------- +func TestContextRenderTemplate(t *testing.T) { + e := New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON)) + rec := httptest.NewRecorder() + + c := e.NewContext(req, rec).(*context) tmpl := &Template{ templates: template.Must(template.New("hello").Parse("Hello, {{.}}!")), } c.echo.Renderer = tmpl err := c.Render(http.StatusOK, "hello", "Jon Snow") - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal("Hello, Jon Snow!", rec.Body.String()) + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, "Hello, Jon Snow!", rec.Body.String()) } +} + +func TestContextRenderErrorsOnNoRenderer(t *testing.T) { + e := New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON)) + rec := httptest.NewRecorder() + + c := e.NewContext(req, rec).(*context) c.echo.Renderer = nil - err = c.Render(http.StatusOK, "hello", "Jon Snow") - assert.Error(err) - - // JSON - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) - err = c.JSON(http.StatusOK, user{1, "Jon Snow"}) - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal(MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) - assert.Equal(userJSON+"\n", rec.Body.String()) - } - - // JSON with "?pretty" - req = httptest.NewRequest(http.MethodGet, "/?pretty", nil) - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) - err = c.JSON(http.StatusOK, user{1, "Jon Snow"}) - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal(MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) - assert.Equal(userJSONPretty+"\n", rec.Body.String()) - } - req = httptest.NewRequest(http.MethodGet, "/", nil) // reset - - // JSONPretty - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) - err = c.JSONPretty(http.StatusOK, user{1, "Jon Snow"}, " ") - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal(MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) - assert.Equal(userJSONPretty+"\n", rec.Body.String()) - } - - // JSON (error) - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) - err = c.JSON(http.StatusOK, make(chan bool)) - assert.Error(err) - - // JSONP - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) + assert.Error(t, c.Render(http.StatusOK, "hello", "Jon Snow")) +} + +func TestContextJSON(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON)) + c := e.NewContext(req, rec).(*context) + + err := c.JSON(http.StatusOK, user{1, "Jon Snow"}) + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, userJSON+"\n", rec.Body.String()) + } +} + +func TestContextJSONErrorsOut(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON)) + c := e.NewContext(req, rec).(*context) + + err := c.JSON(http.StatusOK, make(chan bool)) + assert.EqualError(t, err, "json: unsupported type: chan bool") +} + +func TestContextJSONPrettyURL(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/?pretty", nil) + c := e.NewContext(req, rec).(*context) + + err := c.JSON(http.StatusOK, user{1, "Jon Snow"}) + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, userJSONPretty+"\n", rec.Body.String()) + } +} + +func TestContextJSONPretty(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + c := e.NewContext(req, rec).(*context) + + err := c.JSONPretty(http.StatusOK, user{1, "Jon Snow"}, " ") + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, userJSONPretty+"\n", rec.Body.String()) + } +} + +func TestContextJSONWithEmptyIntent(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + c := e.NewContext(req, rec).(*context) + + u := user{1, "Jon Snow"} + emptyIndent := "" + buf := new(bytes.Buffer) + + enc := json.NewEncoder(buf) + enc.SetIndent(emptyIndent, emptyIndent) + _ = enc.Encode(u) + err := c.json(http.StatusOK, user{1, "Jon Snow"}, emptyIndent) + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, buf.String(), rec.Body.String()) + } +} + +func TestContextJSONP(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + c := e.NewContext(req, rec).(*context) + callback := "callback" - err = c.JSONP(http.StatusOK, callback, user{1, "Jon Snow"}) - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal(MIMEApplicationJavaScriptCharsetUTF8, rec.Header().Get(HeaderContentType)) - assert.Equal(callback+"("+userJSON+"\n);", rec.Body.String()) - } - - // XML - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) - err = c.XML(http.StatusOK, user{1, "Jon Snow"}) - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal(MIMEApplicationXMLCharsetUTF8, rec.Header().Get(HeaderContentType)) - assert.Equal(xml.Header+userXML, rec.Body.String()) - } - - // XML with "?pretty" - req = httptest.NewRequest(http.MethodGet, "/?pretty", nil) - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) - err = c.XML(http.StatusOK, user{1, "Jon Snow"}) - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal(MIMEApplicationXMLCharsetUTF8, rec.Header().Get(HeaderContentType)) - assert.Equal(xml.Header+userXMLPretty, rec.Body.String()) - } - req = httptest.NewRequest(http.MethodGet, "/", nil) - - // XML (error) - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) - err = c.XML(http.StatusOK, make(chan bool)) - assert.Error(err) - - // XML response write error - c = e.NewContext(req, rec).(*context) - c.response.Writer = responseWriterErr{} - err = c.XML(0, 0) - testify.Error(t, err) - - // XMLPretty - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) - err = c.XMLPretty(http.StatusOK, user{1, "Jon Snow"}, " ") - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal(MIMEApplicationXMLCharsetUTF8, rec.Header().Get(HeaderContentType)) - assert.Equal(xml.Header+userXMLPretty, rec.Body.String()) - } - - t.Run("empty indent", func(t *testing.T) { - var ( - u = user{1, "Jon Snow"} - buf = new(bytes.Buffer) - emptyIndent = "" - ) - - t.Run("json", func(t *testing.T) { - buf.Reset() - assert := testify.New(t) - - // New JSONBlob with empty indent - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) - enc := json.NewEncoder(buf) - enc.SetIndent(emptyIndent, emptyIndent) - err = enc.Encode(u) - err = c.json(http.StatusOK, user{1, "Jon Snow"}, emptyIndent) - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal(MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) - assert.Equal(buf.String(), rec.Body.String()) - } - }) + err := c.JSONP(http.StatusOK, callback, user{1, "Jon Snow"}) + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, MIMEApplicationJavaScriptCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, callback+"("+userJSON+"\n);", rec.Body.String()) + } +} - t.Run("xml", func(t *testing.T) { - buf.Reset() - assert := testify.New(t) - - // New XMLBlob with empty indent - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) - enc := xml.NewEncoder(buf) - enc.Indent(emptyIndent, emptyIndent) - err = enc.Encode(u) - err = c.xml(http.StatusOK, user{1, "Jon Snow"}, emptyIndent) - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal(MIMEApplicationXMLCharsetUTF8, rec.Header().Get(HeaderContentType)) - assert.Equal(xml.Header+buf.String(), rec.Body.String()) - } - }) - }) +func TestContextJSONBlob(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + c := e.NewContext(req, rec).(*context) - // Legacy JSONBlob - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) data, err := json.Marshal(user{1, "Jon Snow"}) - assert.NoError(err) + assert.NoError(t, err) err = c.JSONBlob(http.StatusOK, data) - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal(MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) - assert.Equal(userJSON, rec.Body.String()) - } - - // Legacy JSONPBlob - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) - callback = "callback" - data, err = json.Marshal(user{1, "Jon Snow"}) - assert.NoError(err) + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, userJSON, rec.Body.String()) + } +} + +func TestContextJSONPBlob(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + c := e.NewContext(req, rec).(*context) + + callback := "callback" + data, err := json.Marshal(user{1, "Jon Snow"}) + assert.NoError(t, err) err = c.JSONPBlob(http.StatusOK, callback, data) - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal(MIMEApplicationJavaScriptCharsetUTF8, rec.Header().Get(HeaderContentType)) - assert.Equal(callback+"("+userJSON+");", rec.Body.String()) + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, MIMEApplicationJavaScriptCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, callback+"("+userJSON+");", rec.Body.String()) + } +} + +func TestContextXML(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + c := e.NewContext(req, rec).(*context) + + err := c.XML(http.StatusOK, user{1, "Jon Snow"}) + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, MIMEApplicationXMLCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, xml.Header+userXML, rec.Body.String()) + } +} + +func TestContextXMLPrettyURL(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/?pretty", nil) + c := e.NewContext(req, rec).(*context) + + err := c.XML(http.StatusOK, user{1, "Jon Snow"}) + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, MIMEApplicationXMLCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, xml.Header+userXMLPretty, rec.Body.String()) } +} - // Legacy XMLBlob - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) - data, err = xml.Marshal(user{1, "Jon Snow"}) - assert.NoError(err) +func TestContextXMLPretty(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + c := e.NewContext(req, rec).(*context) + + err := c.XMLPretty(http.StatusOK, user{1, "Jon Snow"}, " ") + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, MIMEApplicationXMLCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, xml.Header+userXMLPretty, rec.Body.String()) + } +} + +func TestContextXMLBlob(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + c := e.NewContext(req, rec).(*context) + + data, err := xml.Marshal(user{1, "Jon Snow"}) + assert.NoError(t, err) err = c.XMLBlob(http.StatusOK, data) - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal(MIMEApplicationXMLCharsetUTF8, rec.Header().Get(HeaderContentType)) - assert.Equal(xml.Header+userXML, rec.Body.String()) - } - - // String - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) - err = c.String(http.StatusOK, "Hello, World!") - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal(MIMETextPlainCharsetUTF8, rec.Header().Get(HeaderContentType)) - assert.Equal("Hello, World!", rec.Body.String()) - } - - // HTML - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) - err = c.HTML(http.StatusOK, "Hello, World!") - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal(MIMETextHTMLCharsetUTF8, rec.Header().Get(HeaderContentType)) - assert.Equal("Hello, World!", rec.Body.String()) - } - - // Stream - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, MIMEApplicationXMLCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, xml.Header+userXML, rec.Body.String()) + } +} + +func TestContextXMLWithEmptyIntent(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + c := e.NewContext(req, rec).(*context) + + u := user{1, "Jon Snow"} + emptyIndent := "" + buf := new(bytes.Buffer) + + enc := xml.NewEncoder(buf) + enc.Indent(emptyIndent, emptyIndent) + _ = enc.Encode(u) + err := c.xml(http.StatusOK, user{1, "Jon Snow"}, emptyIndent) + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, MIMEApplicationXMLCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, xml.Header+buf.String(), rec.Body.String()) + } +} + +type responseWriterErr struct { +} + +func (responseWriterErr) Header() http.Header { + return http.Header{} +} + +func (responseWriterErr) Write([]byte) (int, error) { + return 0, errors.New("responseWriterErr") +} + +func (responseWriterErr) WriteHeader(statusCode int) { +} + +func TestContextXMLError(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/?pretty", nil) + c := e.NewContext(req, rec).(*context) + c.response.Writer = responseWriterErr{} + + err := c.XML(http.StatusOK, make(chan bool)) + assert.EqualError(t, err, "responseWriterErr") +} + +func TestContextString(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/?pretty", nil) + c := e.NewContext(req, rec).(*context) + + err := c.String(http.StatusOK, "Hello, World!") + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, MIMETextPlainCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, "Hello, World!", rec.Body.String()) + } +} + +func TestContextHTML(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/?pretty", nil) + c := e.NewContext(req, rec).(*context) + + err := c.HTML(http.StatusOK, "Hello, World!") + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, MIMETextHTMLCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, "Hello, World!", rec.Body.String()) + } +} + +func TestContextStream(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/?pretty", nil) + c := e.NewContext(req, rec).(*context) + r := strings.NewReader("response from a stream") - err = c.Stream(http.StatusOK, "application/octet-stream", r) - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal("application/octet-stream", rec.Header().Get(HeaderContentType)) - assert.Equal("response from a stream", rec.Body.String()) - } - - // Attachment - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) - err = c.Attachment("_fixture/images/walle.png", "walle.png") - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal("attachment; filename=\"walle.png\"", rec.Header().Get(HeaderContentDisposition)) - assert.Equal(219885, rec.Body.Len()) - } - - // Inline - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) - err = c.Inline("_fixture/images/walle.png", "walle.png") - if assert.NoError(err) { - assert.Equal(http.StatusOK, rec.Code) - assert.Equal("inline; filename=\"walle.png\"", rec.Header().Get(HeaderContentDisposition)) - assert.Equal(219885, rec.Body.Len()) - } - - // NoContent - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) + err := c.Stream(http.StatusOK, "application/octet-stream", r) + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, "application/octet-stream", rec.Header().Get(HeaderContentType)) + assert.Equal(t, "response from a stream", rec.Body.String()) + } +} + +func TestContextAttachment(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/?pretty", nil) + c := e.NewContext(req, rec).(*context) + + err := c.Attachment("_fixture/images/walle.png", "walle.png") + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, "attachment; filename=\"walle.png\"", rec.Header().Get(HeaderContentDisposition)) + assert.Equal(t, 219885, rec.Body.Len()) + } +} + +func TestContextInline(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/?pretty", nil) + c := e.NewContext(req, rec).(*context) + + err := c.Inline("_fixture/images/walle.png", "walle.png") + if assert.NoError(t, err) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, "inline; filename=\"walle.png\"", rec.Header().Get(HeaderContentDisposition)) + assert.Equal(t, 219885, rec.Body.Len()) + } +} + +func TestContextNoContent(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/?pretty", nil) + c := e.NewContext(req, rec).(*context) + c.NoContent(http.StatusOK) - assert.Equal(http.StatusOK, rec.Code) + assert.Equal(t, http.StatusOK, rec.Code) +} + +func TestContextError(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/?pretty", nil) + c := e.NewContext(req, rec).(*context) - // Error - rec = httptest.NewRecorder() - c = e.NewContext(req, rec).(*context) c.Error(errors.New("error")) - assert.Equal(http.StatusInternalServerError, rec.Code) + assert.Equal(t, http.StatusInternalServerError, rec.Code) + assert.True(t, c.Response().Committed) +} + +func TestContextReset(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + c := e.NewContext(req, rec).(*context) - // Reset c.SetParamNames("foo") c.SetParamValues("bar") c.Set("foe", "ban") c.query = url.Values(map[string][]string{"fon": {"baz"}}) + c.Reset(req, httptest.NewRecorder()) - assert.Equal(0, len(c.ParamValues())) - assert.Equal(0, len(c.ParamNames())) - assert.Equal(0, len(c.store)) - assert.Equal("", c.Path()) - assert.Equal(0, len(c.QueryParams())) + + assert.Len(t, c.ParamValues(), 0) + assert.Len(t, c.ParamNames(), 0) + assert.Len(t, c.Path(), 0) + assert.Len(t, c.QueryParams(), 0) + assert.Len(t, c.store, 0) } func TestContext_JSON_CommitsCustomResponseCode(t *testing.T) { @@ -391,11 +489,10 @@ func TestContext_JSON_CommitsCustomResponseCode(t *testing.T) { c := e.NewContext(req, rec).(*context) err := c.JSON(http.StatusCreated, user{1, "Jon Snow"}) - assert := testify.New(t) - if assert.NoError(err) { - assert.Equal(http.StatusCreated, rec.Code) - assert.Equal(MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) - assert.Equal(userJSON+"\n", rec.Body.String()) + if assert.NoError(t, err) { + assert.Equal(t, http.StatusCreated, rec.Code) + assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, userJSON+"\n", rec.Body.String()) } } @@ -406,9 +503,8 @@ func TestContext_JSON_DoesntCommitResponseCodePrematurely(t *testing.T) { c := e.NewContext(req, rec).(*context) err := c.JSON(http.StatusCreated, map[string]float64{"a": math.NaN()}) - assert := testify.New(t) - if assert.Error(err) { - assert.False(c.response.Committed) + if assert.Error(t, err) { + assert.False(t, c.response.Committed) } } @@ -422,22 +518,20 @@ func TestContextCookie(t *testing.T) { rec := httptest.NewRecorder() c := e.NewContext(req, rec).(*context) - assert := testify.New(t) - // Read single cookie, err := c.Cookie("theme") - if assert.NoError(err) { - assert.Equal("theme", cookie.Name) - assert.Equal("light", cookie.Value) + if assert.NoError(t, err) { + assert.Equal(t, "theme", cookie.Name) + assert.Equal(t, "light", cookie.Value) } // Read multiple for _, cookie := range c.Cookies() { switch cookie.Name { case "theme": - assert.Equal("light", cookie.Value) + assert.Equal(t, "light", cookie.Value) case "user": - assert.Equal("Jon Snow", cookie.Value) + assert.Equal(t, "Jon Snow", cookie.Value) } } @@ -452,11 +546,11 @@ func TestContextCookie(t *testing.T) { HttpOnly: true, } c.SetCookie(cookie) - assert.Contains(rec.Header().Get(HeaderSetCookie), "SSID") - assert.Contains(rec.Header().Get(HeaderSetCookie), "Ap4PGTEq") - assert.Contains(rec.Header().Get(HeaderSetCookie), "labstack.com") - assert.Contains(rec.Header().Get(HeaderSetCookie), "Secure") - assert.Contains(rec.Header().Get(HeaderSetCookie), "HttpOnly") + assert.Contains(t, rec.Header().Get(HeaderSetCookie), "SSID") + assert.Contains(t, rec.Header().Get(HeaderSetCookie), "Ap4PGTEq") + assert.Contains(t, rec.Header().Get(HeaderSetCookie), "labstack.com") + assert.Contains(t, rec.Header().Get(HeaderSetCookie), "Secure") + assert.Contains(t, rec.Header().Get(HeaderSetCookie), "HttpOnly") } func TestContextPath(t *testing.T) { @@ -469,14 +563,12 @@ func TestContextPath(t *testing.T) { c := e.NewContext(nil, nil) r.Find(http.MethodGet, "/users/1", c) - assert := testify.New(t) - - assert.Equal("/users/:id", c.Path()) + assert.Equal(t, "/users/:id", c.Path()) r.Add(http.MethodGet, "/users/:uid/files/:fid", handler) c = e.NewContext(nil, nil) r.Find(http.MethodGet, "/users/1/files/1", c) - assert.Equal("/users/:uid/files/:fid", c.Path()) + assert.Equal(t, "/users/:uid/files/:fid", c.Path()) } func TestContextPathParam(t *testing.T) { @@ -486,15 +578,15 @@ func TestContextPathParam(t *testing.T) { // ParamNames c.SetParamNames("uid", "fid") - testify.EqualValues(t, []string{"uid", "fid"}, c.ParamNames()) + assert.EqualValues(t, []string{"uid", "fid"}, c.ParamNames()) // ParamValues c.SetParamValues("101", "501") - testify.EqualValues(t, []string{"101", "501"}, c.ParamValues()) + assert.EqualValues(t, []string{"101", "501"}, c.ParamValues()) // Param - testify.Equal(t, "501", c.Param("fid")) - testify.Equal(t, "", c.Param("undefined")) + assert.Equal(t, "501", c.Param("fid")) + assert.Equal(t, "", c.Param("undefined")) } func TestContextGetAndSetParam(t *testing.T) { @@ -507,23 +599,21 @@ func TestContextGetAndSetParam(t *testing.T) { // round-trip param values with modification paramVals := c.ParamValues() - testify.EqualValues(t, []string{""}, c.ParamValues()) + assert.EqualValues(t, []string{""}, c.ParamValues()) paramVals[0] = "bar" c.SetParamValues(paramVals...) - testify.EqualValues(t, []string{"bar"}, c.ParamValues()) + assert.EqualValues(t, []string{"bar"}, c.ParamValues()) // shouldn't explode during Reset() afterwards! - testify.NotPanics(t, func() { + assert.NotPanics(t, func() { c.Reset(nil, nil) }) } // Issue #1655 func TestContextSetParamNamesShouldUpdateEchoMaxParam(t *testing.T) { - assert := testify.New(t) - e := New() - assert.Equal(0, *e.maxParam) + assert.Equal(t, 0, *e.maxParam) expectedOneParam := []string{"one"} expectedTwoParams := []string{"one", "two"} @@ -533,23 +623,23 @@ func TestContextSetParamNamesShouldUpdateEchoMaxParam(t *testing.T) { c := e.NewContext(nil, nil) c.SetParamNames("1", "2") c.SetParamValues(expectedTwoParams...) - assert.Equal(2, *e.maxParam) - assert.EqualValues(expectedTwoParams, c.ParamValues()) + assert.Equal(t, 2, *e.maxParam) + assert.EqualValues(t, expectedTwoParams, c.ParamValues()) c.SetParamNames("1") - assert.Equal(2, *e.maxParam) + assert.Equal(t, 2, *e.maxParam) // Here for backward compatibility the ParamValues remains as they are - assert.EqualValues(expectedOneParam, c.ParamValues()) + assert.EqualValues(t, expectedOneParam, c.ParamValues()) c.SetParamNames("1", "2", "3") - assert.Equal(3, *e.maxParam) + assert.Equal(t, 3, *e.maxParam) // Here for backward compatibility the ParamValues remains as they are, but the len is extended to e.maxParam - assert.EqualValues(expectedThreeParams, c.ParamValues()) + assert.EqualValues(t, expectedThreeParams, c.ParamValues()) c.SetParamValues("A", "B", "C", "D") - assert.Equal(3, *e.maxParam) + assert.Equal(t, 3, *e.maxParam) // Here D shouldn't be returned - assert.EqualValues(expectedABCParams, c.ParamValues()) + assert.EqualValues(t, expectedABCParams, c.ParamValues()) } func TestContextFormValue(t *testing.T) { @@ -563,13 +653,13 @@ func TestContextFormValue(t *testing.T) { c := e.NewContext(req, nil) // FormValue - testify.Equal(t, "Jon Snow", c.FormValue("name")) - testify.Equal(t, "jon@labstack.com", c.FormValue("email")) + assert.Equal(t, "Jon Snow", c.FormValue("name")) + assert.Equal(t, "jon@labstack.com", c.FormValue("email")) // FormParams params, err := c.FormParams() - if testify.NoError(t, err) { - testify.Equal(t, url.Values{ + if assert.NoError(t, err) { + assert.Equal(t, url.Values{ "name": []string{"Jon Snow"}, "email": []string{"jon@labstack.com"}, }, params) @@ -580,8 +670,8 @@ func TestContextFormValue(t *testing.T) { req.Header.Add(HeaderContentType, MIMEMultipartForm) c = e.NewContext(req, nil) params, err = c.FormParams() - testify.Nil(t, params) - testify.Error(t, err) + assert.Nil(t, params) + assert.Error(t, err) } func TestContextQueryParam(t *testing.T) { @@ -593,11 +683,11 @@ func TestContextQueryParam(t *testing.T) { c := e.NewContext(req, nil) // QueryParam - testify.Equal(t, "Jon Snow", c.QueryParam("name")) - testify.Equal(t, "jon@labstack.com", c.QueryParam("email")) + assert.Equal(t, "Jon Snow", c.QueryParam("name")) + assert.Equal(t, "jon@labstack.com", c.QueryParam("email")) // QueryParams - testify.Equal(t, url.Values{ + assert.Equal(t, url.Values{ "name": []string{"Jon Snow"}, "email": []string{"jon@labstack.com"}, }, c.QueryParams()) @@ -608,7 +698,7 @@ func TestContextFormFile(t *testing.T) { buf := new(bytes.Buffer) mr := multipart.NewWriter(buf) w, err := mr.CreateFormFile("file", "test") - if testify.NoError(t, err) { + if assert.NoError(t, err) { w.Write([]byte("test")) } mr.Close() @@ -617,8 +707,8 @@ func TestContextFormFile(t *testing.T) { rec := httptest.NewRecorder() c := e.NewContext(req, rec) f, err := c.FormFile("file") - if testify.NoError(t, err) { - testify.Equal(t, "test", f.Filename) + if assert.NoError(t, err) { + assert.Equal(t, "test", f.Filename) } } @@ -633,8 +723,8 @@ func TestContextMultipartForm(t *testing.T) { rec := httptest.NewRecorder() c := e.NewContext(req, rec) f, err := c.MultipartForm() - if testify.NoError(t, err) { - testify.NotNil(t, f) + if assert.NoError(t, err) { + assert.NotNil(t, f) } } @@ -643,16 +733,16 @@ func TestContextRedirect(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) - testify.Equal(t, nil, c.Redirect(http.StatusMovedPermanently, "http://labstack.github.io/echo")) - testify.Equal(t, http.StatusMovedPermanently, rec.Code) - testify.Equal(t, "http://labstack.github.io/echo", rec.Header().Get(HeaderLocation)) - testify.Error(t, c.Redirect(310, "http://labstack.github.io/echo")) + assert.Equal(t, nil, c.Redirect(http.StatusMovedPermanently, "http://labstack.github.io/echo")) + assert.Equal(t, http.StatusMovedPermanently, rec.Code) + assert.Equal(t, "http://labstack.github.io/echo", rec.Header().Get(HeaderLocation)) + assert.Error(t, c.Redirect(310, "http://labstack.github.io/echo")) } func TestContextStore(t *testing.T) { var c Context = new(context) c.Set("name", "Jon Snow") - testify.Equal(t, "Jon Snow", c.Get("name")) + assert.Equal(t, "Jon Snow", c.Get("name")) } func BenchmarkContext_Store(b *testing.B) { @@ -682,19 +772,19 @@ func TestContextHandler(t *testing.T) { c := e.NewContext(nil, nil) r.Find(http.MethodGet, "/handler", c) err := c.Handler()(c) - testify.Equal(t, "handler", b.String()) - testify.NoError(t, err) + assert.Equal(t, "handler", b.String()) + assert.NoError(t, err) } func TestContext_SetHandler(t *testing.T) { var c Context = new(context) - testify.Nil(t, c.Handler()) + assert.Nil(t, c.Handler()) c.SetHandler(func(c Context) error { return nil }) - testify.NotNil(t, c.Handler()) + assert.NotNil(t, c.Handler()) } func TestContext_Path(t *testing.T) { @@ -703,7 +793,7 @@ func TestContext_Path(t *testing.T) { var c Context = new(context) c.SetPath(path) - testify.Equal(t, path, c.Path()) + assert.Equal(t, path, c.Path()) } type validator struct{} @@ -716,10 +806,10 @@ func TestContext_Validate(t *testing.T) { e := New() c := e.NewContext(nil, nil) - testify.Error(t, c.Validate(struct{}{})) + assert.Error(t, c.Validate(struct{}{})) e.Validator = &validator{} - testify.NoError(t, c.Validate(struct{}{})) + assert.NoError(t, c.Validate(struct{}{})) } func TestContext_QueryString(t *testing.T) { @@ -730,18 +820,18 @@ func TestContext_QueryString(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/?"+queryString, nil) c := e.NewContext(req, nil) - testify.Equal(t, queryString, c.QueryString()) + assert.Equal(t, queryString, c.QueryString()) } func TestContext_Request(t *testing.T) { var c Context = new(context) - testify.Nil(t, c.Request()) + assert.Nil(t, c.Request()) req := httptest.NewRequest(http.MethodGet, "/path", nil) c.SetRequest(req) - testify.Equal(t, req, c.Request()) + assert.Equal(t, req, c.Request()) } func TestContext_Scheme(t *testing.T) { @@ -798,14 +888,14 @@ func TestContext_Scheme(t *testing.T) { } for _, tt := range tests { - testify.Equal(t, tt.s, tt.c.Scheme()) + assert.Equal(t, tt.s, tt.c.Scheme()) } } func TestContext_IsWebSocket(t *testing.T) { tests := []struct { c Context - ws testify.BoolAssertionFunc + ws assert.BoolAssertionFunc }{ { &context{ @@ -813,7 +903,7 @@ func TestContext_IsWebSocket(t *testing.T) { Header: http.Header{HeaderUpgrade: []string{"websocket"}}, }, }, - testify.True, + assert.True, }, { &context{ @@ -821,13 +911,13 @@ func TestContext_IsWebSocket(t *testing.T) { Header: http.Header{HeaderUpgrade: []string{"Websocket"}}, }, }, - testify.True, + assert.True, }, { &context{ request: &http.Request{}, }, - testify.False, + assert.False, }, { &context{ @@ -835,7 +925,7 @@ func TestContext_IsWebSocket(t *testing.T) { Header: http.Header{HeaderUpgrade: []string{"other"}}, }, }, - testify.False, + assert.False, }, } @@ -854,8 +944,8 @@ func TestContext_Bind(t *testing.T) { req.Header.Add(HeaderContentType, MIMEApplicationJSON) err := c.Bind(u) - testify.NoError(t, err) - testify.Equal(t, &user{1, "Jon Snow"}, u) + assert.NoError(t, err) + assert.Equal(t, &user{1, "Jon Snow"}, u) } func TestContext_Logger(t *testing.T) { @@ -863,15 +953,15 @@ func TestContext_Logger(t *testing.T) { c := e.NewContext(nil, nil) log1 := c.Logger() - testify.NotNil(t, log1) + assert.NotNil(t, log1) log2 := log.New("echo2") c.SetLogger(log2) - testify.Equal(t, log2, c.Logger()) + assert.Equal(t, log2, c.Logger()) // Resetting the context returns the initial logger c.Reset(nil, nil) - testify.Equal(t, log1, c.Logger()) + assert.Equal(t, log1, c.Logger()) } func TestContext_RealIP(t *testing.T) { @@ -959,6 +1049,6 @@ func TestContext_RealIP(t *testing.T) { } for _, tt := range tests { - testify.Equal(t, tt.s, tt.c.RealIP()) + assert.Equal(t, tt.s, tt.c.RealIP()) } } diff --git a/json_test.go b/json_test.go index 27ee43e73..8fb9ebc96 100644 --- a/json_test.go +++ b/json_test.go @@ -1,7 +1,7 @@ package echo import ( - testify "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" "strings" @@ -16,16 +16,14 @@ func TestDefaultJSONCodec_Encode(t *testing.T) { rec := httptest.NewRecorder() c := e.NewContext(req, rec).(*context) - assert := testify.New(t) - // Echo - assert.Equal(e, c.Echo()) + assert.Equal(t, e, c.Echo()) // Request - assert.NotNil(c.Request()) + assert.NotNil(t, c.Request()) // Response - assert.NotNil(c.Response()) + assert.NotNil(t, c.Response()) //-------- // Default JSON encoder @@ -34,16 +32,16 @@ func TestDefaultJSONCodec_Encode(t *testing.T) { enc := new(DefaultJSONSerializer) err := enc.Serialize(c, user{1, "Jon Snow"}, "") - if assert.NoError(err) { - assert.Equal(userJSON+"\n", rec.Body.String()) + if assert.NoError(t, err) { + assert.Equal(t, userJSON+"\n", rec.Body.String()) } req = httptest.NewRequest(http.MethodPost, "/", nil) rec = httptest.NewRecorder() c = e.NewContext(req, rec).(*context) err = enc.Serialize(c, user{1, "Jon Snow"}, " ") - if assert.NoError(err) { - assert.Equal(userJSONPretty+"\n", rec.Body.String()) + if assert.NoError(t, err) { + assert.Equal(t, userJSONPretty+"\n", rec.Body.String()) } } @@ -55,16 +53,14 @@ func TestDefaultJSONCodec_Decode(t *testing.T) { rec := httptest.NewRecorder() c := e.NewContext(req, rec).(*context) - assert := testify.New(t) - // Echo - assert.Equal(e, c.Echo()) + assert.Equal(t, e, c.Echo()) // Request - assert.NotNil(c.Request()) + assert.NotNil(t, c.Request()) // Response - assert.NotNil(c.Response()) + assert.NotNil(t, c.Response()) //-------- // Default JSON encoder @@ -74,8 +70,8 @@ func TestDefaultJSONCodec_Decode(t *testing.T) { var u = user{} err := enc.Deserialize(c, &u) - if assert.NoError(err) { - assert.Equal(u, user{ID: 1, Name: "Jon Snow"}) + if assert.NoError(t, err) { + assert.Equal(t, u, user{ID: 1, Name: "Jon Snow"}) } var userUnmarshalSyntaxError = user{} @@ -83,8 +79,8 @@ func TestDefaultJSONCodec_Decode(t *testing.T) { rec = httptest.NewRecorder() c = e.NewContext(req, rec).(*context) err = enc.Deserialize(c, &userUnmarshalSyntaxError) - assert.IsType(&HTTPError{}, err) - assert.EqualError(err, "code=400, message=Syntax error: offset=1, error=invalid character 'i' looking for beginning of value, internal=invalid character 'i' looking for beginning of value") + assert.IsType(t, &HTTPError{}, err) + assert.EqualError(t, err, "code=400, message=Syntax error: offset=1, error=invalid character 'i' looking for beginning of value, internal=invalid character 'i' looking for beginning of value") var userUnmarshalTypeError = struct { ID string `json:"id"` @@ -95,7 +91,7 @@ func TestDefaultJSONCodec_Decode(t *testing.T) { rec = httptest.NewRecorder() c = e.NewContext(req, rec).(*context) err = enc.Deserialize(c, &userUnmarshalTypeError) - assert.IsType(&HTTPError{}, err) - assert.EqualError(err, "code=400, message=Unmarshal type error: expected=string, got=number, field=id, offset=7, internal=json: cannot unmarshal number into Go struct field .id of type string") + assert.IsType(t, &HTTPError{}, err) + assert.EqualError(t, err, "code=400, message=Unmarshal type error: expected=string, got=number, field=id, offset=7, internal=json: cannot unmarshal number into Go struct field .id of type string") } From 14daeb968049b71296a80b91abd3883afd02b4d1 Mon Sep 17 00:00:00 2001 From: Martti T Date: Tue, 7 Nov 2023 14:10:06 +0200 Subject: [PATCH 04/74] Security: c.Attachment and c.Inline should escape name in `Content-Disposition` header to avoid 'Reflect File Download' vulnerability. (#2541) This is same as Go std does it https://github.com/golang/go/blob/9d836d41d0d9df3acabf7f9607d3b09188a9bfc6/src/mime/multipart/writer.go#L132 --- context.go | 4 ++- context_test.go | 82 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 65 insertions(+), 21 deletions(-) diff --git a/context.go b/context.go index 27da28a9c..6a1811685 100644 --- a/context.go +++ b/context.go @@ -584,8 +584,10 @@ func (c *context) Inline(file, name string) error { return c.contentDisposition(file, name, "inline") } +var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") + func (c *context) contentDisposition(file, name, dispositionType string) error { - c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", dispositionType, name)) + c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf(`%s; filename="%s"`, dispositionType, quoteEscaper.Replace(name))) return c.File(file) } diff --git a/context_test.go b/context_test.go index 85b221446..01a8784b8 100644 --- a/context_test.go +++ b/context_test.go @@ -414,30 +414,72 @@ func TestContextStream(t *testing.T) { } func TestContextAttachment(t *testing.T) { - e := New() - rec := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/?pretty", nil) - c := e.NewContext(req, rec).(*context) - - err := c.Attachment("_fixture/images/walle.png", "walle.png") - if assert.NoError(t, err) { - assert.Equal(t, http.StatusOK, rec.Code) - assert.Equal(t, "attachment; filename=\"walle.png\"", rec.Header().Get(HeaderContentDisposition)) - assert.Equal(t, 219885, rec.Body.Len()) + var testCases = []struct { + name string + whenName string + expectHeader string + }{ + { + name: "ok", + whenName: "walle.png", + expectHeader: `attachment; filename="walle.png"`, + }, + { + name: "ok, escape quotes in malicious filename", + whenName: `malicious.sh"; \"; dummy=.txt`, + expectHeader: `attachment; filename="malicious.sh\"; \\\"; dummy=.txt"`, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + c := e.NewContext(req, rec).(*context) + + err := c.Attachment("_fixture/images/walle.png", tc.whenName) + if assert.NoError(t, err) { + assert.Equal(t, tc.expectHeader, rec.Header().Get(HeaderContentDisposition)) + + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, 219885, rec.Body.Len()) + } + }) } } func TestContextInline(t *testing.T) { - e := New() - rec := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/?pretty", nil) - c := e.NewContext(req, rec).(*context) - - err := c.Inline("_fixture/images/walle.png", "walle.png") - if assert.NoError(t, err) { - assert.Equal(t, http.StatusOK, rec.Code) - assert.Equal(t, "inline; filename=\"walle.png\"", rec.Header().Get(HeaderContentDisposition)) - assert.Equal(t, 219885, rec.Body.Len()) + var testCases = []struct { + name string + whenName string + expectHeader string + }{ + { + name: "ok", + whenName: "walle.png", + expectHeader: `inline; filename="walle.png"`, + }, + { + name: "ok, escape quotes in malicious filename", + whenName: `malicious.sh"; \"; dummy=.txt`, + expectHeader: `inline; filename="malicious.sh\"; \\\"; dummy=.txt"`, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + e := New() + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + c := e.NewContext(req, rec).(*context) + + err := c.Inline("_fixture/images/walle.png", tc.whenName) + if assert.NoError(t, err) { + assert.Equal(t, tc.expectHeader, rec.Header().Get(HeaderContentDisposition)) + + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, 219885, rec.Body.Len()) + } + }) } } From 4b26cde851bc7a51e624c04dcc5d37be1ce0c84f Mon Sep 17 00:00:00 2001 From: Martti T Date: Tue, 7 Nov 2023 14:19:32 +0200 Subject: [PATCH 05/74] Changelog for v4.11.3 (#2542) --- CHANGELOG.md | 13 +++++++++++++ echo.go | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40016c9ed..8490ab2c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## v4.11.3 - 2023-11-07 + +**Security** + +* 'c.Attachment' and 'c.Inline' should escape filename in 'Content-Disposition' header to avoid 'Reflect File Download' vulnerability. [#2541](https://github.com/labstack/echo/pull/2541) + +**Enhancements** + +* Tests: refactor context tests to be separate functions [#2540](https://github.com/labstack/echo/pull/2540) +* Proxy middleware: reuse echo request context [#2537](https://github.com/labstack/echo/pull/2537) +* Mark unmarshallable yaml struct tags as ignored [#2536](https://github.com/labstack/echo/pull/2536) + + ## v4.11.2 - 2023-10-11 **Security** diff --git a/echo.go b/echo.go index 8bdf97539..0ac644924 100644 --- a/echo.go +++ b/echo.go @@ -259,7 +259,7 @@ const ( const ( // Version of Echo - Version = "4.11.2" + Version = "4.11.3" website = "https://echo.labstack.com" // http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo banner = ` From 584cb85a6b749846ac26a8cd151244ab281f2abc Mon Sep 17 00:00:00 2001 From: Martti T Date: Tue, 7 Nov 2023 15:09:43 +0200 Subject: [PATCH 06/74] request logger: add example for Slog https://pkg.go.dev/log/slog (#2543) --- middleware/request_logger.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/middleware/request_logger.go b/middleware/request_logger.go index ce76230c7..f82f6b622 100644 --- a/middleware/request_logger.go +++ b/middleware/request_logger.go @@ -8,6 +8,30 @@ import ( "github.com/labstack/echo/v4" ) +// Example for `slog` https://pkg.go.dev/log/slog +// logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) +// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ +// LogStatus: true, +// LogURI: true, +// LogError: true, +// HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code +// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error { +// if v.Error == nil { +// logger.LogAttrs(context.Background(), slog.LevelInfo, "REQUEST", +// slog.String("uri", v.URI), +// slog.Int("status", v.Status), +// ) +// } else { +// logger.LogAttrs(context.Background(), slog.LevelError, "REQUEST_ERROR", +// slog.String("uri", v.URI), +// slog.Int("status", v.Status), +// slog.String("err", v.Error.Error()), +// ) +// } +// return nil +// }, +// })) +// // Example for `fmt.Printf` // e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ // LogStatus: true, From 287a82c228efce23fac50e84d37e8690896bf5a5 Mon Sep 17 00:00:00 2001 From: Nicu Maxian Date: Tue, 19 Dec 2023 18:07:23 +0200 Subject: [PATCH 07/74] Upgrade golang.org/x/crypto to v0.17.0 to fix vulnerability issue (#2562) Co-authored-by: Nicu MAXIAN --- go.mod | 6 +++--- go.sum | 10 +++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 367dcb8cb..e4944b016 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/labstack/gommon v0.4.0 github.com/stretchr/testify v1.8.4 github.com/valyala/fasttemplate v1.2.2 - golang.org/x/crypto v0.14.0 + golang.org/x/crypto v0.17.0 golang.org/x/net v0.17.0 golang.org/x/time v0.3.0 ) @@ -18,7 +18,7 @@ require ( github.com/mattn/go-isatty v0.0.19 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5b8ba6bcb..5664e0e6c 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,9 @@ github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -56,20 +57,23 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 209c6a199af0d6443f640528351064ba31b5f864 Mon Sep 17 00:00:00 2001 From: Martti T Date: Wed, 20 Dec 2023 15:17:20 +0200 Subject: [PATCH 08/74] Update deps and mark Go version to 1.18 as this is what golang.org/x/* use. (#2563) --- go.mod | 10 ++++----- go.sum | 70 +++++++--------------------------------------------------- 2 files changed, 13 insertions(+), 67 deletions(-) diff --git a/go.mod b/go.mod index e4944b016..089ffb140 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,21 @@ module github.com/labstack/echo/v4 -go 1.17 +go 1.18 require ( github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/labstack/gommon v0.4.0 + github.com/labstack/gommon v0.4.2 github.com/stretchr/testify v1.8.4 github.com/valyala/fasttemplate v1.2.2 golang.org/x/crypto v0.17.0 - golang.org/x/net v0.17.0 - golang.org/x/time v0.3.0 + golang.org/x/net v0.19.0 + golang.org/x/time v0.5.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect golang.org/x/sys v0.15.0 // indirect diff --git a/go.sum b/go.sum index 5664e0e6c..0584b7e59 100644 --- a/go.sum +++ b/go.sum @@ -1,89 +1,35 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= -github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 226e4f076a69de85b71cf059d8a3c0fa8feafcaf Mon Sep 17 00:00:00 2001 From: Martti T Date: Wed, 20 Dec 2023 15:24:30 +0200 Subject: [PATCH 09/74] Changelog for v4.11.4 (#2564) Changelog for v4.11.4 --- CHANGELOG.md | 12 ++++++++++++ echo.go | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8490ab2c8..cc17e28d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## v4.11.4 - 2023-12-20 + +**Security** + +* Upgrade golang.org/x/crypto to v0.17.0 to fix vulnerability [issue](https://pkg.go.dev/vuln/GO-2023-2402) [#2562](https://github.com/labstack/echo/pull/2562) + +**Enhancements** + +* Update deps and mark Go version to 1.18 as this is what golang.org/x/* use [#2563](https://github.com/labstack/echo/pull/2563) +* Request logger: add example for Slog https://pkg.go.dev/log/slog [#2543](https://github.com/labstack/echo/pull/2543) + + ## v4.11.3 - 2023-11-07 **Security** diff --git a/echo.go b/echo.go index 0ac644924..9924ac86d 100644 --- a/echo.go +++ b/echo.go @@ -259,7 +259,7 @@ const ( const ( // Version of Echo - Version = "4.11.3" + Version = "4.11.4" website = "https://echo.labstack.com" // http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo banner = ` From 60fc2fb1b76f5613fc41aa9315cad6e8c96c6859 Mon Sep 17 00:00:00 2001 From: Martti T Date: Wed, 20 Dec 2023 15:32:51 +0200 Subject: [PATCH 10/74] binder: make binding to Map work better with string destinations (#2554) --- bind.go | 22 ++++++++++++++++++--- bind_test.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/bind.go b/bind.go index 374a2aec5..6f41ce587 100644 --- a/bind.go +++ b/bind.go @@ -131,10 +131,26 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri typ := reflect.TypeOf(destination).Elem() val := reflect.ValueOf(destination).Elem() - // Map - if typ.Kind() == reflect.Map { + // Support binding to limited Map destinations: + // - map[string][]string, + // - map[string]string <-- (binds first value from data slice) + // - map[string]interface{} + // You are better off binding to struct but there are user who want this map feature. Source of data for these cases are: + // params,query,header,form as these sources produce string values, most of the time slice of strings, actually. + if typ.Kind() == reflect.Map && typ.Key().Kind() == reflect.String { + k := typ.Elem().Kind() + isElemInterface := k == reflect.Interface + isElemString := k == reflect.String + isElemSliceOfStrings := k == reflect.Slice && typ.Elem().Elem().Kind() == reflect.String + if !(isElemSliceOfStrings || isElemString || isElemInterface) { + return nil + } for k, v := range data { - val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0])) + if isElemString { + val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0])) + } else { + val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v)) + } } return nil } diff --git a/bind_test.go b/bind_test.go index c35283dcf..c11723303 100644 --- a/bind_test.go +++ b/bind_test.go @@ -429,6 +429,62 @@ func TestBindUnsupportedMediaType(t *testing.T) { testBindError(t, strings.NewReader(invalidContent), MIMEApplicationJSON, &json.SyntaxError{}) } +func TestDefaultBinder_bindDataToMap(t *testing.T) { + exampleData := map[string][]string{ + "multiple": {"1", "2"}, + "single": {"3"}, + } + + t.Run("ok, bind to map[string]string", func(t *testing.T) { + dest := map[string]string{} + assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param")) + assert.Equal(t, + map[string]string{ + "multiple": "1", + "single": "3", + }, + dest, + ) + }) + + t.Run("ok, bind to map[string][]string", func(t *testing.T) { + dest := map[string][]string{} + assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param")) + assert.Equal(t, + map[string][]string{ + "multiple": {"1", "2"}, + "single": {"3"}, + }, + dest, + ) + }) + + t.Run("ok, bind to map[string]interface", func(t *testing.T) { + dest := map[string]interface{}{} + assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param")) + assert.Equal(t, + map[string]interface{}{ + "multiple": []string{"1", "2"}, + "single": []string{"3"}, + }, + dest, + ) + }) + + t.Run("ok, bind to map[string]int skips", func(t *testing.T) { + dest := map[string]int{} + assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param")) + assert.Equal(t, map[string]int{}, dest) + }) + + t.Run("ok, bind to map[string][]int skips", func(t *testing.T) { + dest := map[string][]int{} + assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param")) + assert.Equal(t, map[string][]int{}, dest) + }) + +} + func TestBindbindData(t *testing.T) { ts := new(bindTestStruct) b := new(DefaultBinder) From d26212069089c65de25653d361932c0dd6f4d379 Mon Sep 17 00:00:00 2001 From: Marcus Kohlberg <78424526+marcuskohlberg@users.noreply.github.com> Date: Tue, 23 Jan 2024 04:26:05 +0100 Subject: [PATCH 11/74] README.md: add Encore as sponsor (#2579) There wasn't a sponsors section so I had to design one, hope you think it makes sense. --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 18accea75..0a302072d 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,18 @@ For older versions, please use the latest v3 tag. - Automatic TLS via Let’s Encrypt - HTTP/2 support +## Sponsors + + +
+ +Click [here](https://github.com/sponsors/labstack) for more information on sponsorship. + ## Benchmarks Date: 2020/11/11
From b835498241989eea914fb63b774de801e6c16833 Mon Sep 17 00:00:00 2001 From: Martti T Date: Wed, 24 Jan 2024 17:45:40 +0200 Subject: [PATCH 12/74] Reorder paragraphs in README.md (#2581) --- README.md | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 0a302072d..351ba3c55 100644 --- a/README.md +++ b/README.md @@ -9,20 +9,18 @@ [![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack) [![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo/master/LICENSE) -## Supported Go versions +## Echo -Latest version of Echo supports last four Go major [releases](https://go.dev/doc/devel/release) and might work with -older versions. +High performance, extensible, minimalist Go web framework. -As of version 4.0.0, Echo is available as a [Go module](https://github.com/golang/go/wiki/Modules). -Therefore a Go version capable of understanding /vN suffixed imports is required: +* [Official website](https://echo.labstack.com) +* [Quick start](https://echo.labstack.com/docs/quick-start) +* [Middlewares](https://echo.labstack.com/docs/category/middleware) -Any of these versions will allow you to import Echo as `github.com/labstack/echo/v4` which is the recommended -way of using Echo going forward. +Help and questions: [Github Discussions](https://github.com/labstack/echo/discussions) -For older versions, please use the latest v3 tag. -## Feature Overview +### Feature Overview - Optimized HTTP router which smartly prioritize routes - Build robust and scalable RESTful APIs @@ -69,6 +67,7 @@ The benchmarks above were run on an Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz // go get github.com/labstack/echo/{version} go get github.com/labstack/echo/v4 ``` +Latest version of Echo supports last four Go major [releases](https://go.dev/doc/devel/release) and might work with older versions. ### Example @@ -129,10 +128,6 @@ of middlewares in this list. Please send a PR to add your own library here. -## Help - -- [Forum](https://github.com/labstack/echo/discussions) - ## Contribute **Use issues for everything** From f12fdb09cd4d7afc749d132f12b60422753d8ecb Mon Sep 17 00:00:00 2001 From: Martti T Date: Sun, 28 Jan 2024 17:16:51 +0200 Subject: [PATCH 13/74] CI: upgrade actions/checkout to v4 and actions/setup-go to v5 (#2584) * CI: upgrade actions/checkout to v4 * CI: upgrade actions/setup-go to v5 --- .github/workflows/checks.yml | 4 ++-- .github/workflows/echo.yml | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 440f0ec52..fbd6d9571 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -21,10 +21,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Go ${{ matrix.go }} - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ env.LATEST_GO_VERSION }} check-latest: true diff --git a/.github/workflows/echo.yml b/.github/workflows/echo.yml index c240dd0c5..5722dcbe9 100644 --- a/.github/workflows/echo.yml +++ b/.github/workflows/echo.yml @@ -30,10 +30,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Go ${{ matrix.go }} - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} @@ -53,18 +53,18 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code (Previous) - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.base_ref }} path: previous - name: Checkout Code (New) - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: new - name: Set up Go ${{ matrix.go }} - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ env.LATEST_GO_VERSION }} From 76994d17d59d25c53c4e333d2a2048410e0748e1 Mon Sep 17 00:00:00 2001 From: Suwon Chae Date: Tue, 6 Feb 2024 14:41:33 +0900 Subject: [PATCH 14/74] Remove default charset from 'application/json' Content-Type header (#2568) Fixes #2567 --- context.go | 4 ++-- context_test.go | 12 ++++++------ echo.go | 7 ++++++- middleware/decompress_test.go | 2 +- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/context.go b/context.go index 6a1811685..d4cba8447 100644 --- a/context.go +++ b/context.go @@ -489,7 +489,7 @@ func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error } func (c *context) json(code int, i interface{}, indent string) error { - c.writeContentType(MIMEApplicationJSONCharsetUTF8) + c.writeContentType(MIMEApplicationJSON) c.response.Status = code return c.echo.JSONSerializer.Serialize(c, i, indent) } @@ -507,7 +507,7 @@ func (c *context) JSONPretty(code int, i interface{}, indent string) (err error) } func (c *context) JSONBlob(code int, b []byte) (err error) { - return c.Blob(code, MIMEApplicationJSONCharsetUTF8, b) + return c.Blob(code, MIMEApplicationJSON, b) } func (c *context) JSONP(code int, callback string, i interface{}) (err error) { diff --git a/context_test.go b/context_test.go index 01a8784b8..4ca2cc84b 100644 --- a/context_test.go +++ b/context_test.go @@ -154,7 +154,7 @@ func TestContextJSON(t *testing.T) { err := c.JSON(http.StatusOK, user{1, "Jon Snow"}) if assert.NoError(t, err) { assert.Equal(t, http.StatusOK, rec.Code) - assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, MIMEApplicationJSON, rec.Header().Get(HeaderContentType)) assert.Equal(t, userJSON+"\n", rec.Body.String()) } } @@ -178,7 +178,7 @@ func TestContextJSONPrettyURL(t *testing.T) { err := c.JSON(http.StatusOK, user{1, "Jon Snow"}) if assert.NoError(t, err) { assert.Equal(t, http.StatusOK, rec.Code) - assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, MIMEApplicationJSON, rec.Header().Get(HeaderContentType)) assert.Equal(t, userJSONPretty+"\n", rec.Body.String()) } } @@ -192,7 +192,7 @@ func TestContextJSONPretty(t *testing.T) { err := c.JSONPretty(http.StatusOK, user{1, "Jon Snow"}, " ") if assert.NoError(t, err) { assert.Equal(t, http.StatusOK, rec.Code) - assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, MIMEApplicationJSON, rec.Header().Get(HeaderContentType)) assert.Equal(t, userJSONPretty+"\n", rec.Body.String()) } } @@ -213,7 +213,7 @@ func TestContextJSONWithEmptyIntent(t *testing.T) { err := c.json(http.StatusOK, user{1, "Jon Snow"}, emptyIndent) if assert.NoError(t, err) { assert.Equal(t, http.StatusOK, rec.Code) - assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, MIMEApplicationJSON, rec.Header().Get(HeaderContentType)) assert.Equal(t, buf.String(), rec.Body.String()) } } @@ -244,7 +244,7 @@ func TestContextJSONBlob(t *testing.T) { err = c.JSONBlob(http.StatusOK, data) if assert.NoError(t, err) { assert.Equal(t, http.StatusOK, rec.Code) - assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, MIMEApplicationJSON, rec.Header().Get(HeaderContentType)) assert.Equal(t, userJSON, rec.Body.String()) } } @@ -533,7 +533,7 @@ func TestContext_JSON_CommitsCustomResponseCode(t *testing.T) { if assert.NoError(t, err) { assert.Equal(t, http.StatusCreated, rec.Code) - assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType)) + assert.Equal(t, MIMEApplicationJSON, rec.Header().Get(HeaderContentType)) assert.Equal(t, userJSON+"\n", rec.Body.String()) } } diff --git a/echo.go b/echo.go index 9924ac86d..7b6a0907d 100644 --- a/echo.go +++ b/echo.go @@ -169,7 +169,12 @@ const ( // MIME types const ( - MIMEApplicationJSON = "application/json" + // MIMEApplicationJSON JavaScript Object Notation (JSON) https://www.rfc-editor.org/rfc/rfc8259 + MIMEApplicationJSON = "application/json" + // Deprecated: Please use MIMEApplicationJSON instead. JSON should be encoded using UTF-8 by default. + // No "charset" parameter is defined for this registration. + // Adding one really has no effect on compliant recipients. + // See RFC 8259, section 8.1. https://datatracker.ietf.org/doc/html/rfc8259#section-8.1 MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8 MIMEApplicationJavaScript = "application/javascript" MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8 diff --git a/middleware/decompress_test.go b/middleware/decompress_test.go index 2e73ba80e..351e0e708 100644 --- a/middleware/decompress_test.go +++ b/middleware/decompress_test.go @@ -131,7 +131,7 @@ func TestDecompressSkipper(t *testing.T) { rec := httptest.NewRecorder() c := e.NewContext(req, rec) e.ServeHTTP(rec, req) - assert.Equal(t, rec.Header().Get(echo.HeaderContentType), echo.MIMEApplicationJSONCharsetUTF8) + assert.Equal(t, rec.Header().Get(echo.HeaderContentType), echo.MIMEApplicationJSON) reqBody, err := io.ReadAll(c.Request().Body) assert.NoError(t, err) assert.Equal(t, body, string(reqBody)) From 51c54f473486a5c6e5a9117aca3a6425d24d2731 Mon Sep 17 00:00:00 2001 From: toim Date: Wed, 7 Feb 2024 07:23:31 +0200 Subject: [PATCH 15/74] CI: Use Go 1.22 --- .github/workflows/checks.yml | 2 +- .github/workflows/echo.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index fbd6d9571..9ae5dbd5a 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -14,7 +14,7 @@ permissions: env: # run static analysis only with the latest Go version - LATEST_GO_VERSION: "1.21" + LATEST_GO_VERSION: "1.22" jobs: check: diff --git a/.github/workflows/echo.yml b/.github/workflows/echo.yml index 5722dcbe9..cb3dc448b 100644 --- a/.github/workflows/echo.yml +++ b/.github/workflows/echo.yml @@ -14,7 +14,7 @@ permissions: env: # run coverage and benchmarks only with the latest Go version - LATEST_GO_VERSION: "1.21" + LATEST_GO_VERSION: "1.22" jobs: test: @@ -25,7 +25,7 @@ jobs: # Echo tests with last four major releases (unless there are pressing vulnerabilities) # As we depend on `golang.org/x/` libraries which only support last 2 Go releases we could have situations when # we derive from last four major releases promise. - go: ["1.18", "1.19", "1.20", "1.21"] + go: ["1.19", "1.20", "1.21", "1.22"] name: ${{ matrix.os }} @ Go ${{ matrix.go }} runs-on: ${{ matrix.os }} steps: From 29aab274b3810dfd4e1be172d5a569ac3b9efcd6 Mon Sep 17 00:00:00 2001 From: toim Date: Wed, 7 Feb 2024 07:37:19 +0200 Subject: [PATCH 16/74] In Go 1.22 finding name of function with reflection has changed. change tests to work with that. --- echo_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/echo_test.go b/echo_test.go index a352e4026..416479191 100644 --- a/echo_test.go +++ b/echo_test.go @@ -1572,7 +1572,7 @@ func TestEcho_OnAddRouteHandler(t *testing.T) { }) } - e.GET("/static", NotFoundHandler) + e.GET("/static", dummyHandler) e.Host("domain.site").GET("/static/*", dummyHandler, func(next HandlerFunc) HandlerFunc { return func(c Context) error { return next(c) @@ -1582,7 +1582,7 @@ func TestEcho_OnAddRouteHandler(t *testing.T) { assert.Len(t, added, 2) assert.Equal(t, "", added[0].host) - assert.Equal(t, Route{Method: http.MethodGet, Path: "/static", Name: "github.com/labstack/echo/v4.glob..func1"}, added[0].route) + assert.Equal(t, Route{Method: http.MethodGet, Path: "/static", Name: "github.com/labstack/echo/v4.TestEcho_OnAddRouteHandler.func1"}, added[0].route) assert.Len(t, added[0].middleware, 0) assert.Equal(t, "domain.site", added[1].host) From ea529bbab6602db8bd9fc0746405a3687ffbd885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Georg=20M=C3=BCller?= Date: Tue, 6 Feb 2024 16:18:12 +0100 Subject: [PATCH 17/74] binder: allow binding to a nil map --- bind.go | 3 +++ bind_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/bind.go b/bind.go index 6f41ce587..51f4689e7 100644 --- a/bind.go +++ b/bind.go @@ -145,6 +145,9 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri if !(isElemSliceOfStrings || isElemString || isElemInterface) { return nil } + if val.IsNil() { + val.Set(reflect.MakeMap(typ)) + } for k, v := range data { if isElemString { val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0])) diff --git a/bind_test.go b/bind_test.go index c11723303..cffccfb35 100644 --- a/bind_test.go +++ b/bind_test.go @@ -447,6 +447,18 @@ func TestDefaultBinder_bindDataToMap(t *testing.T) { ) }) + t.Run("ok, bind to map[string]string with nil map", func(t *testing.T) { + var dest map[string]string + assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param")) + assert.Equal(t, + map[string]string{ + "multiple": "1", + "single": "3", + }, + dest, + ) + }) + t.Run("ok, bind to map[string][]string", func(t *testing.T) { dest := map[string][]string{} assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param")) @@ -459,6 +471,18 @@ func TestDefaultBinder_bindDataToMap(t *testing.T) { ) }) + t.Run("ok, bind to map[string][]string with nil map", func(t *testing.T) { + var dest map[string][]string + assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param")) + assert.Equal(t, + map[string][]string{ + "multiple": {"1", "2"}, + "single": {"3"}, + }, + dest, + ) + }) + t.Run("ok, bind to map[string]interface", func(t *testing.T) { dest := map[string]interface{}{} assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param")) @@ -471,18 +495,41 @@ func TestDefaultBinder_bindDataToMap(t *testing.T) { ) }) + t.Run("ok, bind to map[string]interface with nil map", func(t *testing.T) { + var dest map[string]interface{} + assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param")) + assert.Equal(t, + map[string]interface{}{ + "multiple": []string{"1", "2"}, + "single": []string{"3"}, + }, + dest, + ) + }) + t.Run("ok, bind to map[string]int skips", func(t *testing.T) { dest := map[string]int{} assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param")) assert.Equal(t, map[string]int{}, dest) }) + t.Run("ok, bind to map[string]int skips with nil map", func(t *testing.T) { + var dest map[string]int + assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param")) + assert.Equal(t, map[string]int(nil), dest) + }) + t.Run("ok, bind to map[string][]int skips", func(t *testing.T) { dest := map[string][]int{} assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param")) assert.Equal(t, map[string][]int{}, dest) }) + t.Run("ok, bind to map[string][]int skips with nil map", func(t *testing.T) { + var dest map[string][]int + assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param")) + assert.Equal(t, map[string][]int(nil), dest) + }) } func TestBindbindData(t *testing.T) { From fa70db801e3df89c7de8b8da161c3f41a1fe84d7 Mon Sep 17 00:00:00 2001 From: Ryo Kusnadi Date: Sun, 18 Feb 2024 20:47:13 +0700 Subject: [PATCH 18/74] Add Skipper Unit Test In BasicBasicAuthConfig and Add More Detail Explanation regarding BasicAuthValidator (#2461) * Add Skipper Unit Test In BasicBasicAuthConfig and Add More detail explanation regarding BasicAuthValidator * Simplify Skipper Unit Test --- middleware/basic_auth.go | 2 ++ middleware/basic_auth_test.go | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/middleware/basic_auth.go b/middleware/basic_auth.go index f9e8caafe..07a5761b8 100644 --- a/middleware/basic_auth.go +++ b/middleware/basic_auth.go @@ -25,6 +25,8 @@ type ( } // BasicAuthValidator defines a function to validate BasicAuth credentials. + // The function should return a boolean indicating whether the credentials are valid, + // and an error if any error occurs during the validation process. BasicAuthValidator func(string, string, echo.Context) (bool, error) ) diff --git a/middleware/basic_auth_test.go b/middleware/basic_auth_test.go index 20e769214..2e133e071 100644 --- a/middleware/basic_auth_test.go +++ b/middleware/basic_auth_test.go @@ -32,7 +32,6 @@ func TestBasicAuth(t *testing.T) { assert.NoError(t, h(c)) h = BasicAuthWithConfig(BasicAuthConfig{ - Skipper: nil, Validator: f, Realm: "someRealm", })(func(c echo.Context) error { @@ -72,4 +71,20 @@ func TestBasicAuth(t *testing.T) { req.Header.Set(echo.HeaderAuthorization, auth) he = h(c).(*echo.HTTPError) assert.Equal(t, http.StatusUnauthorized, he.Code) + + h = BasicAuthWithConfig(BasicAuthConfig{ + Validator: f, + Realm: "someRealm", + Skipper: func(c echo.Context) bool { + return true + }, + })(func(c echo.Context) error { + return c.String(http.StatusOK, "test") + }) + + // Skipped Request + auth = basic + " " + base64.StdEncoding.EncodeToString([]byte("joe:skip")) + req.Header.Set(echo.HeaderAuthorization, auth) + assert.NoError(t, h(c)) + } From 34717b717df914b4c511610ef44ac0339316875f Mon Sep 17 00:00:00 2001 From: teslaedison <156734008+teslaedison@users.noreply.github.com> Date: Thu, 7 Mar 2024 03:43:59 +0800 Subject: [PATCH 19/74] fix some typos (#2603) Signed-off-by: teslaedison --- context.go | 2 +- echo.go | 2 +- ip.go | 2 +- middleware/request_logger_test.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/context.go b/context.go index d4cba8447..d917f3bc9 100644 --- a/context.go +++ b/context.go @@ -335,7 +335,7 @@ func (c *context) SetParamNames(names ...string) { if len(c.pvalues) < l { // Keeping the old pvalues just for backward compatibility, but it sounds that doesn't make sense to keep them, - // probably those values will be overriden in a Context#SetParamValues + // probably those values will be overridden in a Context#SetParamValues newPvalues := make([]string, l) copy(newPvalues, c.pvalues) c.pvalues = newPvalues diff --git a/echo.go b/echo.go index 7b6a0907d..1599f5cb7 100644 --- a/echo.go +++ b/echo.go @@ -419,7 +419,7 @@ func (e *Echo) Routers() map[string]*Router { // // NOTE: In case errors happens in middleware call-chain that is returning from handler (which did not return an error). // When handler has already sent response (ala c.JSON()) and there is error in middleware that is returning from -// handler. Then the error that global error handler received will be ignored because we have already "commited" the +// handler. Then the error that global error handler received will be ignored because we have already "committed" the // response and status code header has been sent to the client. func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) { diff --git a/ip.go b/ip.go index 1bcd756ae..905268abf 100644 --- a/ip.go +++ b/ip.go @@ -64,7 +64,7 @@ XFF: "x" "x, a" "x, a, b" ``` In this case, use **first _untrustable_ IP reading from right**. Never use first one reading from left, as it is -configurable by client. Here "trustable" means "you are sure the IP address belongs to your infrastructre". +configurable by client. Here "trustable" means "you are sure the IP address belongs to your infrastructure". In above example, if `b` and `c` are trustable, the IP address of the client is `a` for both cases, never be `x`. In Echo, use `ExtractIPFromXFFHeader(...TrustOption)`. diff --git a/middleware/request_logger_test.go b/middleware/request_logger_test.go index 51d617abb..f3c5f8425 100644 --- a/middleware/request_logger_test.go +++ b/middleware/request_logger_test.go @@ -194,7 +194,7 @@ func TestRequestLogger_LogValuesFuncError(t *testing.T) { e.ServeHTTP(rec, req) // NOTE: when global error handler received error returned from middleware the status has already - // been written to the client and response has been "commited" therefore global error handler does not do anything + // been written to the client and response has been "committed" therefore global error handler does not do anything // and error that bubbled up in middleware chain will not be reflected in response code. assert.Equal(t, http.StatusTeapot, rec.Code) assert.Equal(t, http.StatusTeapot, expect.Status) From 3e04e3e2f25cb37932e5fcb55574d42138a652ce Mon Sep 17 00:00:00 2001 From: pomadev <45284098+pomadev@users.noreply.github.com> Date: Thu, 7 Mar 2024 04:52:53 +0900 Subject: [PATCH 20/74] fix: some typos (#2596) --- echo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/echo.go b/echo.go index 1599f5cb7..eb8a79f38 100644 --- a/echo.go +++ b/echo.go @@ -70,7 +70,7 @@ type ( filesystem common // startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get - // listener address info (on which interface/port was listener binded) without having data races. + // listener address info (on which interface/port was listener bound) without having data races. startupMutex sync.RWMutex colorer *color.Color From bc1e1904f1f7b641b3c5eca11be634735a3688f9 Mon Sep 17 00:00:00 2001 From: Martti T Date: Sat, 9 Mar 2024 10:50:47 +0200 Subject: [PATCH 21/74] Allow ResponseWriters to unwrap writers when flushing/hijacking (#2595) * Allow ResponseWriters to unwrap writers when flushing/hijacking --- middleware/body_dump.go | 12 +++++-- middleware/body_dump_test.go | 50 +++++++++++++++++++++++++++ middleware/compress.go | 10 +++--- middleware/compress_test.go | 30 ++++++++++++++++ middleware/middleware_test.go | 46 ++++++++++++++++++++++++ middleware/responsecontroller_1.19.go | 41 ++++++++++++++++++++++ middleware/responsecontroller_1.20.go | 17 +++++++++ response.go | 8 +++-- response_test.go | 25 ++++++++++++++ responsecontroller_1.19.go | 41 ++++++++++++++++++++++ responsecontroller_1.20.go | 17 +++++++++ 11 files changed, 289 insertions(+), 8 deletions(-) create mode 100644 middleware/responsecontroller_1.19.go create mode 100644 middleware/responsecontroller_1.20.go create mode 100644 responsecontroller_1.19.go create mode 100644 responsecontroller_1.20.go diff --git a/middleware/body_dump.go b/middleware/body_dump.go index fa7891b16..946ffc58f 100644 --- a/middleware/body_dump.go +++ b/middleware/body_dump.go @@ -3,6 +3,7 @@ package middleware import ( "bufio" "bytes" + "errors" "io" "net" "net/http" @@ -98,9 +99,16 @@ func (w *bodyDumpResponseWriter) Write(b []byte) (int, error) { } func (w *bodyDumpResponseWriter) Flush() { - w.ResponseWriter.(http.Flusher).Flush() + err := responseControllerFlush(w.ResponseWriter) + if err != nil && errors.Is(err, http.ErrNotSupported) { + panic(errors.New("response writer flushing is not supported")) + } } func (w *bodyDumpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { - return w.ResponseWriter.(http.Hijacker).Hijack() + return responseControllerHijack(w.ResponseWriter) +} + +func (w *bodyDumpResponseWriter) Unwrap() http.ResponseWriter { + return w.ResponseWriter } diff --git a/middleware/body_dump_test.go b/middleware/body_dump_test.go index de1de3356..a68930b49 100644 --- a/middleware/body_dump_test.go +++ b/middleware/body_dump_test.go @@ -87,3 +87,53 @@ func TestBodyDumpFails(t *testing.T) { } }) } + +func TestBodyDumpResponseWriter_CanNotFlush(t *testing.T) { + bdrw := bodyDumpResponseWriter{ + ResponseWriter: new(testResponseWriterNoFlushHijack), // this RW does not support flush + } + + assert.PanicsWithError(t, "response writer flushing is not supported", func() { + bdrw.Flush() + }) +} + +func TestBodyDumpResponseWriter_CanFlush(t *testing.T) { + trwu := testResponseWriterUnwrapperHijack{testResponseWriterUnwrapper: testResponseWriterUnwrapper{rw: httptest.NewRecorder()}} + bdrw := bodyDumpResponseWriter{ + ResponseWriter: &trwu, + } + + bdrw.Flush() + assert.Equal(t, 1, trwu.unwrapCalled) +} + +func TestBodyDumpResponseWriter_CanUnwrap(t *testing.T) { + trwu := &testResponseWriterUnwrapper{rw: httptest.NewRecorder()} + bdrw := bodyDumpResponseWriter{ + ResponseWriter: trwu, + } + + result := bdrw.Unwrap() + assert.Equal(t, trwu, result) +} + +func TestBodyDumpResponseWriter_CanHijack(t *testing.T) { + trwu := testResponseWriterUnwrapperHijack{testResponseWriterUnwrapper: testResponseWriterUnwrapper{rw: httptest.NewRecorder()}} + bdrw := bodyDumpResponseWriter{ + ResponseWriter: &trwu, // this RW supports hijacking through unwrapping + } + + _, _, err := bdrw.Hijack() + assert.EqualError(t, err, "can hijack") +} + +func TestBodyDumpResponseWriter_CanNotHijack(t *testing.T) { + trwu := testResponseWriterUnwrapper{rw: httptest.NewRecorder()} + bdrw := bodyDumpResponseWriter{ + ResponseWriter: &trwu, // this RW supports hijacking through unwrapping + } + + _, _, err := bdrw.Hijack() + assert.EqualError(t, err, "feature not supported") +} diff --git a/middleware/compress.go b/middleware/compress.go index 3e9bd3201..c77062d92 100644 --- a/middleware/compress.go +++ b/middleware/compress.go @@ -191,13 +191,15 @@ func (w *gzipResponseWriter) Flush() { } w.Writer.(*gzip.Writer).Flush() - if flusher, ok := w.ResponseWriter.(http.Flusher); ok { - flusher.Flush() - } + _ = responseControllerFlush(w.ResponseWriter) +} + +func (w *gzipResponseWriter) Unwrap() http.ResponseWriter { + return w.ResponseWriter } func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { - return w.ResponseWriter.(http.Hijacker).Hijack() + return responseControllerHijack(w.ResponseWriter) } func (w *gzipResponseWriter) Push(target string, opts *http.PushOptions) error { diff --git a/middleware/compress_test.go b/middleware/compress_test.go index 0ed16c813..6c5ce4123 100644 --- a/middleware/compress_test.go +++ b/middleware/compress_test.go @@ -311,6 +311,36 @@ func TestGzipWithStatic(t *testing.T) { } } +func TestGzipResponseWriter_CanUnwrap(t *testing.T) { + trwu := &testResponseWriterUnwrapper{rw: httptest.NewRecorder()} + bdrw := gzipResponseWriter{ + ResponseWriter: trwu, + } + + result := bdrw.Unwrap() + assert.Equal(t, trwu, result) +} + +func TestGzipResponseWriter_CanHijack(t *testing.T) { + trwu := testResponseWriterUnwrapperHijack{testResponseWriterUnwrapper: testResponseWriterUnwrapper{rw: httptest.NewRecorder()}} + bdrw := gzipResponseWriter{ + ResponseWriter: &trwu, // this RW supports hijacking through unwrapping + } + + _, _, err := bdrw.Hijack() + assert.EqualError(t, err, "can hijack") +} + +func TestGzipResponseWriter_CanNotHijack(t *testing.T) { + trwu := testResponseWriterUnwrapper{rw: httptest.NewRecorder()} + bdrw := gzipResponseWriter{ + ResponseWriter: &trwu, // this RW supports hijacking through unwrapping + } + + _, _, err := bdrw.Hijack() + assert.EqualError(t, err, "feature not supported") +} + func BenchmarkGzip(b *testing.B) { e := echo.New() diff --git a/middleware/middleware_test.go b/middleware/middleware_test.go index 44f44142c..990568d55 100644 --- a/middleware/middleware_test.go +++ b/middleware/middleware_test.go @@ -1,7 +1,10 @@ package middleware import ( + "bufio" + "errors" "github.com/stretchr/testify/assert" + "net" "net/http" "net/http/httptest" "regexp" @@ -90,3 +93,46 @@ func TestRewriteURL(t *testing.T) { }) } } + +type testResponseWriterNoFlushHijack struct { +} + +func (w *testResponseWriterNoFlushHijack) WriteHeader(statusCode int) { +} + +func (w *testResponseWriterNoFlushHijack) Write([]byte) (int, error) { + return 0, nil +} + +func (w *testResponseWriterNoFlushHijack) Header() http.Header { + return nil +} + +type testResponseWriterUnwrapper struct { + unwrapCalled int + rw http.ResponseWriter +} + +func (w *testResponseWriterUnwrapper) WriteHeader(statusCode int) { +} + +func (w *testResponseWriterUnwrapper) Write([]byte) (int, error) { + return 0, nil +} + +func (w *testResponseWriterUnwrapper) Header() http.Header { + return nil +} + +func (w *testResponseWriterUnwrapper) Unwrap() http.ResponseWriter { + w.unwrapCalled++ + return w.rw +} + +type testResponseWriterUnwrapperHijack struct { + testResponseWriterUnwrapper +} + +func (w *testResponseWriterUnwrapperHijack) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return nil, nil, errors.New("can hijack") +} diff --git a/middleware/responsecontroller_1.19.go b/middleware/responsecontroller_1.19.go new file mode 100644 index 000000000..104784fd0 --- /dev/null +++ b/middleware/responsecontroller_1.19.go @@ -0,0 +1,41 @@ +//go:build !go1.20 + +package middleware + +import ( + "bufio" + "fmt" + "net" + "net/http" +) + +// TODO: remove when Go 1.23 is released and we do not support 1.19 anymore +func responseControllerFlush(rw http.ResponseWriter) error { + for { + switch t := rw.(type) { + case interface{ FlushError() error }: + return t.FlushError() + case http.Flusher: + t.Flush() + return nil + case interface{ Unwrap() http.ResponseWriter }: + rw = t.Unwrap() + default: + return fmt.Errorf("%w", http.ErrNotSupported) + } + } +} + +// TODO: remove when Go 1.23 is released and we do not support 1.19 anymore +func responseControllerHijack(rw http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) { + for { + switch t := rw.(type) { + case http.Hijacker: + return t.Hijack() + case interface{ Unwrap() http.ResponseWriter }: + rw = t.Unwrap() + default: + return nil, nil, fmt.Errorf("%w", http.ErrNotSupported) + } + } +} diff --git a/middleware/responsecontroller_1.20.go b/middleware/responsecontroller_1.20.go new file mode 100644 index 000000000..02a0cb754 --- /dev/null +++ b/middleware/responsecontroller_1.20.go @@ -0,0 +1,17 @@ +//go:build go1.20 + +package middleware + +import ( + "bufio" + "net" + "net/http" +) + +func responseControllerFlush(rw http.ResponseWriter) error { + return http.NewResponseController(rw).Flush() +} + +func responseControllerHijack(rw http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) { + return http.NewResponseController(rw).Hijack() +} diff --git a/response.go b/response.go index d9c9aa6e0..117881cc6 100644 --- a/response.go +++ b/response.go @@ -2,6 +2,7 @@ package echo import ( "bufio" + "errors" "net" "net/http" ) @@ -84,14 +85,17 @@ func (r *Response) Write(b []byte) (n int, err error) { // buffered data to the client. // See [http.Flusher](https://golang.org/pkg/net/http/#Flusher) func (r *Response) Flush() { - r.Writer.(http.Flusher).Flush() + err := responseControllerFlush(r.Writer) + if err != nil && errors.Is(err, http.ErrNotSupported) { + panic(errors.New("response writer flushing is not supported")) + } } // Hijack implements the http.Hijacker interface to allow an HTTP handler to // take over the connection. // See [http.Hijacker](https://golang.org/pkg/net/http/#Hijacker) func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) { - return r.Writer.(http.Hijacker).Hijack() + return responseControllerHijack(r.Writer) } // Unwrap returns the original http.ResponseWriter. diff --git a/response_test.go b/response_test.go index e4fd636d8..e457a0193 100644 --- a/response_test.go +++ b/response_test.go @@ -57,6 +57,31 @@ func TestResponse_Flush(t *testing.T) { assert.True(t, rec.Flushed) } +type testResponseWriter struct { +} + +func (w *testResponseWriter) WriteHeader(statusCode int) { +} + +func (w *testResponseWriter) Write([]byte) (int, error) { + return 0, nil +} + +func (w *testResponseWriter) Header() http.Header { + return nil +} + +func TestResponse_FlushPanics(t *testing.T) { + e := New() + rw := new(testResponseWriter) + res := &Response{echo: e, Writer: rw} + + // we test that we behave as before unwrapping flushers - flushing writer that does not support it causes panic + assert.PanicsWithError(t, "response writer flushing is not supported", func() { + res.Flush() + }) +} + func TestResponse_ChangeStatusCodeBeforeWrite(t *testing.T) { e := New() rec := httptest.NewRecorder() diff --git a/responsecontroller_1.19.go b/responsecontroller_1.19.go new file mode 100644 index 000000000..75c6e3e58 --- /dev/null +++ b/responsecontroller_1.19.go @@ -0,0 +1,41 @@ +//go:build !go1.20 + +package echo + +import ( + "bufio" + "fmt" + "net" + "net/http" +) + +// TODO: remove when Go 1.23 is released and we do not support 1.19 anymore +func responseControllerFlush(rw http.ResponseWriter) error { + for { + switch t := rw.(type) { + case interface{ FlushError() error }: + return t.FlushError() + case http.Flusher: + t.Flush() + return nil + case interface{ Unwrap() http.ResponseWriter }: + rw = t.Unwrap() + default: + return fmt.Errorf("%w", http.ErrNotSupported) + } + } +} + +// TODO: remove when Go 1.23 is released and we do not support 1.19 anymore +func responseControllerHijack(rw http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) { + for { + switch t := rw.(type) { + case http.Hijacker: + return t.Hijack() + case interface{ Unwrap() http.ResponseWriter }: + rw = t.Unwrap() + default: + return nil, nil, fmt.Errorf("%w", http.ErrNotSupported) + } + } +} diff --git a/responsecontroller_1.20.go b/responsecontroller_1.20.go new file mode 100644 index 000000000..fa2fe8b3f --- /dev/null +++ b/responsecontroller_1.20.go @@ -0,0 +1,17 @@ +//go:build go1.20 + +package echo + +import ( + "bufio" + "net" + "net/http" +) + +func responseControllerFlush(rw http.ResponseWriter) error { + return http.NewResponseController(rw).Flush() +} + +func responseControllerHijack(rw http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) { + return http.NewResponseController(rw).Hijack() +} From a842444e8f8b81cfc72b50e16f8134ecf5eda645 Mon Sep 17 00:00:00 2001 From: Martti T Date: Sat, 9 Mar 2024 11:21:24 +0200 Subject: [PATCH 22/74] Add SPDX licence comments to files. See https://spdx.dev/learn/handling-license-info/ (#2604) --- bind.go | 3 +++ bind_test.go | 3 +++ binder.go | 3 +++ binder_external_test.go | 3 +++ binder_test.go | 3 +++ context.go | 3 +++ context_fs.go | 3 +++ context_fs_test.go | 3 +++ context_test.go | 3 +++ echo.go | 3 +++ echo_fs.go | 3 +++ echo_fs_test.go | 3 +++ echo_test.go | 3 +++ group.go | 3 +++ group_fs.go | 3 +++ group_fs_test.go | 3 +++ group_test.go | 3 +++ ip.go | 3 +++ ip_test.go | 3 +++ json.go | 3 +++ json_test.go | 3 +++ log.go | 3 +++ middleware/basic_auth.go | 3 +++ middleware/basic_auth_test.go | 3 +++ middleware/body_dump.go | 3 +++ middleware/body_dump_test.go | 3 +++ middleware/body_limit.go | 3 +++ middleware/body_limit_test.go | 3 +++ middleware/compress.go | 3 +++ middleware/compress_test.go | 3 +++ middleware/context_timeout.go | 3 +++ middleware/context_timeout_test.go | 3 +++ middleware/cors.go | 3 +++ middleware/cors_test.go | 3 +++ middleware/csrf.go | 3 +++ middleware/csrf_test.go | 3 +++ middleware/decompress.go | 3 +++ middleware/decompress_test.go | 3 +++ middleware/extractor.go | 3 +++ middleware/extractor_test.go | 3 +++ middleware/jwt.go | 3 +++ middleware/jwt_test.go | 3 +++ middleware/key_auth.go | 3 +++ middleware/key_auth_test.go | 3 +++ middleware/logger.go | 3 +++ middleware/logger_test.go | 3 +++ middleware/method_override.go | 3 +++ middleware/method_override_test.go | 3 +++ middleware/middleware.go | 3 +++ middleware/middleware_test.go | 3 +++ middleware/proxy.go | 3 +++ middleware/proxy_test.go | 3 +++ middleware/rate_limiter.go | 3 +++ middleware/rate_limiter_test.go | 3 +++ middleware/recover.go | 3 +++ middleware/recover_test.go | 3 +++ middleware/redirect.go | 3 +++ middleware/redirect_test.go | 3 +++ middleware/request_id.go | 3 +++ middleware/request_id_test.go | 3 +++ middleware/request_logger.go | 3 +++ middleware/request_logger_test.go | 3 +++ middleware/responsecontroller_1.19.go | 3 +++ middleware/responsecontroller_1.20.go | 3 +++ middleware/rewrite.go | 3 +++ middleware/rewrite_test.go | 3 +++ middleware/secure.go | 3 +++ middleware/secure_test.go | 3 +++ middleware/slash.go | 3 +++ middleware/slash_test.go | 3 +++ middleware/static.go | 3 +++ middleware/static_other.go | 3 +++ middleware/static_test.go | 3 +++ middleware/static_windows.go | 3 +++ middleware/timeout.go | 3 +++ middleware/timeout_test.go | 3 +++ middleware/util.go | 3 +++ middleware/util_test.go | 3 +++ response.go | 3 +++ response_test.go | 3 +++ responsecontroller_1.19.go | 3 +++ responsecontroller_1.20.go | 3 +++ router.go | 3 +++ router_test.go | 3 +++ 84 files changed, 252 insertions(+) diff --git a/bind.go b/bind.go index 51f4689e7..353c51325 100644 --- a/bind.go +++ b/bind.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/bind_test.go b/bind_test.go index cffccfb35..c0272e712 100644 --- a/bind_test.go +++ b/bind_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/binder.go b/binder.go index 8e7b81413..ebabeaf96 100644 --- a/binder.go +++ b/binder.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/binder_external_test.go b/binder_external_test.go index f1aecb52b..e44055a23 100644 --- a/binder_external_test.go +++ b/binder_external_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + // run tests as external package to get real feel for API package echo_test diff --git a/binder_test.go b/binder_test.go index 0b27cae64..d552b604d 100644 --- a/binder_test.go +++ b/binder_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/context.go b/context.go index d917f3bc9..2b4acae32 100644 --- a/context.go +++ b/context.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/context_fs.go b/context_fs.go index 1038f892e..1c25baf12 100644 --- a/context_fs.go +++ b/context_fs.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/context_fs_test.go b/context_fs_test.go index 51346c956..83232ea45 100644 --- a/context_fs_test.go +++ b/context_fs_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/context_test.go b/context_test.go index 4ca2cc84b..463e10a60 100644 --- a/context_test.go +++ b/context_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/echo.go b/echo.go index eb8a79f38..4d11af04a 100644 --- a/echo.go +++ b/echo.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + /* Package echo implements high performance, minimalist Go web framework. diff --git a/echo_fs.go b/echo_fs.go index 9f83a0351..a7b231f31 100644 --- a/echo_fs.go +++ b/echo_fs.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/echo_fs_test.go b/echo_fs_test.go index eb072a28d..e882a0682 100644 --- a/echo_fs_test.go +++ b/echo_fs_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/echo_test.go b/echo_test.go index 416479191..f09544127 100644 --- a/echo_test.go +++ b/echo_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/group.go b/group.go index 749a5caab..e69d80b7f 100644 --- a/group.go +++ b/group.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/group_fs.go b/group_fs.go index aedc4c6a9..c1b7ec2d3 100644 --- a/group_fs.go +++ b/group_fs.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/group_fs_test.go b/group_fs_test.go index 958d9efb1..8bcd547d1 100644 --- a/group_fs_test.go +++ b/group_fs_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/group_test.go b/group_test.go index d22f564b0..a97371418 100644 --- a/group_test.go +++ b/group_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/ip.go b/ip.go index 905268abf..5374dc018 100644 --- a/ip.go +++ b/ip.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/ip_test.go b/ip_test.go index 38c4a1cac..20e3127a8 100644 --- a/ip_test.go +++ b/ip_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/json.go b/json.go index 16b2d0577..6da0aaf97 100644 --- a/json.go +++ b/json.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/json_test.go b/json_test.go index 8fb9ebc96..0b15ed1a1 100644 --- a/json_test.go +++ b/json_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/log.go b/log.go index 3f8de5904..b9ec3d561 100644 --- a/log.go +++ b/log.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/middleware/basic_auth.go b/middleware/basic_auth.go index 07a5761b8..7e809f5f7 100644 --- a/middleware/basic_auth.go +++ b/middleware/basic_auth.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/basic_auth_test.go b/middleware/basic_auth_test.go index 2e133e071..6e07065bf 100644 --- a/middleware/basic_auth_test.go +++ b/middleware/basic_auth_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/body_dump.go b/middleware/body_dump.go index 946ffc58f..e7b20981c 100644 --- a/middleware/body_dump.go +++ b/middleware/body_dump.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/body_dump_test.go b/middleware/body_dump_test.go index a68930b49..e880af45b 100644 --- a/middleware/body_dump_test.go +++ b/middleware/body_dump_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/body_limit.go b/middleware/body_limit.go index 99e3ac547..81972304e 100644 --- a/middleware/body_limit.go +++ b/middleware/body_limit.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/body_limit_test.go b/middleware/body_limit_test.go index 0fd66ee0f..d14c2b649 100644 --- a/middleware/body_limit_test.go +++ b/middleware/body_limit_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/compress.go b/middleware/compress.go index c77062d92..681c0346f 100644 --- a/middleware/compress.go +++ b/middleware/compress.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/compress_test.go b/middleware/compress_test.go index 6c5ce4123..4bbdfdbc2 100644 --- a/middleware/compress_test.go +++ b/middleware/compress_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/context_timeout.go b/middleware/context_timeout.go index 1937693f1..e67173f21 100644 --- a/middleware/context_timeout.go +++ b/middleware/context_timeout.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/context_timeout_test.go b/middleware/context_timeout_test.go index 24c6203e7..e69bcd268 100644 --- a/middleware/context_timeout_test.go +++ b/middleware/context_timeout_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/cors.go b/middleware/cors.go index 7ace2f224..dd7030e56 100644 --- a/middleware/cors.go +++ b/middleware/cors.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/cors_test.go b/middleware/cors_test.go index 797600c5c..64e5c6542 100644 --- a/middleware/cors_test.go +++ b/middleware/cors_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/csrf.go b/middleware/csrf.go index adf12210b..015473d9f 100644 --- a/middleware/csrf.go +++ b/middleware/csrf.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/csrf_test.go b/middleware/csrf_test.go index 6b20297ee..98e5d04f6 100644 --- a/middleware/csrf_test.go +++ b/middleware/csrf_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/decompress.go b/middleware/decompress.go index a73c9738b..3dded53c5 100644 --- a/middleware/decompress.go +++ b/middleware/decompress.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/decompress_test.go b/middleware/decompress_test.go index 351e0e708..63b1a68f5 100644 --- a/middleware/decompress_test.go +++ b/middleware/decompress_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/extractor.go b/middleware/extractor.go index 5d9cee6d0..3f2741407 100644 --- a/middleware/extractor.go +++ b/middleware/extractor.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/extractor_test.go b/middleware/extractor_test.go index 428c5563e..42cbcfeab 100644 --- a/middleware/extractor_test.go +++ b/middleware/extractor_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/jwt.go b/middleware/jwt.go index bc318c976..276bdfe39 100644 --- a/middleware/jwt.go +++ b/middleware/jwt.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + //go:build go1.15 // +build go1.15 diff --git a/middleware/jwt_test.go b/middleware/jwt_test.go index 90e8cad81..bbe4b8808 100644 --- a/middleware/jwt_test.go +++ b/middleware/jwt_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + //go:build go1.15 // +build go1.15 diff --git a/middleware/key_auth.go b/middleware/key_auth.go index f6fcc5d69..f7ce8c18a 100644 --- a/middleware/key_auth.go +++ b/middleware/key_auth.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/key_auth_test.go b/middleware/key_auth_test.go index ff8968c38..447f0bee8 100644 --- a/middleware/key_auth_test.go +++ b/middleware/key_auth_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/logger.go b/middleware/logger.go index 7958d873b..43fd59ffc 100644 --- a/middleware/logger.go +++ b/middleware/logger.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/logger_test.go b/middleware/logger_test.go index 9f35a70bc..d5236e1ac 100644 --- a/middleware/logger_test.go +++ b/middleware/logger_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/method_override.go b/middleware/method_override.go index 92b14d2ed..668a57a41 100644 --- a/middleware/method_override.go +++ b/middleware/method_override.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/method_override_test.go b/middleware/method_override_test.go index 5760b1581..0000d1d80 100644 --- a/middleware/method_override_test.go +++ b/middleware/method_override_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/middleware.go b/middleware/middleware.go index 664f71f45..8dfb8dda6 100644 --- a/middleware/middleware.go +++ b/middleware/middleware.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/middleware_test.go b/middleware/middleware_test.go index 990568d55..7f3dc3866 100644 --- a/middleware/middleware_test.go +++ b/middleware/middleware_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/proxy.go b/middleware/proxy.go index 16b00d645..ddf4b7f06 100644 --- a/middleware/proxy.go +++ b/middleware/proxy.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/proxy_test.go b/middleware/proxy_test.go index 1c93ba031..e87229ab5 100644 --- a/middleware/proxy_test.go +++ b/middleware/proxy_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/rate_limiter.go b/middleware/rate_limiter.go index 1d24df52a..a58b16491 100644 --- a/middleware/rate_limiter.go +++ b/middleware/rate_limiter.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/rate_limiter_test.go b/middleware/rate_limiter_test.go index f66961fe2..1de7b63e5 100644 --- a/middleware/rate_limiter_test.go +++ b/middleware/rate_limiter_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/recover.go b/middleware/recover.go index 0466cfe56..35f38e72c 100644 --- a/middleware/recover.go +++ b/middleware/recover.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/recover_test.go b/middleware/recover_test.go index 3e0d35d79..8fa34fa5c 100644 --- a/middleware/recover_test.go +++ b/middleware/recover_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/redirect.go b/middleware/redirect.go index 13877db38..b772ac131 100644 --- a/middleware/redirect.go +++ b/middleware/redirect.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/redirect_test.go b/middleware/redirect_test.go index 9d1b56205..88068ea2e 100644 --- a/middleware/redirect_test.go +++ b/middleware/redirect_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/request_id.go b/middleware/request_id.go index e29c8f50d..411737cb4 100644 --- a/middleware/request_id.go +++ b/middleware/request_id.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/request_id_test.go b/middleware/request_id_test.go index 21b777826..4e68b126a 100644 --- a/middleware/request_id_test.go +++ b/middleware/request_id_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/request_logger.go b/middleware/request_logger.go index f82f6b622..7c18200b0 100644 --- a/middleware/request_logger.go +++ b/middleware/request_logger.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/request_logger_test.go b/middleware/request_logger_test.go index f3c5f8425..c612f5c22 100644 --- a/middleware/request_logger_test.go +++ b/middleware/request_logger_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/responsecontroller_1.19.go b/middleware/responsecontroller_1.19.go index 104784fd0..ddf6b64c0 100644 --- a/middleware/responsecontroller_1.19.go +++ b/middleware/responsecontroller_1.19.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + //go:build !go1.20 package middleware diff --git a/middleware/responsecontroller_1.20.go b/middleware/responsecontroller_1.20.go index 02a0cb754..bc03059bc 100644 --- a/middleware/responsecontroller_1.20.go +++ b/middleware/responsecontroller_1.20.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + //go:build go1.20 package middleware diff --git a/middleware/rewrite.go b/middleware/rewrite.go index 2090eac04..260dbb1f5 100644 --- a/middleware/rewrite.go +++ b/middleware/rewrite.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/rewrite_test.go b/middleware/rewrite_test.go index 47d707c30..d137b2d13 100644 --- a/middleware/rewrite_test.go +++ b/middleware/rewrite_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/secure.go b/middleware/secure.go index 6c4051723..b70854ddc 100644 --- a/middleware/secure.go +++ b/middleware/secure.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/secure_test.go b/middleware/secure_test.go index 79bd172ae..b579a6d21 100644 --- a/middleware/secure_test.go +++ b/middleware/secure_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/slash.go b/middleware/slash.go index a3bf807ec..774cc5582 100644 --- a/middleware/slash.go +++ b/middleware/slash.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/slash_test.go b/middleware/slash_test.go index ddb071045..1b365cfea 100644 --- a/middleware/slash_test.go +++ b/middleware/slash_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/static.go b/middleware/static.go index 24a5f59b9..15a838175 100644 --- a/middleware/static.go +++ b/middleware/static.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/static_other.go b/middleware/static_other.go index 0337b22af..35dbfb38e 100644 --- a/middleware/static_other.go +++ b/middleware/static_other.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + //go:build !windows package middleware diff --git a/middleware/static_test.go b/middleware/static_test.go index f26d97a95..a10ab8000 100644 --- a/middleware/static_test.go +++ b/middleware/static_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/static_windows.go b/middleware/static_windows.go index 0ab119859..e294020a1 100644 --- a/middleware/static_windows.go +++ b/middleware/static_windows.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/timeout.go b/middleware/timeout.go index 4e8836c85..a47bd4b3b 100644 --- a/middleware/timeout.go +++ b/middleware/timeout.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/timeout_test.go b/middleware/timeout_test.go index 98d96baef..e8415d636 100644 --- a/middleware/timeout_test.go +++ b/middleware/timeout_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/util.go b/middleware/util.go index 4d2d172fc..09428eb0b 100644 --- a/middleware/util.go +++ b/middleware/util.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/middleware/util_test.go b/middleware/util_test.go index d0f20bba6..b54f12627 100644 --- a/middleware/util_test.go +++ b/middleware/util_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package middleware import ( diff --git a/response.go b/response.go index 117881cc6..7ca522eb1 100644 --- a/response.go +++ b/response.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/response_test.go b/response_test.go index e457a0193..70cba9776 100644 --- a/response_test.go +++ b/response_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/responsecontroller_1.19.go b/responsecontroller_1.19.go index 75c6e3e58..782dab3a3 100644 --- a/responsecontroller_1.19.go +++ b/responsecontroller_1.19.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + //go:build !go1.20 package echo diff --git a/responsecontroller_1.20.go b/responsecontroller_1.20.go index fa2fe8b3f..6d77c07f8 100644 --- a/responsecontroller_1.20.go +++ b/responsecontroller_1.20.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + //go:build go1.20 package echo diff --git a/router.go b/router.go index ee6f3fa48..0a9b7d267 100644 --- a/router.go +++ b/router.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( diff --git a/router_test.go b/router_test.go index 619cce092..52d9a0abb 100644 --- a/router_test.go +++ b/router_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + package echo import ( From f0966790fb018524dc9ead2898a97e3ee532d135 Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Sat, 9 Mar 2024 11:23:12 +0200 Subject: [PATCH 23/74] Upgrade deps --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 089ffb140..89a0e86a0 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,8 @@ require ( github.com/labstack/gommon v0.4.2 github.com/stretchr/testify v1.8.4 github.com/valyala/fasttemplate v1.2.2 - golang.org/x/crypto v0.17.0 - golang.org/x/net v0.19.0 + golang.org/x/crypto v0.21.0 + golang.org/x/net v0.22.0 golang.org/x/time v0.5.0 ) @@ -18,7 +18,7 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 0584b7e59..397a22dc5 100644 --- a/go.sum +++ b/go.sum @@ -17,14 +17,14 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= From 5f7bedfb86e10bf0024236adfea544d0f5a82689 Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Sat, 9 Mar 2024 11:23:55 +0200 Subject: [PATCH 24/74] update makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 6aff6a89f..f9e5afb09 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,6 @@ benchmark: ## Run benchmarks help: ## Display this help screen @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' -goversion ?= "1.17" -test_version: ## Run tests inside Docker with given version (defaults to 1.17 oldest supported). Example: make test_version goversion=1.17 +goversion ?= "1.19" +test_version: ## Run tests inside Docker with given version (defaults to 1.19 oldest supported). Example: make test_version goversion=1.19 @docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check" From 3598f295f95f316bbeb252b7b332fe34e120815c Mon Sep 17 00:00:00 2001 From: Martti T Date: Sat, 9 Mar 2024 17:53:07 +0200 Subject: [PATCH 25/74] Change type definition blocks to single declarations. This helps copy/pasting Echo code in examples. (#2606) --- bind.go | 28 ++-- bind_test.go | 170 ++++++++++---------- context.go | 284 +++++++++++++++++----------------- context_test.go | 8 +- echo.go | 237 ++++++++++++++-------------- echo_test.go | 10 +- group.go | 22 ++- log.go | 67 ++++---- middleware/basic_auth.go | 48 +++--- middleware/body_dump.go | 42 +++-- middleware/body_limit.go | 42 +++-- middleware/compress.go | 80 +++++----- middleware/cors.go | 208 ++++++++++++------------- middleware/csrf.go | 138 ++++++++--------- middleware/decompress.go | 30 ++-- middleware/jwt.go | 244 ++++++++++++++--------------- middleware/key_auth.go | 106 ++++++------- middleware/logger.go | 136 ++++++++-------- middleware/method_override.go | 34 ++-- middleware/middleware.go | 14 +- middleware/proxy.go | 204 ++++++++++++------------ middleware/rate_limiter.go | 90 +++++------ middleware/recover.go | 92 ++++++----- middleware/request_id.go | 40 +++-- middleware/rewrite.go | 52 +++---- middleware/secure.go | 150 +++++++++--------- middleware/slash.go | 30 ++-- middleware/static.go | 78 +++++----- middleware/timeout.go | 14 +- response.go | 26 ++-- router.go | 102 ++++++------ 31 files changed, 1364 insertions(+), 1462 deletions(-) diff --git a/bind.go b/bind.go index 353c51325..5e29be8e5 100644 --- a/bind.go +++ b/bind.go @@ -14,23 +14,21 @@ import ( "strings" ) -type ( - // Binder is the interface that wraps the Bind method. - Binder interface { - Bind(i interface{}, c Context) error - } +// Binder is the interface that wraps the Bind method. +type Binder interface { + Bind(i interface{}, c Context) error +} - // DefaultBinder is the default implementation of the Binder interface. - DefaultBinder struct{} +// DefaultBinder is the default implementation of the Binder interface. +type DefaultBinder struct{} - // BindUnmarshaler is the interface used to wrap the UnmarshalParam method. - // Types that don't implement this, but do implement encoding.TextUnmarshaler - // will use that interface instead. - BindUnmarshaler interface { - // UnmarshalParam decodes and assigns a value from an form or query param. - UnmarshalParam(param string) error - } -) +// BindUnmarshaler is the interface used to wrap the UnmarshalParam method. +// Types that don't implement this, but do implement encoding.TextUnmarshaler +// will use that interface instead. +type BindUnmarshaler interface { + // UnmarshalParam decodes and assigns a value from an form or query param. + UnmarshalParam(param string) error +} // BindPathParams binds path params to bindable object func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error { diff --git a/bind_test.go b/bind_test.go index c0272e712..05f8ef43c 100644 --- a/bind_test.go +++ b/bind_test.go @@ -22,91 +22,91 @@ import ( "github.com/stretchr/testify/assert" ) -type ( - bindTestStruct struct { - I int - PtrI *int - I8 int8 - PtrI8 *int8 - I16 int16 - PtrI16 *int16 - I32 int32 - PtrI32 *int32 - I64 int64 - PtrI64 *int64 - UI uint - PtrUI *uint - UI8 uint8 - PtrUI8 *uint8 - UI16 uint16 - PtrUI16 *uint16 - UI32 uint32 - PtrUI32 *uint32 - UI64 uint64 - PtrUI64 *uint64 - B bool - PtrB *bool - F32 float32 - PtrF32 *float32 - F64 float64 - PtrF64 *float64 - S string - PtrS *string - cantSet string - DoesntExist string - GoT time.Time - GoTptr *time.Time - T Timestamp - Tptr *Timestamp - SA StringArray - } - bindTestStructWithTags struct { - I int `json:"I" form:"I"` - PtrI *int `json:"PtrI" form:"PtrI"` - I8 int8 `json:"I8" form:"I8"` - PtrI8 *int8 `json:"PtrI8" form:"PtrI8"` - I16 int16 `json:"I16" form:"I16"` - PtrI16 *int16 `json:"PtrI16" form:"PtrI16"` - I32 int32 `json:"I32" form:"I32"` - PtrI32 *int32 `json:"PtrI32" form:"PtrI32"` - I64 int64 `json:"I64" form:"I64"` - PtrI64 *int64 `json:"PtrI64" form:"PtrI64"` - UI uint `json:"UI" form:"UI"` - PtrUI *uint `json:"PtrUI" form:"PtrUI"` - UI8 uint8 `json:"UI8" form:"UI8"` - PtrUI8 *uint8 `json:"PtrUI8" form:"PtrUI8"` - UI16 uint16 `json:"UI16" form:"UI16"` - PtrUI16 *uint16 `json:"PtrUI16" form:"PtrUI16"` - UI32 uint32 `json:"UI32" form:"UI32"` - PtrUI32 *uint32 `json:"PtrUI32" form:"PtrUI32"` - UI64 uint64 `json:"UI64" form:"UI64"` - PtrUI64 *uint64 `json:"PtrUI64" form:"PtrUI64"` - B bool `json:"B" form:"B"` - PtrB *bool `json:"PtrB" form:"PtrB"` - F32 float32 `json:"F32" form:"F32"` - PtrF32 *float32 `json:"PtrF32" form:"PtrF32"` - F64 float64 `json:"F64" form:"F64"` - PtrF64 *float64 `json:"PtrF64" form:"PtrF64"` - S string `json:"S" form:"S"` - PtrS *string `json:"PtrS" form:"PtrS"` - cantSet string - DoesntExist string `json:"DoesntExist" form:"DoesntExist"` - GoT time.Time `json:"GoT" form:"GoT"` - GoTptr *time.Time `json:"GoTptr" form:"GoTptr"` - T Timestamp `json:"T" form:"T"` - Tptr *Timestamp `json:"Tptr" form:"Tptr"` - SA StringArray `json:"SA" form:"SA"` - } - Timestamp time.Time - TA []Timestamp - StringArray []string - Struct struct { - Foo string - } - Bar struct { - Baz int `json:"baz" query:"baz"` - } -) +type bindTestStruct struct { + I int + PtrI *int + I8 int8 + PtrI8 *int8 + I16 int16 + PtrI16 *int16 + I32 int32 + PtrI32 *int32 + I64 int64 + PtrI64 *int64 + UI uint + PtrUI *uint + UI8 uint8 + PtrUI8 *uint8 + UI16 uint16 + PtrUI16 *uint16 + UI32 uint32 + PtrUI32 *uint32 + UI64 uint64 + PtrUI64 *uint64 + B bool + PtrB *bool + F32 float32 + PtrF32 *float32 + F64 float64 + PtrF64 *float64 + S string + PtrS *string + cantSet string + DoesntExist string + GoT time.Time + GoTptr *time.Time + T Timestamp + Tptr *Timestamp + SA StringArray +} + +type bindTestStructWithTags struct { + I int `json:"I" form:"I"` + PtrI *int `json:"PtrI" form:"PtrI"` + I8 int8 `json:"I8" form:"I8"` + PtrI8 *int8 `json:"PtrI8" form:"PtrI8"` + I16 int16 `json:"I16" form:"I16"` + PtrI16 *int16 `json:"PtrI16" form:"PtrI16"` + I32 int32 `json:"I32" form:"I32"` + PtrI32 *int32 `json:"PtrI32" form:"PtrI32"` + I64 int64 `json:"I64" form:"I64"` + PtrI64 *int64 `json:"PtrI64" form:"PtrI64"` + UI uint `json:"UI" form:"UI"` + PtrUI *uint `json:"PtrUI" form:"PtrUI"` + UI8 uint8 `json:"UI8" form:"UI8"` + PtrUI8 *uint8 `json:"PtrUI8" form:"PtrUI8"` + UI16 uint16 `json:"UI16" form:"UI16"` + PtrUI16 *uint16 `json:"PtrUI16" form:"PtrUI16"` + UI32 uint32 `json:"UI32" form:"UI32"` + PtrUI32 *uint32 `json:"PtrUI32" form:"PtrUI32"` + UI64 uint64 `json:"UI64" form:"UI64"` + PtrUI64 *uint64 `json:"PtrUI64" form:"PtrUI64"` + B bool `json:"B" form:"B"` + PtrB *bool `json:"PtrB" form:"PtrB"` + F32 float32 `json:"F32" form:"F32"` + PtrF32 *float32 `json:"PtrF32" form:"PtrF32"` + F64 float64 `json:"F64" form:"F64"` + PtrF64 *float64 `json:"PtrF64" form:"PtrF64"` + S string `json:"S" form:"S"` + PtrS *string `json:"PtrS" form:"PtrS"` + cantSet string + DoesntExist string `json:"DoesntExist" form:"DoesntExist"` + GoT time.Time `json:"GoT" form:"GoT"` + GoTptr *time.Time `json:"GoTptr" form:"GoTptr"` + T Timestamp `json:"T" form:"T"` + Tptr *Timestamp `json:"Tptr" form:"Tptr"` + SA StringArray `json:"SA" form:"SA"` +} + +type Timestamp time.Time +type TA []Timestamp +type StringArray []string +type Struct struct { + Foo string +} +type Bar struct { + Baz int `json:"baz" query:"baz"` +} func (t *Timestamp) UnmarshalParam(src string) error { ts, err := time.Parse(time.RFC3339, src) diff --git a/context.go b/context.go index 2b4acae32..a5177e884 100644 --- a/context.go +++ b/context.go @@ -16,204 +16,202 @@ import ( "sync" ) -type ( - // Context represents the context of the current HTTP request. It holds request and - // response objects, path, path parameters, data and registered handler. - Context interface { - // Request returns `*http.Request`. - Request() *http.Request +// Context represents the context of the current HTTP request. It holds request and +// response objects, path, path parameters, data and registered handler. +type Context interface { + // Request returns `*http.Request`. + Request() *http.Request - // SetRequest sets `*http.Request`. - SetRequest(r *http.Request) + // SetRequest sets `*http.Request`. + SetRequest(r *http.Request) - // SetResponse sets `*Response`. - SetResponse(r *Response) + // SetResponse sets `*Response`. + SetResponse(r *Response) - // Response returns `*Response`. - Response() *Response + // Response returns `*Response`. + Response() *Response - // IsTLS returns true if HTTP connection is TLS otherwise false. - IsTLS() bool + // IsTLS returns true if HTTP connection is TLS otherwise false. + IsTLS() bool - // IsWebSocket returns true if HTTP connection is WebSocket otherwise false. - IsWebSocket() bool + // IsWebSocket returns true if HTTP connection is WebSocket otherwise false. + IsWebSocket() bool - // Scheme returns the HTTP protocol scheme, `http` or `https`. - Scheme() string + // Scheme returns the HTTP protocol scheme, `http` or `https`. + Scheme() string - // RealIP returns the client's network address based on `X-Forwarded-For` - // or `X-Real-IP` request header. - // The behavior can be configured using `Echo#IPExtractor`. - RealIP() string + // RealIP returns the client's network address based on `X-Forwarded-For` + // or `X-Real-IP` request header. + // The behavior can be configured using `Echo#IPExtractor`. + RealIP() string - // Path returns the registered path for the handler. - Path() string + // Path returns the registered path for the handler. + Path() string - // SetPath sets the registered path for the handler. - SetPath(p string) + // SetPath sets the registered path for the handler. + SetPath(p string) - // Param returns path parameter by name. - Param(name string) string + // Param returns path parameter by name. + Param(name string) string - // ParamNames returns path parameter names. - ParamNames() []string + // ParamNames returns path parameter names. + ParamNames() []string - // SetParamNames sets path parameter names. - SetParamNames(names ...string) + // SetParamNames sets path parameter names. + SetParamNames(names ...string) - // ParamValues returns path parameter values. - ParamValues() []string + // ParamValues returns path parameter values. + ParamValues() []string - // SetParamValues sets path parameter values. - SetParamValues(values ...string) + // SetParamValues sets path parameter values. + SetParamValues(values ...string) - // QueryParam returns the query param for the provided name. - QueryParam(name string) string + // QueryParam returns the query param for the provided name. + QueryParam(name string) string - // QueryParams returns the query parameters as `url.Values`. - QueryParams() url.Values + // QueryParams returns the query parameters as `url.Values`. + QueryParams() url.Values - // QueryString returns the URL query string. - QueryString() string + // QueryString returns the URL query string. + QueryString() string - // FormValue returns the form field value for the provided name. - FormValue(name string) string + // FormValue returns the form field value for the provided name. + FormValue(name string) string - // FormParams returns the form parameters as `url.Values`. - FormParams() (url.Values, error) + // FormParams returns the form parameters as `url.Values`. + FormParams() (url.Values, error) - // FormFile returns the multipart form file for the provided name. - FormFile(name string) (*multipart.FileHeader, error) + // FormFile returns the multipart form file for the provided name. + FormFile(name string) (*multipart.FileHeader, error) - // MultipartForm returns the multipart form. - MultipartForm() (*multipart.Form, error) + // MultipartForm returns the multipart form. + MultipartForm() (*multipart.Form, error) - // Cookie returns the named cookie provided in the request. - Cookie(name string) (*http.Cookie, error) + // Cookie returns the named cookie provided in the request. + Cookie(name string) (*http.Cookie, error) - // SetCookie adds a `Set-Cookie` header in HTTP response. - SetCookie(cookie *http.Cookie) + // SetCookie adds a `Set-Cookie` header in HTTP response. + SetCookie(cookie *http.Cookie) - // Cookies returns the HTTP cookies sent with the request. - Cookies() []*http.Cookie + // Cookies returns the HTTP cookies sent with the request. + Cookies() []*http.Cookie - // Get retrieves data from the context. - Get(key string) interface{} + // Get retrieves data from the context. + Get(key string) interface{} - // Set saves data in the context. - Set(key string, val interface{}) + // Set saves data in the context. + Set(key string, val interface{}) - // Bind binds path params, query params and the request body into provided type `i`. The default binder - // binds body based on Content-Type header. - Bind(i interface{}) error + // Bind binds path params, query params and the request body into provided type `i`. The default binder + // binds body based on Content-Type header. + Bind(i interface{}) error - // Validate validates provided `i`. It is usually called after `Context#Bind()`. - // Validator must be registered using `Echo#Validator`. - Validate(i interface{}) error + // Validate validates provided `i`. It is usually called after `Context#Bind()`. + // Validator must be registered using `Echo#Validator`. + Validate(i interface{}) error - // Render renders a template with data and sends a text/html response with status - // code. Renderer must be registered using `Echo.Renderer`. - Render(code int, name string, data interface{}) error + // Render renders a template with data and sends a text/html response with status + // code. Renderer must be registered using `Echo.Renderer`. + Render(code int, name string, data interface{}) error - // HTML sends an HTTP response with status code. - HTML(code int, html string) error + // HTML sends an HTTP response with status code. + HTML(code int, html string) error - // HTMLBlob sends an HTTP blob response with status code. - HTMLBlob(code int, b []byte) error + // HTMLBlob sends an HTTP blob response with status code. + HTMLBlob(code int, b []byte) error - // String sends a string response with status code. - String(code int, s string) error + // String sends a string response with status code. + String(code int, s string) error - // JSON sends a JSON response with status code. - JSON(code int, i interface{}) error + // JSON sends a JSON response with status code. + JSON(code int, i interface{}) error - // JSONPretty sends a pretty-print JSON with status code. - JSONPretty(code int, i interface{}, indent string) error + // JSONPretty sends a pretty-print JSON with status code. + JSONPretty(code int, i interface{}, indent string) error - // JSONBlob sends a JSON blob response with status code. - JSONBlob(code int, b []byte) error + // JSONBlob sends a JSON blob response with status code. + JSONBlob(code int, b []byte) error - // JSONP sends a JSONP response with status code. It uses `callback` to construct - // the JSONP payload. - JSONP(code int, callback string, i interface{}) error + // JSONP sends a JSONP response with status code. It uses `callback` to construct + // the JSONP payload. + JSONP(code int, callback string, i interface{}) error - // JSONPBlob sends a JSONP blob response with status code. It uses `callback` - // to construct the JSONP payload. - JSONPBlob(code int, callback string, b []byte) error + // JSONPBlob sends a JSONP blob response with status code. It uses `callback` + // to construct the JSONP payload. + JSONPBlob(code int, callback string, b []byte) error - // XML sends an XML response with status code. - XML(code int, i interface{}) error + // XML sends an XML response with status code. + XML(code int, i interface{}) error - // XMLPretty sends a pretty-print XML with status code. - XMLPretty(code int, i interface{}, indent string) error + // XMLPretty sends a pretty-print XML with status code. + XMLPretty(code int, i interface{}, indent string) error - // XMLBlob sends an XML blob response with status code. - XMLBlob(code int, b []byte) error + // XMLBlob sends an XML blob response with status code. + XMLBlob(code int, b []byte) error - // Blob sends a blob response with status code and content type. - Blob(code int, contentType string, b []byte) error + // Blob sends a blob response with status code and content type. + Blob(code int, contentType string, b []byte) error - // Stream sends a streaming response with status code and content type. - Stream(code int, contentType string, r io.Reader) error + // Stream sends a streaming response with status code and content type. + Stream(code int, contentType string, r io.Reader) error - // File sends a response with the content of the file. - File(file string) error + // File sends a response with the content of the file. + File(file string) error - // Attachment sends a response as attachment, prompting client to save the - // file. - Attachment(file string, name string) error + // Attachment sends a response as attachment, prompting client to save the + // file. + Attachment(file string, name string) error - // Inline sends a response as inline, opening the file in the browser. - Inline(file string, name string) error + // Inline sends a response as inline, opening the file in the browser. + Inline(file string, name string) error - // NoContent sends a response with no body and a status code. - NoContent(code int) error + // NoContent sends a response with no body and a status code. + NoContent(code int) error - // Redirect redirects the request to a provided URL with status code. - Redirect(code int, url string) error + // Redirect redirects the request to a provided URL with status code. + Redirect(code int, url string) error - // Error invokes the registered global HTTP error handler. Generally used by middleware. - // A side-effect of calling global error handler is that now Response has been committed (sent to the client) and - // middlewares up in chain can not change Response status code or Response body anymore. - // - // Avoid using this method in handlers as no middleware will be able to effectively handle errors after that. - Error(err error) + // Error invokes the registered global HTTP error handler. Generally used by middleware. + // A side-effect of calling global error handler is that now Response has been committed (sent to the client) and + // middlewares up in chain can not change Response status code or Response body anymore. + // + // Avoid using this method in handlers as no middleware will be able to effectively handle errors after that. + Error(err error) - // Handler returns the matched handler by router. - Handler() HandlerFunc + // Handler returns the matched handler by router. + Handler() HandlerFunc - // SetHandler sets the matched handler by router. - SetHandler(h HandlerFunc) + // SetHandler sets the matched handler by router. + SetHandler(h HandlerFunc) - // Logger returns the `Logger` instance. - Logger() Logger + // Logger returns the `Logger` instance. + Logger() Logger - // SetLogger Set the logger - SetLogger(l Logger) + // SetLogger Set the logger + SetLogger(l Logger) - // Echo returns the `Echo` instance. - Echo() *Echo + // Echo returns the `Echo` instance. + Echo() *Echo - // Reset resets the context after request completes. It must be called along - // with `Echo#AcquireContext()` and `Echo#ReleaseContext()`. - // See `Echo#ServeHTTP()` - Reset(r *http.Request, w http.ResponseWriter) - } + // Reset resets the context after request completes. It must be called along + // with `Echo#AcquireContext()` and `Echo#ReleaseContext()`. + // See `Echo#ServeHTTP()` + Reset(r *http.Request, w http.ResponseWriter) +} - context struct { - request *http.Request - response *Response - path string - pnames []string - pvalues []string - query url.Values - handler HandlerFunc - store Map - echo *Echo - logger Logger - lock sync.RWMutex - } -) +type context struct { + request *http.Request + response *Response + path string + pnames []string + pvalues []string + query url.Values + handler HandlerFunc + store Map + echo *Echo + logger Logger + lock sync.RWMutex +} const ( // ContextKeyHeaderAllow is set by Router for getting value for `Allow` header in later stages of handler call chain. diff --git a/context_test.go b/context_test.go index 463e10a60..e5c4a215a 100644 --- a/context_test.go +++ b/context_test.go @@ -25,11 +25,9 @@ import ( "github.com/stretchr/testify/assert" ) -type ( - Template struct { - templates *template.Template - } -) +type Template struct { + templates *template.Template +} var testUser = user{1, "Jon Snow"} diff --git a/echo.go b/echo.go index 4d11af04a..6e4ed9a8d 100644 --- a/echo.go +++ b/echo.go @@ -63,97 +63,95 @@ import ( "golang.org/x/net/http2/h2c" ) -type ( - // Echo is the top-level framework instance. - // - // Goroutine safety: Do not mutate Echo instance fields after server has started. Accessing these - // fields from handlers/middlewares and changing field values at the same time leads to data-races. - // Adding new routes after the server has been started is also not safe! - Echo struct { - filesystem - common - // startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get - // listener address info (on which interface/port was listener bound) without having data races. - startupMutex sync.RWMutex - colorer *color.Color - - // premiddleware are middlewares that are run before routing is done. In case a pre-middleware returns - // an error the router is not executed and the request will end up in the global error handler. - premiddleware []MiddlewareFunc - middleware []MiddlewareFunc - maxParam *int - router *Router - routers map[string]*Router - pool sync.Pool - - StdLogger *stdLog.Logger - Server *http.Server - TLSServer *http.Server - Listener net.Listener - TLSListener net.Listener - AutoTLSManager autocert.Manager - DisableHTTP2 bool - Debug bool - HideBanner bool - HidePort bool - HTTPErrorHandler HTTPErrorHandler - Binder Binder - JSONSerializer JSONSerializer - Validator Validator - Renderer Renderer - Logger Logger - IPExtractor IPExtractor - ListenerNetwork string - - // OnAddRouteHandler is called when Echo adds new route to specific host router. - OnAddRouteHandler func(host string, route Route, handler HandlerFunc, middleware []MiddlewareFunc) - } - - // Route contains a handler and information for matching against requests. - Route struct { - Method string `json:"method"` - Path string `json:"path"` - Name string `json:"name"` - } - - // HTTPError represents an error that occurred while handling a request. - HTTPError struct { - Code int `json:"-"` - Message interface{} `json:"message"` - Internal error `json:"-"` // Stores the error returned by an external dependency - } - - // MiddlewareFunc defines a function to process middleware. - MiddlewareFunc func(next HandlerFunc) HandlerFunc - - // HandlerFunc defines a function to serve HTTP requests. - HandlerFunc func(c Context) error - - // HTTPErrorHandler is a centralized HTTP error handler. - HTTPErrorHandler func(err error, c Context) - - // Validator is the interface that wraps the Validate function. - Validator interface { - Validate(i interface{}) error - } - - // JSONSerializer is the interface that encodes and decodes JSON to and from interfaces. - JSONSerializer interface { - Serialize(c Context, i interface{}, indent string) error - Deserialize(c Context, i interface{}) error - } - - // Renderer is the interface that wraps the Render function. - Renderer interface { - Render(io.Writer, string, interface{}, Context) error - } - - // Map defines a generic map of type `map[string]interface{}`. - Map map[string]interface{} - - // Common struct for Echo & Group. - common struct{} -) +// Echo is the top-level framework instance. +// +// Goroutine safety: Do not mutate Echo instance fields after server has started. Accessing these +// fields from handlers/middlewares and changing field values at the same time leads to data-races. +// Adding new routes after the server has been started is also not safe! +type Echo struct { + filesystem + common + // startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get + // listener address info (on which interface/port was listener bound) without having data races. + startupMutex sync.RWMutex + colorer *color.Color + + // premiddleware are middlewares that are run before routing is done. In case a pre-middleware returns + // an error the router is not executed and the request will end up in the global error handler. + premiddleware []MiddlewareFunc + middleware []MiddlewareFunc + maxParam *int + router *Router + routers map[string]*Router + pool sync.Pool + + StdLogger *stdLog.Logger + Server *http.Server + TLSServer *http.Server + Listener net.Listener + TLSListener net.Listener + AutoTLSManager autocert.Manager + DisableHTTP2 bool + Debug bool + HideBanner bool + HidePort bool + HTTPErrorHandler HTTPErrorHandler + Binder Binder + JSONSerializer JSONSerializer + Validator Validator + Renderer Renderer + Logger Logger + IPExtractor IPExtractor + ListenerNetwork string + + // OnAddRouteHandler is called when Echo adds new route to specific host router. + OnAddRouteHandler func(host string, route Route, handler HandlerFunc, middleware []MiddlewareFunc) +} + +// Route contains a handler and information for matching against requests. +type Route struct { + Method string `json:"method"` + Path string `json:"path"` + Name string `json:"name"` +} + +// HTTPError represents an error that occurred while handling a request. +type HTTPError struct { + Code int `json:"-"` + Message interface{} `json:"message"` + Internal error `json:"-"` // Stores the error returned by an external dependency +} + +// MiddlewareFunc defines a function to process middleware. +type MiddlewareFunc func(next HandlerFunc) HandlerFunc + +// HandlerFunc defines a function to serve HTTP requests. +type HandlerFunc func(c Context) error + +// HTTPErrorHandler is a centralized HTTP error handler. +type HTTPErrorHandler func(err error, c Context) + +// Validator is the interface that wraps the Validate function. +type Validator interface { + Validate(i interface{}) error +} + +// JSONSerializer is the interface that encodes and decodes JSON to and from interfaces. +type JSONSerializer interface { + Serialize(c Context, i interface{}, indent string) error + Deserialize(c Context, i interface{}) error +} + +// Renderer is the interface that wraps the Render function. +type Renderer interface { + Render(io.Writer, string, interface{}, Context) error +} + +// Map defines a generic map of type `map[string]interface{}`. +type Map map[string]interface{} + +// Common struct for Echo & Group. +type common struct{} // HTTP methods // NOTE: Deprecated, please use the stdlib constants directly instead. @@ -282,21 +280,19 @@ ____________________________________O/_______ ` ) -var ( - methods = [...]string{ - http.MethodConnect, - http.MethodDelete, - http.MethodGet, - http.MethodHead, - http.MethodOptions, - http.MethodPatch, - http.MethodPost, - PROPFIND, - http.MethodPut, - http.MethodTrace, - REPORT, - } -) +var methods = [...]string{ + http.MethodConnect, + http.MethodDelete, + http.MethodGet, + http.MethodHead, + http.MethodOptions, + http.MethodPatch, + http.MethodPost, + PROPFIND, + http.MethodPut, + http.MethodTrace, + REPORT, +} // Errors var ( @@ -349,22 +345,23 @@ var ( ErrInvalidListenerNetwork = errors.New("invalid listener network") ) -// Error handlers -var ( - NotFoundHandler = func(c Context) error { - return ErrNotFound - } +// NotFoundHandler is the handler that router uses in case there was no matching route found. Returns an error that results +// HTTP 404 status code. +var NotFoundHandler = func(c Context) error { + return ErrNotFound +} - MethodNotAllowedHandler = func(c Context) error { - // See RFC 7231 section 7.4.1: An origin server MUST generate an Allow field in a 405 (Method Not Allowed) - // response and MAY do so in any other response. For disabled resources an empty Allow header may be returned - routerAllowMethods, ok := c.Get(ContextKeyHeaderAllow).(string) - if ok && routerAllowMethods != "" { - c.Response().Header().Set(HeaderAllow, routerAllowMethods) - } - return ErrMethodNotAllowed +// MethodNotAllowedHandler is the handler thar router uses in case there was no matching route found but there was +// another matching routes for that requested URL. Returns an error that results HTTP 405 Method Not Allowed status code. +var MethodNotAllowedHandler = func(c Context) error { + // See RFC 7231 section 7.4.1: An origin server MUST generate an Allow field in a 405 (Method Not Allowed) + // response and MAY do so in any other response. For disabled resources an empty Allow header may be returned + routerAllowMethods, ok := c.Get(ContextKeyHeaderAllow).(string) + if ok && routerAllowMethods != "" { + c.Response().Header().Set(HeaderAllow, routerAllowMethods) } -) + return ErrMethodNotAllowed +} // New creates an instance of Echo. func New() (e *Echo) { diff --git a/echo_test.go b/echo_test.go index f09544127..57c257b17 100644 --- a/echo_test.go +++ b/echo_test.go @@ -25,12 +25,10 @@ import ( "golang.org/x/net/http2" ) -type ( - user struct { - ID int `json:"id" xml:"id" form:"id" query:"id" param:"id" header:"id"` - Name string `json:"name" xml:"name" form:"name" query:"name" param:"name" header:"name"` - } -) +type user struct { + ID int `json:"id" xml:"id" form:"id" query:"id" param:"id" header:"id"` + Name string `json:"name" xml:"name" form:"name" query:"name" param:"name" header:"name"` +} const ( userJSON = `{"id":1,"name":"Jon Snow"}` diff --git a/group.go b/group.go index e69d80b7f..eca25c947 100644 --- a/group.go +++ b/group.go @@ -7,18 +7,16 @@ import ( "net/http" ) -type ( - // Group is a set of sub-routes for a specified route. It can be used for inner - // routes that share a common middleware or functionality that should be separate - // from the parent echo instance while still inheriting from it. - Group struct { - common - host string - prefix string - middleware []MiddlewareFunc - echo *Echo - } -) +// Group is a set of sub-routes for a specified route. It can be used for inner +// routes that share a common middleware or functionality that should be separate +// from the parent echo instance while still inheriting from it. +type Group struct { + common + host string + prefix string + middleware []MiddlewareFunc + echo *Echo +} // Use implements `Echo#Use()` for sub-routes within the Group. func (g *Group) Use(middleware ...MiddlewareFunc) { diff --git a/log.go b/log.go index b9ec3d561..0acd9ff03 100644 --- a/log.go +++ b/log.go @@ -4,41 +4,38 @@ package echo import ( - "io" - "github.com/labstack/gommon/log" + "io" ) -type ( - // Logger defines the logging interface. - Logger interface { - Output() io.Writer - SetOutput(w io.Writer) - Prefix() string - SetPrefix(p string) - Level() log.Lvl - SetLevel(v log.Lvl) - SetHeader(h string) - Print(i ...interface{}) - Printf(format string, args ...interface{}) - Printj(j log.JSON) - Debug(i ...interface{}) - Debugf(format string, args ...interface{}) - Debugj(j log.JSON) - Info(i ...interface{}) - Infof(format string, args ...interface{}) - Infoj(j log.JSON) - Warn(i ...interface{}) - Warnf(format string, args ...interface{}) - Warnj(j log.JSON) - Error(i ...interface{}) - Errorf(format string, args ...interface{}) - Errorj(j log.JSON) - Fatal(i ...interface{}) - Fatalj(j log.JSON) - Fatalf(format string, args ...interface{}) - Panic(i ...interface{}) - Panicj(j log.JSON) - Panicf(format string, args ...interface{}) - } -) +// Logger defines the logging interface. +type Logger interface { + Output() io.Writer + SetOutput(w io.Writer) + Prefix() string + SetPrefix(p string) + Level() log.Lvl + SetLevel(v log.Lvl) + SetHeader(h string) + Print(i ...interface{}) + Printf(format string, args ...interface{}) + Printj(j log.JSON) + Debug(i ...interface{}) + Debugf(format string, args ...interface{}) + Debugj(j log.JSON) + Info(i ...interface{}) + Infof(format string, args ...interface{}) + Infoj(j log.JSON) + Warn(i ...interface{}) + Warnf(format string, args ...interface{}) + Warnj(j log.JSON) + Error(i ...interface{}) + Errorf(format string, args ...interface{}) + Errorj(j log.JSON) + Fatal(i ...interface{}) + Fatalj(j log.JSON) + Fatalf(format string, args ...interface{}) + Panic(i ...interface{}) + Panicj(j log.JSON) + Panicf(format string, args ...interface{}) +} diff --git a/middleware/basic_auth.go b/middleware/basic_auth.go index 7e809f5f7..9285f29fd 100644 --- a/middleware/basic_auth.go +++ b/middleware/basic_auth.go @@ -12,39 +12,35 @@ import ( "github.com/labstack/echo/v4" ) -type ( - // BasicAuthConfig defines the config for BasicAuth middleware. - BasicAuthConfig struct { - // Skipper defines a function to skip middleware. - Skipper Skipper - - // Validator is a function to validate BasicAuth credentials. - // Required. - Validator BasicAuthValidator - - // Realm is a string to define realm attribute of BasicAuth. - // Default value "Restricted". - Realm string - } +// BasicAuthConfig defines the config for BasicAuth middleware. +type BasicAuthConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Validator is a function to validate BasicAuth credentials. + // Required. + Validator BasicAuthValidator + + // Realm is a string to define realm attribute of BasicAuth. + // Default value "Restricted". + Realm string +} - // BasicAuthValidator defines a function to validate BasicAuth credentials. - // The function should return a boolean indicating whether the credentials are valid, - // and an error if any error occurs during the validation process. - BasicAuthValidator func(string, string, echo.Context) (bool, error) -) +// BasicAuthValidator defines a function to validate BasicAuth credentials. +// The function should return a boolean indicating whether the credentials are valid, +// and an error if any error occurs during the validation process. +type BasicAuthValidator func(string, string, echo.Context) (bool, error) const ( basic = "basic" defaultRealm = "Restricted" ) -var ( - // DefaultBasicAuthConfig is the default BasicAuth middleware config. - DefaultBasicAuthConfig = BasicAuthConfig{ - Skipper: DefaultSkipper, - Realm: defaultRealm, - } -) +// DefaultBasicAuthConfig is the default BasicAuth middleware config. +var DefaultBasicAuthConfig = BasicAuthConfig{ + Skipper: DefaultSkipper, + Realm: defaultRealm, +} // BasicAuth returns an BasicAuth middleware. // diff --git a/middleware/body_dump.go b/middleware/body_dump.go index e7b20981c..b06f76202 100644 --- a/middleware/body_dump.go +++ b/middleware/body_dump.go @@ -14,32 +14,28 @@ import ( "github.com/labstack/echo/v4" ) -type ( - // BodyDumpConfig defines the config for BodyDump middleware. - BodyDumpConfig struct { - // Skipper defines a function to skip middleware. - Skipper Skipper - - // Handler receives request and response payload. - // Required. - Handler BodyDumpHandler - } +// BodyDumpConfig defines the config for BodyDump middleware. +type BodyDumpConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Handler receives request and response payload. + // Required. + Handler BodyDumpHandler +} - // BodyDumpHandler receives the request and response payload. - BodyDumpHandler func(echo.Context, []byte, []byte) +// BodyDumpHandler receives the request and response payload. +type BodyDumpHandler func(echo.Context, []byte, []byte) - bodyDumpResponseWriter struct { - io.Writer - http.ResponseWriter - } -) +type bodyDumpResponseWriter struct { + io.Writer + http.ResponseWriter +} -var ( - // DefaultBodyDumpConfig is the default BodyDump middleware config. - DefaultBodyDumpConfig = BodyDumpConfig{ - Skipper: DefaultSkipper, - } -) +// DefaultBodyDumpConfig is the default BodyDump middleware config. +var DefaultBodyDumpConfig = BodyDumpConfig{ + Skipper: DefaultSkipper, +} // BodyDump returns a BodyDump middleware. // diff --git a/middleware/body_limit.go b/middleware/body_limit.go index 81972304e..7d3c665f2 100644 --- a/middleware/body_limit.go +++ b/middleware/body_limit.go @@ -12,31 +12,27 @@ import ( "github.com/labstack/gommon/bytes" ) -type ( - // BodyLimitConfig defines the config for BodyLimit middleware. - BodyLimitConfig struct { - // Skipper defines a function to skip middleware. - Skipper Skipper - - // Maximum allowed size for a request body, it can be specified - // as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P. - Limit string `yaml:"limit"` - limit int64 - } +// BodyLimitConfig defines the config for BodyLimit middleware. +type BodyLimitConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Maximum allowed size for a request body, it can be specified + // as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P. + Limit string `yaml:"limit"` + limit int64 +} - limitedReader struct { - BodyLimitConfig - reader io.ReadCloser - read int64 - } -) +type limitedReader struct { + BodyLimitConfig + reader io.ReadCloser + read int64 +} -var ( - // DefaultBodyLimitConfig is the default BodyLimit middleware config. - DefaultBodyLimitConfig = BodyLimitConfig{ - Skipper: DefaultSkipper, - } -) +// DefaultBodyLimitConfig is the default BodyLimit middleware config. +var DefaultBodyLimitConfig = BodyLimitConfig{ + Skipper: DefaultSkipper, +} // BodyLimit returns a BodyLimit middleware. // diff --git a/middleware/compress.go b/middleware/compress.go index 681c0346f..557bdc8e2 100644 --- a/middleware/compress.go +++ b/middleware/compress.go @@ -16,54 +16,50 @@ import ( "github.com/labstack/echo/v4" ) -type ( - // GzipConfig defines the config for Gzip middleware. - GzipConfig struct { - // Skipper defines a function to skip middleware. - Skipper Skipper - - // Gzip compression level. - // Optional. Default value -1. - Level int `yaml:"level"` - - // Length threshold before gzip compression is applied. - // Optional. Default value 0. - // - // Most of the time you will not need to change the default. Compressing - // a short response might increase the transmitted data because of the - // gzip format overhead. Compressing the response will also consume CPU - // and time on the server and the client (for decompressing). Depending on - // your use case such a threshold might be useful. - // - // See also: - // https://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits - MinLength int - } +// GzipConfig defines the config for Gzip middleware. +type GzipConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Gzip compression level. + // Optional. Default value -1. + Level int `yaml:"level"` + + // Length threshold before gzip compression is applied. + // Optional. Default value 0. + // + // Most of the time you will not need to change the default. Compressing + // a short response might increase the transmitted data because of the + // gzip format overhead. Compressing the response will also consume CPU + // and time on the server and the client (for decompressing). Depending on + // your use case such a threshold might be useful. + // + // See also: + // https://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits + MinLength int +} - gzipResponseWriter struct { - io.Writer - http.ResponseWriter - wroteHeader bool - wroteBody bool - minLength int - minLengthExceeded bool - buffer *bytes.Buffer - code int - } -) +type gzipResponseWriter struct { + io.Writer + http.ResponseWriter + wroteHeader bool + wroteBody bool + minLength int + minLengthExceeded bool + buffer *bytes.Buffer + code int +} const ( gzipScheme = "gzip" ) -var ( - // DefaultGzipConfig is the default Gzip middleware config. - DefaultGzipConfig = GzipConfig{ - Skipper: DefaultSkipper, - Level: -1, - MinLength: 0, - } -) +// DefaultGzipConfig is the default Gzip middleware config. +var DefaultGzipConfig = GzipConfig{ + Skipper: DefaultSkipper, + Level: -1, + MinLength: 0, +} // Gzip returns a middleware which compresses HTTP response using gzip compression // scheme. diff --git a/middleware/cors.go b/middleware/cors.go index dd7030e56..7af6a76f3 100644 --- a/middleware/cors.go +++ b/middleware/cors.go @@ -12,113 +12,109 @@ import ( "github.com/labstack/echo/v4" ) -type ( - // CORSConfig defines the config for CORS middleware. - CORSConfig struct { - // Skipper defines a function to skip middleware. - Skipper Skipper - - // AllowOrigins determines the value of the Access-Control-Allow-Origin - // response header. This header defines a list of origins that may access the - // resource. The wildcard characters '*' and '?' are supported and are - // converted to regex fragments '.*' and '.' accordingly. - // - // Security: use extreme caution when handling the origin, and carefully - // validate any logic. Remember that attackers may register hostile domain names. - // See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html - // - // Optional. Default value []string{"*"}. - // - // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin - AllowOrigins []string `yaml:"allow_origins"` - - // AllowOriginFunc is a custom function to validate the origin. It takes the - // origin as an argument and returns true if allowed or false otherwise. If - // an error is returned, it is returned by the handler. If this option is - // set, AllowOrigins is ignored. - // - // Security: use extreme caution when handling the origin, and carefully - // validate any logic. Remember that attackers may register hostile domain names. - // See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html - // - // Optional. - AllowOriginFunc func(origin string) (bool, error) `yaml:"-"` - - // AllowMethods determines the value of the Access-Control-Allow-Methods - // response header. This header specified the list of methods allowed when - // accessing the resource. This is used in response to a preflight request. - // - // Optional. Default value DefaultCORSConfig.AllowMethods. - // If `allowMethods` is left empty, this middleware will fill for preflight - // request `Access-Control-Allow-Methods` header value - // from `Allow` header that echo.Router set into context. - // - // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods - AllowMethods []string `yaml:"allow_methods"` - - // AllowHeaders determines the value of the Access-Control-Allow-Headers - // response header. This header is used in response to a preflight request to - // indicate which HTTP headers can be used when making the actual request. - // - // Optional. Default value []string{}. - // - // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers - AllowHeaders []string `yaml:"allow_headers"` - - // AllowCredentials determines the value of the - // Access-Control-Allow-Credentials response header. This header indicates - // whether or not the response to the request can be exposed when the - // credentials mode (Request.credentials) is true. When used as part of a - // response to a preflight request, this indicates whether or not the actual - // request can be made using credentials. See also - // [MDN: Access-Control-Allow-Credentials]. - // - // Optional. Default value false, in which case the header is not set. - // - // Security: avoid using `AllowCredentials = true` with `AllowOrigins = *`. - // See "Exploiting CORS misconfigurations for Bitcoins and bounties", - // https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html - // - // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials - AllowCredentials bool `yaml:"allow_credentials"` - - // UnsafeWildcardOriginWithAllowCredentials UNSAFE/INSECURE: allows wildcard '*' origin to be used with AllowCredentials - // flag. In that case we consider any origin allowed and send it back to the client with `Access-Control-Allow-Origin` header. - // - // This is INSECURE and potentially leads to [cross-origin](https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties) - // attacks. See: https://github.com/labstack/echo/issues/2400 for discussion on the subject. - // - // Optional. Default value is false. - UnsafeWildcardOriginWithAllowCredentials bool `yaml:"unsafe_wildcard_origin_with_allow_credentials"` - - // ExposeHeaders determines the value of Access-Control-Expose-Headers, which - // defines a list of headers that clients are allowed to access. - // - // Optional. Default value []string{}, in which case the header is not set. - // - // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Header - ExposeHeaders []string `yaml:"expose_headers"` - - // MaxAge determines the value of the Access-Control-Max-Age response header. - // This header indicates how long (in seconds) the results of a preflight - // request can be cached. - // The header is set only if MaxAge != 0, negative value sends "0" which instructs browsers not to cache that response. - // - // Optional. Default value 0 - meaning header is not sent. - // - // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age - MaxAge int `yaml:"max_age"` - } -) +// CORSConfig defines the config for CORS middleware. +type CORSConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // AllowOrigins determines the value of the Access-Control-Allow-Origin + // response header. This header defines a list of origins that may access the + // resource. The wildcard characters '*' and '?' are supported and are + // converted to regex fragments '.*' and '.' accordingly. + // + // Security: use extreme caution when handling the origin, and carefully + // validate any logic. Remember that attackers may register hostile domain names. + // See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html + // + // Optional. Default value []string{"*"}. + // + // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin + AllowOrigins []string `yaml:"allow_origins"` + + // AllowOriginFunc is a custom function to validate the origin. It takes the + // origin as an argument and returns true if allowed or false otherwise. If + // an error is returned, it is returned by the handler. If this option is + // set, AllowOrigins is ignored. + // + // Security: use extreme caution when handling the origin, and carefully + // validate any logic. Remember that attackers may register hostile domain names. + // See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html + // + // Optional. + AllowOriginFunc func(origin string) (bool, error) `yaml:"-"` + + // AllowMethods determines the value of the Access-Control-Allow-Methods + // response header. This header specified the list of methods allowed when + // accessing the resource. This is used in response to a preflight request. + // + // Optional. Default value DefaultCORSConfig.AllowMethods. + // If `allowMethods` is left empty, this middleware will fill for preflight + // request `Access-Control-Allow-Methods` header value + // from `Allow` header that echo.Router set into context. + // + // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods + AllowMethods []string `yaml:"allow_methods"` + + // AllowHeaders determines the value of the Access-Control-Allow-Headers + // response header. This header is used in response to a preflight request to + // indicate which HTTP headers can be used when making the actual request. + // + // Optional. Default value []string{}. + // + // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers + AllowHeaders []string `yaml:"allow_headers"` + + // AllowCredentials determines the value of the + // Access-Control-Allow-Credentials response header. This header indicates + // whether or not the response to the request can be exposed when the + // credentials mode (Request.credentials) is true. When used as part of a + // response to a preflight request, this indicates whether or not the actual + // request can be made using credentials. See also + // [MDN: Access-Control-Allow-Credentials]. + // + // Optional. Default value false, in which case the header is not set. + // + // Security: avoid using `AllowCredentials = true` with `AllowOrigins = *`. + // See "Exploiting CORS misconfigurations for Bitcoins and bounties", + // https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html + // + // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials + AllowCredentials bool `yaml:"allow_credentials"` + + // UnsafeWildcardOriginWithAllowCredentials UNSAFE/INSECURE: allows wildcard '*' origin to be used with AllowCredentials + // flag. In that case we consider any origin allowed and send it back to the client with `Access-Control-Allow-Origin` header. + // + // This is INSECURE and potentially leads to [cross-origin](https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties) + // attacks. See: https://github.com/labstack/echo/issues/2400 for discussion on the subject. + // + // Optional. Default value is false. + UnsafeWildcardOriginWithAllowCredentials bool `yaml:"unsafe_wildcard_origin_with_allow_credentials"` + + // ExposeHeaders determines the value of Access-Control-Expose-Headers, which + // defines a list of headers that clients are allowed to access. + // + // Optional. Default value []string{}, in which case the header is not set. + // + // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Header + ExposeHeaders []string `yaml:"expose_headers"` + + // MaxAge determines the value of the Access-Control-Max-Age response header. + // This header indicates how long (in seconds) the results of a preflight + // request can be cached. + // The header is set only if MaxAge != 0, negative value sends "0" which instructs browsers not to cache that response. + // + // Optional. Default value 0 - meaning header is not sent. + // + // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age + MaxAge int `yaml:"max_age"` +} -var ( - // DefaultCORSConfig is the default CORS middleware config. - DefaultCORSConfig = CORSConfig{ - Skipper: DefaultSkipper, - AllowOrigins: []string{"*"}, - AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete}, - } -) +// DefaultCORSConfig is the default CORS middleware config. +var DefaultCORSConfig = CORSConfig{ + Skipper: DefaultSkipper, + AllowOrigins: []string{"*"}, + AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete}, +} // CORS returns a Cross-Origin Resource Sharing (CORS) middleware. // See also [MDN: Cross-Origin Resource Sharing (CORS)]. diff --git a/middleware/csrf.go b/middleware/csrf.go index 015473d9f..92f4019dc 100644 --- a/middleware/csrf.go +++ b/middleware/csrf.go @@ -11,82 +11,78 @@ import ( "github.com/labstack/echo/v4" ) -type ( - // CSRFConfig defines the config for CSRF middleware. - CSRFConfig struct { - // Skipper defines a function to skip middleware. - Skipper Skipper - - // TokenLength is the length of the generated token. - TokenLength uint8 `yaml:"token_length"` - // Optional. Default value 32. - - // TokenLookup is a string in the form of ":" or ":,:" that is used - // to extract token from the request. - // Optional. Default value "header:X-CSRF-Token". - // Possible values: - // - "header:" or "header::" - // - "query:" - // - "form:" - // Multiple sources example: - // - "header:X-CSRF-Token,query:csrf" - TokenLookup string `yaml:"token_lookup"` - - // Context key to store generated CSRF token into context. - // Optional. Default value "csrf". - ContextKey string `yaml:"context_key"` - - // Name of the CSRF cookie. This cookie will store CSRF token. - // Optional. Default value "csrf". - CookieName string `yaml:"cookie_name"` - - // Domain of the CSRF cookie. - // Optional. Default value none. - CookieDomain string `yaml:"cookie_domain"` - - // Path of the CSRF cookie. - // Optional. Default value none. - CookiePath string `yaml:"cookie_path"` - - // Max age (in seconds) of the CSRF cookie. - // Optional. Default value 86400 (24hr). - CookieMaxAge int `yaml:"cookie_max_age"` - - // Indicates if CSRF cookie is secure. - // Optional. Default value false. - CookieSecure bool `yaml:"cookie_secure"` - - // Indicates if CSRF cookie is HTTP only. - // Optional. Default value false. - CookieHTTPOnly bool `yaml:"cookie_http_only"` - - // Indicates SameSite mode of the CSRF cookie. - // Optional. Default value SameSiteDefaultMode. - CookieSameSite http.SameSite `yaml:"cookie_same_site"` - - // ErrorHandler defines a function which is executed for returning custom errors. - ErrorHandler CSRFErrorHandler - } +// CSRFConfig defines the config for CSRF middleware. +type CSRFConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // TokenLength is the length of the generated token. + TokenLength uint8 `yaml:"token_length"` + // Optional. Default value 32. + + // TokenLookup is a string in the form of ":" or ":,:" that is used + // to extract token from the request. + // Optional. Default value "header:X-CSRF-Token". + // Possible values: + // - "header:" or "header::" + // - "query:" + // - "form:" + // Multiple sources example: + // - "header:X-CSRF-Token,query:csrf" + TokenLookup string `yaml:"token_lookup"` + + // Context key to store generated CSRF token into context. + // Optional. Default value "csrf". + ContextKey string `yaml:"context_key"` + + // Name of the CSRF cookie. This cookie will store CSRF token. + // Optional. Default value "csrf". + CookieName string `yaml:"cookie_name"` + + // Domain of the CSRF cookie. + // Optional. Default value none. + CookieDomain string `yaml:"cookie_domain"` + + // Path of the CSRF cookie. + // Optional. Default value none. + CookiePath string `yaml:"cookie_path"` + + // Max age (in seconds) of the CSRF cookie. + // Optional. Default value 86400 (24hr). + CookieMaxAge int `yaml:"cookie_max_age"` + + // Indicates if CSRF cookie is secure. + // Optional. Default value false. + CookieSecure bool `yaml:"cookie_secure"` + + // Indicates if CSRF cookie is HTTP only. + // Optional. Default value false. + CookieHTTPOnly bool `yaml:"cookie_http_only"` + + // Indicates SameSite mode of the CSRF cookie. + // Optional. Default value SameSiteDefaultMode. + CookieSameSite http.SameSite `yaml:"cookie_same_site"` + + // ErrorHandler defines a function which is executed for returning custom errors. + ErrorHandler CSRFErrorHandler +} - // CSRFErrorHandler is a function which is executed for creating custom errors. - CSRFErrorHandler func(err error, c echo.Context) error -) +// CSRFErrorHandler is a function which is executed for creating custom errors. +type CSRFErrorHandler func(err error, c echo.Context) error // ErrCSRFInvalid is returned when CSRF check fails var ErrCSRFInvalid = echo.NewHTTPError(http.StatusForbidden, "invalid csrf token") -var ( - // DefaultCSRFConfig is the default CSRF middleware config. - DefaultCSRFConfig = CSRFConfig{ - Skipper: DefaultSkipper, - TokenLength: 32, - TokenLookup: "header:" + echo.HeaderXCSRFToken, - ContextKey: "csrf", - CookieName: "_csrf", - CookieMaxAge: 86400, - CookieSameSite: http.SameSiteDefaultMode, - } -) +// DefaultCSRFConfig is the default CSRF middleware config. +var DefaultCSRFConfig = CSRFConfig{ + Skipper: DefaultSkipper, + TokenLength: 32, + TokenLookup: "header:" + echo.HeaderXCSRFToken, + ContextKey: "csrf", + CookieName: "_csrf", + CookieMaxAge: 86400, + CookieSameSite: http.SameSiteDefaultMode, +} // CSRF returns a Cross-Site Request Forgery (CSRF) middleware. // See: https://en.wikipedia.org/wiki/Cross-site_request_forgery diff --git a/middleware/decompress.go b/middleware/decompress.go index 3dded53c5..0c56176ee 100644 --- a/middleware/decompress.go +++ b/middleware/decompress.go @@ -12,16 +12,14 @@ import ( "github.com/labstack/echo/v4" ) -type ( - // DecompressConfig defines the config for Decompress middleware. - DecompressConfig struct { - // Skipper defines a function to skip middleware. - Skipper Skipper - - // GzipDecompressPool defines an interface to provide the sync.Pool used to create/store Gzip readers - GzipDecompressPool Decompressor - } -) +// DecompressConfig defines the config for Decompress middleware. +type DecompressConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // GzipDecompressPool defines an interface to provide the sync.Pool used to create/store Gzip readers + GzipDecompressPool Decompressor +} // GZIPEncoding content-encoding header if set to "gzip", decompress body contents. const GZIPEncoding string = "gzip" @@ -31,13 +29,11 @@ type Decompressor interface { gzipDecompressPool() sync.Pool } -var ( - //DefaultDecompressConfig defines the config for decompress middleware - DefaultDecompressConfig = DecompressConfig{ - Skipper: DefaultSkipper, - GzipDecompressPool: &DefaultGzipDecompressPool{}, - } -) +// DefaultDecompressConfig defines the config for decompress middleware +var DefaultDecompressConfig = DecompressConfig{ + Skipper: DefaultSkipper, + GzipDecompressPool: &DefaultGzipDecompressPool{}, +} // DefaultGzipDecompressPool is the default implementation of Decompressor interface type DefaultGzipDecompressPool struct { diff --git a/middleware/jwt.go b/middleware/jwt.go index 276bdfe39..a6bf16f95 100644 --- a/middleware/jwt.go +++ b/middleware/jwt.go @@ -15,139 +15,135 @@ import ( "reflect" ) -type ( - // JWTConfig defines the config for JWT middleware. - JWTConfig struct { - // Skipper defines a function to skip middleware. - Skipper Skipper - - // BeforeFunc defines a function which is executed just before the middleware. - BeforeFunc BeforeFunc - - // SuccessHandler defines a function which is executed for a valid token before middleware chain continues with next - // middleware or handler. - SuccessHandler JWTSuccessHandler - - // ErrorHandler defines a function which is executed for an invalid token. - // It may be used to define a custom JWT error. - ErrorHandler JWTErrorHandler - - // ErrorHandlerWithContext is almost identical to ErrorHandler, but it's passed the current context. - ErrorHandlerWithContext JWTErrorHandlerWithContext - - // ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandlerWithContext decides to - // ignore the error (by returning `nil`). - // This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality. - // In that case you can use ErrorHandlerWithContext to set a default public JWT token value in the request context - // and continue. Some logic down the remaining execution chain needs to check that (public) token value then. - ContinueOnIgnoredError bool - - // Signing key to validate token. - // This is one of the three options to provide a token validation key. - // The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey. - // Required if neither user-defined KeyFunc nor SigningKeys is provided. - SigningKey interface{} - - // Map of signing keys to validate token with kid field usage. - // This is one of the three options to provide a token validation key. - // The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey. - // Required if neither user-defined KeyFunc nor SigningKey is provided. - SigningKeys map[string]interface{} - - // Signing method used to check the token's signing algorithm. - // Optional. Default value HS256. - SigningMethod string - - // Context key to store user information from the token into context. - // Optional. Default value "user". - ContextKey string - - // Claims are extendable claims data defining token content. Used by default ParseTokenFunc implementation. - // Not used if custom ParseTokenFunc is set. - // Optional. Default value jwt.MapClaims - Claims jwt.Claims - - // TokenLookup is a string in the form of ":" or ":,:" that is used - // to extract token from the request. - // Optional. Default value "header:Authorization". - // Possible values: - // - "header:" or "header::" - // `` is argument value to cut/trim prefix of the extracted value. This is useful if header - // value has static prefix like `Authorization: ` where part that we - // want to cut is ` ` note the space at the end. - // In case of JWT tokens `Authorization: Bearer ` prefix we cut is `Bearer `. - // If prefix is left empty the whole value is returned. - // - "query:" - // - "param:" - // - "cookie:" - // - "form:" - // Multiple sources example: - // - "header:Authorization,cookie:myowncookie" - TokenLookup string - - // TokenLookupFuncs defines a list of user-defined functions that extract JWT token from the given context. - // This is one of the two options to provide a token extractor. - // The order of precedence is user-defined TokenLookupFuncs, and TokenLookup. - // You can also provide both if you want. - TokenLookupFuncs []ValuesExtractor - - // AuthScheme to be used in the Authorization header. - // Optional. Default value "Bearer". - AuthScheme string - - // KeyFunc defines a user-defined function that supplies the public key for a token validation. - // The function shall take care of verifying the signing algorithm and selecting the proper key. - // A user-defined KeyFunc can be useful if tokens are issued by an external party. - // Used by default ParseTokenFunc implementation. - // - // When a user-defined KeyFunc is provided, SigningKey, SigningKeys, and SigningMethod are ignored. - // This is one of the three options to provide a token validation key. - // The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey. - // Required if neither SigningKeys nor SigningKey is provided. - // Not used if custom ParseTokenFunc is set. - // Default to an internal implementation verifying the signing algorithm and selecting the proper key. - KeyFunc jwt.Keyfunc - - // ParseTokenFunc defines a user-defined function that parses token from given auth. Returns an error when token - // parsing fails or parsed token is invalid. - // Defaults to implementation using `github.com/golang-jwt/jwt` as JWT implementation library - ParseTokenFunc func(auth string, c echo.Context) (interface{}, error) - } +// JWTConfig defines the config for JWT middleware. +type JWTConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // BeforeFunc defines a function which is executed just before the middleware. + BeforeFunc BeforeFunc + + // SuccessHandler defines a function which is executed for a valid token before middleware chain continues with next + // middleware or handler. + SuccessHandler JWTSuccessHandler + + // ErrorHandler defines a function which is executed for an invalid token. + // It may be used to define a custom JWT error. + ErrorHandler JWTErrorHandler + + // ErrorHandlerWithContext is almost identical to ErrorHandler, but it's passed the current context. + ErrorHandlerWithContext JWTErrorHandlerWithContext + + // ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandlerWithContext decides to + // ignore the error (by returning `nil`). + // This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality. + // In that case you can use ErrorHandlerWithContext to set a default public JWT token value in the request context + // and continue. Some logic down the remaining execution chain needs to check that (public) token value then. + ContinueOnIgnoredError bool + + // Signing key to validate token. + // This is one of the three options to provide a token validation key. + // The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey. + // Required if neither user-defined KeyFunc nor SigningKeys is provided. + SigningKey interface{} + + // Map of signing keys to validate token with kid field usage. + // This is one of the three options to provide a token validation key. + // The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey. + // Required if neither user-defined KeyFunc nor SigningKey is provided. + SigningKeys map[string]interface{} + + // Signing method used to check the token's signing algorithm. + // Optional. Default value HS256. + SigningMethod string + + // Context key to store user information from the token into context. + // Optional. Default value "user". + ContextKey string + + // Claims are extendable claims data defining token content. Used by default ParseTokenFunc implementation. + // Not used if custom ParseTokenFunc is set. + // Optional. Default value jwt.MapClaims + Claims jwt.Claims + + // TokenLookup is a string in the form of ":" or ":,:" that is used + // to extract token from the request. + // Optional. Default value "header:Authorization". + // Possible values: + // - "header:" or "header::" + // `` is argument value to cut/trim prefix of the extracted value. This is useful if header + // value has static prefix like `Authorization: ` where part that we + // want to cut is ` ` note the space at the end. + // In case of JWT tokens `Authorization: Bearer ` prefix we cut is `Bearer `. + // If prefix is left empty the whole value is returned. + // - "query:" + // - "param:" + // - "cookie:" + // - "form:" + // Multiple sources example: + // - "header:Authorization,cookie:myowncookie" + TokenLookup string + + // TokenLookupFuncs defines a list of user-defined functions that extract JWT token from the given context. + // This is one of the two options to provide a token extractor. + // The order of precedence is user-defined TokenLookupFuncs, and TokenLookup. + // You can also provide both if you want. + TokenLookupFuncs []ValuesExtractor + + // AuthScheme to be used in the Authorization header. + // Optional. Default value "Bearer". + AuthScheme string + + // KeyFunc defines a user-defined function that supplies the public key for a token validation. + // The function shall take care of verifying the signing algorithm and selecting the proper key. + // A user-defined KeyFunc can be useful if tokens are issued by an external party. + // Used by default ParseTokenFunc implementation. + // + // When a user-defined KeyFunc is provided, SigningKey, SigningKeys, and SigningMethod are ignored. + // This is one of the three options to provide a token validation key. + // The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey. + // Required if neither SigningKeys nor SigningKey is provided. + // Not used if custom ParseTokenFunc is set. + // Default to an internal implementation verifying the signing algorithm and selecting the proper key. + KeyFunc jwt.Keyfunc + + // ParseTokenFunc defines a user-defined function that parses token from given auth. Returns an error when token + // parsing fails or parsed token is invalid. + // Defaults to implementation using `github.com/golang-jwt/jwt` as JWT implementation library + ParseTokenFunc func(auth string, c echo.Context) (interface{}, error) +} - // JWTSuccessHandler defines a function which is executed for a valid token. - JWTSuccessHandler func(c echo.Context) +// JWTSuccessHandler defines a function which is executed for a valid token. +type JWTSuccessHandler func(c echo.Context) - // JWTErrorHandler defines a function which is executed for an invalid token. - JWTErrorHandler func(err error) error +// JWTErrorHandler defines a function which is executed for an invalid token. +type JWTErrorHandler func(err error) error - // JWTErrorHandlerWithContext is almost identical to JWTErrorHandler, but it's passed the current context. - JWTErrorHandlerWithContext func(err error, c echo.Context) error -) +// JWTErrorHandlerWithContext is almost identical to JWTErrorHandler, but it's passed the current context. +type JWTErrorHandlerWithContext func(err error, c echo.Context) error // Algorithms const ( AlgorithmHS256 = "HS256" ) -// Errors -var ( - ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt") - ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "invalid or expired jwt") -) - -var ( - // DefaultJWTConfig is the default JWT auth middleware config. - DefaultJWTConfig = JWTConfig{ - Skipper: DefaultSkipper, - SigningMethod: AlgorithmHS256, - ContextKey: "user", - TokenLookup: "header:" + echo.HeaderAuthorization, - TokenLookupFuncs: nil, - AuthScheme: "Bearer", - Claims: jwt.MapClaims{}, - KeyFunc: nil, - } -) +// ErrJWTMissing is error that is returned when no JWToken was extracted from the request. +var ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt") + +// ErrJWTInvalid is error that is returned when middleware could not parse JWT correctly. +var ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "invalid or expired jwt") + +// DefaultJWTConfig is the default JWT auth middleware config. +var DefaultJWTConfig = JWTConfig{ + Skipper: DefaultSkipper, + SigningMethod: AlgorithmHS256, + ContextKey: "user", + TokenLookup: "header:" + echo.HeaderAuthorization, + TokenLookupFuncs: nil, + AuthScheme: "Bearer", + Claims: jwt.MapClaims{}, + KeyFunc: nil, +} // JWT returns a JSON Web Token (JWT) auth middleware. // diff --git a/middleware/key_auth.go b/middleware/key_auth.go index f7ce8c18a..79bee207c 100644 --- a/middleware/key_auth.go +++ b/middleware/key_auth.go @@ -9,69 +9,65 @@ import ( "net/http" ) -type ( - // KeyAuthConfig defines the config for KeyAuth middleware. - KeyAuthConfig struct { - // Skipper defines a function to skip middleware. - Skipper Skipper - - // KeyLookup is a string in the form of ":" or ":,:" that is used - // to extract key from the request. - // Optional. Default value "header:Authorization". - // Possible values: - // - "header:" or "header::" - // `` is argument value to cut/trim prefix of the extracted value. This is useful if header - // value has static prefix like `Authorization: ` where part that we - // want to cut is ` ` note the space at the end. - // In case of basic authentication `Authorization: Basic ` prefix we want to remove is `Basic `. - // - "query:" - // - "form:" - // - "cookie:" - // Multiple sources example: - // - "header:Authorization,header:X-Api-Key" - KeyLookup string - - // AuthScheme to be used in the Authorization header. - // Optional. Default value "Bearer". - AuthScheme string - - // Validator is a function to validate key. - // Required. - Validator KeyAuthValidator - - // ErrorHandler defines a function which is executed for an invalid key. - // It may be used to define a custom error. - ErrorHandler KeyAuthErrorHandler - - // ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandler decides to - // ignore the error (by returning `nil`). - // This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality. - // In that case you can use ErrorHandler to set a default public key auth value in the request context - // and continue. Some logic down the remaining execution chain needs to check that (public) key auth value then. - ContinueOnIgnoredError bool - } - - // KeyAuthValidator defines a function to validate KeyAuth credentials. - KeyAuthValidator func(auth string, c echo.Context) (bool, error) +// KeyAuthConfig defines the config for KeyAuth middleware. +type KeyAuthConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // KeyLookup is a string in the form of ":" or ":,:" that is used + // to extract key from the request. + // Optional. Default value "header:Authorization". + // Possible values: + // - "header:" or "header::" + // `` is argument value to cut/trim prefix of the extracted value. This is useful if header + // value has static prefix like `Authorization: ` where part that we + // want to cut is ` ` note the space at the end. + // In case of basic authentication `Authorization: Basic ` prefix we want to remove is `Basic `. + // - "query:" + // - "form:" + // - "cookie:" + // Multiple sources example: + // - "header:Authorization,header:X-Api-Key" + KeyLookup string + + // AuthScheme to be used in the Authorization header. + // Optional. Default value "Bearer". + AuthScheme string + + // Validator is a function to validate key. + // Required. + Validator KeyAuthValidator + + // ErrorHandler defines a function which is executed for an invalid key. + // It may be used to define a custom error. + ErrorHandler KeyAuthErrorHandler + + // ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandler decides to + // ignore the error (by returning `nil`). + // This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality. + // In that case you can use ErrorHandler to set a default public key auth value in the request context + // and continue. Some logic down the remaining execution chain needs to check that (public) key auth value then. + ContinueOnIgnoredError bool +} - // KeyAuthErrorHandler defines a function which is executed for an invalid key. - KeyAuthErrorHandler func(err error, c echo.Context) error -) +// KeyAuthValidator defines a function to validate KeyAuth credentials. +type KeyAuthValidator func(auth string, c echo.Context) (bool, error) -var ( - // DefaultKeyAuthConfig is the default KeyAuth middleware config. - DefaultKeyAuthConfig = KeyAuthConfig{ - Skipper: DefaultSkipper, - KeyLookup: "header:" + echo.HeaderAuthorization, - AuthScheme: "Bearer", - } -) +// KeyAuthErrorHandler defines a function which is executed for an invalid key. +type KeyAuthErrorHandler func(err error, c echo.Context) error // ErrKeyAuthMissing is error type when KeyAuth middleware is unable to extract value from lookups type ErrKeyAuthMissing struct { Err error } +// DefaultKeyAuthConfig is the default KeyAuth middleware config. +var DefaultKeyAuthConfig = KeyAuthConfig{ + Skipper: DefaultSkipper, + KeyLookup: "header:" + echo.HeaderAuthorization, + AuthScheme: "Bearer", +} + // Error returns errors text func (e *ErrKeyAuthMissing) Error() string { return e.Err.Error() diff --git a/middleware/logger.go b/middleware/logger.go index 43fd59ffc..910fce8cf 100644 --- a/middleware/logger.go +++ b/middleware/logger.go @@ -17,77 +17,73 @@ import ( "github.com/valyala/fasttemplate" ) -type ( - // LoggerConfig defines the config for Logger middleware. - LoggerConfig struct { - // Skipper defines a function to skip middleware. - Skipper Skipper - - // Tags to construct the logger format. - // - // - time_unix - // - time_unix_milli - // - time_unix_micro - // - time_unix_nano - // - time_rfc3339 - // - time_rfc3339_nano - // - time_custom - // - id (Request ID) - // - remote_ip - // - uri - // - host - // - method - // - path - // - route - // - protocol - // - referer - // - user_agent - // - status - // - error - // - latency (In nanoseconds) - // - latency_human (Human readable) - // - bytes_in (Bytes received) - // - bytes_out (Bytes sent) - // - header: - // - query: - // - form: - // - custom (see CustomTagFunc field) - // - // Example "${remote_ip} ${status}" - // - // Optional. Default value DefaultLoggerConfig.Format. - Format string `yaml:"format"` - - // Optional. Default value DefaultLoggerConfig.CustomTimeFormat. - CustomTimeFormat string `yaml:"custom_time_format"` - - // CustomTagFunc is function called for `${custom}` tag to output user implemented text by writing it to buf. - // Make sure that outputted text creates valid JSON string with other logged tags. - // Optional. - CustomTagFunc func(c echo.Context, buf *bytes.Buffer) (int, error) - - // Output is a writer where logs in JSON format are written. - // Optional. Default value os.Stdout. - Output io.Writer - - template *fasttemplate.Template - colorer *color.Color - pool *sync.Pool - } -) +// LoggerConfig defines the config for Logger middleware. +type LoggerConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Tags to construct the logger format. + // + // - time_unix + // - time_unix_milli + // - time_unix_micro + // - time_unix_nano + // - time_rfc3339 + // - time_rfc3339_nano + // - time_custom + // - id (Request ID) + // - remote_ip + // - uri + // - host + // - method + // - path + // - route + // - protocol + // - referer + // - user_agent + // - status + // - error + // - latency (In nanoseconds) + // - latency_human (Human readable) + // - bytes_in (Bytes received) + // - bytes_out (Bytes sent) + // - header: + // - query: + // - form: + // - custom (see CustomTagFunc field) + // + // Example "${remote_ip} ${status}" + // + // Optional. Default value DefaultLoggerConfig.Format. + Format string `yaml:"format"` + + // Optional. Default value DefaultLoggerConfig.CustomTimeFormat. + CustomTimeFormat string `yaml:"custom_time_format"` + + // CustomTagFunc is function called for `${custom}` tag to output user implemented text by writing it to buf. + // Make sure that outputted text creates valid JSON string with other logged tags. + // Optional. + CustomTagFunc func(c echo.Context, buf *bytes.Buffer) (int, error) + + // Output is a writer where logs in JSON format are written. + // Optional. Default value os.Stdout. + Output io.Writer + + template *fasttemplate.Template + colorer *color.Color + pool *sync.Pool +} -var ( - // DefaultLoggerConfig is the default Logger middleware config. - DefaultLoggerConfig = LoggerConfig{ - Skipper: DefaultSkipper, - Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` + - `"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` + - `"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` + - `,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n", - CustomTimeFormat: "2006-01-02 15:04:05.00000", - colorer: color.New(), - } -) +// DefaultLoggerConfig is the default Logger middleware config. +var DefaultLoggerConfig = LoggerConfig{ + Skipper: DefaultSkipper, + Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` + + `"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` + + `"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` + + `,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n", + CustomTimeFormat: "2006-01-02 15:04:05.00000", + colorer: color.New(), +} // Logger returns a middleware that logs HTTP requests. func Logger() echo.MiddlewareFunc { diff --git a/middleware/method_override.go b/middleware/method_override.go index 668a57a41..3991e1029 100644 --- a/middleware/method_override.go +++ b/middleware/method_override.go @@ -9,28 +9,24 @@ import ( "github.com/labstack/echo/v4" ) -type ( - // MethodOverrideConfig defines the config for MethodOverride middleware. - MethodOverrideConfig struct { - // Skipper defines a function to skip middleware. - Skipper Skipper +// MethodOverrideConfig defines the config for MethodOverride middleware. +type MethodOverrideConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper - // Getter is a function that gets overridden method from the request. - // Optional. Default values MethodFromHeader(echo.HeaderXHTTPMethodOverride). - Getter MethodOverrideGetter - } + // Getter is a function that gets overridden method from the request. + // Optional. Default values MethodFromHeader(echo.HeaderXHTTPMethodOverride). + Getter MethodOverrideGetter +} - // MethodOverrideGetter is a function that gets overridden method from the request - MethodOverrideGetter func(echo.Context) string -) +// MethodOverrideGetter is a function that gets overridden method from the request +type MethodOverrideGetter func(echo.Context) string -var ( - // DefaultMethodOverrideConfig is the default MethodOverride middleware config. - DefaultMethodOverrideConfig = MethodOverrideConfig{ - Skipper: DefaultSkipper, - Getter: MethodFromHeader(echo.HeaderXHTTPMethodOverride), - } -) +// DefaultMethodOverrideConfig is the default MethodOverride middleware config. +var DefaultMethodOverrideConfig = MethodOverrideConfig{ + Skipper: DefaultSkipper, + Getter: MethodFromHeader(echo.HeaderXHTTPMethodOverride), +} // MethodOverride returns a MethodOverride middleware. // MethodOverride middleware checks for the overridden method from the request and diff --git a/middleware/middleware.go b/middleware/middleware.go index 8dfb8dda6..6f33cc5c1 100644 --- a/middleware/middleware.go +++ b/middleware/middleware.go @@ -12,14 +12,12 @@ import ( "github.com/labstack/echo/v4" ) -type ( - // Skipper defines a function to skip middleware. Returning true skips processing - // the middleware. - Skipper func(c echo.Context) bool +// Skipper defines a function to skip middleware. Returning true skips processing +// the middleware. +type Skipper func(c echo.Context) bool - // BeforeFunc defines a function which is executed just before the middleware. - BeforeFunc func(c echo.Context) -) +// BeforeFunc defines a function which is executed just before the middleware. +type BeforeFunc func(c echo.Context) func captureTokens(pattern *regexp.Regexp, input string) *strings.Replacer { groups := pattern.FindAllStringSubmatch(input, -1) @@ -56,7 +54,7 @@ func rewriteURL(rewriteRegex map[*regexp.Regexp]string, req *http.Request) error return nil } - // Depending how HTTP request is sent RequestURI could contain Scheme://Host/path or be just /path. + // Depending on how HTTP request is sent RequestURI could contain Scheme://Host/path or be just /path. // We only want to use path part for rewriting and therefore trim prefix if it exists rawURI := req.RequestURI if rawURI != "" && rawURI[0] != '/' { diff --git a/middleware/proxy.go b/middleware/proxy.go index ddf4b7f06..f6b302af1 100644 --- a/middleware/proxy.go +++ b/middleware/proxy.go @@ -22,117 +22,113 @@ import ( // TODO: Handle TLS proxy -type ( - // ProxyConfig defines the config for Proxy middleware. - ProxyConfig struct { - // Skipper defines a function to skip middleware. - Skipper Skipper - - // Balancer defines a load balancing technique. - // Required. - Balancer ProxyBalancer - - // RetryCount defines the number of times a failed proxied request should be retried - // using the next available ProxyTarget. Defaults to 0, meaning requests are never retried. - RetryCount int - - // RetryFilter defines a function used to determine if a failed request to a - // ProxyTarget should be retried. The RetryFilter will only be called when the number - // of previous retries is less than RetryCount. If the function returns true, the - // request will be retried. The provided error indicates the reason for the request - // failure. When the ProxyTarget is unavailable, the error will be an instance of - // echo.HTTPError with a Code of http.StatusBadGateway. In all other cases, the error - // will indicate an internal error in the Proxy middleware. When a RetryFilter is not - // specified, all requests that fail with http.StatusBadGateway will be retried. A custom - // RetryFilter can be provided to only retry specific requests. Note that RetryFilter is - // only called when the request to the target fails, or an internal error in the Proxy - // middleware has occurred. Successful requests that return a non-200 response code cannot - // be retried. - RetryFilter func(c echo.Context, e error) bool - - // ErrorHandler defines a function which can be used to return custom errors from - // the Proxy middleware. ErrorHandler is only invoked when there has been - // either an internal error in the Proxy middleware or the ProxyTarget is - // unavailable. Due to the way requests are proxied, ErrorHandler is not invoked - // when a ProxyTarget returns a non-200 response. In these cases, the response - // is already written so errors cannot be modified. ErrorHandler is only - // invoked after all retry attempts have been exhausted. - ErrorHandler func(c echo.Context, err error) error - - // Rewrite defines URL path rewrite rules. The values captured in asterisk can be - // retrieved by index e.g. $1, $2 and so on. - // Examples: - // "/old": "/new", - // "/api/*": "/$1", - // "/js/*": "/public/javascripts/$1", - // "/users/*/orders/*": "/user/$1/order/$2", - Rewrite map[string]string - - // RegexRewrite defines rewrite rules using regexp.Rexexp with captures - // Every capture group in the values can be retrieved by index e.g. $1, $2 and so on. - // Example: - // "^/old/[0.9]+/": "/new", - // "^/api/.+?/(.*)": "/v2/$1", - RegexRewrite map[*regexp.Regexp]string - - // Context key to store selected ProxyTarget into context. - // Optional. Default value "target". - ContextKey string - - // To customize the transport to remote. - // Examples: If custom TLS certificates are required. - Transport http.RoundTripper - - // ModifyResponse defines function to modify response from ProxyTarget. - ModifyResponse func(*http.Response) error - } +// ProxyConfig defines the config for Proxy middleware. +type ProxyConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Balancer defines a load balancing technique. + // Required. + Balancer ProxyBalancer + + // RetryCount defines the number of times a failed proxied request should be retried + // using the next available ProxyTarget. Defaults to 0, meaning requests are never retried. + RetryCount int + + // RetryFilter defines a function used to determine if a failed request to a + // ProxyTarget should be retried. The RetryFilter will only be called when the number + // of previous retries is less than RetryCount. If the function returns true, the + // request will be retried. The provided error indicates the reason for the request + // failure. When the ProxyTarget is unavailable, the error will be an instance of + // echo.HTTPError with a Code of http.StatusBadGateway. In all other cases, the error + // will indicate an internal error in the Proxy middleware. When a RetryFilter is not + // specified, all requests that fail with http.StatusBadGateway will be retried. A custom + // RetryFilter can be provided to only retry specific requests. Note that RetryFilter is + // only called when the request to the target fails, or an internal error in the Proxy + // middleware has occurred. Successful requests that return a non-200 response code cannot + // be retried. + RetryFilter func(c echo.Context, e error) bool + + // ErrorHandler defines a function which can be used to return custom errors from + // the Proxy middleware. ErrorHandler is only invoked when there has been + // either an internal error in the Proxy middleware or the ProxyTarget is + // unavailable. Due to the way requests are proxied, ErrorHandler is not invoked + // when a ProxyTarget returns a non-200 response. In these cases, the response + // is already written so errors cannot be modified. ErrorHandler is only + // invoked after all retry attempts have been exhausted. + ErrorHandler func(c echo.Context, err error) error + + // Rewrite defines URL path rewrite rules. The values captured in asterisk can be + // retrieved by index e.g. $1, $2 and so on. + // Examples: + // "/old": "/new", + // "/api/*": "/$1", + // "/js/*": "/public/javascripts/$1", + // "/users/*/orders/*": "/user/$1/order/$2", + Rewrite map[string]string + + // RegexRewrite defines rewrite rules using regexp.Rexexp with captures + // Every capture group in the values can be retrieved by index e.g. $1, $2 and so on. + // Example: + // "^/old/[0.9]+/": "/new", + // "^/api/.+?/(.*)": "/v2/$1", + RegexRewrite map[*regexp.Regexp]string + + // Context key to store selected ProxyTarget into context. + // Optional. Default value "target". + ContextKey string + + // To customize the transport to remote. + // Examples: If custom TLS certificates are required. + Transport http.RoundTripper + + // ModifyResponse defines function to modify response from ProxyTarget. + ModifyResponse func(*http.Response) error +} - // ProxyTarget defines the upstream target. - ProxyTarget struct { - Name string - URL *url.URL - Meta echo.Map - } +// ProxyTarget defines the upstream target. +type ProxyTarget struct { + Name string + URL *url.URL + Meta echo.Map +} - // ProxyBalancer defines an interface to implement a load balancing technique. - ProxyBalancer interface { - AddTarget(*ProxyTarget) bool - RemoveTarget(string) bool - Next(echo.Context) *ProxyTarget - } +// ProxyBalancer defines an interface to implement a load balancing technique. +type ProxyBalancer interface { + AddTarget(*ProxyTarget) bool + RemoveTarget(string) bool + Next(echo.Context) *ProxyTarget +} - // TargetProvider defines an interface that gives the opportunity for balancer - // to return custom errors when selecting target. - TargetProvider interface { - NextTarget(echo.Context) (*ProxyTarget, error) - } +// TargetProvider defines an interface that gives the opportunity for balancer +// to return custom errors when selecting target. +type TargetProvider interface { + NextTarget(echo.Context) (*ProxyTarget, error) +} - commonBalancer struct { - targets []*ProxyTarget - mutex sync.Mutex - } +type commonBalancer struct { + targets []*ProxyTarget + mutex sync.Mutex +} - // RandomBalancer implements a random load balancing technique. - randomBalancer struct { - commonBalancer - random *rand.Rand - } +// RandomBalancer implements a random load balancing technique. +type randomBalancer struct { + commonBalancer + random *rand.Rand +} - // RoundRobinBalancer implements a round-robin load balancing technique. - roundRobinBalancer struct { - commonBalancer - // tracking the index on `targets` slice for the next `*ProxyTarget` to be used - i int - } -) +// RoundRobinBalancer implements a round-robin load balancing technique. +type roundRobinBalancer struct { + commonBalancer + // tracking the index on `targets` slice for the next `*ProxyTarget` to be used + i int +} -var ( - // DefaultProxyConfig is the default Proxy middleware config. - DefaultProxyConfig = ProxyConfig{ - Skipper: DefaultSkipper, - ContextKey: "target", - } -) +// DefaultProxyConfig is the default Proxy middleware config. +var DefaultProxyConfig = ProxyConfig{ + Skipper: DefaultSkipper, + ContextKey: "target", +} func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/middleware/rate_limiter.go b/middleware/rate_limiter.go index a58b16491..d4724fd2a 100644 --- a/middleware/rate_limiter.go +++ b/middleware/rate_limiter.go @@ -12,39 +12,34 @@ import ( "golang.org/x/time/rate" ) -type ( - // RateLimiterStore is the interface to be implemented by custom stores. - RateLimiterStore interface { - // Stores for the rate limiter have to implement the Allow method - Allow(identifier string) (bool, error) - } -) +// RateLimiterStore is the interface to be implemented by custom stores. +type RateLimiterStore interface { + // Stores for the rate limiter have to implement the Allow method + Allow(identifier string) (bool, error) +} -type ( - // RateLimiterConfig defines the configuration for the rate limiter - RateLimiterConfig struct { - Skipper Skipper - BeforeFunc BeforeFunc - // IdentifierExtractor uses echo.Context to extract the identifier for a visitor - IdentifierExtractor Extractor - // Store defines a store for the rate limiter - Store RateLimiterStore - // ErrorHandler provides a handler to be called when IdentifierExtractor returns an error - ErrorHandler func(context echo.Context, err error) error - // DenyHandler provides a handler to be called when RateLimiter denies access - DenyHandler func(context echo.Context, identifier string, err error) error - } - // Extractor is used to extract data from echo.Context - Extractor func(context echo.Context) (string, error) -) +// RateLimiterConfig defines the configuration for the rate limiter +type RateLimiterConfig struct { + Skipper Skipper + BeforeFunc BeforeFunc + // IdentifierExtractor uses echo.Context to extract the identifier for a visitor + IdentifierExtractor Extractor + // Store defines a store for the rate limiter + Store RateLimiterStore + // ErrorHandler provides a handler to be called when IdentifierExtractor returns an error + ErrorHandler func(context echo.Context, err error) error + // DenyHandler provides a handler to be called when RateLimiter denies access + DenyHandler func(context echo.Context, identifier string, err error) error +} -// errors -var ( - // ErrRateLimitExceeded denotes an error raised when rate limit is exceeded - ErrRateLimitExceeded = echo.NewHTTPError(http.StatusTooManyRequests, "rate limit exceeded") - // ErrExtractorError denotes an error raised when extractor function is unsuccessful - ErrExtractorError = echo.NewHTTPError(http.StatusForbidden, "error while extracting identifier") -) +// Extractor is used to extract data from echo.Context +type Extractor func(context echo.Context) (string, error) + +// ErrRateLimitExceeded denotes an error raised when rate limit is exceeded +var ErrRateLimitExceeded = echo.NewHTTPError(http.StatusTooManyRequests, "rate limit exceeded") + +// ErrExtractorError denotes an error raised when extractor function is unsuccessful +var ErrExtractorError = echo.NewHTTPError(http.StatusForbidden, "error while extracting identifier") // DefaultRateLimiterConfig defines default values for RateLimiterConfig var DefaultRateLimiterConfig = RateLimiterConfig{ @@ -153,25 +148,24 @@ func RateLimiterWithConfig(config RateLimiterConfig) echo.MiddlewareFunc { } } -type ( - // RateLimiterMemoryStore is the built-in store implementation for RateLimiter - RateLimiterMemoryStore struct { - visitors map[string]*Visitor - mutex sync.Mutex - rate rate.Limit // for more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit. +// RateLimiterMemoryStore is the built-in store implementation for RateLimiter +type RateLimiterMemoryStore struct { + visitors map[string]*Visitor + mutex sync.Mutex + rate rate.Limit // for more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit. - burst int - expiresIn time.Duration - lastCleanup time.Time + burst int + expiresIn time.Duration + lastCleanup time.Time - timeNow func() time.Time - } - // Visitor signifies a unique user's limiter details - Visitor struct { - *rate.Limiter - lastSeen time.Time - } -) + timeNow func() time.Time +} + +// Visitor signifies a unique user's limiter details +type Visitor struct { + *rate.Limiter + lastSeen time.Time +} /* NewRateLimiterMemoryStore returns an instance of RateLimiterMemoryStore with diff --git a/middleware/recover.go b/middleware/recover.go index 35f38e72c..e6a5940e4 100644 --- a/middleware/recover.go +++ b/middleware/recover.go @@ -12,56 +12,52 @@ import ( "github.com/labstack/gommon/log" ) -type ( +// LogErrorFunc defines a function for custom logging in the middleware. +type LogErrorFunc func(c echo.Context, err error, stack []byte) error + +// RecoverConfig defines the config for Recover middleware. +type RecoverConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Size of the stack to be printed. + // Optional. Default value 4KB. + StackSize int `yaml:"stack_size"` + + // DisableStackAll disables formatting stack traces of all other goroutines + // into buffer after the trace for the current goroutine. + // Optional. Default value false. + DisableStackAll bool `yaml:"disable_stack_all"` + + // DisablePrintStack disables printing stack trace. + // Optional. Default value as false. + DisablePrintStack bool `yaml:"disable_print_stack"` + + // LogLevel is log level to printing stack trace. + // Optional. Default value 0 (Print). + LogLevel log.Lvl + // LogErrorFunc defines a function for custom logging in the middleware. - LogErrorFunc func(c echo.Context, err error, stack []byte) error - - // RecoverConfig defines the config for Recover middleware. - RecoverConfig struct { - // Skipper defines a function to skip middleware. - Skipper Skipper - - // Size of the stack to be printed. - // Optional. Default value 4KB. - StackSize int `yaml:"stack_size"` - - // DisableStackAll disables formatting stack traces of all other goroutines - // into buffer after the trace for the current goroutine. - // Optional. Default value false. - DisableStackAll bool `yaml:"disable_stack_all"` - - // DisablePrintStack disables printing stack trace. - // Optional. Default value as false. - DisablePrintStack bool `yaml:"disable_print_stack"` - - // LogLevel is log level to printing stack trace. - // Optional. Default value 0 (Print). - LogLevel log.Lvl - - // LogErrorFunc defines a function for custom logging in the middleware. - // If it's set you don't need to provide LogLevel for config. - // If this function returns nil, the centralized HTTPErrorHandler will not be called. - LogErrorFunc LogErrorFunc - - // DisableErrorHandler disables the call to centralized HTTPErrorHandler. - // The recovered error is then passed back to upstream middleware, instead of swallowing the error. - // Optional. Default value false. - DisableErrorHandler bool `yaml:"disable_error_handler"` - } -) + // If it's set you don't need to provide LogLevel for config. + // If this function returns nil, the centralized HTTPErrorHandler will not be called. + LogErrorFunc LogErrorFunc + + // DisableErrorHandler disables the call to centralized HTTPErrorHandler. + // The recovered error is then passed back to upstream middleware, instead of swallowing the error. + // Optional. Default value false. + DisableErrorHandler bool `yaml:"disable_error_handler"` +} -var ( - // DefaultRecoverConfig is the default Recover middleware config. - DefaultRecoverConfig = RecoverConfig{ - Skipper: DefaultSkipper, - StackSize: 4 << 10, // 4 KB - DisableStackAll: false, - DisablePrintStack: false, - LogLevel: 0, - LogErrorFunc: nil, - DisableErrorHandler: false, - } -) +// DefaultRecoverConfig is the default Recover middleware config. +var DefaultRecoverConfig = RecoverConfig{ + Skipper: DefaultSkipper, + StackSize: 4 << 10, // 4 KB + DisableStackAll: false, + DisablePrintStack: false, + LogLevel: 0, + LogErrorFunc: nil, + DisableErrorHandler: false, +} // Recover returns a middleware which recovers from panics anywhere in the chain // and handles the control to the centralized HTTPErrorHandler. diff --git a/middleware/request_id.go b/middleware/request_id.go index 411737cb4..14bd4fd15 100644 --- a/middleware/request_id.go +++ b/middleware/request_id.go @@ -7,32 +7,28 @@ import ( "github.com/labstack/echo/v4" ) -type ( - // RequestIDConfig defines the config for RequestID middleware. - RequestIDConfig struct { - // Skipper defines a function to skip middleware. - Skipper Skipper +// RequestIDConfig defines the config for RequestID middleware. +type RequestIDConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper - // Generator defines a function to generate an ID. - // Optional. Defaults to generator for random string of length 32. - Generator func() string + // Generator defines a function to generate an ID. + // Optional. Defaults to generator for random string of length 32. + Generator func() string - // RequestIDHandler defines a function which is executed for a request id. - RequestIDHandler func(echo.Context, string) + // RequestIDHandler defines a function which is executed for a request id. + RequestIDHandler func(echo.Context, string) - // TargetHeader defines what header to look for to populate the id - TargetHeader string - } -) + // TargetHeader defines what header to look for to populate the id + TargetHeader string +} -var ( - // DefaultRequestIDConfig is the default RequestID middleware config. - DefaultRequestIDConfig = RequestIDConfig{ - Skipper: DefaultSkipper, - Generator: generator, - TargetHeader: echo.HeaderXRequestID, - } -) +// DefaultRequestIDConfig is the default RequestID middleware config. +var DefaultRequestIDConfig = RequestIDConfig{ + Skipper: DefaultSkipper, + Generator: generator, + TargetHeader: echo.HeaderXRequestID, +} // RequestID returns a X-Request-ID middleware. func RequestID() echo.MiddlewareFunc { diff --git a/middleware/rewrite.go b/middleware/rewrite.go index 260dbb1f5..4c19cc1cc 100644 --- a/middleware/rewrite.go +++ b/middleware/rewrite.go @@ -9,37 +9,33 @@ import ( "github.com/labstack/echo/v4" ) -type ( - // RewriteConfig defines the config for Rewrite middleware. - RewriteConfig struct { - // Skipper defines a function to skip middleware. - Skipper Skipper +// RewriteConfig defines the config for Rewrite middleware. +type RewriteConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper - // Rules defines the URL path rewrite rules. The values captured in asterisk can be - // retrieved by index e.g. $1, $2 and so on. - // Example: - // "/old": "/new", - // "/api/*": "/$1", - // "/js/*": "/public/javascripts/$1", - // "/users/*/orders/*": "/user/$1/order/$2", - // Required. - Rules map[string]string `yaml:"rules"` + // Rules defines the URL path rewrite rules. The values captured in asterisk can be + // retrieved by index e.g. $1, $2 and so on. + // Example: + // "/old": "/new", + // "/api/*": "/$1", + // "/js/*": "/public/javascripts/$1", + // "/users/*/orders/*": "/user/$1/order/$2", + // Required. + Rules map[string]string `yaml:"rules"` - // RegexRules defines the URL path rewrite rules using regexp.Rexexp with captures - // Every capture group in the values can be retrieved by index e.g. $1, $2 and so on. - // Example: - // "^/old/[0.9]+/": "/new", - // "^/api/.+?/(.*)": "/v2/$1", - RegexRules map[*regexp.Regexp]string `yaml:"-"` - } -) + // RegexRules defines the URL path rewrite rules using regexp.Rexexp with captures + // Every capture group in the values can be retrieved by index e.g. $1, $2 and so on. + // Example: + // "^/old/[0.9]+/": "/new", + // "^/api/.+?/(.*)": "/v2/$1", + RegexRules map[*regexp.Regexp]string `yaml:"-"` +} -var ( - // DefaultRewriteConfig is the default Rewrite middleware config. - DefaultRewriteConfig = RewriteConfig{ - Skipper: DefaultSkipper, - } -) +// DefaultRewriteConfig is the default Rewrite middleware config. +var DefaultRewriteConfig = RewriteConfig{ + Skipper: DefaultSkipper, +} // Rewrite returns a Rewrite middleware. // diff --git a/middleware/secure.go b/middleware/secure.go index b70854ddc..c904abf1a 100644 --- a/middleware/secure.go +++ b/middleware/secure.go @@ -9,84 +9,80 @@ import ( "github.com/labstack/echo/v4" ) -type ( - // SecureConfig defines the config for Secure middleware. - SecureConfig struct { - // Skipper defines a function to skip middleware. - Skipper Skipper - - // XSSProtection provides protection against cross-site scripting attack (XSS) - // by setting the `X-XSS-Protection` header. - // Optional. Default value "1; mode=block". - XSSProtection string `yaml:"xss_protection"` - - // ContentTypeNosniff provides protection against overriding Content-Type - // header by setting the `X-Content-Type-Options` header. - // Optional. Default value "nosniff". - ContentTypeNosniff string `yaml:"content_type_nosniff"` - - // XFrameOptions can be used to indicate whether or not a browser should - // be allowed to render a page in a ,