|  | // Copyright 2011 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package multipart | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "errors" | 
|  | "io" | 
|  | "io/ioutil" | 
|  | "net/textproto" | 
|  | "os" | 
|  | ) | 
|  |  | 
|  | // TODO(adg,bradfitz): find a way to unify the DoS-prevention strategy here | 
|  | // with that of the http package's ParseForm. | 
|  |  | 
|  | // ReadForm parses an entire multipart message whose parts have | 
|  | // a Content-Disposition of "form-data". | 
|  | // It stores up to maxMemory bytes of the file parts in memory | 
|  | // and the remainder on disk in temporary files. | 
|  | func (r *Reader) ReadForm(maxMemory int64) (*Form, error) { | 
|  | return r.readForm(maxMemory) | 
|  | } | 
|  |  | 
|  | func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { | 
|  | form := &Form{make(map[string][]string), make(map[string][]*FileHeader)} | 
|  | defer func() { | 
|  | if err != nil { | 
|  | form.RemoveAll() | 
|  | } | 
|  | }() | 
|  |  | 
|  | maxValueBytes := int64(10 << 20) // 10 MB is a lot of text. | 
|  | for { | 
|  | p, err := r.NextPart() | 
|  | if err == io.EOF { | 
|  | break | 
|  | } | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  |  | 
|  | name := p.FormName() | 
|  | if name == "" { | 
|  | continue | 
|  | } | 
|  | filename := p.FileName() | 
|  |  | 
|  | var b bytes.Buffer | 
|  |  | 
|  | if filename == "" { | 
|  | // value, store as string in memory | 
|  | n, err := io.CopyN(&b, p, maxValueBytes) | 
|  | if err != nil && err != io.EOF { | 
|  | return nil, err | 
|  | } | 
|  | maxValueBytes -= n | 
|  | if maxValueBytes == 0 { | 
|  | return nil, errors.New("multipart: message too large") | 
|  | } | 
|  | form.Value[name] = append(form.Value[name], b.String()) | 
|  | continue | 
|  | } | 
|  |  | 
|  | // file, store in memory or on disk | 
|  | fh := &FileHeader{ | 
|  | Filename: filename, | 
|  | Header:   p.Header, | 
|  | } | 
|  | n, err := io.CopyN(&b, p, maxMemory+1) | 
|  | if err != nil && err != io.EOF { | 
|  | return nil, err | 
|  | } | 
|  | if n > maxMemory { | 
|  | // too big, write to disk and flush buffer | 
|  | file, err := ioutil.TempFile("", "multipart-") | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | _, err = io.Copy(file, io.MultiReader(&b, p)) | 
|  | if cerr := file.Close(); err == nil { | 
|  | err = cerr | 
|  | } | 
|  | if err != nil { | 
|  | os.Remove(file.Name()) | 
|  | return nil, err | 
|  | } | 
|  | fh.tmpfile = file.Name() | 
|  | } else { | 
|  | fh.content = b.Bytes() | 
|  | maxMemory -= n | 
|  | } | 
|  | form.File[name] = append(form.File[name], fh) | 
|  | } | 
|  |  | 
|  | return form, nil | 
|  | } | 
|  |  | 
|  | // Form is a parsed multipart form. | 
|  | // Its File parts are stored either in memory or on disk, | 
|  | // and are accessible via the *FileHeader's Open method. | 
|  | // Its Value parts are stored as strings. | 
|  | // Both are keyed by field name. | 
|  | type Form struct { | 
|  | Value map[string][]string | 
|  | File  map[string][]*FileHeader | 
|  | } | 
|  |  | 
|  | // RemoveAll removes any temporary files associated with a Form. | 
|  | func (f *Form) RemoveAll() error { | 
|  | var err error | 
|  | for _, fhs := range f.File { | 
|  | for _, fh := range fhs { | 
|  | if fh.tmpfile != "" { | 
|  | e := os.Remove(fh.tmpfile) | 
|  | if e != nil && err == nil { | 
|  | err = e | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return err | 
|  | } | 
|  |  | 
|  | // A FileHeader describes a file part of a multipart request. | 
|  | type FileHeader struct { | 
|  | Filename string | 
|  | Header   textproto.MIMEHeader | 
|  |  | 
|  | content []byte | 
|  | tmpfile string | 
|  | } | 
|  |  | 
|  | // Open opens and returns the FileHeader's associated File. | 
|  | func (fh *FileHeader) Open() (File, error) { | 
|  | if b := fh.content; b != nil { | 
|  | r := io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b))) | 
|  | return sectionReadCloser{r}, nil | 
|  | } | 
|  | return os.Open(fh.tmpfile) | 
|  | } | 
|  |  | 
|  | // File is an interface to access the file part of a multipart message. | 
|  | // Its contents may be either stored in memory or on disk. | 
|  | // If stored on disk, the File's underlying concrete type will be an *os.File. | 
|  | type File interface { | 
|  | io.Reader | 
|  | io.ReaderAt | 
|  | io.Seeker | 
|  | io.Closer | 
|  | } | 
|  |  | 
|  | // helper types to turn a []byte into a File | 
|  |  | 
|  | type sectionReadCloser struct { | 
|  | *io.SectionReader | 
|  | } | 
|  |  | 
|  | func (rc sectionReadCloser) Close() error { | 
|  | return nil | 
|  | } |