diff options
Diffstat (limited to 'src/pkg/mime/multipart/multipart.go')
-rw-r--r-- | src/pkg/mime/multipart/multipart.go | 197 |
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")) } |