From 962daf0a1c92fd90365821c2bb7176bc22f8cf43 Mon Sep 17 00:00:00 2001 From: Alexander Pochill Date: Thu, 6 May 2021 13:29:02 +0200 Subject: [PATCH 1/3] feat: Bind data using headers as source Currently, echo supports binding data from query, path or body. Sometimes we need to read bind data from headers. It would be nice to automatically bind those using the `bindData` func, which is already well prepared to accept `http.Header`. I didn't add this to the `Bind` func, so this will not happen automatically. Main reason is backwards compatability. It might be confusing if variables are bound from headers when upgrading, and might even have become a security issue as pointed out in #1670. --- bind.go | 9 ++++++++- bind_test.go | 15 +++++++++++++++ echo_test.go | 4 ++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/bind.go b/bind.go index 08d398916..3e860f429 100644 --- a/bind.go +++ b/bind.go @@ -97,6 +97,13 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) { return nil } +func (b *DefaultBinder) BindHeaders(c Context, i interface{}) error { + if err := b.bindData(i, c.Request().Header, "header"); err != nil { + return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) + } + return nil +} + // Bind implements the `Binder#Bind` function. // Binding is done in following order: 1) path params; 2) query params; 3) request body. Each step COULD override previous // step binded values. For single source binding use their own methods BindBody, BindQueryParams, BindPathParams. @@ -134,7 +141,7 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri // !struct if typ.Kind() != reflect.Struct { - if tag == "param" || tag == "query" { + if tag == "param" || tag == "query" || tag == "header" { // incompatible type, data is probably to be found in the body return nil } diff --git a/bind_test.go b/bind_test.go index 73398034e..63eb5ab85 100644 --- a/bind_test.go +++ b/bind_test.go @@ -266,6 +266,21 @@ func TestBindQueryParamsCaseSensitivePrioritized(t *testing.T) { } } +func TestBindHeaderParam(t *testing.T) { + e := New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set("Name", "Jon Doe") + req.Header.Set("Id", "2") + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + u := new(user) + err := (&DefaultBinder{}).BindHeaders(c, u) + if assert.NoError(t, err) { + assert.Equal(t, 2, u.ID) + assert.Equal(t, "Jon Doe", u.Name) + } +} + func TestBindUnmarshalParam(t *testing.T) { e := New() req := httptest.NewRequest(http.MethodGet, "/?ts=2016-12-06T19:09:05Z&sa=one,two,three&ta=2016-12-06T19:09:05Z&ta=2016-12-06T19:09:05Z&ST=baz", nil) diff --git a/echo_test.go b/echo_test.go index ba498831b..e5bd371dd 100644 --- a/echo_test.go +++ b/echo_test.go @@ -24,8 +24,8 @@ import ( type ( user struct { - ID int `json:"id" xml:"id" form:"id" query:"id" param:"id"` - Name string `json:"name" xml:"name" form:"name" query:"name" param:"name"` + 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"` } ) From e30a3536ea3ce553b97e6546549a4ecee6c6d93a Mon Sep 17 00:00:00 2001 From: Alexander Pochill Date: Sun, 9 May 2021 09:02:59 +0200 Subject: [PATCH 2/3] Add docs for BindHeaders --- bind.go | 1 + 1 file changed, 1 insertion(+) diff --git a/bind.go b/bind.go index 3e860f429..2caf91c8b 100644 --- a/bind.go +++ b/bind.go @@ -97,6 +97,7 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) { return nil } +// BindHeaders binds HTTP headers to a bindable object func (b *DefaultBinder) BindHeaders(c Context, i interface{}) error { if err := b.bindData(i, c.Request().Header, "header"); err != nil { return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) From 11d6bf8cabd3ba2fc954955c12d9b2be6aa8912a Mon Sep 17 00:00:00 2001 From: Alexander Pochill Date: Thu, 13 May 2021 11:27:51 +0200 Subject: [PATCH 3/3] Add test for BindHeader with invalid data type --- bind_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/bind_test.go b/bind_test.go index 63eb5ab85..b78b0abc5 100644 --- a/bind_test.go +++ b/bind_test.go @@ -281,6 +281,22 @@ func TestBindHeaderParam(t *testing.T) { } } +func TestBindHeaderParamBadType(t *testing.T) { + e := New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set("Id", "salamander") + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + u := new(user) + err := (&DefaultBinder{}).BindHeaders(c, u) + assert.Error(t, err) + + httpErr, ok := err.(*HTTPError) + if assert.True(t, ok) { + assert.Equal(t, http.StatusBadRequest, httpErr.Code) + } +} + func TestBindUnmarshalParam(t *testing.T) { e := New() req := httptest.NewRequest(http.MethodGet, "/?ts=2016-12-06T19:09:05Z&sa=one,two,three&ta=2016-12-06T19:09:05Z&ta=2016-12-06T19:09:05Z&ST=baz", nil)