Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/fuzz/dataformat/multipart_test.go
2843 views
1
package dataformat
2
3
import (
4
"testing"
5
6
mapsutil "github.com/projectdiscovery/utils/maps"
7
"github.com/stretchr/testify/assert"
8
"github.com/stretchr/testify/require"
9
)
10
11
func TestMultiPartFormEncode(t *testing.T) {
12
tests := []struct {
13
name string
14
fields map[string]any
15
wantErr bool
16
expected map[string]any
17
}{
18
{
19
name: "duplicate fields ([]string) - checkbox scenario",
20
fields: map[string]any{
21
"interests": []string{"sports", "music", "reading"},
22
"colors": []string{"red", "blue"},
23
},
24
expected: map[string]any{
25
"interests": []string{"sports", "music", "reading"},
26
"colors": []string{"red", "blue"},
27
},
28
},
29
{
30
name: "single string fields - backward compatibility",
31
fields: map[string]any{
32
"username": "john",
33
"email": "[email protected]",
34
},
35
expected: map[string]any{
36
"username": "john",
37
"email": "[email protected]",
38
},
39
},
40
{
41
name: "mixed types",
42
fields: map[string]any{
43
"string": "text",
44
"array": []string{"item1", "item2"},
45
"number": 42, // tests fmt.Sprint fallback
46
"float": 3.14, // tests float conversion
47
"boolean": true, // tests boolean conversion
48
"zero": 0, // tests zero value
49
"emptyStr": "", // tests empty string
50
"negative": -123, // tests negative number
51
"nil": nil, // tests nil value
52
"mixedArray": []any{"str", 123, false, nil}, // tests mixed type array
53
},
54
expected: map[string]any{
55
"string": "text",
56
"array": []string{"item1", "item2"},
57
"number": "42", // numbers are converted to strings in multipart
58
"float": "3.14", // floats are converted to strings
59
"boolean": "true", // booleans are converted to strings
60
"zero": "0", // zero value converted to string
61
"emptyStr": "", // empty string remains empty
62
"negative": "-123", // negative numbers converted to strings
63
"nil": "", // nil values converted to "" string
64
"mixedArray": []string{"str", "123", "false", ""}, // mixed array converted to string array
65
},
66
},
67
{
68
name: "empty array - should not appear in output",
69
fields: map[string]any{
70
"emptyArray": []string{},
71
"normalField": "value",
72
},
73
expected: map[string]any{
74
"normalField": "value",
75
// emptyArray should not appear in decoded output
76
},
77
},
78
}
79
80
for _, tt := range tests {
81
t.Run(tt.name, func(t *testing.T) {
82
defer func() {
83
if r := recover(); r != nil {
84
t.Errorf("Test panicked: %v", r)
85
}
86
}()
87
88
form := NewMultiPartForm()
89
form.boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"
90
91
kv := mapsutil.NewOrderedMap[string, any]()
92
for k, v := range tt.fields {
93
kv.Set(k, v)
94
}
95
96
encoded, err := form.Encode(KVOrderedMap(&kv))
97
98
if tt.wantErr {
99
require.Error(t, err)
100
return
101
}
102
103
require.NoError(t, err)
104
105
// Decode the encoded multipart data
106
decoded, err := form.Decode(encoded)
107
require.NoError(t, err)
108
109
// Compare decoded values with expected values
110
for expectedKey, expectedValue := range tt.expected {
111
actualValue := decoded.Get(expectedKey)
112
switch expected := expectedValue.(type) {
113
case []string:
114
actual, ok := actualValue.([]string)
115
require.True(t, ok, "Expected []string for key %s, got %T", expectedKey, actualValue)
116
assert.ElementsMatch(t, expected, actual, "Values mismatch for key %s", expectedKey)
117
case []any:
118
actual, ok := actualValue.([]any)
119
require.True(t, ok, "Expected []any for key %s, got %T", expectedKey, actualValue)
120
assert.ElementsMatch(t, expected, actual, "Values mismatch for key %s", expectedKey)
121
case string:
122
actual, ok := actualValue.(string)
123
require.True(t, ok, "Expected string for key %s, got %T", expectedKey, actualValue)
124
assert.Equal(t, expected, actual, "Values mismatch for key %s", expectedKey)
125
default:
126
assert.Equal(t, expected, actualValue, "Values mismatch for key %s", expectedKey)
127
}
128
}
129
130
// Ensure no unexpected keys are present in decoded output
131
decoded.Iterate(func(key string, value any) bool {
132
_, exists := tt.expected[key]
133
assert.True(t, exists, "Unexpected key %s found in decoded output", key)
134
return true
135
})
136
137
t.Logf("Encoded output:\n%s", encoded)
138
})
139
}
140
}
141
142
func TestMultiPartFormRoundTrip(t *testing.T) {
143
defer func() {
144
if r := recover(); r != nil {
145
t.Errorf("Test panicked: %v", r)
146
}
147
}()
148
149
form := NewMultiPartForm()
150
form.boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"
151
152
original := mapsutil.NewOrderedMap[string, any]()
153
original.Set("username", "john")
154
original.Set("interests", []string{"sports", "music", "reading"})
155
156
encoded, err := form.Encode(KVOrderedMap(&original))
157
require.NoError(t, err)
158
159
decoded, err := form.Decode(encoded)
160
require.NoError(t, err)
161
162
assert.Equal(t, "john", decoded.Get("username"))
163
assert.ElementsMatch(t, []string{"sports", "music", "reading"}, decoded.Get("interests"))
164
165
t.Logf("Encoded output:\n%s", encoded)
166
}
167
168
func TestMultiPartFormFileUpload(t *testing.T) {
169
defer func() {
170
if r := recover(); r != nil {
171
t.Errorf("Test panicked: %v", r)
172
}
173
}()
174
175
// Test decoding of a manually crafted multipart form with files
176
form := NewMultiPartForm()
177
form.boundary = "----WebKitFormBoundaryFileUploadTest"
178
179
// Manually craft a multipart form with file uploads
180
multipartData := `------WebKitFormBoundaryFileUploadTest
181
Content-Disposition: form-data; name="name"
182
183
John Doe
184
------WebKitFormBoundaryFileUploadTest
185
Content-Disposition: form-data; name="email"
186
187
[email protected]
188
------WebKitFormBoundaryFileUploadTest
189
Content-Disposition: form-data; name="profile_picture"; filename="profile.jpg"
190
Content-Type: image/jpeg
191
192
fake_jpeg_binary_data_here
193
------WebKitFormBoundaryFileUploadTest
194
Content-Disposition: form-data; name="documents"; filename="resume.pdf"
195
Content-Type: application/pdf
196
197
fake_pdf_content_1
198
------WebKitFormBoundaryFileUploadTest
199
Content-Disposition: form-data; name="documents"; filename="cover_letter.pdf"
200
Content-Type: application/pdf
201
202
fake_pdf_content_2
203
------WebKitFormBoundaryFileUploadTest
204
Content-Disposition: form-data; name="skills"
205
206
Go
207
------WebKitFormBoundaryFileUploadTest
208
Content-Disposition: form-data; name="skills"
209
210
JavaScript
211
------WebKitFormBoundaryFileUploadTest
212
Content-Disposition: form-data; name="skills"
213
214
Python
215
------WebKitFormBoundaryFileUploadTest--
216
`
217
218
// Test decoding
219
decoded, err := form.Decode(multipartData)
220
require.NoError(t, err)
221
222
// Verify regular fields
223
assert.Equal(t, "John Doe", decoded.Get("name"))
224
assert.Equal(t, "[email protected]", decoded.Get("email"))
225
assert.Equal(t, []string{"Go", "JavaScript", "Python"}, decoded.Get("skills"))
226
227
// Verify file fields
228
profilePicture := decoded.Get("profile_picture")
229
require.NotNil(t, profilePicture)
230
profileArray, ok := profilePicture.([]interface{})
231
require.True(t, ok, "Expected []interface{} for profile_picture")
232
require.Len(t, profileArray, 1)
233
assert.Equal(t, "fake_jpeg_binary_data_here", profileArray[0])
234
235
documents := decoded.Get("documents")
236
require.NotNil(t, documents)
237
documentsArray, ok := documents.([]interface{})
238
require.True(t, ok, "Expected []interface{} for documents")
239
require.Len(t, documentsArray, 2)
240
assert.Contains(t, documentsArray, "fake_pdf_content_1")
241
assert.Contains(t, documentsArray, "fake_pdf_content_2")
242
}
243
244
func TestMultiPartForm_SetGetFileMetadata(t *testing.T) {
245
form := NewMultiPartForm()
246
metadata := FileMetadata{
247
ContentType: "image/jpeg",
248
Filename: "test.jpg",
249
}
250
form.SetFileMetadata("avatar", metadata)
251
252
// Test GetFileMetadata for existing field
253
retrievedMetadata, exists := form.GetFileMetadata("avatar")
254
assert.True(t, exists)
255
assert.Equal(t, metadata.ContentType, retrievedMetadata.ContentType)
256
assert.Equal(t, metadata.Filename, retrievedMetadata.Filename)
257
258
// Test GetFileMetadata for non-existing field
259
_, exists = form.GetFileMetadata("nonexistent")
260
assert.False(t, exists)
261
}
262
263
func TestMultiPartForm_FilesMetadataInitialization(t *testing.T) {
264
form := NewMultiPartForm()
265
assert.NotNil(t, form.filesMetadata)
266
267
metadata := FileMetadata{
268
ContentType: "text/plain",
269
Filename: "test.txt",
270
}
271
form.SetFileMetadata("file", metadata)
272
273
retrievedMetadata, exists := form.GetFileMetadata("file")
274
assert.True(t, exists)
275
assert.Equal(t, metadata, retrievedMetadata)
276
}
277
278
func TestMultiPartForm_BoundaryValidation(t *testing.T) {
279
form := NewMultiPartForm()
280
281
// Test valid boundary
282
err := form.ParseBoundary("multipart/form-data; boundary=testboundary")
283
assert.NoError(t, err)
284
assert.Equal(t, "testboundary", form.boundary)
285
286
// Test missing boundary
287
err = form.ParseBoundary("multipart/form-data")
288
assert.Error(t, err)
289
assert.Contains(t, err.Error(), "no boundary found")
290
291
// Test boundary too long (over 70 characters)
292
longBoundary := "multipart/form-data; boundary=" + string(make([]byte, 71))
293
for i := range longBoundary[len("multipart/form-data; boundary="):] {
294
longBoundary = longBoundary[:len("multipart/form-data; boundary=")+i] + "a" + longBoundary[len("multipart/form-data; boundary=")+i+1:]
295
}
296
297
err = form.ParseBoundary(longBoundary)
298
assert.Error(t, err)
299
assert.Contains(t, err.Error(), "boundary exceeds maximum length")
300
}
301
302
func TestMultiPartForm_DecodeRequiresBoundary(t *testing.T) {
303
form := NewMultiPartForm()
304
305
// Decode should fail if boundary is not set
306
_, err := form.Decode("some data")
307
assert.Error(t, err)
308
assert.Contains(t, err.Error(), "boundary not set")
309
}
310
311
func TestMultiPartForm_MultipleFilesMetadata(t *testing.T) {
312
form := NewMultiPartForm()
313
form.boundary = "----WebKitFormBoundaryMultiFileTest"
314
315
// Test with multiple files having the same field name
316
multipartData := `------WebKitFormBoundaryMultiFileTest
317
Content-Disposition: form-data; name="documents"; filename="file1.txt"
318
Content-Type: text/plain
319
320
content1
321
------WebKitFormBoundaryMultiFileTest
322
Content-Disposition: form-data; name="documents"; filename="file2.txt"
323
Content-Type: text/plain
324
325
content2
326
------WebKitFormBoundaryMultiFileTest--
327
`
328
329
decoded, err := form.Decode(multipartData)
330
require.NoError(t, err)
331
332
// Verify files are decoded correctly
333
documents := decoded.Get("documents")
334
require.NotNil(t, documents)
335
documentsArray, ok := documents.([]interface{})
336
require.True(t, ok)
337
require.Len(t, documentsArray, 2)
338
assert.Contains(t, documentsArray, "content1")
339
assert.Contains(t, documentsArray, "content2")
340
341
// Verify metadata for the field exists (should be from the first file)
342
metadata, exists := form.GetFileMetadata("documents")
343
assert.True(t, exists)
344
assert.Equal(t, "text/plain", metadata.ContentType)
345
assert.Equal(t, "file1.txt", metadata.Filename) // Should be from first file, not last
346
}
347
348
func TestMultiPartForm_SetFileMetadataWithNilMap(t *testing.T) {
349
form := &MultiPartForm{}
350
351
// SetFileMetadata should handle nil filesMetadata
352
metadata := FileMetadata{
353
ContentType: "application/pdf",
354
Filename: "document.pdf",
355
}
356
form.SetFileMetadata("doc", metadata)
357
358
// Should be able to retrieve the metadata
359
retrievedMetadata, exists := form.GetFileMetadata("doc")
360
assert.True(t, exists)
361
assert.Equal(t, metadata, retrievedMetadata)
362
}
363
364
func TestMultiPartForm_GetFileMetadataWithNilMap(t *testing.T) {
365
form := &MultiPartForm{}
366
367
// GetFileMetadata should handle nil filesMetadata gracefully
368
_, exists := form.GetFileMetadata("anything")
369
assert.False(t, exists)
370
}
371
372