Home | History | Annotate | Download | only in multipart
      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 			_, 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 		} else {
     92 			fh.content = b.Bytes()
     93 			maxMemory -= n
     94 		}
     95 		form.File[name] = append(form.File[name], fh)
     96 	}
     97 
     98 	return form, nil
     99 }
    100 
    101 // Form is a parsed multipart form.
    102 // Its File parts are stored either in memory or on disk,
    103 // and are accessible via the *FileHeader's Open method.
    104 // Its Value parts are stored as strings.
    105 // Both are keyed by field name.
    106 type Form struct {
    107 	Value map[string][]string
    108 	File  map[string][]*FileHeader
    109 }
    110 
    111 // RemoveAll removes any temporary files associated with a Form.
    112 func (f *Form) RemoveAll() error {
    113 	var err error
    114 	for _, fhs := range f.File {
    115 		for _, fh := range fhs {
    116 			if fh.tmpfile != "" {
    117 				e := os.Remove(fh.tmpfile)
    118 				if e != nil && err == nil {
    119 					err = e
    120 				}
    121 			}
    122 		}
    123 	}
    124 	return err
    125 }
    126 
    127 // A FileHeader describes a file part of a multipart request.
    128 type FileHeader struct {
    129 	Filename string
    130 	Header   textproto.MIMEHeader
    131 
    132 	content []byte
    133 	tmpfile string
    134 }
    135 
    136 // Open opens and returns the FileHeader's associated File.
    137 func (fh *FileHeader) Open() (File, error) {
    138 	if b := fh.content; b != nil {
    139 		r := io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b)))
    140 		return sectionReadCloser{r}, nil
    141 	}
    142 	return os.Open(fh.tmpfile)
    143 }
    144 
    145 // File is an interface to access the file part of a multipart message.
    146 // Its contents may be either stored in memory or on disk.
    147 // If stored on disk, the File's underlying concrete type will be an *os.File.
    148 type File interface {
    149 	io.Reader
    150 	io.ReaderAt
    151 	io.Seeker
    152 	io.Closer
    153 }
    154 
    155 // helper types to turn a []byte into a File
    156 
    157 type sectionReadCloser struct {
    158 	*io.SectionReader
    159 }
    160 
    161 func (rc sectionReadCloser) Close() error {
    162 	return nil
    163 }
    164