github.com/Kolosok86/http@v0.1.2/internal/multipart/formdata.go (about) 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 5 package multipart 6 7 import ( 8 "bytes" 9 "errors" 10 "io" 11 "math" 12 "os" 13 "strings" 14 15 "github.com/Kolosok86/http/textproto" 16 ) 17 18 // ErrMessageTooLarge is returned by ReadForm if the message form 19 // data is too large to be processed. 20 var 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. 32 func (r *Reader) ReadForm(maxMemory int64) (*Form, error) { 33 return r.readForm(maxMemory) 34 } 35 36 func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { 37 form := &Form{make(map[string][]string), make(map[string][]*FileHeader)} 38 var ( 39 file *os.File 40 fileOff int64 41 ) 42 numDiskFiles := 0 43 combineFiles := strings.Contains(os.Getenv("MULTIPARTFILES"), "1") 44 45 defer func() { 46 if file != nil { 47 if cerr := file.Close(); err == nil { 48 err = cerr 49 } 50 } 51 if combineFiles && numDiskFiles > 1 { 52 for _, fhs := range form.File { 53 for _, fh := range fhs { 54 fh.tmpshared = true 55 } 56 } 57 } 58 if err != nil { 59 form.RemoveAll() 60 if file != nil { 61 os.Remove(file.Name()) 62 } 63 } 64 }() 65 66 // maxFileMemoryBytes is the maximum bytes of file data we will store in memory. 67 // Data past this limit is written to disk. 68 // This limit strictly applies to content, not metadata (filenames, MIME headers, etc.), 69 // since metadata is always stored in memory, not disk. 70 // 71 // maxMemoryBytes is the maximum bytes we will store in memory, including file content, 72 // non-file part values, metdata, and map entry overhead. 73 // 74 // We reserve an additional 10 MB in maxMemoryBytes for non-file data. 75 // 76 // The relationship between these parameters, as well as the overly-large and 77 // unconfigurable 10 MB added on to maxMemory, is unfortunate but difficult to change 78 // within the constraints of the API as documented. 79 maxFileMemoryBytes := maxMemory 80 if maxFileMemoryBytes == math.MaxInt64 { 81 maxFileMemoryBytes-- 82 } 83 maxMemoryBytes := maxMemory + int64(10<<20) 84 if maxMemoryBytes <= 0 { 85 if maxMemory < 0 { 86 maxMemoryBytes = 0 87 } else { 88 maxMemoryBytes = math.MaxInt64 89 } 90 } 91 for { 92 p, err := r.nextPart(false, maxMemoryBytes) 93 if err == io.EOF { 94 break 95 } 96 if err != nil { 97 return nil, err 98 } 99 100 name := p.FormName() 101 if name == "" { 102 continue 103 } 104 filename := p.FileName() 105 106 // Multiple values for the same key (one map entry, longer slice) are cheaper 107 // than the same number of values for different keys (many map entries), but 108 // using a consistent per-value cost for overhead is simpler. 109 maxMemoryBytes -= int64(len(name)) 110 maxMemoryBytes -= 100 // map overhead 111 if maxMemoryBytes < 0 { 112 // We can't actually take this path, since nextPart would already have 113 // rejected the MIME headers for being too large. Check anyway. 114 return nil, ErrMessageTooLarge 115 } 116 117 var b bytes.Buffer 118 119 if filename == "" { 120 // value, store as string in memory 121 n, err := io.CopyN(&b, p, maxMemoryBytes+1) 122 if err != nil && err != io.EOF { 123 return nil, err 124 } 125 maxMemoryBytes -= n 126 if maxMemoryBytes < 0 { 127 return nil, ErrMessageTooLarge 128 } 129 form.Value[name] = append(form.Value[name], b.String()) 130 continue 131 } 132 133 // file, store in memory or on disk 134 maxMemoryBytes -= mimeHeaderSize(p.Header) 135 if maxMemoryBytes < 0 { 136 return nil, ErrMessageTooLarge 137 } 138 fh := &FileHeader{ 139 Filename: filename, 140 Header: p.Header, 141 } 142 n, err := io.CopyN(&b, p, maxFileMemoryBytes+1) 143 if err != nil && err != io.EOF { 144 return nil, err 145 } 146 if n > maxFileMemoryBytes { 147 if file == nil { 148 file, err = os.CreateTemp(r.tempDir, "multipart-") 149 if err != nil { 150 return nil, err 151 } 152 } 153 numDiskFiles++ 154 size, err := io.Copy(file, io.MultiReader(&b, p)) 155 if err != nil { 156 return nil, err 157 } 158 fh.tmpfile = file.Name() 159 fh.Size = size 160 fh.tmpoff = fileOff 161 fileOff += size 162 if !combineFiles { 163 if err := file.Close(); err != nil { 164 return nil, err 165 } 166 file = nil 167 } 168 } else { 169 fh.content = b.Bytes() 170 fh.Size = int64(len(fh.content)) 171 maxFileMemoryBytes -= n 172 maxMemoryBytes -= n 173 } 174 form.File[name] = append(form.File[name], fh) 175 } 176 177 return form, nil 178 } 179 180 func mimeHeaderSize(h textproto.MIMEHeader) (size int64) { 181 for k, vs := range h { 182 size += int64(len(k)) 183 size += 100 // map entry overhead 184 for _, v := range vs { 185 size += int64(len(v)) 186 } 187 } 188 return size 189 } 190 191 // Form is a parsed multipart form. 192 // Its File parts are stored either in memory or on disk, 193 // and are accessible via the *FileHeader's Open method. 194 // Its Value parts are stored as strings. 195 // Both are keyed by field name. 196 type Form struct { 197 Value map[string][]string 198 File map[string][]*FileHeader 199 } 200 201 // RemoveAll removes any temporary files associated with a Form. 202 func (f *Form) RemoveAll() error { 203 var err error 204 for _, fhs := range f.File { 205 for _, fh := range fhs { 206 if fh.tmpfile != "" { 207 e := os.Remove(fh.tmpfile) 208 if e != nil && !errors.Is(e, os.ErrNotExist) && err == nil { 209 err = e 210 } 211 } 212 } 213 } 214 return err 215 } 216 217 // A FileHeader describes a file part of a multipart request. 218 type FileHeader struct { 219 Filename string 220 Header textproto.MIMEHeader 221 Size int64 222 223 content []byte 224 tmpfile string 225 tmpoff int64 226 tmpshared bool 227 } 228 229 // Open opens and returns the FileHeader's associated File. 230 func (fh *FileHeader) Open() (File, error) { 231 if b := fh.content; b != nil { 232 r := io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b))) 233 return sectionReadCloser{r, nil}, nil 234 } 235 if fh.tmpshared { 236 f, err := os.Open(fh.tmpfile) 237 if err != nil { 238 return nil, err 239 } 240 r := io.NewSectionReader(f, fh.tmpoff, fh.Size) 241 return sectionReadCloser{r, f}, nil 242 } 243 return os.Open(fh.tmpfile) 244 } 245 246 // File is an interface to access the file part of a multipart message. 247 // Its contents may be either stored in memory or on disk. 248 // If stored on disk, the File's underlying concrete type will be an *os.File. 249 type File interface { 250 io.Reader 251 io.ReaderAt 252 io.Seeker 253 io.Closer 254 } 255 256 // helper types to turn a []byte into a File 257 258 type sectionReadCloser struct { 259 *io.SectionReader 260 io.Closer 261 } 262 263 func (rc sectionReadCloser) Close() error { 264 if rc.Closer != nil { 265 return rc.Closer.Close() 266 } 267 return nil 268 }