Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/fuzz/dataformat/multipart.go
2843 views
1
package dataformat
2
3
import (
4
"bytes"
5
"fmt"
6
"io"
7
"mime"
8
"mime/multipart"
9
"net/textproto"
10
11
mapsutil "github.com/projectdiscovery/utils/maps"
12
)
13
14
type MultiPartForm struct {
15
boundary string
16
filesMetadata map[string]FileMetadata
17
}
18
19
type FileMetadata struct {
20
ContentType string
21
Filename string
22
}
23
24
var (
25
_ DataFormat = &MultiPartForm{}
26
)
27
28
// NewMultiPartForm returns a new MultiPartForm encoder
29
func NewMultiPartForm() *MultiPartForm {
30
return &MultiPartForm{
31
filesMetadata: make(map[string]FileMetadata),
32
}
33
}
34
35
// SetFileMetadata sets the file metadata for a given field name
36
func (m *MultiPartForm) SetFileMetadata(fieldName string, metadata FileMetadata) {
37
if m.filesMetadata == nil {
38
m.filesMetadata = make(map[string]FileMetadata)
39
}
40
41
m.filesMetadata[fieldName] = metadata
42
}
43
44
// GetFileMetadata gets the file metadata for a given field name
45
func (m *MultiPartForm) GetFileMetadata(fieldName string) (FileMetadata, bool) {
46
if m.filesMetadata == nil {
47
return FileMetadata{}, false
48
}
49
50
metadata, exists := m.filesMetadata[fieldName]
51
52
return metadata, exists
53
}
54
55
// IsType returns true if the data is MultiPartForm encoded
56
func (m *MultiPartForm) IsType(data string) bool {
57
// This method should be implemented to detect if the data is multipart form encoded
58
return false
59
}
60
61
// Encode encodes the data into MultiPartForm format
62
func (m *MultiPartForm) Encode(data KV) (string, error) {
63
var b bytes.Buffer
64
w := multipart.NewWriter(&b)
65
if err := w.SetBoundary(m.boundary); err != nil {
66
return "", err
67
}
68
69
var Itererr error
70
data.Iterate(func(key string, value any) bool {
71
var fw io.Writer
72
var err error
73
74
if fileMetadata, ok := m.filesMetadata[key]; ok {
75
if filesArray, isArray := value.([]any); isArray {
76
for _, file := range filesArray {
77
h := make(textproto.MIMEHeader)
78
h.Set("Content-Disposition",
79
fmt.Sprintf(`form-data; name=%q; filename=%q`,
80
key, fileMetadata.Filename))
81
h.Set("Content-Type", fileMetadata.ContentType)
82
83
if fw, err = w.CreatePart(h); err != nil {
84
Itererr = err
85
return false
86
}
87
88
if _, err = fw.Write([]byte(file.(string))); err != nil {
89
Itererr = err
90
return false
91
}
92
}
93
94
return true
95
}
96
}
97
98
// Add field
99
var values []string
100
switch v := value.(type) {
101
case nil:
102
values = []string{""}
103
case string:
104
values = []string{v}
105
case []string:
106
values = v
107
case []any:
108
values = make([]string, len(v))
109
for i, item := range v {
110
if item == nil {
111
values[i] = ""
112
} else {
113
values[i] = fmt.Sprint(item)
114
}
115
}
116
default:
117
values = []string{fmt.Sprintf("%v", v)}
118
}
119
120
for _, val := range values {
121
if fw, err = w.CreateFormField(key); err != nil {
122
Itererr = err
123
return false
124
}
125
if _, err = fw.Write([]byte(val)); err != nil {
126
Itererr = err
127
return false
128
}
129
}
130
return true
131
})
132
if Itererr != nil {
133
return "", Itererr
134
}
135
136
_ = w.Close()
137
return b.String(), nil
138
}
139
140
// ParseBoundary parses the boundary from the content type
141
func (m *MultiPartForm) ParseBoundary(contentType string) error {
142
_, params, err := mime.ParseMediaType(contentType)
143
if err != nil {
144
return err
145
}
146
m.boundary = params["boundary"]
147
if m.boundary == "" {
148
return fmt.Errorf("no boundary found in the content type")
149
}
150
151
// NOTE(dwisiswant0): boundary cannot exceed 70 characters according to
152
// RFC-2046.
153
if len(m.boundary) > 70 {
154
return fmt.Errorf("boundary exceeds maximum length of 70 characters")
155
}
156
157
return nil
158
}
159
160
// Decode decodes the data from MultiPartForm format
161
func (m *MultiPartForm) Decode(data string) (KV, error) {
162
if m.boundary == "" {
163
return KV{}, fmt.Errorf("boundary not set, call ParseBoundary first")
164
}
165
166
// Create a buffer from the string data
167
b := bytes.NewBufferString(data)
168
r := multipart.NewReader(b, m.boundary)
169
170
form, err := r.ReadForm(32 << 20) // 32MB is the max memory used to parse the form
171
if err != nil {
172
return KV{}, err
173
}
174
defer func() {
175
_ = form.RemoveAll()
176
}()
177
178
result := mapsutil.NewOrderedMap[string, any]()
179
for key, values := range form.Value {
180
if len(values) > 1 {
181
result.Set(key, values)
182
} else {
183
result.Set(key, values[0])
184
}
185
}
186
187
if m.filesMetadata == nil {
188
m.filesMetadata = make(map[string]FileMetadata)
189
}
190
191
for key, files := range form.File {
192
fileContents := []interface{}{}
193
var fileMetadataList []FileMetadata
194
195
for _, fileHeader := range files {
196
file, err := fileHeader.Open()
197
if err != nil {
198
return KV{}, err
199
}
200
201
buffer := new(bytes.Buffer)
202
if _, err := buffer.ReadFrom(file); err != nil {
203
_ = file.Close()
204
205
return KV{}, err
206
}
207
_ = file.Close()
208
209
fileContents = append(fileContents, buffer.String())
210
211
fileMetadataList = append(fileMetadataList, FileMetadata{
212
ContentType: fileHeader.Header.Get("Content-Type"),
213
Filename: fileHeader.Filename,
214
})
215
}
216
217
result.Set(key, fileContents)
218
219
// NOTE(dwisiswant0): store the first file's metadata instead of the
220
// last one
221
if len(fileMetadataList) > 0 {
222
m.filesMetadata[key] = fileMetadataList[0]
223
}
224
}
225
return KVOrderedMap(&result), nil
226
}
227
228
// Name returns the name of the encoder
229
func (m *MultiPartForm) Name() string {
230
return "multipart/form-data"
231
}
232
233