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
5package multipart
6
7import (
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.
18var 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.
30func (r *Reader) ReadForm(maxMemory int64) (*Form, error) {
31	return r.readForm(maxMemory)
32}
33
34func (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.
118type Form struct {
119	Value map[string][]string
120	File  map[string][]*FileHeader
121}
122
123// RemoveAll removes any temporary files associated with a Form.
124func (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.
140type 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.
150func (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.
161type 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
170type sectionReadCloser struct {
171	*io.SectionReader
172}
173
174func (rc sectionReadCloser) Close() error {
175	return nil
176}
177