diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 9cf56527..dcf5b01f 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -76,15 +76,25 @@ func (form formSource) TrySet(value reflect.Value, field reflect.StructField, ta } func mappingByPtr(ptr any, setter setter, tag string) error { - _, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag) + _, err := mappingWithDepth(reflect.ValueOf(ptr), emptyField, setter, tag, 0) return err } func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { + return mappingWithDepth(value, field, setter, tag, 0) +} + +const maxMappingDepth = 10 + +func mappingWithDepth(value reflect.Value, field reflect.StructField, setter setter, tag string, depth int) (bool, error) { if field.Tag.Get(tag) == "-" { // just ignoring this field return false, nil } + if depth > maxMappingDepth { + return false, nil + } + vKind := value.Kind() if vKind == reflect.Ptr { @@ -94,7 +104,8 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag isNew = true vPtr = reflect.New(value.Type().Elem()) } - isSet, err := mapping(vPtr.Elem(), field, setter, tag) + + isSet, err := mappingWithDepth(vPtr.Elem(), field, setter, tag, depth+1) if err != nil { return false, err } @@ -123,7 +134,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag if sf.PkgPath != "" && !sf.Anonymous { // unexported continue } - ok, err := mapping(value.Field(i), sf, setter, tag) + ok, err := mappingWithDepth(value.Field(i), sf, setter, tag, depth+1) if err != nil { return false, err } diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 55b967a3..5e7d39c6 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -635,3 +635,62 @@ func TestMappingCustomArrayForm(t *testing.T) { expected, _ := convertTo(val) assert.Equal(t, expected, s.FileData) } + +// Test case for infinite recursion bug fix +func TestInfiniteRecursionBug(t *testing.T) { + type Req struct { + Parent *Req `form:"parent"` + } + + var s Req + err := MapFormWithTag(&s, map[string][]string{}, "form") + + assert.NoError(t, err) + assert.Nil(t, s.Parent) +} + +// Test case for the user's specific example from GitHub issue +func TestInfiniteRecursionUserExample(t *testing.T) { + type Req struct { + Foo string `form:"foo"` + Bar string `form:"bar"` + Foo1 string `form:"Foo"` + Bar1 string `form:"Bar"` + Foo2 *Req `form:"foo2"` + } + + var s Req + + formData := map[string][]string{ + "foo": {"test_foo"}, + "bar": {"test_bar"}, + } + + err := MapFormWithTag(&s, formData, "form") + + assert.NoError(t, err) + assert.Equal(t, "test_foo", s.Foo) + assert.Equal(t, "test_bar", s.Bar) +} + +// Test case for nested self-reference that should work when data is provided +func TestValidNestedSelfReference(t *testing.T) { + type Req struct { + Name string `form:"name"` + Parent *Req `form:"parent"` + } + + var s Req + + formData := map[string][]string{ + "name": {"child"}, + "parent": {`{"name": "parent_name"}`}, + } + + err := MapFormWithTag(&s, formData, "form") + + assert.NoError(t, err) + assert.Equal(t, "child", s.Name) + assert.NotNil(t, s.Parent) + assert.Equal(t, "parent_name", s.Parent.Name) +}