summaryrefslogtreecommitdiff
path: root/src/pkg/mime/multipart/multipart.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/mime/multipart/multipart.go')
-rw-r--r--src/pkg/mime/multipart/multipart.go197
1 files changed, 105 insertions, 92 deletions
diff --git a/src/pkg/mime/multipart/multipart.go b/src/pkg/mime/multipart/multipart.go
index 60329fe17..e0b747c3f 100644
--- a/src/pkg/mime/multipart/multipart.go
+++ b/src/pkg/mime/multipart/multipart.go
@@ -15,13 +15,13 @@ package multipart
import (
"bufio"
"bytes"
- "fmt"
"io"
"io/ioutil"
"mime"
"net/textproto"
"os"
"regexp"
+ "strings"
)
var headerRegexp *regexp.Regexp = regexp.MustCompile("^([a-zA-Z0-9\\-]+): *([^\r\n]+)")
@@ -79,28 +79,25 @@ func (p *Part) FormName() string {
// NewReader creates a new multipart Reader reading from r using the
// given MIME boundary.
func NewReader(reader io.Reader, boundary string) Reader {
- b := []byte("\r\n--" + boundary + "--")
return &multiReader{
- bufReader: bufio.NewReader(reader),
-
- nlDashBoundary: b[:len(b)-2],
- dashBoundaryDash: b[2:],
- dashBoundary: b[2 : len(b)-2],
+ boundary: boundary,
+ dashBoundary: "--" + boundary,
+ endLine: "--" + boundary + "--",
+ bufReader: bufio.NewReader(reader),
}
}
// Implementation ....
-func newPart(mr *multiReader) (*Part, os.Error) {
- bp := &Part{
- Header: make(map[string][]string),
- mr: mr,
- buffer: new(bytes.Buffer),
- }
- if err := bp.populateHeaders(); err != nil {
- return nil, err
+func newPart(mr *multiReader) (bp *Part, err os.Error) {
+ bp = new(Part)
+ bp.Header = make(map[string][]string)
+ bp.mr = mr
+ bp.buffer = new(bytes.Buffer)
+ if err = bp.populateHeaders(); err != nil {
+ bp = nil
}
- return bp, nil
+ return
}
func (bp *Part) populateHeaders() os.Error {
@@ -125,49 +122,44 @@ func (bp *Part) populateHeaders() os.Error {
// Read reads the body of a part, after its headers and before the
// next part (if any) begins.
func (bp *Part) Read(p []byte) (n int, err os.Error) {
- if bp.buffer.Len() >= len(p) {
- // Internal buffer of unconsumed data is large enough for
- // the read request. No need to parse more at the moment.
- return bp.buffer.Read(p)
- }
- peek, err := bp.mr.bufReader.Peek(4096) // TODO(bradfitz): add buffer size accessor
- unexpectedEof := err == os.EOF
- if err != nil && !unexpectedEof {
- return 0, fmt.Errorf("multipart: Part Read: %v", err)
- }
- if peek == nil {
- panic("nil peek buf")
- }
+ for {
+ if bp.buffer.Len() >= len(p) {
+ // Internal buffer of unconsumed data is large enough for
+ // the read request. No need to parse more at the moment.
+ break
+ }
+ if !bp.mr.ensureBufferedLine() {
+ return 0, io.ErrUnexpectedEOF
+ }
+ if bp.mr.bufferedLineIsBoundary() {
+ // Don't consume this line
+ break
+ }
- // Search the peek buffer for "\r\n--boundary". If found,
- // consume everything up to the boundary. If not, consume only
- // as much of the peek buffer as cannot hold the boundary
- // string.
- nCopy := 0
- foundBoundary := false
- if idx := bytes.Index(peek, bp.mr.nlDashBoundary); idx != -1 {
- nCopy = idx
- foundBoundary = true
- } else if safeCount := len(peek) - len(bp.mr.nlDashBoundary); safeCount > 0 {
- nCopy = safeCount
- } else if unexpectedEof {
- // If we've run out of peek buffer and the boundary
- // wasn't found (and can't possibly fit), we must have
- // hit the end of the file unexpectedly.
- return 0, io.ErrUnexpectedEOF
- }
- if nCopy > 0 {
- if _, err := io.Copyn(bp.buffer, bp.mr.bufReader, int64(nCopy)); err != nil {
- return 0, err
+ // Write all of this line, except the final CRLF
+ s := *bp.mr.bufferedLine
+ if strings.HasSuffix(s, "\r\n") {
+ bp.mr.consumeLine()
+ if !bp.mr.ensureBufferedLine() {
+ return 0, io.ErrUnexpectedEOF
+ }
+ if bp.mr.bufferedLineIsBoundary() {
+ // The final \r\n isn't ours. It logically belongs
+ // to the boundary line which follows.
+ bp.buffer.WriteString(s[0 : len(s)-2])
+ } else {
+ bp.buffer.WriteString(s)
+ }
+ break
}
+ if strings.HasSuffix(s, "\n") {
+ bp.buffer.WriteString(s)
+ bp.mr.consumeLine()
+ continue
+ }
+ return 0, os.NewError("multipart parse error during Read; unexpected line: " + s)
}
- n, err = bp.buffer.Read(p)
- if err == os.EOF && !foundBoundary {
- // If the boundary hasn't been reached there's more to
- // read, so don't pass through an EOF from the buffer
- err = nil
- }
- return
+ return bp.buffer.Read(p)
}
func (bp *Part) Close() os.Error {
@@ -176,12 +168,46 @@ func (bp *Part) Close() os.Error {
}
type multiReader struct {
- bufReader *bufio.Reader
+ boundary string
+ dashBoundary string // --boundary
+ endLine string // --boundary--
+ bufferedLine *string
+
+ bufReader *bufio.Reader
currentPart *Part
partsRead int
+}
- nlDashBoundary, dashBoundaryDash, dashBoundary []byte
+func (mr *multiReader) eof() bool {
+ return mr.bufferedLine == nil &&
+ !mr.readLine()
+}
+
+func (mr *multiReader) readLine() bool {
+ lineBytes, err := mr.bufReader.ReadSlice('\n')
+ if err != nil {
+ // TODO: care about err being EOF or not?
+ return false
+ }
+ line := string(lineBytes)
+ mr.bufferedLine = &line
+ return true
+}
+
+func (mr *multiReader) bufferedLineIsBoundary() bool {
+ return strings.HasPrefix(*mr.bufferedLine, mr.dashBoundary)
+}
+
+func (mr *multiReader) ensureBufferedLine() bool {
+ if mr.bufferedLine == nil {
+ return mr.readLine()
+ }
+ return true
+}
+
+func (mr *multiReader) consumeLine() {
+ mr.bufferedLine = nil
}
func (mr *multiReader) NextPart() (*Part, os.Error) {
@@ -189,14 +215,13 @@ func (mr *multiReader) NextPart() (*Part, os.Error) {
mr.currentPart.Close()
}
- expectNewPart := false
for {
- line, err := mr.bufReader.ReadSlice('\n')
- if err != nil {
- return nil, fmt.Errorf("multipart: NextPart: %v", err)
+ if mr.eof() {
+ return nil, io.ErrUnexpectedEOF
}
- if mr.isBoundaryDelimiterLine(line) {
+ if isBoundaryDelimiterLine(*mr.bufferedLine, mr.dashBoundary) {
+ mr.consumeLine()
mr.partsRead++
bp, err := newPart(mr)
if err != nil {
@@ -206,67 +231,55 @@ func (mr *multiReader) NextPart() (*Part, os.Error) {
return bp, nil
}
- if hasPrefixThenNewline(line, mr.dashBoundaryDash) {
+ if hasPrefixThenNewline(*mr.bufferedLine, mr.endLine) {
+ mr.consumeLine()
// Expected EOF (no error)
- // TODO(bradfitz): should return an os.EOF error here, not using nil for errors
return nil, nil
}
- if expectNewPart {
- return nil, fmt.Errorf("multipart: expecting a new Part; got line %q", string(line))
- }
-
if mr.partsRead == 0 {
// skip line
+ mr.consumeLine()
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)
- expectNewPart = true
- continue
- }
-
- return nil, fmt.Errorf("multipart: unexpected line in Next(): %q", line)
+ return nil, os.NewError("Unexpected line in Next().")
}
panic("unreachable")
}
-func (mr *multiReader) isBoundaryDelimiterLine(line []byte) bool {
+func isBoundaryDelimiterLine(line, dashPrefix string) bool {
// http://tools.ietf.org/html/rfc2046#section-5.1
// The boundary delimiter line is then defined as a line
// consisting entirely of two hyphen characters ("-",
// decimal value 45) followed by the boundary parameter
// value from the Content-Type header field, optional linear
// whitespace, and a terminating CRLF.
- if !bytes.HasPrefix(line, mr.dashBoundary) {
+ if !strings.HasPrefix(line, dashPrefix) {
return false
}
- if bytes.HasSuffix(line, []byte("\r\n")) {
- return onlyHorizontalWhitespace(line[len(mr.dashBoundary) : len(line)-2])
+ if strings.HasSuffix(line, "\r\n") {
+ return onlyHorizontalWhitespace(line[len(dashPrefix) : len(line)-2])
}
// 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 strings.HasSuffix(line, "\n") {
+ return onlyHorizontalWhitespace(line[len(dashPrefix) : len(line)-1])
}
return false
}
-func onlyHorizontalWhitespace(s []byte) bool {
- for _, b := range s {
- if b != ' ' && b != '\t' {
+func onlyHorizontalWhitespace(s string) bool {
+ for i := 0; i < len(s); i++ {
+ if s[i] != ' ' && s[i] != '\t' {
return false
}
}
return true
}
-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")))
+func hasPrefixThenNewline(s, prefix string) bool {
+ return strings.HasPrefix(s, prefix) &&
+ (len(s) == len(prefix)+1 && strings.HasSuffix(s, "\n") ||
+ len(s) == len(prefix)+2 && strings.HasSuffix(s, "\r\n"))
}