summaryrefslogtreecommitdiff
path: root/libgo/go/mime/multipart/multipart.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/mime/multipart/multipart.go')
-rw-r--r--libgo/go/mime/multipart/multipart.go280
1 files changed, 280 insertions, 0 deletions
diff --git a/libgo/go/mime/multipart/multipart.go b/libgo/go/mime/multipart/multipart.go
new file mode 100644
index 000000000..1d855c74c
--- /dev/null
+++ b/libgo/go/mime/multipart/multipart.go
@@ -0,0 +1,280 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+//
+
+/*
+Package multipart implements MIME multipart parsing, as defined in RFC
+2046.
+
+The implementation is sufficient for HTTP (RFC 2388) and the multipart
+bodies generated by popular browsers.
+*/
+package multipart
+
+import (
+ "bufio"
+ "bytes"
+ "io"
+ "mime"
+ "os"
+ "regexp"
+ "strings"
+)
+
+var headerRegexp *regexp.Regexp = regexp.MustCompile("^([a-zA-Z0-9\\-]+): *([^\r\n]+)")
+
+// Reader is an iterator over parts in a MIME multipart body.
+// Reader's underlying parser consumes its input as needed. Seeking
+// isn't supported.
+type Reader interface {
+ // NextPart returns the next part in the multipart, or (nil,
+ // nil) on EOF. An error is returned if the underlying reader
+ // reports errors, or on truncated or otherwise malformed
+ // input.
+ NextPart() (*Part, os.Error)
+}
+
+// A Part represents a single part in a multipart body.
+type Part struct {
+ // The headers of the body, if any, with the keys canonicalized
+ // in the same fashion that the Go http.Request headers are.
+ // i.e. "foo-bar" changes case to "Foo-Bar"
+ Header map[string]string
+
+ buffer *bytes.Buffer
+ mr *multiReader
+}
+
+// FormName returns the name parameter if p has a Content-Disposition
+// of type "form-data". Otherwise it returns the empty string.
+func (p *Part) FormName() string {
+ // See http://tools.ietf.org/html/rfc2183 section 2 for EBNF
+ // of Content-Disposition value format.
+ v, ok := p.Header["Content-Disposition"]
+ if !ok {
+ return ""
+ }
+ d, params := mime.ParseMediaType(v)
+ if d != "form-data" {
+ return ""
+ }
+ return params["name"]
+}
+
+// NewReader creates a new multipart Reader reading from r using the
+// given MIME boundary.
+func NewReader(reader io.Reader, boundary string) Reader {
+ return &multiReader{
+ boundary: boundary,
+ dashBoundary: "--" + boundary,
+ endLine: "--" + boundary + "--",
+ bufReader: bufio.NewReader(reader),
+ }
+}
+
+// Implementation ....
+
+type devNullWriter bool
+
+func (*devNullWriter) Write(p []byte) (n int, err os.Error) {
+ return len(p), nil
+}
+
+var devNull = devNullWriter(false)
+
+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
+}
+
+func (bp *Part) populateHeaders() os.Error {
+ for {
+ line, err := bp.mr.bufReader.ReadString('\n')
+ if err != nil {
+ return err
+ }
+ if line == "\n" || line == "\r\n" {
+ return nil
+ }
+ if matches := headerRegexp.FindStringSubmatch(line); len(matches) == 3 {
+ key := matches[1]
+ value := matches[2]
+ // TODO: canonicalize headers ala http.Request.Header?
+ bp.Header[key] = value
+ continue
+ }
+ return os.NewError("Unexpected header line found parsing multipart body")
+ }
+ panic("unreachable")
+}
+
+// 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) {
+ 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
+ }
+
+ // 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)
+ }
+ return bp.buffer.Read(p)
+}
+
+func (bp *Part) Close() os.Error {
+ io.Copy(&devNull, bp)
+ return nil
+}
+
+type multiReader struct {
+ boundary string
+ dashBoundary string // --boundary
+ endLine string // --boundary--
+
+ bufferedLine *string
+
+ bufReader *bufio.Reader
+ currentPart *Part
+ partsRead int
+}
+
+func (mr *multiReader) eof() bool {
+ return mr.bufferedLine == nil &&
+ !mr.readLine()
+}
+
+func (mr *multiReader) readLine() bool {
+ line, err := mr.bufReader.ReadString('\n')
+ if err != nil {
+ // TODO: care about err being EOF or not?
+ return false
+ }
+ 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) {
+ if mr.currentPart != nil {
+ mr.currentPart.Close()
+ }
+
+ for {
+ if mr.eof() {
+ return nil, io.ErrUnexpectedEOF
+ }
+
+ if isBoundaryDelimiterLine(*mr.bufferedLine, mr.dashBoundary) {
+ mr.consumeLine()
+ mr.partsRead++
+ bp, err := newPart(mr)
+ if err != nil {
+ return nil, err
+ }
+ mr.currentPart = bp
+ return bp, nil
+ }
+
+ if hasPrefixThenNewline(*mr.bufferedLine, mr.endLine) {
+ mr.consumeLine()
+ // Expected EOF (no error)
+ return nil, nil
+ }
+
+ if mr.partsRead == 0 {
+ // skip line
+ mr.consumeLine()
+ continue
+ }
+
+ return nil, os.NewError("Unexpected line in Next().")
+ }
+ panic("unreachable")
+}
+
+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 !strings.HasPrefix(line, dashPrefix) {
+ return false
+ }
+ 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 strings.HasSuffix(line, "\n") {
+ return onlyHorizontalWhitespace(line[len(dashPrefix) : len(line)-1])
+ }
+ return false
+}
+
+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 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"))
+}