| // 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" | |
| "math" | |
| "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 { | |
| if maxMemory < 0 { | |
| maxValueBytes = 0 | |
| } else { | |
| maxValueBytes = math.MaxInt64 | |
| } | |
| } | |
| 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 := os.CreateTemp("", "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 | |
| } |