Skip to content

Commit 7846e3f

Browse files
authored
feat: Bind data using headers as source (#1866)
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. * Add docs for BindHeaders * Add test for BindHeader with invalid data type
1 parent 2acb24a commit 7846e3f

File tree

3 files changed

+42
-3
lines changed

3 files changed

+42
-3
lines changed

bind.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
9797
return nil
9898
}
9999

100+
// BindHeaders binds HTTP headers to a bindable object
101+
func (b *DefaultBinder) BindHeaders(c Context, i interface{}) error {
102+
if err := b.bindData(i, c.Request().Header, "header"); err != nil {
103+
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
104+
}
105+
return nil
106+
}
107+
100108
// Bind implements the `Binder#Bind` function.
101109
// Binding is done in following order: 1) path params; 2) query params; 3) request body. Each step COULD override previous
102110
// step binded values. For single source binding use their own methods BindBody, BindQueryParams, BindPathParams.
@@ -134,7 +142,7 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
134142

135143
// !struct
136144
if typ.Kind() != reflect.Struct {
137-
if tag == "param" || tag == "query" {
145+
if tag == "param" || tag == "query" || tag == "header" {
138146
// incompatible type, data is probably to be found in the body
139147
return nil
140148
}

bind_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,37 @@ func TestBindQueryParamsCaseSensitivePrioritized(t *testing.T) {
269269
}
270270
}
271271

272+
func TestBindHeaderParam(t *testing.T) {
273+
e := New()
274+
req := httptest.NewRequest(http.MethodGet, "/", nil)
275+
req.Header.Set("Name", "Jon Doe")
276+
req.Header.Set("Id", "2")
277+
rec := httptest.NewRecorder()
278+
c := e.NewContext(req, rec)
279+
u := new(user)
280+
err := (&DefaultBinder{}).BindHeaders(c, u)
281+
if assert.NoError(t, err) {
282+
assert.Equal(t, 2, u.ID)
283+
assert.Equal(t, "Jon Doe", u.Name)
284+
}
285+
}
286+
287+
func TestBindHeaderParamBadType(t *testing.T) {
288+
e := New()
289+
req := httptest.NewRequest(http.MethodGet, "/", nil)
290+
req.Header.Set("Id", "salamander")
291+
rec := httptest.NewRecorder()
292+
c := e.NewContext(req, rec)
293+
u := new(user)
294+
err := (&DefaultBinder{}).BindHeaders(c, u)
295+
assert.Error(t, err)
296+
297+
httpErr, ok := err.(*HTTPError)
298+
if assert.True(t, ok) {
299+
assert.Equal(t, http.StatusBadRequest, httpErr.Code)
300+
}
301+
}
302+
272303
func TestBindUnmarshalParam(t *testing.T) {
273304
e := New()
274305
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)

echo_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ import (
2424

2525
type (
2626
user struct {
27-
ID int `json:"id" xml:"id" form:"id" query:"id" param:"id"`
28-
Name string `json:"name" xml:"name" form:"name" query:"name" param:"name"`
27+
ID int `json:"id" xml:"id" form:"id" query:"id" param:"id" header:"id"`
28+
Name string `json:"name" xml:"name" form:"name" query:"name" param:"name" header:"name"`
2929
}
3030
)
3131

0 commit comments

Comments
 (0)