Path: blob/dev/pkg/fuzz/dataformat/multipart.go
2843 views
package dataformat12import (3"bytes"4"fmt"5"io"6"mime"7"mime/multipart"8"net/textproto"910mapsutil "github.com/projectdiscovery/utils/maps"11)1213type MultiPartForm struct {14boundary string15filesMetadata map[string]FileMetadata16}1718type FileMetadata struct {19ContentType string20Filename string21}2223var (24_ DataFormat = &MultiPartForm{}25)2627// NewMultiPartForm returns a new MultiPartForm encoder28func NewMultiPartForm() *MultiPartForm {29return &MultiPartForm{30filesMetadata: make(map[string]FileMetadata),31}32}3334// SetFileMetadata sets the file metadata for a given field name35func (m *MultiPartForm) SetFileMetadata(fieldName string, metadata FileMetadata) {36if m.filesMetadata == nil {37m.filesMetadata = make(map[string]FileMetadata)38}3940m.filesMetadata[fieldName] = metadata41}4243// GetFileMetadata gets the file metadata for a given field name44func (m *MultiPartForm) GetFileMetadata(fieldName string) (FileMetadata, bool) {45if m.filesMetadata == nil {46return FileMetadata{}, false47}4849metadata, exists := m.filesMetadata[fieldName]5051return metadata, exists52}5354// IsType returns true if the data is MultiPartForm encoded55func (m *MultiPartForm) IsType(data string) bool {56// This method should be implemented to detect if the data is multipart form encoded57return false58}5960// Encode encodes the data into MultiPartForm format61func (m *MultiPartForm) Encode(data KV) (string, error) {62var b bytes.Buffer63w := multipart.NewWriter(&b)64if err := w.SetBoundary(m.boundary); err != nil {65return "", err66}6768var Itererr error69data.Iterate(func(key string, value any) bool {70var fw io.Writer71var err error7273if fileMetadata, ok := m.filesMetadata[key]; ok {74if filesArray, isArray := value.([]any); isArray {75for _, file := range filesArray {76h := make(textproto.MIMEHeader)77h.Set("Content-Disposition",78fmt.Sprintf(`form-data; name=%q; filename=%q`,79key, fileMetadata.Filename))80h.Set("Content-Type", fileMetadata.ContentType)8182if fw, err = w.CreatePart(h); err != nil {83Itererr = err84return false85}8687if _, err = fw.Write([]byte(file.(string))); err != nil {88Itererr = err89return false90}91}9293return true94}95}9697// Add field98var values []string99switch v := value.(type) {100case nil:101values = []string{""}102case string:103values = []string{v}104case []string:105values = v106case []any:107values = make([]string, len(v))108for i, item := range v {109if item == nil {110values[i] = ""111} else {112values[i] = fmt.Sprint(item)113}114}115default:116values = []string{fmt.Sprintf("%v", v)}117}118119for _, val := range values {120if fw, err = w.CreateFormField(key); err != nil {121Itererr = err122return false123}124if _, err = fw.Write([]byte(val)); err != nil {125Itererr = err126return false127}128}129return true130})131if Itererr != nil {132return "", Itererr133}134135_ = w.Close()136return b.String(), nil137}138139// ParseBoundary parses the boundary from the content type140func (m *MultiPartForm) ParseBoundary(contentType string) error {141_, params, err := mime.ParseMediaType(contentType)142if err != nil {143return err144}145m.boundary = params["boundary"]146if m.boundary == "" {147return fmt.Errorf("no boundary found in the content type")148}149150// NOTE(dwisiswant0): boundary cannot exceed 70 characters according to151// RFC-2046.152if len(m.boundary) > 70 {153return fmt.Errorf("boundary exceeds maximum length of 70 characters")154}155156return nil157}158159// Decode decodes the data from MultiPartForm format160func (m *MultiPartForm) Decode(data string) (KV, error) {161if m.boundary == "" {162return KV{}, fmt.Errorf("boundary not set, call ParseBoundary first")163}164165// Create a buffer from the string data166b := bytes.NewBufferString(data)167r := multipart.NewReader(b, m.boundary)168169form, err := r.ReadForm(32 << 20) // 32MB is the max memory used to parse the form170if err != nil {171return KV{}, err172}173defer func() {174_ = form.RemoveAll()175}()176177result := mapsutil.NewOrderedMap[string, any]()178for key, values := range form.Value {179if len(values) > 1 {180result.Set(key, values)181} else {182result.Set(key, values[0])183}184}185186if m.filesMetadata == nil {187m.filesMetadata = make(map[string]FileMetadata)188}189190for key, files := range form.File {191fileContents := []interface{}{}192var fileMetadataList []FileMetadata193194for _, fileHeader := range files {195file, err := fileHeader.Open()196if err != nil {197return KV{}, err198}199200buffer := new(bytes.Buffer)201if _, err := buffer.ReadFrom(file); err != nil {202_ = file.Close()203204return KV{}, err205}206_ = file.Close()207208fileContents = append(fileContents, buffer.String())209210fileMetadataList = append(fileMetadataList, FileMetadata{211ContentType: fileHeader.Header.Get("Content-Type"),212Filename: fileHeader.Filename,213})214}215216result.Set(key, fileContents)217218// NOTE(dwisiswant0): store the first file's metadata instead of the219// last one220if len(fileMetadataList) > 0 {221m.filesMetadata[key] = fileMetadataList[0]222}223}224return KVOrderedMap(&result), nil225}226227// Name returns the name of the encoder228func (m *MultiPartForm) Name() string {229return "multipart/form-data"230}231232233