| // 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" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "mime" |
| "mime/quotedprintable" |
| "net/textproto" |
| "strings" |
| ) |
| |
| var emptyParams = make(map[string]string) |
| |
| // This constant needs to be at least 76 for this package to work correctly. |
| // This is because \r\n--separator_of_len_70- would fill the buffer and it |
| // wouldn't be safe to consume a single byte from it. |
| const peekBufferSize = 4096 |
| |
| // 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. |
| // For example, "foo-bar" changes case to "Foo-Bar" |
| // |
| // As a special case, if the "Content-Transfer-Encoding" header |
| // has a value of "quoted-printable", that header is instead |
| // hidden from this map and the body is transparently decoded |
| // during Read calls. |
| Header textproto.MIMEHeader |
| |
| mr *Reader |
| |
| disposition string |
| dispositionParams map[string]string |
| |
| // r is either a reader directly reading from mr, or it's a |
| // wrapper around such a reader, decoding the |
| // Content-Transfer-Encoding |
| r io.Reader |
| |
| n int // known data bytes waiting in mr.bufReader |
| total int64 // total data bytes read already |
| err error // error to return when n == 0 |
| readErr error // read error observed from mr.bufReader |
| } |
| |
| // 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 https://tools.ietf.org/html/rfc2183 section 2 for EBNF |
| // of Content-Disposition value format. |
| if p.dispositionParams == nil { |
| p.parseContentDisposition() |
| } |
| if p.disposition != "form-data" { |
| return "" |
| } |
| return p.dispositionParams["name"] |
| } |
| |
| // FileName returns the filename parameter of the Part's |
| // Content-Disposition header. |
| func (p *Part) FileName() string { |
| if p.dispositionParams == nil { |
| p.parseContentDisposition() |
| } |
| return p.dispositionParams["filename"] |
| } |
| |
| func (p *Part) parseContentDisposition() { |
| v := p.Header.Get("Content-Disposition") |
| var err error |
| p.disposition, p.dispositionParams, err = mime.ParseMediaType(v) |
| if err != nil { |
| p.dispositionParams = emptyParams |
| } |
| } |
| |
| // NewReader creates a new multipart Reader reading from r using the |
| // given MIME boundary. |
| // |
| // The boundary is usually obtained from the "boundary" parameter of |
| // the message's "Content-Type" header. Use mime.ParseMediaType to |
| // parse such headers. |
| func NewReader(r io.Reader, boundary string) *Reader { |
| b := []byte("\r\n--" + boundary + "--") |
| return &Reader{ |
| bufReader: bufio.NewReaderSize(&stickyErrorReader{r: r}, peekBufferSize), |
| nl: b[:2], |
| nlDashBoundary: b[:len(b)-2], |
| dashBoundaryDash: b[2:], |
| dashBoundary: b[2 : len(b)-2], |
| } |
| } |
| |
| // stickyErrorReader is an io.Reader which never calls Read on its |
| // underlying Reader once an error has been seen. (the io.Reader |
| // interface's contract promises nothing about the return values of |
| // Read calls after an error, yet this package does do multiple Reads |
| // after error) |
| type stickyErrorReader struct { |
| r io.Reader |
| err error |
| } |
| |
| func (r *stickyErrorReader) Read(p []byte) (n int, _ error) { |
| if r.err != nil { |
| return 0, r.err |
| } |
| n, r.err = r.r.Read(p) |
| return n, r.err |
| } |
| |
| func newPart(mr *Reader) (*Part, error) { |
| bp := &Part{ |
| Header: make(map[string][]string), |
| mr: mr, |
| } |
| if err := bp.populateHeaders(); err != nil { |
| return nil, err |
| } |
| bp.r = partReader{bp} |
| const cte = "Content-Transfer-Encoding" |
| if strings.EqualFold(bp.Header.Get(cte), "quoted-printable") { |
| bp.Header.Del(cte) |
| bp.r = quotedprintable.NewReader(bp.r) |
| } |
| return bp, nil |
| } |
| |
| func (bp *Part) populateHeaders() error { |
| r := textproto.NewReader(bp.mr.bufReader) |
| header, err := r.ReadMIMEHeader() |
| if err == nil { |
| bp.Header = header |
| } |
| return err |
| } |
| |
| // Read reads the body of a part, after its headers and before the |
| // next part (if any) begins. |
| func (p *Part) Read(d []byte) (n int, err error) { |
| return p.r.Read(d) |
| } |
| |
| // partReader implements io.Reader by reading raw bytes directly from the |
| // wrapped *Part, without doing any Transfer-Encoding decoding. |
| type partReader struct { |
| p *Part |
| } |
| |
| func (pr partReader) Read(d []byte) (int, error) { |
| p := pr.p |
| br := p.mr.bufReader |
| |
| // Read into buffer until we identify some data to return, |
| // or we find a reason to stop (boundary or read error). |
| for p.n == 0 && p.err == nil { |
| peek, _ := br.Peek(br.Buffered()) |
| p.n, p.err = scanUntilBoundary(peek, p.mr.dashBoundary, p.mr.nlDashBoundary, p.total, p.readErr) |
| if p.n == 0 && p.err == nil { |
| // Force buffered I/O to read more into buffer. |
| _, p.readErr = br.Peek(len(peek) + 1) |
| if p.readErr == io.EOF { |
| p.readErr = io.ErrUnexpectedEOF |
| } |
| } |
| } |
| |
| // Read out from "data to return" part of buffer. |
| if p.n == 0 { |
| return 0, p.err |
| } |
| n := len(d) |
| if n > p.n { |
| n = p.n |
| } |
| n, _ = br.Read(d[:n]) |
| p.total += int64(n) |
| p.n -= n |
| if p.n == 0 { |
| return n, p.err |
| } |
| return n, nil |
| } |
| |
| // scanUntilBoundary scans buf to identify how much of it can be safely |
| // returned as part of the Part body. |
| // dashBoundary is "--boundary". |
| // nlDashBoundary is "\r\n--boundary" or "\n--boundary", depending on what mode we are in. |
| // The comments below (and the name) assume "\n--boundary", but either is accepted. |
| // total is the number of bytes read out so far. If total == 0, then a leading "--boundary" is recognized. |
| // readErr is the read error, if any, that followed reading the bytes in buf. |
| // scanUntilBoundary returns the number of data bytes from buf that can be |
| // returned as part of the Part body and also the error to return (if any) |
| // once those data bytes are done. |
| func scanUntilBoundary(buf, dashBoundary, nlDashBoundary []byte, total int64, readErr error) (int, error) { |
| if total == 0 { |
| // At beginning of body, allow dashBoundary. |
| if bytes.HasPrefix(buf, dashBoundary) { |
| switch matchAfterPrefix(buf, dashBoundary, readErr) { |
| case -1: |
| return len(dashBoundary), nil |
| case 0: |
| return 0, nil |
| case +1: |
| return 0, io.EOF |
| } |
| } |
| if bytes.HasPrefix(dashBoundary, buf) { |
| return 0, readErr |
| } |
| } |
| |
| // Search for "\n--boundary". |
| if i := bytes.Index(buf, nlDashBoundary); i >= 0 { |
| switch matchAfterPrefix(buf[i:], nlDashBoundary, readErr) { |
| case -1: |
| return i + len(nlDashBoundary), nil |
| case 0: |
| return i, nil |
| case +1: |
| return i, io.EOF |
| } |
| } |
| if bytes.HasPrefix(nlDashBoundary, buf) { |
| return 0, readErr |
| } |
| |
| // Otherwise, anything up to the final \n is not part of the boundary |
| // and so must be part of the body. |
| // Also if the section from the final \n onward is not a prefix of the boundary, |
| // it too must be part of the body. |
| i := bytes.LastIndexByte(buf, nlDashBoundary[0]) |
| if i >= 0 && bytes.HasPrefix(nlDashBoundary, buf[i:]) { |
| return i, nil |
| } |
| return len(buf), readErr |
| } |
| |
| // matchAfterPrefix checks whether buf should be considered to match the boundary. |
| // The prefix is "--boundary" or "\r\n--boundary" or "\n--boundary", |
| // and the caller has verified already that bytes.HasPrefix(buf, prefix) is true. |
| // |
| // matchAfterPrefix returns +1 if the buffer does match the boundary, |
| // meaning the prefix is followed by a dash, space, tab, cr, nl, or end of input. |
| // It returns -1 if the buffer definitely does NOT match the boundary, |
| // meaning the prefix is followed by some other character. |
| // For example, "--foobar" does not match "--foo". |
| // It returns 0 more input needs to be read to make the decision, |
| // meaning that len(buf) == len(prefix) and readErr == nil. |
| func matchAfterPrefix(buf, prefix []byte, readErr error) int { |
| if len(buf) == len(prefix) { |
| if readErr != nil { |
| return +1 |
| } |
| return 0 |
| } |
| c := buf[len(prefix)] |
| if c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '-' { |
| return +1 |
| } |
| return -1 |
| } |
| |
| func (p *Part) Close() error { |
| io.Copy(ioutil.Discard, p) |
| return nil |
| } |
| |
| // 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 struct { |
| bufReader *bufio.Reader |
| |
| currentPart *Part |
| partsRead int |
| |
| nl []byte // "\r\n" or "\n" (set after seeing first boundary line) |
| nlDashBoundary []byte // nl + "--boundary" |
| dashBoundaryDash []byte // "--boundary--" |
| dashBoundary []byte // "--boundary" |
| } |
| |
| // NextPart returns the next part in the multipart or an error. |
| // When there are no more parts, the error io.EOF is returned. |
| func (r *Reader) NextPart() (*Part, error) { |
| if r.currentPart != nil { |
| r.currentPart.Close() |
| } |
| if string(r.dashBoundary) == "--" { |
| return nil, fmt.Errorf("multipart: boundary is empty") |
| } |
| expectNewPart := false |
| for { |
| line, err := r.bufReader.ReadSlice('\n') |
| |
| if err == io.EOF && r.isFinalBoundary(line) { |
| // If the buffer ends in "--boundary--" without the |
| // trailing "\r\n", ReadSlice will return an error |
| // (since it's missing the '\n'), but this is a valid |
| // multipart EOF so we need to return io.EOF instead of |
| // a fmt-wrapped one. |
| return nil, io.EOF |
| } |
| if err != nil { |
| return nil, fmt.Errorf("multipart: NextPart: %v", err) |
| } |
| |
| if r.isBoundaryDelimiterLine(line) { |
| r.partsRead++ |
| bp, err := newPart(r) |
| if err != nil { |
| return nil, err |
| } |
| r.currentPart = bp |
| return bp, nil |
| } |
| |
| if r.isFinalBoundary(line) { |
| // Expected EOF |
| return nil, io.EOF |
| } |
| |
| if expectNewPart { |
| return nil, fmt.Errorf("multipart: expecting a new Part; got line %q", string(line)) |
| } |
| |
| if r.partsRead == 0 { |
| // skip line |
| continue |
| } |
| |
| // 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, r.nl) { |
| expectNewPart = true |
| continue |
| } |
| |
| return nil, fmt.Errorf("multipart: unexpected line in Next(): %q", line) |
| } |
| } |
| |
| // isFinalBoundary reports whether line is the final boundary line |
| // indicating that all parts are over. |
| // It matches `^--boundary--[ \t]*(\r\n)?$` |
| func (mr *Reader) isFinalBoundary(line []byte) bool { |
| if !bytes.HasPrefix(line, mr.dashBoundaryDash) { |
| return false |
| } |
| rest := line[len(mr.dashBoundaryDash):] |
| rest = skipLWSPChar(rest) |
| return len(rest) == 0 || bytes.Equal(rest, mr.nl) |
| } |
| |
| func (mr *Reader) isBoundaryDelimiterLine(line []byte) (ret bool) { |
| // https://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) { |
| return false |
| } |
| rest := line[len(mr.dashBoundary):] |
| rest = skipLWSPChar(rest) |
| |
| // On the first part, see our lines are ending in \n instead of \r\n |
| // and switch into that mode if so. This is a violation of the spec, |
| // but occurs in practice. |
| if mr.partsRead == 0 && len(rest) == 1 && rest[0] == '\n' { |
| mr.nl = mr.nl[1:] |
| mr.nlDashBoundary = mr.nlDashBoundary[1:] |
| } |
| return bytes.Equal(rest, mr.nl) |
| } |
| |
| // skipLWSPChar returns b with leading spaces and tabs removed. |
| // RFC 822 defines: |
| // LWSP-char = SPACE / HTAB |
| func skipLWSPChar(b []byte) []byte { |
| for len(b) > 0 && (b[0] == ' ' || b[0] == '\t') { |
| b = b[1:] |
| } |
| return b |
| } |