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 "io/ioutil" 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 for { 45 p, err := r.NextPart() 46 if err == io.EOF { 47 break 48 } 49 if err != nil { 50 return nil, err 51 } 52 53 name := p.FormName() 54 if name == "" { 55 continue 56 } 57 filename := p.FileName() 58 59 var b bytes.Buffer 60 61 if filename == "" { 62 // value, store as string in memory 63 n, err := io.CopyN(&b, p, maxValueBytes+1) 64 if err != nil && err != io.EOF { 65 return nil, err 66 } 67 maxValueBytes -= n 68 if maxValueBytes < 0 { 69 return nil, ErrMessageTooLarge 70 } 71 form.Value[name] = append(form.Value[name], b.String()) 72 continue 73 } 74 75 // file, store in memory or on disk 76 fh := &FileHeader{ 77 Filename: filename, 78 Header: p.Header, 79 } 80 n, err := io.CopyN(&b, p, maxMemory+1) 81 if err != nil && err != io.EOF { 82 return nil, err 83 } 84 if n > maxMemory { 85 // too big, write to disk and flush buffer 86 file, err := ioutil.TempFile("", "multipart-") 87 if err != nil { 88 return nil, err 89 } 90 size, err := io.Copy(file, io.MultiReader(&b, p)) 91 if cerr := file.Close(); err == nil { 92 err = cerr 93 } 94 if err != nil { 95 os.Remove(file.Name()) 96 return nil, err 97 } 98 fh.tmpfile = file.Name() 99 fh.Size = size 100 } else { 101 fh.content = b.Bytes() 102 fh.Size = int64(len(fh.content)) 103 maxMemory -= n 104 maxValueBytes -= n 105 } 106 form.File[name] = append(form.File[name], fh) 107 } 108 109 return form, nil 110} 111 112// Form is a parsed multipart form. 113// Its File parts are stored either in memory or on disk, 114// and are accessible via the *FileHeader's Open method. 115// Its Value parts are stored as strings. 116// Both are keyed by field name. 117type Form struct { 118 Value map[string][]string 119 File map[string][]*FileHeader 120} 121 122// RemoveAll removes any temporary files associated with a Form. 123func (f *Form) RemoveAll() error { 124 var err error 125 for _, fhs := range f.File { 126 for _, fh := range fhs { 127 if fh.tmpfile != "" { 128 e := os.Remove(fh.tmpfile) 129 if e != nil && err == nil { 130 err = e 131 } 132 } 133 } 134 } 135 return err 136} 137 138// A FileHeader describes a file part of a multipart request. 139type FileHeader struct { 140 Filename string 141 Header textproto.MIMEHeader 142 Size int64 143 144 content []byte 145 tmpfile string 146} 147 148// Open opens and returns the FileHeader's associated File. 149func (fh *FileHeader) Open() (File, error) { 150 if b := fh.content; b != nil { 151 r := io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b))) 152 return sectionReadCloser{r}, nil 153 } 154 return os.Open(fh.tmpfile) 155} 156 157// File is an interface to access the file part of a multipart message. 158// Its contents may be either stored in memory or on disk. 159// If stored on disk, the File's underlying concrete type will be an *os.File. 160type File interface { 161 io.Reader 162 io.ReaderAt 163 io.Seeker 164 io.Closer 165} 166 167// helper types to turn a []byte into a File 168 169type sectionReadCloser struct { 170 *io.SectionReader 171} 172 173func (rc sectionReadCloser) Close() error { 174 return nil 175} 176