diff --git a/pkg/fuzz/dataformat/multipart.go b/pkg/fuzz/dataformat/multipart.go index 97af6207f7..05f8fa831b 100644 --- a/pkg/fuzz/dataformat/multipart.go +++ b/pkg/fuzz/dataformat/multipart.go @@ -76,15 +76,34 @@ func (m *MultiPartForm) Encode(data KV) (string, error) { return true } - // Add field - if fw, err = w.CreateFormField(key); err != nil { - Itererr = err - return false + // Handle form field values - can be string or []string for duplicate fields + var values []string + switch v := value.(type) { + case string: + values = []string{v} + case []string: + values = v + case []any: + // Handle []any by converting each element to string + values = make([]string, len(v)) + for i, elem := range v { + values[i] = fmt.Sprint(elem) + } + default: + // Fallback: attempt string conversion + values = []string{fmt.Sprint(v)} } - if _, err = fw.Write([]byte(value.(string))); err != nil { - Itererr = err - return false + // Write all values for this field + for _, val := range values { + if fw, err = w.CreateFormField(key); err != nil { + Itererr = err + return false + } + if _, err = fw.Write([]byte(val)); err != nil { + Itererr = err + return false + } } return true }) diff --git a/pkg/fuzz/dataformat/multipart_test.go b/pkg/fuzz/dataformat/multipart_test.go new file mode 100644 index 0000000000..27c277a354 --- /dev/null +++ b/pkg/fuzz/dataformat/multipart_test.go @@ -0,0 +1,94 @@ +package dataformat + +import ( + "testing" + + mapsutil "github.com/projectdiscovery/utils/maps" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMultiPartFormEncode(t *testing.T) { + tests := []struct { + name string + fields map[string]any + wantErr bool + contains []string // strings that should appear in encoded output + }{ + { + name: "duplicate fields ([]string) - checkbox scenario", + fields: map[string]any{ + "interests": []string{"sports", "music", "reading"}, + "colors": []string{"red", "blue"}, + }, + contains: []string{"interests", "sports", "music", "reading", "colors", "red", "blue"}, + }, + { + name: "single string fields - backward compatibility", + fields: map[string]any{ + "username": "john", + "email": "john@example.com", + }, + contains: []string{"username", "john", "email", "john@example.com"}, + }, + { + name: "mixed types", + fields: map[string]any{ + "string": "text", + "array": []string{"item1", "item2"}, + "number": 42, // tests fmt.Sprint fallback + }, + contains: []string{"string", "text", "array", "item1", "item2", "number", "42"}, + }, + { + name: "empty array - should not appear in output", + fields: map[string]any{ + "emptyArray": []string{}, + "normalField": "value", + }, + contains: []string{"normalField", "value"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + form := NewMultiPartForm() + form.boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW" + + kv := mapsutil.NewOrderedMap[string, any]() + for k, v := range tt.fields { + kv.Set(k, v) + } + + encoded, err := form.Encode(KVOrderedMap(&kv)) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + for _, expected := range tt.contains { + assert.Contains(t, encoded, expected) + } + }) + } +} + +func TestMultiPartFormRoundTrip(t *testing.T) { + form := NewMultiPartForm() + form.boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW" + + original := mapsutil.NewOrderedMap[string, any]() + original.Set("username", "john") + original.Set("interests", []string{"sports", "music", "reading"}) + + encoded, err := form.Encode(KVOrderedMap(&original)) + require.NoError(t, err) + + decoded, err := form.Decode(encoded) + require.NoError(t, err) + + assert.Equal(t, "john", decoded.Get("username")) + assert.ElementsMatch(t, []string{"sports", "music", "reading"}, decoded.Get("interests")) +}