Skip to content

Commit

Permalink
mime/multipart: add field Reader.MaxMIMEHeaderSize
Browse files Browse the repository at this point in the history
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
  • Loading branch information
starius committed Dec 19, 2024
1 parent 4f0561f commit 177458d
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 2 deletions.
20 changes: 18 additions & 2 deletions src/mime/multipart/multipart.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,11 @@ func (p *Part) Close() error {
// Reader's underlying parser consumes its input as needed. Seeking
// isn't supported.
type Reader struct {
// MaxMIMEHeaderSize is the maximum size of a MIME header we will parse,
// including header keys, values, and map overhead. If not set, then the
// default value of 10 << 20 is used.
MaxMIMEHeaderSize int64

bufReader *bufio.Reader
tempDir string // used in tests

Expand Down Expand Up @@ -362,14 +367,25 @@ func maxMIMEHeaders() int64 {
return 10000
}

// maxMIMEHeaderSize returns the maximum size of a MIME header we will parse.
// It uses the value of Reader.MaxMIMEHeaderSize if it is not 0, otherwise the
// value of maxMIMEHeaderSize constant is used.
func (r *Reader) maxMIMEHeaderSize() int64 {
if r.MaxMIMEHeaderSize != 0 {
return r.MaxMIMEHeaderSize
} else {
return maxMIMEHeaderSize
}
}

// NextPart returns the next part in the multipart or an error.
// When there are no more parts, the error [io.EOF] is returned.
//
// As a special case, if the "Content-Transfer-Encoding" header
// has a value of "quoted-printable", that header is instead
// hidden and the body is transparently decoded during Read calls.
func (r *Reader) NextPart() (*Part, error) {
return r.nextPart(false, maxMIMEHeaderSize, maxMIMEHeaders())
return r.nextPart(false, r.maxMIMEHeaderSize(), maxMIMEHeaders())
}

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

func (r *Reader) nextPart(rawPart bool, maxMIMEHeaderSize, maxMIMEHeaders int64) (*Part, error) {
Expand Down
35 changes: 35 additions & 0 deletions src/mime/multipart/multipart_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,41 @@ Cases:
}
}

func TestParseMaxMIMEHeaderSize(t *testing.T) {
sep := "MyBoundary"
in := `This is a multi-part message. This line is ignored.
--MyBoundary
Header1: value1
Header2: value2
Header3: value3
Header4: value4
Header5: value5
Header6: value6
Header7: value7
Header8: value8
My value
The end.
--MyBoundary--`

in = strings.Replace(in, "\n", "\r\n", -1)

// Control.
r := NewReader(strings.NewReader(in), sep)
_, err := r.NextPart()
if err != nil {
t.Fatalf("control failed: %v", err)
}

// Test MaxMIMEHeaderSize.
r = NewReader(strings.NewReader(in), sep)
r.MaxMIMEHeaderSize = 100
_, err = r.NextPart()
if err != ErrMessageTooLarge {
t.Fatalf("test failed: %v", err)
}
}

func partsFromReader(r *Reader) ([]headerBody, error) {
got := []headerBody{}
for {
Expand Down

0 comments on commit 177458d

Please sign in to comment.