Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 54 additions & 10 deletions pkg/fuzz/dataformat/multipart.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,29 @@ var (

// NewMultiPartForm returns a new MultiPartForm encoder
func NewMultiPartForm() *MultiPartForm {
return &MultiPartForm{}
return &MultiPartForm{
filesMetadata: make(map[string]FileMetadata),
}
}

// SetFileMetadata sets the file metadata for a given field name
func (m *MultiPartForm) SetFileMetadata(fieldName string, metadata FileMetadata) {
if m.filesMetadata == nil {
m.filesMetadata = make(map[string]FileMetadata)
}

m.filesMetadata[fieldName] = metadata
}

// GetFileMetadata gets the file metadata for a given field name
func (m *MultiPartForm) GetFileMetadata(fieldName string) (FileMetadata, bool) {
if m.filesMetadata == nil {
return FileMetadata{}, false
}

metadata, exists := m.filesMetadata[fieldName]

return metadata, exists
}

// IsType returns true if the data is MultiPartForm encoded
Expand Down Expand Up @@ -125,16 +147,24 @@ func (m *MultiPartForm) ParseBoundary(contentType string) error {
if m.boundary == "" {
return fmt.Errorf("no boundary found in the content type")
}

// NOTE(dwisiswant0): boundary cannot exceed 70 characters according to
// RFC-2046.
if len(m.boundary) > 70 {
return fmt.Errorf("boundary exceeds maximum length of 70 characters")
}

return nil
}

// Decode decodes the data from MultiPartForm format
func (m *MultiPartForm) Decode(data string) (KV, error) {
if m.boundary == "" {
return KV{}, fmt.Errorf("boundary not set, call ParseBoundary first")
}

// Create a buffer from the string data
b := bytes.NewBufferString(data)
// The boundary parameter should be extracted from the Content-Type header of the HTTP request
// which is not available in this context, so this is a placeholder for demonstration.
// You will need to pass the actual boundary value to this function.
r := multipart.NewReader(b, m.boundary)

form, err := r.ReadForm(32 << 20) // 32MB is the max memory used to parse the form
Expand All @@ -153,30 +183,44 @@ func (m *MultiPartForm) Decode(data string) (KV, error) {
result.Set(key, values[0])
}
}
m.filesMetadata = make(map[string]FileMetadata)

if m.filesMetadata == nil {
m.filesMetadata = make(map[string]FileMetadata)
}

for key, files := range form.File {
fileContents := []interface{}{}
var fileMetadataList []FileMetadata

for _, fileHeader := range files {
file, err := fileHeader.Open()
if err != nil {
return KV{}, err
}
defer func() {
_ = file.Close()
}()

buffer := new(bytes.Buffer)
if _, err := buffer.ReadFrom(file); err != nil {
_ = file.Close()

return KV{}, err
}
_ = file.Close()

fileContents = append(fileContents, buffer.String())

m.filesMetadata[key] = FileMetadata{
fileMetadataList = append(fileMetadataList, FileMetadata{
ContentType: fileHeader.Header.Get("Content-Type"),
Filename: fileHeader.Filename,
}
})
}

result.Set(key, fileContents)

// NOTE(dwisiswant0): store the first file's metadata instead of the
// last one
if len(fileMetadataList) > 0 {
m.filesMetadata[key] = fileMetadataList[0]
}
}
return KVOrderedMap(&result), nil
}
Expand Down
128 changes: 128 additions & 0 deletions pkg/fuzz/dataformat/multipart_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,131 @@ Python
assert.Contains(t, documentsArray, "fake_pdf_content_1")
assert.Contains(t, documentsArray, "fake_pdf_content_2")
}

func TestMultiPartForm_SetGetFileMetadata(t *testing.T) {
form := NewMultiPartForm()
metadata := FileMetadata{
ContentType: "image/jpeg",
Filename: "test.jpg",
}
form.SetFileMetadata("avatar", metadata)

// Test GetFileMetadata for existing field
retrievedMetadata, exists := form.GetFileMetadata("avatar")
assert.True(t, exists)
assert.Equal(t, metadata.ContentType, retrievedMetadata.ContentType)
assert.Equal(t, metadata.Filename, retrievedMetadata.Filename)

// Test GetFileMetadata for non-existing field
_, exists = form.GetFileMetadata("nonexistent")
assert.False(t, exists)
}

func TestMultiPartForm_FilesMetadataInitialization(t *testing.T) {
form := NewMultiPartForm()
assert.NotNil(t, form.filesMetadata)

metadata := FileMetadata{
ContentType: "text/plain",
Filename: "test.txt",
}
form.SetFileMetadata("file", metadata)

retrievedMetadata, exists := form.GetFileMetadata("file")
assert.True(t, exists)
assert.Equal(t, metadata, retrievedMetadata)
}

func TestMultiPartForm_BoundaryValidation(t *testing.T) {
form := NewMultiPartForm()

// Test valid boundary
err := form.ParseBoundary("multipart/form-data; boundary=testboundary")
assert.NoError(t, err)
assert.Equal(t, "testboundary", form.boundary)

// Test missing boundary
err = form.ParseBoundary("multipart/form-data")
assert.Error(t, err)
assert.Contains(t, err.Error(), "no boundary found")

// Test boundary too long (over 70 characters)
longBoundary := "multipart/form-data; boundary=" + string(make([]byte, 71))
for i := range longBoundary[len("multipart/form-data; boundary="):] {
longBoundary = longBoundary[:len("multipart/form-data; boundary=")+i] + "a" + longBoundary[len("multipart/form-data; boundary=")+i+1:]
}

err = form.ParseBoundary(longBoundary)
assert.Error(t, err)
assert.Contains(t, err.Error(), "boundary exceeds maximum length")
}

func TestMultiPartForm_DecodeRequiresBoundary(t *testing.T) {
form := NewMultiPartForm()

// Decode should fail if boundary is not set
_, err := form.Decode("some data")
assert.Error(t, err)
assert.Contains(t, err.Error(), "boundary not set")
}

func TestMultiPartForm_MultipleFilesMetadata(t *testing.T) {
form := NewMultiPartForm()
form.boundary = "----WebKitFormBoundaryMultiFileTest"

// Test with multiple files having the same field name
multipartData := `------WebKitFormBoundaryMultiFileTest
Content-Disposition: form-data; name="documents"; filename="file1.txt"
Content-Type: text/plain

content1
------WebKitFormBoundaryMultiFileTest
Content-Disposition: form-data; name="documents"; filename="file2.txt"
Content-Type: text/plain

content2
------WebKitFormBoundaryMultiFileTest--
`

decoded, err := form.Decode(multipartData)
require.NoError(t, err)

// Verify files are decoded correctly
documents := decoded.Get("documents")
require.NotNil(t, documents)
documentsArray, ok := documents.([]interface{})
require.True(t, ok)
require.Len(t, documentsArray, 2)
assert.Contains(t, documentsArray, "content1")
assert.Contains(t, documentsArray, "content2")

// Verify metadata for the field exists (should be from the first file)
metadata, exists := form.GetFileMetadata("documents")
assert.True(t, exists)
assert.Equal(t, "text/plain", metadata.ContentType)
assert.Equal(t, "file1.txt", metadata.Filename) // Should be from first file, not last
}

func TestMultiPartForm_SetFileMetadataWithNilMap(t *testing.T) {
form := &MultiPartForm{}

// SetFileMetadata should handle nil filesMetadata
metadata := FileMetadata{
ContentType: "application/pdf",
Filename: "document.pdf",
}
form.SetFileMetadata("doc", metadata)

// Should be able to retrieve the metadata
retrievedMetadata, exists := form.GetFileMetadata("doc")
assert.True(t, exists)
assert.Equal(t, metadata, retrievedMetadata)
}

func TestMultiPartForm_GetFileMetadataWithNilMap(t *testing.T) {
form := &MultiPartForm{}

// GetFileMetadata should handle nil filesMetadata gracefully
_, exists := form.GetFileMetadata("anything")
assert.False(t, exists)
}
Loading