| // 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" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "net/textproto" |
| "os" |
| ) |
| |
| // ErrMessageTooLarge is returned by ReadForm if the message form |
| // data is too large to be processed. |
| var ErrMessageTooLarge = errors.New("multipart: message too large") |
| |
| // 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 + 10MB (reserved for non-file parts) |
| // in memory. File parts which can't be stored in memory will be stored on |
| // disk in temporary files. |
| // It returns ErrMessageTooLarge if all non-file parts can't be stored in |
| // memory. |
| 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() |
| } |
| }() |
| |
| // Reserve an additional 10 MB for non-file parts. |
| maxValueBytes := maxMemory + int64(10<<20) |
| if maxValueBytes <= 0 { |
| return nil, fmt.Errorf("multipart: integer overflow from maxMemory(%d) + 10MiB for non-file parts", maxMemory) |
| } |
| 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+1) |
| if err != nil && err != io.EOF { |
| return nil, err |
| } |
| maxValueBytes -= n |
| if maxValueBytes < 0 { |
| return nil, ErrMessageTooLarge |
| } |
| 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 |
| } |
| size, 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() |
| fh.Size = size |
| } else { |
| fh.content = b.Bytes() |
| fh.Size = int64(len(fh.content)) |
| maxMemory -= n |
| maxValueBytes -= 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 |
| Size int64 |
| |
| 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 |
| } |