"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 emptyParams = make(map[string]string)
return &Reader{
bufReader: bufio.NewReader(reader),
+ nl: b[:2],
nlDashBoundary: b[:len(b)-2],
dashBoundaryDash: b[2:],
dashBoundary: b[2 : len(b)-2],
currentPart *Part
partsRead int
- nlDashBoundary, dashBoundaryDash, dashBoundary []byte
+ nl, nlDashBoundary, dashBoundaryDash, dashBoundary []byte
}
// NextPart returns the next part in the multipart or an error.
continue
}
- if bytes.Equal(line, []byte("\r\n")) {
- // Consume the "\r\n" separator between the
- // body of the previous part and the boundary
- // line we now expect will follow. (either a
- // new part or the end boundary)
+ // Consume the "\n" or "\r\n" separator between the
+ // body of the previous part and the boundary line we
+ // now expect will follow. (either a new part or the
+ // end boundary)
+ if bytes.Equal(line, mr.nl) {
expectNewPart = true
continue
}
if !bytes.HasPrefix(line, mr.dashBoundary) {
return false
}
- if bytes.HasSuffix(line, []byte("\r\n")) {
- return onlyHorizontalWhitespace(line[len(mr.dashBoundary) : len(line)-2])
+ if bytes.HasSuffix(line, mr.nl) {
+ return onlyHorizontalWhitespace(line[len(mr.dashBoundary) : len(line)-len(mr.nl)])
}
// Violate the spec and also support newlines without the
// carriage return...
- if bytes.HasSuffix(line, []byte("\n")) {
- return onlyHorizontalWhitespace(line[len(mr.dashBoundary) : len(line)-1])
+ if mr.partsRead == 0 && bytes.HasSuffix(line, lf) {
+ if onlyHorizontalWhitespace(line[len(mr.dashBoundary) : len(line)-1]) {
+ mr.nl = mr.nl[1:]
+ mr.nlDashBoundary = mr.nlDashBoundary[1:]
+ return true
+ }
}
return false
}
func hasPrefixThenNewline(s, prefix []byte) bool {
return bytes.HasPrefix(s, prefix) &&
(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))
}
var longLine = strings.Repeat("\n\n\r\r\r\n\r\000", (1<<20)/8)
-func testMultipartBody() string {
+func testMultipartBody(sep string) string {
testBody := `
This is a multi-part message. This line is ignored.
--MyBoundary
useless trailer
`
- testBody = strings.Replace(testBody, "\n", "\r\n", -1)
+ testBody = strings.Replace(testBody, "\n", sep, -1)
return strings.Replace(testBody, "[longline]", longLine, 1)
}
func TestMultipart(t *testing.T) {
- bodyReader := strings.NewReader(testMultipartBody())
- testMultipart(t, bodyReader)
+ bodyReader := strings.NewReader(testMultipartBody("\r\n"))
+ testMultipart(t, bodyReader, false)
+}
+
+func TestMultipartOnlyNewlines(t *testing.T) {
+ bodyReader := strings.NewReader(testMultipartBody("\n"))
+ testMultipart(t, bodyReader, true)
}
func TestMultipartSlowInput(t *testing.T) {
- bodyReader := strings.NewReader(testMultipartBody())
- testMultipart(t, &slowReader{bodyReader})
+ bodyReader := strings.NewReader(testMultipartBody("\r\n"))
+ 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")
buf := new(bytes.Buffer)
if _, err := io.Copy(buf, part); err != nil {
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
part, err = reader.NextPart()
if _, err := io.Copy(buf, part); err != nil {
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")
// Part4