// 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) (f *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 } defer file.Close() _, err = io.Copy(file, io.MultiReader(&b, p)) 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 }