1// Copyright 2011 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package multipart 6 7import ( 8 "bytes" 9 "errors" 10 "internal/godebug" 11 "io" 12 "math" 13 "net/textproto" 14 "os" 15 "strconv" 16) 17 18// ErrMessageTooLarge is returned by ReadForm if the message form 19// data is too large to be processed. 20var ErrMessageTooLarge = errors.New("multipart: message too large") 21 22// TODO(adg,bradfitz): find a way to unify the DoS-prevention strategy here 23// with that of the http package's ParseForm. 24 25// ReadForm parses an entire multipart message whose parts have 26// a Content-Disposition of "form-data". 27// It stores up to maxMemory bytes + 10MB (reserved for non-file parts) 28// in memory. File parts which can't be stored in memory will be stored on 29// disk in temporary files. 30// It returns [ErrMessageTooLarge] if all non-file parts can't be stored in 31// memory. 32func (r *Reader) ReadForm(maxMemory int64) (*Form, error) { 33 return r.readForm(maxMemory) 34} 35 36var ( 37 multipartfiles = godebug.New("#multipartfiles") // TODO: document and remove # 38 multipartmaxparts = godebug.New("multipartmaxparts") 39) 40 41func (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 // multipartfiles.IncNonDefault() // TODO: uncomment after documenting 52 } 53 maxParts := 1000 54 if s := multipartmaxparts.Value(); s != "" { 55 if v, err := strconv.Atoi(s); err == nil && v >= 0 { 56 maxParts = v 57 multipartmaxparts.IncNonDefault() 58 } 59 } 60 maxHeaders := maxMIMEHeaders() 61 62 defer func() { 63 if file != nil { 64 if cerr := file.Close(); err == nil { 65 err = cerr 66 } 67 } 68 if combineFiles && numDiskFiles > 1 { 69 for _, fhs := range form.File { 70 for _, fh := range fhs { 71 fh.tmpshared = true 72 } 73 } 74 } 75 if err != nil { 76 form.RemoveAll() 77 if file != nil { 78 os.Remove(file.Name()) 79 } 80 } 81 }() 82 83 // maxFileMemoryBytes is the maximum bytes of file data we will store in memory. 84 // Data past this limit is written to disk. 85 // This limit strictly applies to content, not metadata (filenames, MIME headers, etc.), 86 // since metadata is always stored in memory, not disk. 87 // 88 // maxMemoryBytes is the maximum bytes we will store in memory, including file content, 89 // non-file part values, metadata, and map entry overhead. 90 // 91 // We reserve an additional 10 MB in maxMemoryBytes for non-file data. 92 // 93 // The relationship between these parameters, as well as the overly-large and 94 // unconfigurable 10 MB added on to maxMemory, is unfortunate but difficult to change 95 // within the constraints of the API as documented. 96 maxFileMemoryBytes := maxMemory 97 if maxFileMemoryBytes == math.MaxInt64 { 98 maxFileMemoryBytes-- 99 } 100 maxMemoryBytes := maxMemory + int64(10<<20) 101 if maxMemoryBytes <= 0 { 102 if maxMemory < 0 { 103 maxMemoryBytes = 0 104 } else { 105 maxMemoryBytes = math.MaxInt64 106 } 107 } 108 var copyBuf []byte 109 for { 110 p, err := r.nextPart(false, maxMemoryBytes, maxHeaders) 111 if err == io.EOF { 112 break 113 } 114 if err != nil { 115 return nil, err 116 } 117 if maxParts <= 0 { 118 return nil, ErrMessageTooLarge 119 } 120 maxParts-- 121 122 name := p.FormName() 123 if name == "" { 124 continue 125 } 126 filename := p.FileName() 127 128 // Multiple values for the same key (one map entry, longer slice) are cheaper 129 // than the same number of values for different keys (many map entries), but 130 // using a consistent per-value cost for overhead is simpler. 131 const mapEntryOverhead = 200 132 maxMemoryBytes -= int64(len(name)) 133 maxMemoryBytes -= mapEntryOverhead 134 if maxMemoryBytes < 0 { 135 // We can't actually take this path, since nextPart would already have 136 // rejected the MIME headers for being too large. Check anyway. 137 return nil, ErrMessageTooLarge 138 } 139 140 var b bytes.Buffer 141 142 if filename == "" { 143 // value, store as string in memory 144 n, err := io.CopyN(&b, p, maxMemoryBytes+1) 145 if err != nil && err != io.EOF { 146 return nil, err 147 } 148 maxMemoryBytes -= n 149 if maxMemoryBytes < 0 { 150 return nil, ErrMessageTooLarge 151 } 152 form.Value[name] = append(form.Value[name], b.String()) 153 continue 154 } 155 156 // file, store in memory or on disk 157 const fileHeaderSize = 100 158 maxMemoryBytes -= mimeHeaderSize(p.Header) 159 maxMemoryBytes -= mapEntryOverhead 160 maxMemoryBytes -= fileHeaderSize 161 if maxMemoryBytes < 0 { 162 return nil, ErrMessageTooLarge 163 } 164 for _, v := range p.Header { 165 maxHeaders -= int64(len(v)) 166 } 167 fh := &FileHeader{ 168 Filename: filename, 169 Header: p.Header, 170 } 171 n, err := io.CopyN(&b, p, maxFileMemoryBytes+1) 172 if err != nil && err != io.EOF { 173 return nil, err 174 } 175 if n > maxFileMemoryBytes { 176 if file == nil { 177 file, err = os.CreateTemp(r.tempDir, "multipart-") 178 if err != nil { 179 return nil, err 180 } 181 } 182 numDiskFiles++ 183 if _, err := file.Write(b.Bytes()); err != nil { 184 return nil, err 185 } 186 if copyBuf == nil { 187 copyBuf = make([]byte, 32*1024) // same buffer size as io.Copy uses 188 } 189 // os.File.ReadFrom will allocate its own copy buffer if we let io.Copy use it. 190 type writerOnly struct{ io.Writer } 191 remainingSize, err := io.CopyBuffer(writerOnly{file}, p, copyBuf) 192 if err != nil { 193 return nil, err 194 } 195 fh.tmpfile = file.Name() 196 fh.Size = int64(b.Len()) + remainingSize 197 fh.tmpoff = fileOff 198 fileOff += fh.Size 199 if !combineFiles { 200 if err := file.Close(); err != nil { 201 return nil, err 202 } 203 file = nil 204 } 205 } else { 206 fh.content = b.Bytes() 207 fh.Size = int64(len(fh.content)) 208 maxFileMemoryBytes -= n 209 maxMemoryBytes -= n 210 } 211 form.File[name] = append(form.File[name], fh) 212 } 213 214 return form, nil 215} 216 217func mimeHeaderSize(h textproto.MIMEHeader) (size int64) { 218 size = 400 219 for k, vs := range h { 220 size += int64(len(k)) 221 size += 200 // map entry overhead 222 for _, v := range vs { 223 size += int64(len(v)) 224 } 225 } 226 return size 227} 228 229// Form is a parsed multipart form. 230// Its File parts are stored either in memory or on disk, 231// and are accessible via the [*FileHeader]'s Open method. 232// Its Value parts are stored as strings. 233// Both are keyed by field name. 234type Form struct { 235 Value map[string][]string 236 File map[string][]*FileHeader 237} 238 239// RemoveAll removes any temporary files associated with a [Form]. 240func (f *Form) RemoveAll() error { 241 var err error 242 for _, fhs := range f.File { 243 for _, fh := range fhs { 244 if fh.tmpfile != "" { 245 e := os.Remove(fh.tmpfile) 246 if e != nil && !errors.Is(e, os.ErrNotExist) && err == nil { 247 err = e 248 } 249 } 250 } 251 } 252 return err 253} 254 255// A FileHeader describes a file part of a multipart request. 256type FileHeader struct { 257 Filename string 258 Header textproto.MIMEHeader 259 Size int64 260 261 content []byte 262 tmpfile string 263 tmpoff int64 264 tmpshared bool 265} 266 267// Open opens and returns the [FileHeader]'s associated File. 268func (fh *FileHeader) Open() (File, error) { 269 if b := fh.content; b != nil { 270 r := io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b))) 271 return sectionReadCloser{r, nil}, nil 272 } 273 if fh.tmpshared { 274 f, err := os.Open(fh.tmpfile) 275 if err != nil { 276 return nil, err 277 } 278 r := io.NewSectionReader(f, fh.tmpoff, fh.Size) 279 return sectionReadCloser{r, f}, nil 280 } 281 return os.Open(fh.tmpfile) 282} 283 284// File is an interface to access the file part of a multipart message. 285// Its contents may be either stored in memory or on disk. 286// If stored on disk, the File's underlying concrete type will be an *os.File. 287type File interface { 288 io.Reader 289 io.ReaderAt 290 io.Seeker 291 io.Closer 292} 293 294// helper types to turn a []byte into a File 295 296type sectionReadCloser struct { 297 *io.SectionReader 298 io.Closer 299} 300 301func (rc sectionReadCloser) Close() error { 302 if rc.Closer != nil { 303 return rc.Closer.Close() 304 } 305 return nil 306} 307