@@ -46,15 +46,15 @@ func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error {
4646 for i , name := range names {
4747 params [name ] = []string {values [i ]}
4848 }
49- if err := b .bindData (i , params , "param" ); err != nil {
49+ if err := b .bindData (i , params , "param" , nil ); err != nil {
5050 return NewHTTPError (http .StatusBadRequest , err .Error ()).SetInternal (err )
5151 }
5252 return nil
5353}
5454
5555// BindQueryParams binds query params to bindable object
5656func (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 {
5858 return NewHTTPError (http .StatusBadRequest , err .Error ()).SetInternal (err )
5959 }
6060 return nil
@@ -71,9 +71,12 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
7171 return
7272 }
7373
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 :
7780 if err = c .Echo ().JSONSerializer .Deserialize (c , i ); err != nil {
7881 switch err .(type ) {
7982 case * HTTPError :
@@ -82,7 +85,7 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
8285 return NewHTTPError (http .StatusBadRequest , err .Error ()).SetInternal (err )
8386 }
8487 }
85- case strings . HasPrefix ( ctype , MIMEApplicationXML ), strings . HasPrefix ( ctype , MIMETextXML ) :
88+ case MIMEApplicationXML , MIMETextXML :
8689 if err = xml .NewDecoder (req .Body ).Decode (i ); err != nil {
8790 if ute , ok := err .(* xml.UnsupportedTypeError ); ok {
8891 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) {
9194 }
9295 return NewHTTPError (http .StatusBadRequest , err .Error ()).SetInternal (err )
9396 }
94- case strings . HasPrefix ( ctype , MIMEApplicationForm ) :
97+ case MIMEApplicationForm :
9598 params , err := c .FormParams ()
9699 if err != nil {
97100 return NewHTTPError (http .StatusBadRequest , err .Error ()).SetInternal (err )
98101 }
99- if err = b .bindData (i , params , "form" ); err != nil {
102+ if err = b .bindData (i , params , "form" , nil ); err != nil {
100103 return NewHTTPError (http .StatusBadRequest , err .Error ()).SetInternal (err )
101104 }
102- case strings . HasPrefix ( ctype , MIMEMultipartForm ) :
105+ case MIMEMultipartForm :
103106 params , err := c .MultipartForm ()
104107 if err != nil {
105108 return NewHTTPError (http .StatusBadRequest , err .Error ()).SetInternal (err )
@@ -115,7 +118,7 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
115118
116119// BindHeaders binds HTTP headers to a bindable object
117120func (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 {
119122 return NewHTTPError (http .StatusBadRequest , err .Error ()).SetInternal (err )
120123 }
121124 return nil
@@ -141,10 +144,11 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
141144}
142145
143146// 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 ) {
146149 return nil
147150 }
151+ hasFiles := len (dataFiles ) > 0
148152 typ := reflect .TypeOf (destination ).Elem ()
149153 val := reflect .ValueOf (destination ).Elem ()
150154
@@ -188,7 +192,7 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
188192 return errors .New ("binding element must be a struct" )
189193 }
190194
191- for i := 0 ; i < typ .NumField (); i ++ {
195+ for i := 0 ; i < typ .NumField (); i ++ { // iterate over all destination fields
192196 typeField := typ .Field (i )
193197 structField := val .Field (i )
194198 if typeField .Anonymous {
@@ -207,48 +211,31 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
207211 }
208212
209213 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).
211215 // structs that implement BindUnmarshaler are bound only when they have explicit tag
212216 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 {
214218 return err
215219 }
216220 }
217221 // does not have explicit tag and is not an ordinary struct - so move to next field
218222 continue
219223 }
220224
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
244231 }
245232 }
246233 }
247234
248235 inputValue , exists := data [inputFieldName ]
249236 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
252239 // fix this we must check all of the map values in a
253240 // case-insensitive search.
254241 for k , v := range data {
@@ -431,9 +418,49 @@ func setFloatField(value string, bitSize int, field reflect.Value) error {
431418 return err
432419}
433420
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
439466}
0 commit comments