1
0
mirror of https://github.com/golang/go synced 2024-11-25 08:37:57 -07:00

mime/multipart: parse LF-delimited messages, not just CRLF

Against the spec, but appear in the wild.

Fixes #1966

R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/4662059
This commit is contained in:
Brad Fitzpatrick 2011-06-27 21:59:51 -07:00
parent 52cd055f91
commit 6282292f8d
2 changed files with 43 additions and 21 deletions

View File

@ -24,6 +24,11 @@ import (
"regexp" "regexp"
) )
// TODO(bradfitz): inline these once the compiler can inline them in
// read-only situation (such as bytes.HasSuffix)
var lf = []byte("\n")
var crlf = []byte("\r\n")
var headerRegexp *regexp.Regexp = regexp.MustCompile("^([a-zA-Z0-9\\-]+): *([^\r\n]+)") var headerRegexp *regexp.Regexp = regexp.MustCompile("^([a-zA-Z0-9\\-]+): *([^\r\n]+)")
var emptyParams = make(map[string]string) var emptyParams = make(map[string]string)
@ -81,6 +86,7 @@ func NewReader(reader io.Reader, boundary string) *Reader {
return &Reader{ return &Reader{
bufReader: bufio.NewReader(reader), bufReader: bufio.NewReader(reader),
nl: b[:2],
nlDashBoundary: b[:len(b)-2], nlDashBoundary: b[:len(b)-2],
dashBoundaryDash: b[2:], dashBoundaryDash: b[2:],
dashBoundary: b[2 : len(b)-2], dashBoundary: b[2 : len(b)-2],
@ -180,7 +186,7 @@ type Reader struct {
currentPart *Part currentPart *Part
partsRead int partsRead int
nlDashBoundary, dashBoundaryDash, dashBoundary []byte nl, nlDashBoundary, dashBoundaryDash, dashBoundary []byte
} }
// NextPart returns the next part in the multipart or an error. // NextPart returns the next part in the multipart or an error.
@ -221,11 +227,11 @@ func (mr *Reader) NextPart() (*Part, os.Error) {
continue continue
} }
if bytes.Equal(line, []byte("\r\n")) { // Consume the "\n" or "\r\n" separator between the
// Consume the "\r\n" separator between the // body of the previous part and the boundary line we
// body of the previous part and the boundary // now expect will follow. (either a new part or the
// line we now expect will follow. (either a // end boundary)
// new part or the end boundary) if bytes.Equal(line, mr.nl) {
expectNewPart = true expectNewPart = true
continue continue
} }
@ -245,13 +251,17 @@ func (mr *Reader) isBoundaryDelimiterLine(line []byte) bool {
if !bytes.HasPrefix(line, mr.dashBoundary) { if !bytes.HasPrefix(line, mr.dashBoundary) {
return false return false
} }
if bytes.HasSuffix(line, []byte("\r\n")) { if bytes.HasSuffix(line, mr.nl) {
return onlyHorizontalWhitespace(line[len(mr.dashBoundary) : len(line)-2]) return onlyHorizontalWhitespace(line[len(mr.dashBoundary) : len(line)-len(mr.nl)])
} }
// Violate the spec and also support newlines without the // Violate the spec and also support newlines without the
// carriage return... // carriage return...
if bytes.HasSuffix(line, []byte("\n")) { if mr.partsRead == 0 && bytes.HasSuffix(line, lf) {
return onlyHorizontalWhitespace(line[len(mr.dashBoundary) : len(line)-1]) if onlyHorizontalWhitespace(line[len(mr.dashBoundary) : len(line)-1]) {
mr.nl = mr.nl[1:]
mr.nlDashBoundary = mr.nlDashBoundary[1:]
return true
}
} }
return false return false
} }
@ -268,5 +278,5 @@ func onlyHorizontalWhitespace(s []byte) bool {
func hasPrefixThenNewline(s, prefix []byte) bool { func hasPrefixThenNewline(s, prefix []byte) bool {
return bytes.HasPrefix(s, prefix) && return bytes.HasPrefix(s, prefix) &&
(len(s) == len(prefix)+1 && s[len(s)-1] == '\n' || (len(s) == len(prefix)+1 && s[len(s)-1] == '\n' ||
len(s) == len(prefix)+2 && bytes.HasSuffix(s, []byte("\r\n"))) len(s) == len(prefix)+2 && bytes.HasSuffix(s, crlf))
} }

View File

@ -81,7 +81,7 @@ func TestNameAccessors(t *testing.T) {
var longLine = strings.Repeat("\n\n\r\r\r\n\r\000", (1<<20)/8) var longLine = strings.Repeat("\n\n\r\r\r\n\r\000", (1<<20)/8)
func testMultipartBody() string { func testMultipartBody(sep string) string {
testBody := ` testBody := `
This is a multi-part message. This line is ignored. This is a multi-part message. This line is ignored.
--MyBoundary --MyBoundary
@ -112,21 +112,26 @@ never read data
useless trailer useless trailer
` `
testBody = strings.Replace(testBody, "\n", "\r\n", -1) testBody = strings.Replace(testBody, "\n", sep, -1)
return strings.Replace(testBody, "[longline]", longLine, 1) return strings.Replace(testBody, "[longline]", longLine, 1)
} }
func TestMultipart(t *testing.T) { func TestMultipart(t *testing.T) {
bodyReader := strings.NewReader(testMultipartBody()) bodyReader := strings.NewReader(testMultipartBody("\r\n"))
testMultipart(t, bodyReader) testMultipart(t, bodyReader, false)
}
func TestMultipartOnlyNewlines(t *testing.T) {
bodyReader := strings.NewReader(testMultipartBody("\n"))
testMultipart(t, bodyReader, true)
} }
func TestMultipartSlowInput(t *testing.T) { func TestMultipartSlowInput(t *testing.T) {
bodyReader := strings.NewReader(testMultipartBody()) bodyReader := strings.NewReader(testMultipartBody("\r\n"))
testMultipart(t, &slowReader{bodyReader}) testMultipart(t, &slowReader{bodyReader}, false)
} }
func testMultipart(t *testing.T, r io.Reader) { func testMultipart(t *testing.T, r io.Reader, onlyNewlines bool) {
reader := NewReader(r, "MyBoundary") reader := NewReader(r, "MyBoundary")
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
@ -149,8 +154,15 @@ func testMultipart(t *testing.T, r io.Reader) {
if _, err := io.Copy(buf, part); err != nil { if _, err := io.Copy(buf, part); err != nil {
t.Errorf("part 1 copy: %v", err) t.Errorf("part 1 copy: %v", err)
} }
expectEq(t, "My value\r\nThe end.",
buf.String(), "Value of first part") adjustNewlines := func(s string) string {
if onlyNewlines {
return strings.Replace(s, "\r\n", "\n", -1)
}
return s
}
expectEq(t, adjustNewlines("My value\r\nThe end."), buf.String(), "Value of first part")
// Part2 // Part2
part, err = reader.NextPart() part, err = reader.NextPart()
@ -187,7 +199,7 @@ func testMultipart(t *testing.T, r io.Reader) {
if _, err := io.Copy(buf, part); err != nil { if _, err := io.Copy(buf, part); err != nil {
t.Errorf("part 3 copy: %v", err) t.Errorf("part 3 copy: %v", err)
} }
expectEq(t, "Line 1\r\nLine 2\r\nLine 3 ends in a newline, but just one.\r\n", expectEq(t, adjustNewlines("Line 1\r\nLine 2\r\nLine 3 ends in a newline, but just one.\r\n"),
buf.String(), "body of part 3") buf.String(), "body of part 3")
// Part4 // Part4