Skip to content

Commit 1b9c1ec

Browse files
committed
mime/multipart: add field Reader.MaxMIMEHeaderSize
If the field is set, it is used instead of maxMIMEHeaderSize constant, allowing to further constraint memory usage when parsing multipart streams. Fixes #68889
1 parent 656b5b3 commit 1b9c1ec

File tree

3 files changed

+56
-3
lines changed

3 files changed

+56
-3
lines changed

src/mime/multipart/multipart.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,11 @@ func (p *Part) Close() error {
331331
// Reader's underlying parser consumes its input as needed. Seeking
332332
// isn't supported.
333333
type Reader struct {
334+
// MaxMIMEHeaderSize is the maximum size of a MIME header we will parse,
335+
// including header keys, values, and map overhead. If not set, then the
336+
// default value of 10 << 20 is used.
337+
MaxMIMEHeaderSize int64
338+
334339
bufReader *bufio.Reader
335340
tempDir string // used in tests
336341

@@ -362,14 +367,25 @@ func maxMIMEHeaders() int64 {
362367
return 10000
363368
}
364369

370+
// maxMIMEHeaderSize returns the maximum size of a MIME header we will parse.
371+
// It uses the value of Reader.MaxMIMEHeaderSize if it is not 0, otherwise the
372+
// value of maxMIMEHeaderSize constant is used.
373+
func (r *Reader) maxMIMEHeaderSize() int64 {
374+
if r.MaxMIMEHeaderSize != 0 {
375+
return r.MaxMIMEHeaderSize
376+
} else {
377+
return maxMIMEHeaderSize
378+
}
379+
}
380+
365381
// NextPart returns the next part in the multipart or an error.
366382
// When there are no more parts, the error [io.EOF] is returned.
367383
//
368384
// As a special case, if the "Content-Transfer-Encoding" header
369385
// has a value of "quoted-printable", that header is instead
370386
// hidden and the body is transparently decoded during Read calls.
371387
func (r *Reader) NextPart() (*Part, error) {
372-
return r.nextPart(false, maxMIMEHeaderSize, maxMIMEHeaders())
388+
return r.nextPart(false, r.maxMIMEHeaderSize(), maxMIMEHeaders())
373389
}
374390

375391
// NextRawPart returns the next part in the multipart or an error.
@@ -378,7 +394,7 @@ func (r *Reader) NextPart() (*Part, error) {
378394
// Unlike [Reader.NextPart], it does not have special handling for
379395
// "Content-Transfer-Encoding: quoted-printable".
380396
func (r *Reader) NextRawPart() (*Part, error) {
381-
return r.nextPart(true, maxMIMEHeaderSize, maxMIMEHeaders())
397+
return r.nextPart(true, r.maxMIMEHeaderSize(), maxMIMEHeaders())
382398
}
383399

384400
func (r *Reader) nextPart(rawPart bool, maxMIMEHeaderSize, maxMIMEHeaders int64) (*Part, error) {

src/mime/multipart/multipart_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,41 @@ Cases:
931931
}
932932
}
933933

934+
func TestParseMaxMIMEHeaderSize(t *testing.T) {
935+
sep := "MyBoundary"
936+
in := `This is a multi-part message. This line is ignored.
937+
--MyBoundary
938+
Header1: value1
939+
Header2: value2
940+
Header3: value3
941+
Header4: value4
942+
Header5: value5
943+
Header6: value6
944+
Header7: value7
945+
Header8: value8
946+
947+
My value
948+
The end.
949+
--MyBoundary--`
950+
951+
in = strings.Replace(in, "\n", "\r\n", -1)
952+
953+
// Control.
954+
r := NewReader(strings.NewReader(in), sep)
955+
_, err := r.NextPart()
956+
if err != nil {
957+
t.Fatalf("control failed: %v", err)
958+
}
959+
960+
// Test MaxMIMEHeaderSize.
961+
r = NewReader(strings.NewReader(in), sep)
962+
r.MaxMIMEHeaderSize = 100
963+
_, err = r.NextPart()
964+
if err != ErrMessageTooLarge {
965+
t.Fatalf("test failed: %v", err)
966+
}
967+
}
968+
934969
func partsFromReader(r *Reader) ([]headerBody, error) {
935970
got := []headerBody{}
936971
for {

src/net/http/request.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,8 @@ var multipartByReader = &multipart.Form{
493493
// MultipartReader returns a MIME multipart reader if this is a
494494
// multipart/form-data or a multipart/mixed POST request, else returns nil and an error.
495495
// Use this function instead of [Request.ParseMultipartForm] to
496-
// process the request body as a stream.
496+
// process the request body as a stream or to limit maximum MIME header size
497+
// using reader's field MaxMIMEHeaderSize.
497498
func (r *Request) MultipartReader() (*multipart.Reader, error) {
498499
if r.MultipartForm == multipartByReader {
499500
return nil, errors.New("http: MultipartReader called twice")
@@ -1367,6 +1368,7 @@ func (r *Request) ParseForm() error {
13671368
// If ParseForm returns an error, ParseMultipartForm returns it but also
13681369
// continues parsing the request body.
13691370
// After one call to ParseMultipartForm, subsequent calls have no effect.
1371+
// If you want to process the request body as a stream, use [MultipartReader].
13701372
func (r *Request) ParseMultipartForm(maxMemory int64) error {
13711373
if r.MultipartForm == multipartByReader {
13721374
return errors.New("http: multipart handled by MultipartReader")

0 commit comments

Comments
 (0)