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