@@ -46,15 +46,15 @@ func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error {
46
46
for i , name := range names {
47
47
params [name ] = []string {values [i ]}
48
48
}
49
- if err := b .bindData (i , params , "param" ); err != nil {
49
+ if err := b .bindData (i , params , "param" , nil ); err != nil {
50
50
return NewHTTPError (http .StatusBadRequest , err .Error ()).SetInternal (err )
51
51
}
52
52
return nil
53
53
}
54
54
55
55
// BindQueryParams binds query params to bindable object
56
56
func (b * DefaultBinder ) BindQueryParams (c Context , i interface {}) error {
57
- if err := b .bindData (i , c .QueryParams (), "query" ); err != nil {
57
+ if err := b .bindData (i , c .QueryParams (), "query" , nil ); err != nil {
58
58
return NewHTTPError (http .StatusBadRequest , err .Error ()).SetInternal (err )
59
59
}
60
60
return nil
@@ -71,9 +71,12 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
71
71
return
72
72
}
73
73
74
- ctype := req .Header .Get (HeaderContentType )
75
- switch {
76
- case strings .HasPrefix (ctype , MIMEApplicationJSON ):
74
+ // mediatype is found like `mime.ParseMediaType()` does it
75
+ base , _ , _ := strings .Cut (req .Header .Get (HeaderContentType ), ";" )
76
+ mediatype := strings .TrimSpace (base )
77
+
78
+ switch mediatype {
79
+ case MIMEApplicationJSON :
77
80
if err = c .Echo ().JSONSerializer .Deserialize (c , i ); err != nil {
78
81
switch err .(type ) {
79
82
case * HTTPError :
@@ -82,7 +85,7 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
82
85
return NewHTTPError (http .StatusBadRequest , err .Error ()).SetInternal (err )
83
86
}
84
87
}
85
- case strings . HasPrefix ( ctype , MIMEApplicationXML ), strings . HasPrefix ( ctype , MIMETextXML ) :
88
+ case MIMEApplicationXML , MIMETextXML :
86
89
if err = xml .NewDecoder (req .Body ).Decode (i ); err != nil {
87
90
if ute , ok := err .(* xml.UnsupportedTypeError ); ok {
88
91
return NewHTTPError (http .StatusBadRequest , fmt .Sprintf ("Unsupported type error: type=%v, error=%v" , ute .Type , ute .Error ())).SetInternal (err )
@@ -91,15 +94,15 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
91
94
}
92
95
return NewHTTPError (http .StatusBadRequest , err .Error ()).SetInternal (err )
93
96
}
94
- case strings . HasPrefix ( ctype , MIMEApplicationForm ) :
97
+ case MIMEApplicationForm :
95
98
params , err := c .FormParams ()
96
99
if err != nil {
97
100
return NewHTTPError (http .StatusBadRequest , err .Error ()).SetInternal (err )
98
101
}
99
- if err = b .bindData (i , params , "form" ); err != nil {
102
+ if err = b .bindData (i , params , "form" , nil ); err != nil {
100
103
return NewHTTPError (http .StatusBadRequest , err .Error ()).SetInternal (err )
101
104
}
102
- case strings . HasPrefix ( ctype , MIMEMultipartForm ) :
105
+ case MIMEMultipartForm :
103
106
params , err := c .MultipartForm ()
104
107
if err != nil {
105
108
return NewHTTPError (http .StatusBadRequest , err .Error ()).SetInternal (err )
@@ -115,7 +118,7 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
115
118
116
119
// BindHeaders binds HTTP headers to a bindable object
117
120
func (b * DefaultBinder ) BindHeaders (c Context , i interface {}) error {
118
- if err := b .bindData (i , c .Request ().Header , "header" ); err != nil {
121
+ if err := b .bindData (i , c .Request ().Header , "header" , nil ); err != nil {
119
122
return NewHTTPError (http .StatusBadRequest , err .Error ()).SetInternal (err )
120
123
}
121
124
return nil
@@ -141,10 +144,11 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
141
144
}
142
145
143
146
// bindData will bind data ONLY fields in destination struct that have EXPLICIT tag
144
- func (b * DefaultBinder ) bindData (destination interface {}, data map [string ][]string , tag string , files ... map [string ][]* multipart.FileHeader ) error {
145
- if destination == nil || (len (data ) == 0 && len (files ) == 0 ) {
147
+ func (b * DefaultBinder ) bindData (destination interface {}, data map [string ][]string , tag string , dataFiles map [string ][]* multipart.FileHeader ) error {
148
+ if destination == nil || (len (data ) == 0 && len (dataFiles ) == 0 ) {
146
149
return nil
147
150
}
151
+ hasFiles := len (dataFiles ) > 0
148
152
typ := reflect .TypeOf (destination ).Elem ()
149
153
val := reflect .ValueOf (destination ).Elem ()
150
154
@@ -188,7 +192,7 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
188
192
return errors .New ("binding element must be a struct" )
189
193
}
190
194
191
- for i := 0 ; i < typ .NumField (); i ++ {
195
+ for i := 0 ; i < typ .NumField (); i ++ { // iterate over all destination fields
192
196
typeField := typ .Field (i )
193
197
structField := val .Field (i )
194
198
if typeField .Anonymous {
@@ -207,48 +211,31 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
207
211
}
208
212
209
213
if inputFieldName == "" {
210
- // If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contains fields with tags).
214
+ // If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contain fields with tags).
211
215
// structs that implement BindUnmarshaler are bound only when they have explicit tag
212
216
if _ , ok := structField .Addr ().Interface ().(BindUnmarshaler ); ! ok && structFieldKind == reflect .Struct {
213
- if err := b .bindData (structField .Addr ().Interface (), data , tag ); err != nil {
217
+ if err := b .bindData (structField .Addr ().Interface (), data , tag , dataFiles ); err != nil {
214
218
return err
215
219
}
216
220
}
217
221
// does not have explicit tag and is not an ordinary struct - so move to next field
218
222
continue
219
223
}
220
224
221
- // Handle multiple file uploads ([]*multipart.FileHeader, *multipart.FileHeader, []multipart.FileHeader)
222
- if len (files ) > 0 && isMultipartFile (structField .Type ()) {
223
- for _ , fileMap := range files {
224
- fileHeaders , exists := fileMap [inputFieldName ]
225
- if exists && len (fileHeaders ) > 0 {
226
- switch structField .Type () {
227
- case reflect .TypeOf ([]* multipart.FileHeader (nil )):
228
- structField .Set (reflect .ValueOf (fileHeaders ))
229
- continue
230
- case reflect .TypeOf ([]multipart.FileHeader (nil )):
231
- headers := make ([]multipart.FileHeader , len (fileHeaders ))
232
- for i , fileHeader := range fileHeaders {
233
- headers [i ] = * fileHeader
234
- }
235
- structField .Set (reflect .ValueOf (headers ))
236
- continue
237
- case reflect .TypeOf (& multipart.FileHeader {}):
238
- structField .Set (reflect .ValueOf (fileHeaders [0 ]))
239
- continue
240
- case reflect .TypeOf (multipart.FileHeader {}):
241
- structField .Set (reflect .ValueOf (* fileHeaders [0 ]))
242
- continue
243
- }
225
+ if hasFiles {
226
+ if ok , err := isFieldMultipartFile (structField .Type ()); err != nil {
227
+ return err
228
+ } else if ok {
229
+ if ok := setMultipartFileHeaderTypes (structField , inputFieldName , dataFiles ); ok {
230
+ continue
244
231
}
245
232
}
246
233
}
247
234
248
235
inputValue , exists := data [inputFieldName ]
249
236
if ! exists {
250
- // Go json.Unmarshal supports case insensitive binding. However the
251
- // url params are bound case sensitive which is inconsistent. To
237
+ // Go json.Unmarshal supports case- insensitive binding. However the
238
+ // url params are bound case- sensitive which is inconsistent. To
252
239
// fix this we must check all of the map values in a
253
240
// case-insensitive search.
254
241
for k , v := range data {
@@ -431,9 +418,49 @@ func setFloatField(value string, bitSize int, field reflect.Value) error {
431
418
return err
432
419
}
433
420
434
- func isMultipartFile (field reflect.Type ) bool {
435
- return reflect .TypeOf (& multipart.FileHeader {}) == field ||
436
- reflect .TypeOf (multipart.FileHeader {}) == field ||
437
- reflect .TypeOf ([]* multipart.FileHeader (nil )) == field ||
438
- reflect .TypeOf ([]multipart.FileHeader (nil )) == field
421
+ var (
422
+ // NOT supported by bind as you can NOT check easily empty struct being actual file or not
423
+ multipartFileHeaderType = reflect .TypeOf (multipart.FileHeader {})
424
+ // supported by bind as you can check by nil value if file existed or not
425
+ multipartFileHeaderPointerType = reflect .TypeOf (& multipart.FileHeader {})
426
+ multipartFileHeaderSliceType = reflect .TypeOf ([]multipart.FileHeader (nil ))
427
+ multipartFileHeaderPointerSliceType = reflect .TypeOf ([]* multipart.FileHeader (nil ))
428
+ )
429
+
430
+ func isFieldMultipartFile (field reflect.Type ) (bool , error ) {
431
+ switch field {
432
+ case multipartFileHeaderPointerType ,
433
+ multipartFileHeaderSliceType ,
434
+ multipartFileHeaderPointerSliceType :
435
+ return true , nil
436
+ case multipartFileHeaderType :
437
+ return true , errors .New ("binding to multipart.FileHeader struct is not supported, use pointer to struct" )
438
+ default :
439
+ return false , nil
440
+ }
441
+ }
442
+
443
+ func setMultipartFileHeaderTypes (structField reflect.Value , inputFieldName string , files map [string ][]* multipart.FileHeader ) bool {
444
+ fileHeaders := files [inputFieldName ]
445
+ if len (fileHeaders ) == 0 {
446
+ return false
447
+ }
448
+
449
+ result := true
450
+ switch structField .Type () {
451
+ case multipartFileHeaderPointerSliceType :
452
+ structField .Set (reflect .ValueOf (fileHeaders ))
453
+ case multipartFileHeaderSliceType :
454
+ headers := make ([]multipart.FileHeader , len (fileHeaders ))
455
+ for i , fileHeader := range fileHeaders {
456
+ headers [i ] = * fileHeader
457
+ }
458
+ structField .Set (reflect .ValueOf (headers ))
459
+ case multipartFileHeaderPointerType :
460
+ structField .Set (reflect .ValueOf (fileHeaders [0 ]))
461
+ default :
462
+ result = false
463
+ }
464
+
465
+ return result
439
466
}
0 commit comments