Skip to content

Commit 4854cb7

Browse files
committed
feat(context): add FormFiles for multi-file form fields
1 parent 99c395d commit 4854cb7

2 files changed

Lines changed: 102 additions & 0 deletions

File tree

‎context.go‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,24 @@ func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
413413
return fh, nil
414414
}
415415

416+
// FormFiles returns the multipart form files for the provided name.
417+
//
418+
// It is the plural counterpart of FormFile and is meant for form fields that carry more than one file, such as
419+
// `<input type="file" multiple>` or JavaScript FormData appending several files under the same field name.
420+
// FormFile only returns the first file for such fields, FormFiles returns all of them in the order they were sent.
421+
//
422+
// Returns http.ErrMissingFile when no files exist under the given name, matching FormFile behavior.
423+
func (c *Context) FormFiles(name string) ([]*multipart.FileHeader, error) {
424+
if err := c.request.ParseMultipartForm(c.formParseMaxMemory); err != nil {
425+
return nil, err
426+
}
427+
files := c.request.MultipartForm.File[name]
428+
if len(files) == 0 {
429+
return nil, http.ErrMissingFile
430+
}
431+
return files, nil
432+
}
433+
416434
// MultipartForm returns the multipart form.
417435
func (c *Context) MultipartForm() (*multipart.Form, error) {
418436
err := c.request.ParseMultipartForm(c.formParseMaxMemory)

‎context_test.go‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,90 @@ func TestContextFormFile(t *testing.T) {
10181018
}
10191019
}
10201020

1021+
func TestContextFormFiles(t *testing.T) {
1022+
e := New()
1023+
buf := new(bytes.Buffer)
1024+
mw := multipart.NewWriter(buf)
1025+
1026+
// a non-file field and multiple files under the same field name, sent in the order they were written to the form
1027+
if err := mw.WriteField("name", "Jon Snow"); err != nil {
1028+
t.Fatal(err)
1029+
}
1030+
for _, filename := range []string{"a.txt", "b.txt", "c.txt"} {
1031+
w, err := mw.CreateFormFile("files", filename)
1032+
if assert.NoError(t, err) {
1033+
_, _ = w.Write([]byte(filename))
1034+
}
1035+
}
1036+
if err := mw.Close(); err != nil {
1037+
t.Fatal(err)
1038+
}
1039+
req := httptest.NewRequest(http.MethodPost, "/", buf)
1040+
req.Header.Set(HeaderContentType, mw.FormDataContentType())
1041+
rec := httptest.NewRecorder()
1042+
c := e.NewContext(req, rec)
1043+
1044+
files, err := c.FormFiles("files")
1045+
if assert.NoError(t, err) && assert.Len(t, files, 3) {
1046+
assert.Equal(t, "a.txt", files[0].Filename)
1047+
assert.Equal(t, "b.txt", files[1].Filename)
1048+
assert.Equal(t, "c.txt", files[2].Filename)
1049+
}
1050+
}
1051+
1052+
func TestContextFormFiles_singleFile(t *testing.T) {
1053+
e := New()
1054+
buf := new(bytes.Buffer)
1055+
mw := multipart.NewWriter(buf)
1056+
w, err := mw.CreateFormFile("files", "a.txt")
1057+
if assert.NoError(t, err) {
1058+
_, _ = w.Write([]byte("a"))
1059+
}
1060+
if assert.NoError(t, mw.Close()) {
1061+
req := httptest.NewRequest(http.MethodPost, "/", buf)
1062+
req.Header.Set(HeaderContentType, mw.FormDataContentType())
1063+
rec := httptest.NewRecorder()
1064+
c := e.NewContext(req, rec)
1065+
1066+
files, err := c.FormFiles("files")
1067+
if assert.NoError(t, err) && assert.Len(t, files, 1) {
1068+
assert.Equal(t, "a.txt", files[0].Filename)
1069+
}
1070+
}
1071+
}
1072+
1073+
func TestContextFormFiles_missing(t *testing.T) {
1074+
e := New()
1075+
buf := new(bytes.Buffer)
1076+
mw := multipart.NewWriter(buf)
1077+
w, err := mw.CreateFormFile("files", "a.txt")
1078+
if assert.NoError(t, err) {
1079+
_, _ = w.Write([]byte("a"))
1080+
}
1081+
if assert.NoError(t, mw.Close()) {
1082+
req := httptest.NewRequest(http.MethodPost, "/", buf)
1083+
req.Header.Set(HeaderContentType, mw.FormDataContentType())
1084+
rec := httptest.NewRecorder()
1085+
c := e.NewContext(req, rec)
1086+
1087+
files, err := c.FormFiles("other")
1088+
assert.Nil(t, files)
1089+
assert.ErrorIs(t, err, http.ErrMissingFile)
1090+
}
1091+
}
1092+
1093+
func TestContextFormFiles_nonMultipart(t *testing.T) {
1094+
e := New()
1095+
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("name=value"))
1096+
req.Header.Set(HeaderContentType, MIMEApplicationForm)
1097+
rec := httptest.NewRecorder()
1098+
c := e.NewContext(req, rec)
1099+
1100+
files, err := c.FormFiles("files")
1101+
assert.Nil(t, files)
1102+
assert.ErrorIs(t, err, http.ErrNotMultipart)
1103+
}
1104+
10211105
func TestContextMultipartForm(t *testing.T) {
10221106
e := New()
10231107
buf := new(bytes.Buffer)

0 commit comments

Comments
 (0)