Skip to content

Commit 36fe6f2

Browse files
fclaudeaclements
authored andcommitted
[release-branch.go1.5] multipart: fixes problem parsing mime/multipart of certain lengths
When parsing the multipart data, if the delimiter appears but doesn't finish with -- or \n or \r\n, it assumes the data can be consumed. This is incorrect when the peeking buffer finishes with --delimiter- Fixes #12662 Change-Id: I329556a9a206407c0958289bf7a9009229120bb9 Reviewed-on: https://go-review.googlesource.com/14652 TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]> Reviewed-on: https://go-review.googlesource.com/16969 Run-TryBot: Austin Clements <[email protected]> Reviewed-by: Russ Cox <[email protected]>
1 parent 6a18122 commit 36fe6f2

File tree

2 files changed

+61
-3
lines changed

2 files changed

+61
-3
lines changed

src/mime/multipart/multipart.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ import (
2525

2626
var emptyParams = make(map[string]string)
2727

28+
// This constant needs to be at least 76 for this package to work correctly.
29+
// This is because \r\n--separator_of_len_70- would fill the buffer and it
30+
// wouldn't be safe to consume a single byte from it.
31+
const peekBufferSize = 4096
32+
2833
// A Part represents a single part in a multipart body.
2934
type Part struct {
3035
// The headers of the body, if any, with the keys canonicalized
@@ -91,7 +96,7 @@ func (p *Part) parseContentDisposition() {
9196
func NewReader(r io.Reader, boundary string) *Reader {
9297
b := []byte("\r\n--" + boundary + "--")
9398
return &Reader{
94-
bufReader: bufio.NewReader(r),
99+
bufReader: bufio.NewReaderSize(r, peekBufferSize),
95100
nl: b[:2],
96101
nlDashBoundary: b[:len(b)-2],
97102
dashBoundaryDash: b[2:],
@@ -148,7 +153,7 @@ func (pr partReader) Read(d []byte) (n int, err error) {
148153
// the read request. No need to parse more at the moment.
149154
return p.buffer.Read(d)
150155
}
151-
peek, err := p.mr.bufReader.Peek(4096) // TODO(bradfitz): add buffer size accessor
156+
peek, err := p.mr.bufReader.Peek(peekBufferSize) // TODO(bradfitz): add buffer size accessor
152157

153158
// Look for an immediate empty part without a leading \r\n
154159
// before the boundary separator. Some MIME code makes empty
@@ -229,6 +234,7 @@ func (r *Reader) NextPart() (*Part, error) {
229234
expectNewPart := false
230235
for {
231236
line, err := r.bufReader.ReadSlice('\n')
237+
232238
if err == io.EOF && r.isFinalBoundary(line) {
233239
// If the buffer ends in "--boundary--" without the
234240
// trailing "\r\n", ReadSlice will return an error
@@ -343,13 +349,17 @@ func (mr *Reader) peekBufferIsEmptyPart(peek []byte) bool {
343349
// peekBufferSeparatorIndex returns the index of mr.nlDashBoundary in
344350
// peek and whether it is a real boundary (and not a prefix of an
345351
// unrelated separator). To be the end, the peek buffer must contain a
346-
// newline after the boundary.
352+
// newline after the boundary or contain the ending boundary (--separator--).
347353
func (mr *Reader) peekBufferSeparatorIndex(peek []byte) (idx int, isEnd bool) {
348354
idx = bytes.Index(peek, mr.nlDashBoundary)
349355
if idx == -1 {
350356
return
351357
}
358+
352359
peek = peek[idx+len(mr.nlDashBoundary):]
360+
if len(peek) == 0 || len(peek) == 1 && peek[0] == '-' {
361+
return idx, false
362+
}
353363
if len(peek) > 1 && peek[0] == '-' && peek[1] == '-' {
354364
return idx, true
355365
}

src/mime/multipart/multipart_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,54 @@ html things
616616
},
617617
},
618618
},
619+
// Issue 12662: Check that we don't consume the leading \r if the peekBuffer
620+
// ends in '\r\n--separator-'
621+
{
622+
name: "peek buffer boundary condition",
623+
sep: "00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db",
624+
in: strings.Replace(`--00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db
625+
Content-Disposition: form-data; name="block"; filename="block"
626+
Content-Type: application/octet-stream
627+
628+
`+strings.Repeat("A", peekBufferSize-65)+"\n--00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db--", "\n", "\r\n", -1),
629+
want: []headerBody{
630+
{textproto.MIMEHeader{"Content-Type": {`application/octet-stream`}, "Content-Disposition": {`form-data; name="block"; filename="block"`}},
631+
strings.Repeat("A", peekBufferSize-65),
632+
},
633+
},
634+
},
635+
// Issue 12662: Same test as above with \r\n at the end
636+
{
637+
name: "peek buffer boundary condition",
638+
sep: "00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db",
639+
in: strings.Replace(`--00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db
640+
Content-Disposition: form-data; name="block"; filename="block"
641+
Content-Type: application/octet-stream
642+
643+
`+strings.Repeat("A", peekBufferSize-65)+"\n--00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db--\n", "\n", "\r\n", -1),
644+
want: []headerBody{
645+
{textproto.MIMEHeader{"Content-Type": {`application/octet-stream`}, "Content-Disposition": {`form-data; name="block"; filename="block"`}},
646+
strings.Repeat("A", peekBufferSize-65),
647+
},
648+
},
649+
},
650+
// Issue 12662v2: We want to make sure that for short buffers that end with
651+
// '\r\n--separator-' we always consume at least one (valid) symbol from the
652+
// peekBuffer
653+
{
654+
name: "peek buffer boundary condition",
655+
sep: "aaaaaaaaaa00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db",
656+
in: strings.Replace(`--aaaaaaaaaa00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db
657+
Content-Disposition: form-data; name="block"; filename="block"
658+
Content-Type: application/octet-stream
659+
660+
`+strings.Repeat("A", peekBufferSize)+"\n--aaaaaaaaaa00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db--", "\n", "\r\n", -1),
661+
want: []headerBody{
662+
{textproto.MIMEHeader{"Content-Type": {`application/octet-stream`}, "Content-Disposition": {`form-data; name="block"; filename="block"`}},
663+
strings.Repeat("A", peekBufferSize),
664+
},
665+
},
666+
},
619667

620668
roundTripParseTest(),
621669
}

0 commit comments

Comments
 (0)