Path: blob/dev/pkg/fuzz/dataformat/multipart_test.go
2843 views
package dataformat12import (3"testing"45mapsutil "github.com/projectdiscovery/utils/maps"6"github.com/stretchr/testify/assert"7"github.com/stretchr/testify/require"8)910func TestMultiPartFormEncode(t *testing.T) {11tests := []struct {12name string13fields map[string]any14wantErr bool15expected map[string]any16}{17{18name: "duplicate fields ([]string) - checkbox scenario",19fields: map[string]any{20"interests": []string{"sports", "music", "reading"},21"colors": []string{"red", "blue"},22},23expected: map[string]any{24"interests": []string{"sports", "music", "reading"},25"colors": []string{"red", "blue"},26},27},28{29name: "single string fields - backward compatibility",30fields: map[string]any{31"username": "john",32"email": "[email protected]",33},34expected: map[string]any{35"username": "john",36"email": "[email protected]",37},38},39{40name: "mixed types",41fields: map[string]any{42"string": "text",43"array": []string{"item1", "item2"},44"number": 42, // tests fmt.Sprint fallback45"float": 3.14, // tests float conversion46"boolean": true, // tests boolean conversion47"zero": 0, // tests zero value48"emptyStr": "", // tests empty string49"negative": -123, // tests negative number50"nil": nil, // tests nil value51"mixedArray": []any{"str", 123, false, nil}, // tests mixed type array52},53expected: map[string]any{54"string": "text",55"array": []string{"item1", "item2"},56"number": "42", // numbers are converted to strings in multipart57"float": "3.14", // floats are converted to strings58"boolean": "true", // booleans are converted to strings59"zero": "0", // zero value converted to string60"emptyStr": "", // empty string remains empty61"negative": "-123", // negative numbers converted to strings62"nil": "", // nil values converted to "" string63"mixedArray": []string{"str", "123", "false", ""}, // mixed array converted to string array64},65},66{67name: "empty array - should not appear in output",68fields: map[string]any{69"emptyArray": []string{},70"normalField": "value",71},72expected: map[string]any{73"normalField": "value",74// emptyArray should not appear in decoded output75},76},77}7879for _, tt := range tests {80t.Run(tt.name, func(t *testing.T) {81defer func() {82if r := recover(); r != nil {83t.Errorf("Test panicked: %v", r)84}85}()8687form := NewMultiPartForm()88form.boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"8990kv := mapsutil.NewOrderedMap[string, any]()91for k, v := range tt.fields {92kv.Set(k, v)93}9495encoded, err := form.Encode(KVOrderedMap(&kv))9697if tt.wantErr {98require.Error(t, err)99return100}101102require.NoError(t, err)103104// Decode the encoded multipart data105decoded, err := form.Decode(encoded)106require.NoError(t, err)107108// Compare decoded values with expected values109for expectedKey, expectedValue := range tt.expected {110actualValue := decoded.Get(expectedKey)111switch expected := expectedValue.(type) {112case []string:113actual, ok := actualValue.([]string)114require.True(t, ok, "Expected []string for key %s, got %T", expectedKey, actualValue)115assert.ElementsMatch(t, expected, actual, "Values mismatch for key %s", expectedKey)116case []any:117actual, ok := actualValue.([]any)118require.True(t, ok, "Expected []any for key %s, got %T", expectedKey, actualValue)119assert.ElementsMatch(t, expected, actual, "Values mismatch for key %s", expectedKey)120case string:121actual, ok := actualValue.(string)122require.True(t, ok, "Expected string for key %s, got %T", expectedKey, actualValue)123assert.Equal(t, expected, actual, "Values mismatch for key %s", expectedKey)124default:125assert.Equal(t, expected, actualValue, "Values mismatch for key %s", expectedKey)126}127}128129// Ensure no unexpected keys are present in decoded output130decoded.Iterate(func(key string, value any) bool {131_, exists := tt.expected[key]132assert.True(t, exists, "Unexpected key %s found in decoded output", key)133return true134})135136t.Logf("Encoded output:\n%s", encoded)137})138}139}140141func TestMultiPartFormRoundTrip(t *testing.T) {142defer func() {143if r := recover(); r != nil {144t.Errorf("Test panicked: %v", r)145}146}()147148form := NewMultiPartForm()149form.boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"150151original := mapsutil.NewOrderedMap[string, any]()152original.Set("username", "john")153original.Set("interests", []string{"sports", "music", "reading"})154155encoded, err := form.Encode(KVOrderedMap(&original))156require.NoError(t, err)157158decoded, err := form.Decode(encoded)159require.NoError(t, err)160161assert.Equal(t, "john", decoded.Get("username"))162assert.ElementsMatch(t, []string{"sports", "music", "reading"}, decoded.Get("interests"))163164t.Logf("Encoded output:\n%s", encoded)165}166167func TestMultiPartFormFileUpload(t *testing.T) {168defer func() {169if r := recover(); r != nil {170t.Errorf("Test panicked: %v", r)171}172}()173174// Test decoding of a manually crafted multipart form with files175form := NewMultiPartForm()176form.boundary = "----WebKitFormBoundaryFileUploadTest"177178// Manually craft a multipart form with file uploads179multipartData := `------WebKitFormBoundaryFileUploadTest180Content-Disposition: form-data; name="name"181182John Doe183------WebKitFormBoundaryFileUploadTest184Content-Disposition: form-data; name="email"185186[email protected]187------WebKitFormBoundaryFileUploadTest188Content-Disposition: form-data; name="profile_picture"; filename="profile.jpg"189Content-Type: image/jpeg190191fake_jpeg_binary_data_here192------WebKitFormBoundaryFileUploadTest193Content-Disposition: form-data; name="documents"; filename="resume.pdf"194Content-Type: application/pdf195196fake_pdf_content_1197------WebKitFormBoundaryFileUploadTest198Content-Disposition: form-data; name="documents"; filename="cover_letter.pdf"199Content-Type: application/pdf200201fake_pdf_content_2202------WebKitFormBoundaryFileUploadTest203Content-Disposition: form-data; name="skills"204205Go206------WebKitFormBoundaryFileUploadTest207Content-Disposition: form-data; name="skills"208209JavaScript210------WebKitFormBoundaryFileUploadTest211Content-Disposition: form-data; name="skills"212213Python214------WebKitFormBoundaryFileUploadTest--215`216217// Test decoding218decoded, err := form.Decode(multipartData)219require.NoError(t, err)220221// Verify regular fields222assert.Equal(t, "John Doe", decoded.Get("name"))223assert.Equal(t, "[email protected]", decoded.Get("email"))224assert.Equal(t, []string{"Go", "JavaScript", "Python"}, decoded.Get("skills"))225226// Verify file fields227profilePicture := decoded.Get("profile_picture")228require.NotNil(t, profilePicture)229profileArray, ok := profilePicture.([]interface{})230require.True(t, ok, "Expected []interface{} for profile_picture")231require.Len(t, profileArray, 1)232assert.Equal(t, "fake_jpeg_binary_data_here", profileArray[0])233234documents := decoded.Get("documents")235require.NotNil(t, documents)236documentsArray, ok := documents.([]interface{})237require.True(t, ok, "Expected []interface{} for documents")238require.Len(t, documentsArray, 2)239assert.Contains(t, documentsArray, "fake_pdf_content_1")240assert.Contains(t, documentsArray, "fake_pdf_content_2")241}242243func TestMultiPartForm_SetGetFileMetadata(t *testing.T) {244form := NewMultiPartForm()245metadata := FileMetadata{246ContentType: "image/jpeg",247Filename: "test.jpg",248}249form.SetFileMetadata("avatar", metadata)250251// Test GetFileMetadata for existing field252retrievedMetadata, exists := form.GetFileMetadata("avatar")253assert.True(t, exists)254assert.Equal(t, metadata.ContentType, retrievedMetadata.ContentType)255assert.Equal(t, metadata.Filename, retrievedMetadata.Filename)256257// Test GetFileMetadata for non-existing field258_, exists = form.GetFileMetadata("nonexistent")259assert.False(t, exists)260}261262func TestMultiPartForm_FilesMetadataInitialization(t *testing.T) {263form := NewMultiPartForm()264assert.NotNil(t, form.filesMetadata)265266metadata := FileMetadata{267ContentType: "text/plain",268Filename: "test.txt",269}270form.SetFileMetadata("file", metadata)271272retrievedMetadata, exists := form.GetFileMetadata("file")273assert.True(t, exists)274assert.Equal(t, metadata, retrievedMetadata)275}276277func TestMultiPartForm_BoundaryValidation(t *testing.T) {278form := NewMultiPartForm()279280// Test valid boundary281err := form.ParseBoundary("multipart/form-data; boundary=testboundary")282assert.NoError(t, err)283assert.Equal(t, "testboundary", form.boundary)284285// Test missing boundary286err = form.ParseBoundary("multipart/form-data")287assert.Error(t, err)288assert.Contains(t, err.Error(), "no boundary found")289290// Test boundary too long (over 70 characters)291longBoundary := "multipart/form-data; boundary=" + string(make([]byte, 71))292for i := range longBoundary[len("multipart/form-data; boundary="):] {293longBoundary = longBoundary[:len("multipart/form-data; boundary=")+i] + "a" + longBoundary[len("multipart/form-data; boundary=")+i+1:]294}295296err = form.ParseBoundary(longBoundary)297assert.Error(t, err)298assert.Contains(t, err.Error(), "boundary exceeds maximum length")299}300301func TestMultiPartForm_DecodeRequiresBoundary(t *testing.T) {302form := NewMultiPartForm()303304// Decode should fail if boundary is not set305_, err := form.Decode("some data")306assert.Error(t, err)307assert.Contains(t, err.Error(), "boundary not set")308}309310func TestMultiPartForm_MultipleFilesMetadata(t *testing.T) {311form := NewMultiPartForm()312form.boundary = "----WebKitFormBoundaryMultiFileTest"313314// Test with multiple files having the same field name315multipartData := `------WebKitFormBoundaryMultiFileTest316Content-Disposition: form-data; name="documents"; filename="file1.txt"317Content-Type: text/plain318319content1320------WebKitFormBoundaryMultiFileTest321Content-Disposition: form-data; name="documents"; filename="file2.txt"322Content-Type: text/plain323324content2325------WebKitFormBoundaryMultiFileTest--326`327328decoded, err := form.Decode(multipartData)329require.NoError(t, err)330331// Verify files are decoded correctly332documents := decoded.Get("documents")333require.NotNil(t, documents)334documentsArray, ok := documents.([]interface{})335require.True(t, ok)336require.Len(t, documentsArray, 2)337assert.Contains(t, documentsArray, "content1")338assert.Contains(t, documentsArray, "content2")339340// Verify metadata for the field exists (should be from the first file)341metadata, exists := form.GetFileMetadata("documents")342assert.True(t, exists)343assert.Equal(t, "text/plain", metadata.ContentType)344assert.Equal(t, "file1.txt", metadata.Filename) // Should be from first file, not last345}346347func TestMultiPartForm_SetFileMetadataWithNilMap(t *testing.T) {348form := &MultiPartForm{}349350// SetFileMetadata should handle nil filesMetadata351metadata := FileMetadata{352ContentType: "application/pdf",353Filename: "document.pdf",354}355form.SetFileMetadata("doc", metadata)356357// Should be able to retrieve the metadata358retrievedMetadata, exists := form.GetFileMetadata("doc")359assert.True(t, exists)360assert.Equal(t, metadata, retrievedMetadata)361}362363func TestMultiPartForm_GetFileMetadataWithNilMap(t *testing.T) {364form := &MultiPartForm{}365366// GetFileMetadata should handle nil filesMetadata gracefully367_, exists := form.GetFileMetadata("anything")368assert.False(t, exists)369}370371372