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 "io" 11 "math" 12 "net/textproto" 13 "os" 14) 15 16// ErrMessageTooLarge is returned by ReadForm if the message form 17// data is too large to be processed. 18var ErrMessageTooLarge = errors.New("multipart: message too large") 19 20// TODO(adg,bradfitz): find a way to unify the DoS-prevention strategy here 21// with that of the http package's ParseForm. 22 23// ReadForm parses an entire multipart message whose parts have 24// a Content-Disposition of "form-data". 25// It stores up to maxMemory bytes + 10MB (reserved for non-file parts) 26// in memory. File parts which can't be stored in memory will be stored on 27// disk in temporary files. 28// It returns ErrMessageTooLarge if all non-file parts can't be stored in 29// memory. 30func (r *Reader) ReadForm(maxMemory int64) (*Form, error) { 31 return r.readForm(maxMemory) 32} 33 34func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { 35 form := &Form{make(map[string][]string), make(map[string][]*FileHeader)} 36 defer func() { 37 if err != nil { 38 form.RemoveAll() 39 } 40 }() 41 42 // Reserve an additional 10 MB for non-file parts. 43 maxValueBytes := maxMemory + int64(10<<20) 44 if maxValueBytes <= 0 { 45 if maxMemory < 0 { 46 maxValueBytes = 0 47 } else { 48 maxValueBytes = math.MaxInt64 49 } 50 } 51 for { 52 p, err := r.NextPart() 53 if err == io.EOF { 54 break 55 } 56 if err != nil { 57 return nil, err 58 } 59 60 name := p.FormName() 61 if name == "" { 62 continue 63 } 64 filename := p.FileName() 65 66 var b bytes.Buffer 67 68 if filename == "" { 69 // value, store as string in memory 70 n, err := io.CopyN(&b, p, maxValueBytes+1) 71 if err != nil && err != io.EOF { 72 return nil, err 73 } 74 maxValueBytes -= n 75 if maxValueBytes < 0 { 76 return nil, ErrMessageTooLarge 77 } 78 form.Value[name] = append(form.Value[name], b.String()) 79 continue 80 } 81 82 // file, store in memory or on disk 83 fh := &FileHeader{ 84 Filename: filename, 85 Header: p.Header, 86 } 87 n, err := io.CopyN(&b, p, maxMemory+1) 88 if err != nil && err != io.EOF { 89 return nil, err 90 } 91 if n > maxMemory { 92 // too big, write to disk and flush buffer 93 file, err := os.CreateTemp("", "multipart-") 94 if err != nil { 95 return nil, err 96 } 97 size, err := io.Copy(file, io.MultiReader(&b, p)) 98 if cerr := file.Close(); err == nil { 99 err = cerr 100 } 101 if err != nil { 102 os.Remove(file.Name()) 103 return nil, err 104 } 105 fh.tmpfile = file.Name() 106 fh.Size = size 107 } else { 108 fh.content = b.Bytes() 109 fh.Size = int64(len(fh.content)) 110 maxMemory -= n 111 maxValueBytes -= n 112 } 113 form.File[name] = append(form.File[name], fh) 114 } 115 116 return form, nil 117} 118 119// Form is a parsed multipart form. 120// Its File parts are stored either in memory or on disk, 121// and are accessible via the *FileHeader's Open method. 122// Its Value parts are stored as strings. 123// Both are keyed by field name. 124type Form struct { 125 Value map[string][]string 126 File map[string][]*FileHeader 127} 128 129// RemoveAll removes any temporary files associated with a Form. 130func (f *Form) RemoveAll() error { 131 var err error 132 for _, fhs := range f.File { 133 for _, fh := range fhs { 134 if fh.tmpfile != "" { 135 e := os.Remove(fh.tmpfile) 136 if e != nil && err == nil { 137 err = e 138 } 139 } 140 } 141 } 142 return err 143} 144 145// A FileHeader describes a file part of a multipart request. 146type FileHeader struct { 147 Filename string 148 Header textproto.MIMEHeader 149 Size int64 150 151 content []byte 152 tmpfile string 153} 154 155// Open opens and returns the FileHeader's associated File. 156func (fh *FileHeader) Open() (File, error) { 157 if b := fh.content; b != nil { 158 r := io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b))) 159 return sectionReadCloser{r}, nil 160 } 161 return os.Open(fh.tmpfile) 162} 163 164// File is an interface to access the file part of a multipart message. 165// Its contents may be either stored in memory or on disk. 166// If stored on disk, the File's underlying concrete type will be an *os.File. 167type File interface { 168 io.Reader 169 io.ReaderAt 170 io.Seeker 171 io.Closer 172} 173 174// helper types to turn a []byte into a File 175 176type sectionReadCloser struct { 177 *io.SectionReader 178} 179 180func (rc sectionReadCloser) Close() error { 181 return nil 182} 183