diff --git a/src/mime/multipart/multipart_test.go b/src/mime/multipart/multipart_test.go index d74ef61b88..7fbee90ac3 100644 --- a/src/mime/multipart/multipart_test.go +++ b/src/mime/multipart/multipart_test.go @@ -324,6 +324,73 @@ func (s *slowReader) Read(p []byte) (int, error) { return s.r.Read(p[:1]) } +type sentinelReader struct { + // done is closed when this reader is read from. + done chan struct{} +} + +func (s *sentinelReader) Read([]byte) (int, error) { + if s.done != nil { + close(s.done) + s.done = nil + } + return 0, io.EOF +} + +// TestMultipartStreamReadahead tests that PartReader does not block +// on reading past the end of a part, ensuring that it can be used on +// a stream like multipart/x-mixed-replace. See golang.org/issue/15431 +func TestMultipartStreamReadahead(t *testing.T) { + testBody1 := ` +This is a multi-part message. This line is ignored. +--MyBoundary +foo-bar: baz + +Body +--MyBoundary +` + testBody2 := `foo-bar: bop + +Body 2 +--MyBoundary-- +` + done1 := make(chan struct{}) + reader := NewReader( + io.MultiReader( + strings.NewReader(testBody1), + &sentinelReader{done1}, + strings.NewReader(testBody2)), + "MyBoundary") + + var i int + readPart := func(hdr textproto.MIMEHeader, body string) { + part, err := reader.NextPart() + if part == nil || err != nil { + t.Fatalf("Part %d: NextPart failed: %v", i, err) + } + + if !reflect.DeepEqual(part.Header, hdr) { + t.Errorf("Part %d: part.Header = %v, want %v", i, part.Header, hdr) + } + data, err := ioutil.ReadAll(part) + expectEq(t, body, string(data), fmt.Sprintf("Part %d body", i)) + if err != nil { + t.Fatalf("Part %d: ReadAll failed: %v", i, err) + } + i++ + } + + readPart(textproto.MIMEHeader{"Foo-Bar": {"baz"}}, "Body") + + select { + case <-done1: + t.Errorf("Reader read past second boundary") + default: + } + + readPart(textproto.MIMEHeader{"Foo-Bar": {"bop"}}, "Body 2") +} + func TestLineContinuation(t *testing.T) { // This body, extracted from an email, contains headers that span multiple // lines.