mirror of
https://github.com/labstack/echo.git
synced 2025-03-11 14:49:56 +02:00
bind: add support of multipart multi files
This commit is contained in:
parent
ab87b63640
commit
d1ab8e8544
46
bind.go
46
bind.go
@ -8,6 +8,7 @@ import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@ -90,7 +91,7 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
|
||||
}
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
|
||||
case strings.HasPrefix(ctype, MIMEApplicationForm):
|
||||
params, err := c.FormParams()
|
||||
if err != nil {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
@ -98,6 +99,14 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
|
||||
if err = b.bindData(i, params, "form"); err != nil {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
case strings.HasPrefix(ctype, MIMEMultipartForm):
|
||||
params, err := c.MultipartForm()
|
||||
if err != nil {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
if err = b.bindData(i, params.Value, "form", params.File); err != nil {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
default:
|
||||
return ErrUnsupportedMediaType
|
||||
}
|
||||
@ -132,8 +141,8 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
|
||||
}
|
||||
|
||||
// bindData will bind data ONLY fields in destination struct that have EXPLICIT tag
|
||||
func (b *DefaultBinder) bindData(destination interface{}, data map[string][]string, tag string) error {
|
||||
if destination == nil || len(data) == 0 {
|
||||
func (b *DefaultBinder) bindData(destination interface{}, data map[string][]string, tag string, files ...map[string][]*multipart.FileHeader) error {
|
||||
if destination == nil || (len(data) == 0 && len(files) == 0) {
|
||||
return nil
|
||||
}
|
||||
typ := reflect.TypeOf(destination).Elem()
|
||||
@ -209,6 +218,37 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle multiple file uploads ([]*multipart.FileHeader, *multipart.FileHeader, []multipart.FileHeader)
|
||||
if len(files) > 0 {
|
||||
for _, fileMap := range files {
|
||||
fileHeaders, exists := fileMap[inputFieldName]
|
||||
if exists {
|
||||
if structField.Type() == reflect.TypeOf([]*multipart.FileHeader(nil)) {
|
||||
structField.Set(reflect.ValueOf(fileHeaders))
|
||||
continue
|
||||
} else if structField.Type() == reflect.TypeOf([]multipart.FileHeader(nil)) {
|
||||
var headers []multipart.FileHeader
|
||||
for _, fileHeader := range fileHeaders {
|
||||
headers = append(headers, *fileHeader)
|
||||
}
|
||||
structField.Set(reflect.ValueOf(headers))
|
||||
continue
|
||||
} else if structField.Type() == reflect.TypeOf(&multipart.FileHeader{}) {
|
||||
|
||||
if len(fileHeaders) > 0 {
|
||||
structField.Set(reflect.ValueOf(fileHeaders[0]))
|
||||
}
|
||||
continue
|
||||
} else if structField.Type() == reflect.TypeOf(multipart.FileHeader{}) {
|
||||
if len(fileHeaders) > 0 {
|
||||
structField.Set(reflect.ValueOf(*fileHeaders[0]))
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputValue, exists := data[inputFieldName]
|
||||
if !exists {
|
||||
// Go json.Unmarshal supports case insensitive binding. However the
|
||||
|
97
bind_test.go
97
bind_test.go
@ -1102,6 +1102,103 @@ func TestDefaultBinder_BindBody(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type testFile struct {
|
||||
Fieldname string
|
||||
Filename string
|
||||
Content []byte
|
||||
}
|
||||
|
||||
// createRequestMultipartFiles creates a multipart HTTP request with multiple files.
|
||||
func createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request {
|
||||
var body bytes.Buffer
|
||||
mw := multipart.NewWriter(&body)
|
||||
|
||||
for _, file := range files {
|
||||
fw, err := mw.CreateFormFile(file.Fieldname, file.Filename)
|
||||
assert.NoError(t, err)
|
||||
|
||||
n, err := fw.Write(file.Content)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(file.Content), n)
|
||||
}
|
||||
|
||||
err := mw.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, "/", &body)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req.Header.Set("Content-Type", mw.FormDataContentType())
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file testFile) {
|
||||
assert.Equal(t, file.Filename, fh.Filename)
|
||||
assert.Equal(t, int64(len(file.Content)), fh.Size)
|
||||
fl, err := fh.Open()
|
||||
assert.NoError(t, err)
|
||||
body, err := io.ReadAll(fl)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(file.Content), string(body))
|
||||
err = fl.Close()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestFormMultipartBindTwoFiles(t *testing.T) {
|
||||
var args struct {
|
||||
Files []*multipart.FileHeader `form:"files"`
|
||||
}
|
||||
|
||||
files := []testFile{
|
||||
{
|
||||
Fieldname: "files",
|
||||
Filename: "file1.txt",
|
||||
Content: []byte("This is the content of file 1."),
|
||||
},
|
||||
{
|
||||
Fieldname: "files",
|
||||
Filename: "file2.txt",
|
||||
Content: []byte("This is the content of file 2."),
|
||||
},
|
||||
}
|
||||
|
||||
e := New()
|
||||
req := createRequestMultipartFiles(t, files...)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
|
||||
err := c.Bind(&args)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, args.Files, len(files))
|
||||
for idx, file := range files {
|
||||
assertMultipartFileHeader(t, args.Files[idx], file)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormMultipartBindOneFile(t *testing.T) {
|
||||
var args struct {
|
||||
File *multipart.FileHeader `form:"file"`
|
||||
}
|
||||
|
||||
file := testFile{
|
||||
Fieldname: "file",
|
||||
Filename: "file1.txt",
|
||||
Content: []byte("This is the content of file 1."),
|
||||
}
|
||||
|
||||
e := New()
|
||||
req := createRequestMultipartFiles(t, file)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
|
||||
err := c.Bind(&args)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assertMultipartFileHeader(t, args.File, file)
|
||||
}
|
||||
|
||||
func testBindURL(queryString string, target any) error {
|
||||
e := New()
|
||||
req := httptest.NewRequest(http.MethodGet, queryString, nil)
|
||||
|
Loading…
x
Reference in New Issue
Block a user