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