158 lines
3.5 KiB
Go
158 lines
3.5 KiB
Go
|
// 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
|
||
|
}
|