1
2
3
4
5 package multipart
6
7 import (
8 "bytes"
9 "errors"
10 "internal/godebug"
11 "io"
12 "math"
13 "net/textproto"
14 "os"
15 "strconv"
16 )
17
18
19
20 var ErrMessageTooLarge = errors.New("multipart: message too large")
21
22
23
24
25
26
27
28
29
30
31
32 func (r *Reader) ReadForm(maxMemory int64) (*Form, error) {
33 return r.readForm(maxMemory)
34 }
35
36 var (
37 multipartFiles = godebug.New("multipartfiles")
38 multipartMaxParts = godebug.New("multipartmaxparts")
39 )
40
41 func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) {
42 form := &Form{make(map[string][]string), make(map[string][]*FileHeader)}
43 var (
44 file *os.File
45 fileOff int64
46 )
47 numDiskFiles := 0
48 combineFiles := true
49 if multipartFiles.Value() == "distinct" {
50 combineFiles = false
51 }
52 maxParts := 1000
53 if s := multipartMaxParts.Value(); s != "" {
54 if v, err := strconv.Atoi(s); err == nil && v >= 0 {
55 maxParts = v
56 }
57 }
58 maxHeaders := maxMIMEHeaders()
59
60 defer func() {
61 if file != nil {
62 if cerr := file.Close(); err == nil {
63 err = cerr
64 }
65 }
66 if combineFiles && numDiskFiles > 1 {
67 for _, fhs := range form.File {
68 for _, fh := range fhs {
69 fh.tmpshared = true
70 }
71 }
72 }
73 if err != nil {
74 form.RemoveAll()
75 if file != nil {
76 os.Remove(file.Name())
77 }
78 }
79 }()
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94 maxFileMemoryBytes := maxMemory
95 maxMemoryBytes := maxMemory + int64(10<<20)
96 if maxMemoryBytes <= 0 {
97 if maxMemory < 0 {
98 maxMemoryBytes = 0
99 } else {
100 maxMemoryBytes = math.MaxInt64
101 }
102 }
103 var copyBuf []byte
104 for {
105 p, err := r.nextPart(false, maxMemoryBytes, maxHeaders)
106 if err == io.EOF {
107 break
108 }
109 if err != nil {
110 return nil, err
111 }
112 if maxParts <= 0 {
113 return nil, ErrMessageTooLarge
114 }
115 maxParts--
116
117 name := p.FormName()
118 if name == "" {
119 continue
120 }
121 filename := p.FileName()
122
123
124
125
126 const mapEntryOverhead = 200
127 maxMemoryBytes -= int64(len(name))
128 maxMemoryBytes -= mapEntryOverhead
129 if maxMemoryBytes < 0 {
130
131
132 return nil, ErrMessageTooLarge
133 }
134
135 var b bytes.Buffer
136
137 if filename == "" {
138
139 n, err := io.CopyN(&b, p, maxMemoryBytes+1)
140 if err != nil && err != io.EOF {
141 return nil, err
142 }
143 maxMemoryBytes -= n
144 if maxMemoryBytes < 0 {
145 return nil, ErrMessageTooLarge
146 }
147 form.Value[name] = append(form.Value[name], b.String())
148 continue
149 }
150
151
152 const fileHeaderSize = 100
153 maxMemoryBytes -= mimeHeaderSize(p.Header)
154 maxMemoryBytes -= mapEntryOverhead
155 maxMemoryBytes -= fileHeaderSize
156 if maxMemoryBytes < 0 {
157 return nil, ErrMessageTooLarge
158 }
159 for _, v := range p.Header {
160 maxHeaders -= int64(len(v))
161 }
162 fh := &FileHeader{
163 Filename: filename,
164 Header: p.Header,
165 }
166 n, err := io.CopyN(&b, p, maxFileMemoryBytes+1)
167 if err != nil && err != io.EOF {
168 return nil, err
169 }
170 if n > maxFileMemoryBytes {
171 if file == nil {
172 file, err = os.CreateTemp(r.tempDir, "multipart-")
173 if err != nil {
174 return nil, err
175 }
176 }
177 numDiskFiles++
178 if _, err := file.Write(b.Bytes()); err != nil {
179 return nil, err
180 }
181 if copyBuf == nil {
182 copyBuf = make([]byte, 32*1024)
183 }
184
185 type writerOnly struct{ io.Writer }
186 remainingSize, err := io.CopyBuffer(writerOnly{file}, p, copyBuf)
187 if err != nil {
188 return nil, err
189 }
190 fh.tmpfile = file.Name()
191 fh.Size = int64(b.Len()) + remainingSize
192 fh.tmpoff = fileOff
193 fileOff += fh.Size
194 if !combineFiles {
195 if err := file.Close(); err != nil {
196 return nil, err
197 }
198 file = nil
199 }
200 } else {
201 fh.content = b.Bytes()
202 fh.Size = int64(len(fh.content))
203 maxFileMemoryBytes -= n
204 maxMemoryBytes -= n
205 }
206 form.File[name] = append(form.File[name], fh)
207 }
208
209 return form, nil
210 }
211
212 func mimeHeaderSize(h textproto.MIMEHeader) (size int64) {
213 size = 400
214 for k, vs := range h {
215 size += int64(len(k))
216 size += 200
217 for _, v := range vs {
218 size += int64(len(v))
219 }
220 }
221 return size
222 }
223
224
225
226
227
228
229 type Form struct {
230 Value map[string][]string
231 File map[string][]*FileHeader
232 }
233
234
235 func (f *Form) RemoveAll() error {
236 var err error
237 for _, fhs := range f.File {
238 for _, fh := range fhs {
239 if fh.tmpfile != "" {
240 e := os.Remove(fh.tmpfile)
241 if e != nil && !errors.Is(e, os.ErrNotExist) && err == nil {
242 err = e
243 }
244 }
245 }
246 }
247 return err
248 }
249
250
251 type FileHeader struct {
252 Filename string
253 Header textproto.MIMEHeader
254 Size int64
255
256 content []byte
257 tmpfile string
258 tmpoff int64
259 tmpshared bool
260 }
261
262
263 func (fh *FileHeader) Open() (File, error) {
264 if b := fh.content; b != nil {
265 r := io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b)))
266 return sectionReadCloser{r, nil}, nil
267 }
268 if fh.tmpshared {
269 f, err := os.Open(fh.tmpfile)
270 if err != nil {
271 return nil, err
272 }
273 r := io.NewSectionReader(f, fh.tmpoff, fh.Size)
274 return sectionReadCloser{r, f}, nil
275 }
276 return os.Open(fh.tmpfile)
277 }
278
279
280
281
282 type File interface {
283 io.Reader
284 io.ReaderAt
285 io.Seeker
286 io.Closer
287 }
288
289
290
291 type sectionReadCloser struct {
292 *io.SectionReader
293 io.Closer
294 }
295
296 func (rc sectionReadCloser) Close() error {
297 if rc.Closer != nil {
298 return rc.Closer.Close()
299 }
300 return nil
301 }
302
View as plain text