Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 bug: Fix square bracket notation in Multipart FormData #3235

Merged
merged 13 commits into from
Dec 31, 2024
106 changes: 104 additions & 2 deletions bind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"encoding/json"
"errors"
"fmt"
"mime/multipart"
"net/http/httptest"
"reflect"
"testing"
Expand Down Expand Up @@ -988,6 +989,48 @@
Data []Demo `query:"data"`
}

t.Run("MultipartCollectionQueryDotNotation", func(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
c.Request().Reset()

buf := &bytes.Buffer{}
writer := multipart.NewWriter(buf)
require.NoError(t, writer.WriteField("data.0.name", "john"))
require.NoError(t, writer.WriteField("data.1.name", "doe"))
require.NoError(t, writer.Close())

c.Request().Header.SetContentType(writer.FormDataContentType())
c.Request().SetBody(buf.Bytes())
c.Request().Header.SetContentLength(len(c.Body()))

cq := new(CollectionQuery)
require.NoError(t, c.Bind().Body(cq))
require.Len(t, cq.Data, 2)
require.Equal(t, "john", cq.Data[0].Name)
require.Equal(t, "doe", cq.Data[1].Name)
})

t.Run("MultipartCollectionQuerySquareBrackets", func(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
c.Request().Reset()

buf := &bytes.Buffer{}
writer := multipart.NewWriter(buf)
require.NoError(t, writer.WriteField("data[0][name]", "john"))
require.NoError(t, writer.WriteField("data[1][name]", "doe"))
require.NoError(t, writer.Close())

c.Request().Header.SetContentType(writer.FormDataContentType())
c.Request().SetBody(buf.Bytes())
c.Request().Header.SetContentLength(len(c.Body()))

cq := new(CollectionQuery)
require.NoError(t, c.Bind().Body(cq))
require.Len(t, cq.Data, 2)
require.Equal(t, "john", cq.Data[0].Name)
require.Equal(t, "doe", cq.Data[1].Name)
})

t.Run("CollectionQuerySquareBrackets", func(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
c.Request().Reset()
Expand Down Expand Up @@ -1180,13 +1223,67 @@
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})

type Person struct {
Name string `form:"name"`
Age int `form:"age"`
}

type Demo struct {
Name string `form:"name"`
Persons []Person `form:"persons"`
}

buf := &bytes.Buffer{}
writer := multipart.NewWriter(buf)
require.NoError(t, writer.WriteField("name", "john"))

Check failure on line 1238 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

undefined: t

Check failure on line 1238 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

undefined: t

Check failure on line 1238 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

undefined: t

Check failure on line 1238 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

undefined: t

Check failure on line 1238 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-13)

undefined: t

Check failure on line 1238 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-13)

undefined: t

Check failure on line 1238 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

undefined: t

Check failure on line 1238 in bind_test.go

View workflow job for this annotation

GitHub Actions / Compare

undefined: t

Check failure on line 1238 in bind_test.go

View workflow job for this annotation

GitHub Actions / repeated

undefined: t

Check failure on line 1238 in bind_test.go

View workflow job for this annotation

GitHub Actions / repeated

undefined: t
require.NoError(t, writer.Close())

Check failure on line 1239 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

undefined: t

Check failure on line 1239 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

undefined: t

Check failure on line 1239 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

undefined: t

Check failure on line 1239 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

undefined: t

Check failure on line 1239 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-13)

undefined: t

Check failure on line 1239 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-13)

undefined: t

Check failure on line 1239 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

undefined: t

Check failure on line 1239 in bind_test.go

View workflow job for this annotation

GitHub Actions / Compare

undefined: t

Check failure on line 1239 in bind_test.go

View workflow job for this annotation

GitHub Actions / repeated

undefined: t

Check failure on line 1239 in bind_test.go

View workflow job for this annotation

GitHub Actions / repeated

undefined: t
body := buf.Bytes()
gaby marked this conversation as resolved.
Show resolved Hide resolved

c.Request().SetBody(body)
c.Request().Header.SetContentType(MIMEMultipartForm + `;boundary=` + writer.Boundary())
c.Request().Header.SetContentLength(len(body))
d := new(Demo)

b.ReportAllocs()
b.ResetTimer()

for n := 0; n < b.N; n++ {
err = c.Bind().Body(d)
}

require.NoError(b, err)
require.Equal(b, "john", d.Name)
}

// go test -v -run=^$ -bench=Benchmark_Bind_Body_MultipartForm_Nested -benchmem -count=4
func Benchmark_Bind_Body_MultipartForm_Nested(b *testing.B) {
var err error

app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})

type Person struct {
Name string `form:"name"`
Age int `form:"age"`
}

body := []byte("--b\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\njohn\r\n--b--")
type Demo struct {
Name string `form:"name"`
Persons []Person `form:"persons"`
}

buf := &bytes.Buffer{}
writer := multipart.NewWriter(buf)
require.NoError(t, writer.WriteField("name", "john"))

Check failure on line 1277 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

undefined: t

Check failure on line 1277 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

undefined: t

Check failure on line 1277 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-13)

undefined: t

Check failure on line 1277 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

undefined: t

Check failure on line 1277 in bind_test.go

View workflow job for this annotation

GitHub Actions / Compare

undefined: t

Check failure on line 1277 in bind_test.go

View workflow job for this annotation

GitHub Actions / repeated

undefined: t
require.NoError(t, writer.WriteField("persons.0.name", "john"))

Check failure on line 1278 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

undefined: t

Check failure on line 1278 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

undefined: t

Check failure on line 1278 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-13)

undefined: t

Check failure on line 1278 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

undefined: t

Check failure on line 1278 in bind_test.go

View workflow job for this annotation

GitHub Actions / Compare

undefined: t

Check failure on line 1278 in bind_test.go

View workflow job for this annotation

GitHub Actions / repeated

undefined: t
require.NoError(t, writer.WriteField("persons[0][age]", "10"))

Check failure on line 1279 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

undefined: t

Check failure on line 1279 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

undefined: t

Check failure on line 1279 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-13)

undefined: t

Check failure on line 1279 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

undefined: t

Check failure on line 1279 in bind_test.go

View workflow job for this annotation

GitHub Actions / Compare

undefined: t

Check failure on line 1279 in bind_test.go

View workflow job for this annotation

GitHub Actions / repeated

undefined: t
require.NoError(t, writer.WriteField("persons[1][name]", "doe"))

Check failure on line 1280 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

undefined: t

Check failure on line 1280 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

undefined: t

Check failure on line 1280 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-13)

undefined: t

Check failure on line 1280 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

undefined: t

Check failure on line 1280 in bind_test.go

View workflow job for this annotation

GitHub Actions / Compare

undefined: t

Check failure on line 1280 in bind_test.go

View workflow job for this annotation

GitHub Actions / repeated

undefined: t
require.NoError(t, writer.WriteField("persons.1.age", "20"))

Check failure on line 1281 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

undefined: t

Check failure on line 1281 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

undefined: t

Check failure on line 1281 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-13)

undefined: t

Check failure on line 1281 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

undefined: t

Check failure on line 1281 in bind_test.go

View workflow job for this annotation

GitHub Actions / Compare

undefined: t

Check failure on line 1281 in bind_test.go

View workflow job for this annotation

GitHub Actions / repeated

undefined: t
require.NoError(t, writer.Close())

Check failure on line 1282 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

undefined: t (typecheck)

Check failure on line 1282 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, ubuntu-latest)

undefined: t

Check failure on line 1282 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-13)

undefined: t

Check failure on line 1282 in bind_test.go

View workflow job for this annotation

GitHub Actions / unit (1.23.x, macos-latest)

undefined: t

Check failure on line 1282 in bind_test.go

View workflow job for this annotation

GitHub Actions / Compare

undefined: t

Check failure on line 1282 in bind_test.go

View workflow job for this annotation

GitHub Actions / repeated

undefined: t
body := buf.Bytes()

c.Request().SetBody(body)
c.Request().Header.SetContentType(MIMEMultipartForm + `;boundary="b"`)
c.Request().Header.SetContentType(MIMEMultipartForm + `;boundary=` + writer.Boundary())
c.Request().Header.SetContentLength(len(body))
d := new(Demo)

Expand All @@ -1196,8 +1293,13 @@
for n := 0; n < b.N; n++ {
err = c.Bind().Body(d)
}

require.NoError(b, err)
require.Equal(b, "john", d.Name)
require.Equal(b, "john", d.Persons[0].Name)
require.Equal(b, 10, d.Persons[0].Age)
require.Equal(b, "doe", d.Persons[1].Name)
require.Equal(b, 20, d.Persons[1].Age)
}

// go test -v -run=^$ -bench=Benchmark_Bind_Body_Form_Map -benchmem -count=4
Expand Down
22 changes: 22 additions & 0 deletions binder/form.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,27 @@ func (b *formBinding) BindMultipart(reqCtx *fasthttp.RequestCtx, out any) error
return err
}

for key, values := range data.Value {
ReneWerner87 marked this conversation as resolved.
Show resolved Hide resolved
if strings.Contains(key, "[") {
k, err := parseParamSquareBrackets(key)
if err != nil {
return err
}
data.Value[k] = values
delete(data.Value, key) // Remove bracket notation and use dot instead
}

for _, v := range values {
if strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, key) {
delete(data.Value, key)

values := strings.Split(v, ",")
for i := 0; i < len(values); i++ {
data.Value[key] = append(data.Value[key], values[i])
}
}
}
}

gaby marked this conversation as resolved.
Show resolved Hide resolved
return parse(b.Name(), out, data.Value)
}
Loading