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