1
0
mirror of https://github.com/labstack/echo.git synced 2025-01-24 03:16:14 +02:00

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
This commit is contained in:
Alexander Pochill 2021-05-25 14:50:49 +02:00 committed by GitHub
parent 2acb24adb0
commit 7846e3fa6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 42 additions and 3 deletions

10
bind.go
View File

@ -97,6 +97,14 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
return nil 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)
}
return nil
}
// Bind implements the `Binder#Bind` function. // 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 // 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. // 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
// !struct // !struct
if typ.Kind() != reflect.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 // incompatible type, data is probably to be found in the body
return nil return nil
} }

View File

@ -269,6 +269,37 @@ 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 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) { func TestBindUnmarshalParam(t *testing.T) {
e := New() 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) 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)

View File

@ -24,8 +24,8 @@ import (
type ( type (
user struct { user struct {
ID int `json:"id" xml:"id" form:"id" query:"id" param:"id"` 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"` Name string `json:"name" xml:"name" form:"name" query:"name" param:"name" header:"name"`
} }
) )