github.com/tidwall/go@v0.0.0-20170415222209-6694a6888b7d/src/mime/multipart/formdata.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package multipart
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"io"
    11  	"io/ioutil"
    12  	"net/textproto"
    13  	"os"
    14  )
    15  
    16  // TODO(adg,bradfitz): find a way to unify the DoS-prevention strategy here
    17  // with that of the http package's ParseForm.
    18  
    19  // ReadForm parses an entire multipart message whose parts have
    20  // a Content-Disposition of "form-data".
    21  // It stores up to maxMemory bytes of the file parts in memory
    22  // and the remainder on disk in temporary files.
    23  func (r *Reader) ReadForm(maxMemory int64) (*Form, error) {
    24  	return r.readForm(maxMemory)
    25  }
    26  
    27  func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) {
    28  	form := &Form{make(map[string][]string), make(map[string][]*FileHeader)}
    29  	defer func() {
    30  		if err != nil {
    31  			form.RemoveAll()
    32  		}
    33  	}()
    34  
    35  	maxValueBytes := int64(10 << 20) // 10 MB is a lot of text.
    36  	for {
    37  		p, err := r.NextPart()
    38  		if err == io.EOF {
    39  			break
    40  		}
    41  		if err != nil {
    42  			return nil, err
    43  		}
    44  
    45  		name := p.FormName()
    46  		if name == "" {
    47  			continue
    48  		}
    49  		filename := p.FileName()
    50  
    51  		var b bytes.Buffer
    52  
    53  		if filename == "" {
    54  			// value, store as string in memory
    55  			n, err := io.CopyN(&b, p, maxValueBytes)
    56  			if err != nil && err != io.EOF {
    57  				return nil, err
    58  			}
    59  			maxValueBytes -= n
    60  			if maxValueBytes == 0 {
    61  				return nil, errors.New("multipart: message too large")
    62  			}
    63  			form.Value[name] = append(form.Value[name], b.String())
    64  			continue
    65  		}
    66  
    67  		// file, store in memory or on disk
    68  		fh := &FileHeader{
    69  			Filename: filename,
    70  			Header:   p.Header,
    71  		}
    72  		n, err := io.CopyN(&b, p, maxMemory+1)
    73  		if err != nil && err != io.EOF {
    74  			return nil, err
    75  		}
    76  		if n > maxMemory {
    77  			// too big, write to disk and flush buffer
    78  			file, err := ioutil.TempFile("", "multipart-")
    79  			if err != nil {
    80  				return nil, err
    81  			}
    82  			size, err := io.Copy(file, io.MultiReader(&b, p))
    83  			if cerr := file.Close(); err == nil {
    84  				err = cerr
    85  			}
    86  			if err != nil {
    87  				os.Remove(file.Name())
    88  				return nil, err
    89  			}
    90  			fh.tmpfile = file.Name()
    91  			fh.Size = size
    92  		} else {
    93  			fh.content = b.Bytes()
    94  			fh.Size = int64(len(fh.content))
    95  			maxMemory -= n
    96  		}
    97  		form.File[name] = append(form.File[name], fh)
    98  	}
    99  
   100  	return form, nil
   101  }
   102  
   103  // Form is a parsed multipart form.
   104  // Its File parts are stored either in memory or on disk,
   105  // and are accessible via the *FileHeader's Open method.
   106  // Its Value parts are stored as strings.
   107  // Both are keyed by field name.
   108  type Form struct {
   109  	Value map[string][]string
   110  	File  map[string][]*FileHeader
   111  }
   112  
   113  // RemoveAll removes any temporary files associated with a Form.
   114  func (f *Form) RemoveAll() error {
   115  	var err error
   116  	for _, fhs := range f.File {
   117  		for _, fh := range fhs {
   118  			if fh.tmpfile != "" {
   119  				e := os.Remove(fh.tmpfile)
   120  				if e != nil && err == nil {
   121  					err = e
   122  				}
   123  			}
   124  		}
   125  	}
   126  	return err
   127  }
   128  
   129  // A FileHeader describes a file part of a multipart request.
   130  type FileHeader struct {
   131  	Filename string
   132  	Header   textproto.MIMEHeader
   133  	Size     int64
   134  
   135  	content []byte
   136  	tmpfile string
   137  }
   138  
   139  // Open opens and returns the FileHeader's associated File.
   140  func (fh *FileHeader) Open() (File, error) {
   141  	if b := fh.content; b != nil {
   142  		r := io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b)))
   143  		return sectionReadCloser{r}, nil
   144  	}
   145  	return os.Open(fh.tmpfile)
   146  }
   147  
   148  // File is an interface to access the file part of a multipart message.
   149  // Its contents may be either stored in memory or on disk.
   150  // If stored on disk, the File's underlying concrete type will be an *os.File.
   151  type File interface {
   152  	io.Reader
   153  	io.ReaderAt
   154  	io.Seeker
   155  	io.Closer
   156  }
   157  
   158  // helper types to turn a []byte into a File
   159  
   160  type sectionReadCloser struct {
   161  	*io.SectionReader
   162  }
   163  
   164  func (rc sectionReadCloser) Close() error {
   165  	return nil
   166  }