Skip to content

Commit 177458d

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 golang#68889
1 parent 4f0561f commit 177458d

File tree

2 files changed

+53
-2
lines changed

2 files changed

+53
-2
lines changed

src/mime/multipart/multipart.go

Lines changed: 18 additions & 2 deletions
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

Lines changed: 35 additions & 0 deletions
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 {

0 commit comments

Comments
 (0)