// 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" ) // 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) 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 _, hasContentTypeHeader := p.Header["Content-Type"] if !hasContentTypeHeader && 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 }