|
@@ -11,7 +11,6 @@ doesn't back-track or multi-scan.
|
|
*/
|
|
*/
|
|
import (
|
|
import (
|
|
"bytes"
|
|
"bytes"
|
|
- "errors"
|
|
|
|
"fmt"
|
|
"fmt"
|
|
"io"
|
|
"io"
|
|
"net/textproto"
|
|
"net/textproto"
|
|
@@ -20,6 +19,20 @@ import (
|
|
"sync"
|
|
"sync"
|
|
)
|
|
)
|
|
|
|
|
|
|
|
+var (
|
|
|
|
+ MaxNodesErr *Error
|
|
|
|
+ NotMineErr *Error
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+func init() {
|
|
|
|
+ NotMineErr = &Error{
|
|
|
|
+ err: ErrorNotMime,
|
|
|
|
+ }
|
|
|
|
+ MaxNodesErr = &Error{
|
|
|
|
+ err: ErrorMaxNodes,
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
const (
|
|
const (
|
|
// maxBoundaryLen limits the length of the content-boundary.
|
|
// maxBoundaryLen limits the length of the content-boundary.
|
|
// Technically the limit is 79, but here we are more liberal
|
|
// Technically the limit is 79, but here we are more liberal
|
|
@@ -46,8 +59,110 @@ const (
|
|
MaxNodes = 512
|
|
MaxNodes = 512
|
|
)
|
|
)
|
|
|
|
|
|
-var NotMime = errors.New("not Mime")
|
|
|
|
-var MaxNodesErr = errors.New("too many mime part nodes")
|
|
|
|
|
|
+type MimeError int
|
|
|
|
+
|
|
|
|
+const (
|
|
|
|
+ ErrorNotMime MimeError = iota
|
|
|
|
+ ErrorMaxNodes
|
|
|
|
+ ErrorBoundaryTooShort
|
|
|
|
+ ErrorBoundaryLineExpected
|
|
|
|
+ ErrorUnexpectedChar
|
|
|
|
+ ErrorHeaderFieldTooShort
|
|
|
|
+ ErrorBoundaryExceededLength
|
|
|
|
+ ErrorHeaderParseError
|
|
|
|
+ ErrorMissingSubtype
|
|
|
|
+ ErrorUnexpectedTok
|
|
|
|
+ ErrorUnexpectedCommentToken
|
|
|
|
+ ErrorInvalidToken
|
|
|
|
+ ErrorUnexpectedQuotedStrToken
|
|
|
|
+ ErrorParameterExpectingEquals
|
|
|
|
+ ErrorNoHeader
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+func (e MimeError) Error() string {
|
|
|
|
+ switch e {
|
|
|
|
+ case ErrorNotMime:
|
|
|
|
+ return "not Mime"
|
|
|
|
+ case ErrorMaxNodes:
|
|
|
|
+ return "too many mime part nodes"
|
|
|
|
+ case ErrorBoundaryTooShort:
|
|
|
|
+ return "content boundary too short"
|
|
|
|
+ case ErrorBoundaryLineExpected:
|
|
|
|
+ return "boundary new line expected"
|
|
|
|
+ case ErrorUnexpectedChar:
|
|
|
|
+ return "unexpected char"
|
|
|
|
+ case ErrorHeaderFieldTooShort:
|
|
|
|
+ return "header field too short"
|
|
|
|
+ case ErrorBoundaryExceededLength:
|
|
|
|
+ return "boundary exceeded max length"
|
|
|
|
+ case ErrorHeaderParseError:
|
|
|
|
+ return "header parse error"
|
|
|
|
+ case ErrorMissingSubtype:
|
|
|
|
+ return "missing subtype"
|
|
|
|
+ case ErrorUnexpectedTok:
|
|
|
|
+ return "unexpected tok"
|
|
|
|
+ case ErrorUnexpectedCommentToken:
|
|
|
|
+ return "unexpected comment token"
|
|
|
|
+ case ErrorInvalidToken:
|
|
|
|
+ return "invalid token"
|
|
|
|
+ case ErrorUnexpectedQuotedStrToken:
|
|
|
|
+ return "unexpected token"
|
|
|
|
+ case ErrorParameterExpectingEquals:
|
|
|
|
+ return "expecting ="
|
|
|
|
+ case ErrorNoHeader:
|
|
|
|
+ return "parse error, no header"
|
|
|
|
+ }
|
|
|
|
+ return "unknown mime error"
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Error implements the error interface
|
|
|
|
+type Error struct {
|
|
|
|
+ err error
|
|
|
|
+ char byte
|
|
|
|
+ peek byte
|
|
|
|
+ pos uint // msgPos
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (e Error) Error() string {
|
|
|
|
+ if e.char == 0 {
|
|
|
|
+ return e.err.Error()
|
|
|
|
+ }
|
|
|
|
+ return e.err.Error() + " char:[" + string(e.char) + "], peek:" +
|
|
|
|
+ string(e.peek) + ", pos:" + strconv.Itoa(int(e.pos))
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (e *Error) ParseError() bool {
|
|
|
|
+ if e.err != io.EOF && e.err != NotMineErr && e.err != MaxNodesErr {
|
|
|
|
+ return true
|
|
|
|
+ }
|
|
|
|
+ return false
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (p *Parser) newParseError(e error) *Error {
|
|
|
|
+ var peek byte
|
|
|
|
+ offset := 1
|
|
|
|
+ for {
|
|
|
|
+ // reached the end? (don't wait for more bytes to consume)
|
|
|
|
+ if p.pos+offset >= len(p.buf) {
|
|
|
|
+ peek = 0
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ // peek the next byte
|
|
|
|
+ peek := p.buf[p.pos+offset]
|
|
|
|
+ if peek == '\r' {
|
|
|
|
+ // ignore \r
|
|
|
|
+ offset++
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ return &Error{
|
|
|
|
+ err: e,
|
|
|
|
+ char: p.ch,
|
|
|
|
+ peek: peek,
|
|
|
|
+ pos: p.msgPos,
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
|
|
type captureBuffer struct {
|
|
type captureBuffer struct {
|
|
bytes.Buffer
|
|
bytes.Buffer
|
|
@@ -329,17 +444,15 @@ func (p *Parser) boundary(contentBoundary string) (end bool, err error) {
|
|
}()
|
|
}()
|
|
|
|
|
|
if len(contentBoundary) < 1 {
|
|
if len(contentBoundary) < 1 {
|
|
- err = errors.New("content boundary too short")
|
|
|
|
|
|
+ err = ErrorBoundaryTooShort
|
|
}
|
|
}
|
|
boundary := doubleDash + contentBoundary
|
|
boundary := doubleDash + contentBoundary
|
|
p.boundaryMatched = 0
|
|
p.boundaryMatched = 0
|
|
for {
|
|
for {
|
|
if i := bytes.Index(p.buf[p.pos:], []byte(boundary)); i > -1 {
|
|
if i := bytes.Index(p.buf[p.pos:], []byte(boundary)); i > -1 {
|
|
-
|
|
|
|
p.skip(i)
|
|
p.skip(i)
|
|
p.lastBoundaryPos = p.msgPos
|
|
p.lastBoundaryPos = p.msgPos
|
|
p.skip(len(boundary))
|
|
p.skip(len(boundary))
|
|
-
|
|
|
|
if end, err = p.boundaryEnd(); err != nil {
|
|
if end, err = p.boundaryEnd(); err != nil {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
@@ -347,10 +460,9 @@ func (p *Parser) boundary(contentBoundary string) (end bool, err error) {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
if p.ch != '\n' {
|
|
if p.ch != '\n' {
|
|
- err = errors.New("boundary new line expected")
|
|
|
|
|
|
+ err = ErrorBoundaryLineExpected
|
|
}
|
|
}
|
|
return
|
|
return
|
|
-
|
|
|
|
} else {
|
|
} else {
|
|
// search the tail for partial match
|
|
// search the tail for partial match
|
|
// if one is found, load more data and continue the match
|
|
// if one is found, load more data and continue the match
|
|
@@ -392,7 +504,7 @@ func (p *Parser) boundary(contentBoundary string) (end bool, err error) {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
if p.ch != '\n' {
|
|
if p.ch != '\n' {
|
|
- err = errors.New("boundary new line expected")
|
|
|
|
|
|
+ err = ErrorBoundaryLineExpected
|
|
}
|
|
}
|
|
return
|
|
return
|
|
}
|
|
}
|
|
@@ -479,14 +591,12 @@ func (p *Parser) header(mh *Part) (err error) {
|
|
state = 2 // tolerate this error
|
|
state = 2 // tolerate this error
|
|
continue
|
|
continue
|
|
}
|
|
}
|
|
- pc := p.peek()
|
|
|
|
- err = errors.New("unexpected char:[" + string(p.ch) + "], peek:" +
|
|
|
|
- string(pc) + ", pos:" + strconv.Itoa(int(p.msgPos)))
|
|
|
|
|
|
+ err = p.newParseError(ErrorUnexpectedChar)
|
|
return
|
|
return
|
|
}
|
|
}
|
|
if state == 1 {
|
|
if state == 1 {
|
|
if p.accept.Len() < 2 {
|
|
if p.accept.Len() < 2 {
|
|
- err = errors.New("header field too short")
|
|
|
|
|
|
+ err = p.newParseError(ErrorHeaderFieldTooShort)
|
|
return
|
|
return
|
|
}
|
|
}
|
|
p.accept.upper = true
|
|
p.accept.upper = true
|
|
@@ -512,7 +622,7 @@ func (p *Parser) header(mh *Part) (err error) {
|
|
case contentType.parameters[i].name == "boundary":
|
|
case contentType.parameters[i].name == "boundary":
|
|
mh.ContentBoundary = contentType.parameters[i].value
|
|
mh.ContentBoundary = contentType.parameters[i].value
|
|
if len(mh.ContentBoundary) >= maxBoundaryLen {
|
|
if len(mh.ContentBoundary) >= maxBoundaryLen {
|
|
- return errors.New("boundary exceeded max length")
|
|
|
|
|
|
+ return p.newParseError(ErrorBoundaryExceededLength)
|
|
}
|
|
}
|
|
case contentType.parameters[i].name == "charset":
|
|
case contentType.parameters[i].name == "charset":
|
|
mh.Charset = strings.ToUpper(contentType.parameters[i].value)
|
|
mh.Charset = strings.ToUpper(contentType.parameters[i].value)
|
|
@@ -537,7 +647,7 @@ func (p *Parser) header(mh *Part) (err error) {
|
|
state = 0
|
|
state = 0
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
- err = errors.New("header parse error, pos:" + strconv.Itoa(p.pos))
|
|
|
|
|
|
+ err = p.newParseError(ErrorHeaderParseError)
|
|
return
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -583,7 +693,7 @@ func (p *Parser) contentType() (result contentType, err error) {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
if p.ch != '/' {
|
|
if p.ch != '/' {
|
|
- return result, errors.New("missing subtype")
|
|
|
|
|
|
+ return result, p.newParseError(ErrorMissingSubtype)
|
|
}
|
|
}
|
|
p.next()
|
|
p.next()
|
|
|
|
|
|
@@ -655,7 +765,7 @@ func (p *Parser) mimeType() (str string, err error) {
|
|
|
|
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
- err = errors.New("unexpected tok")
|
|
|
|
|
|
+ err = p.newParseError(ErrorUnexpectedTok)
|
|
return
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -675,9 +785,8 @@ func (p *Parser) comment() (err error) {
|
|
// all header fields except for Content-Disposition
|
|
// all header fields except for Content-Disposition
|
|
// can include RFC 822 comments
|
|
// can include RFC 822 comments
|
|
if p.ch != '(' {
|
|
if p.ch != '(' {
|
|
- err = errors.New("unexpected token")
|
|
|
|
|
|
+ err = p.newParseError(ErrorUnexpectedCommentToken)
|
|
}
|
|
}
|
|
-
|
|
|
|
for {
|
|
for {
|
|
p.next()
|
|
p.next()
|
|
if p.ch == ')' {
|
|
if p.ch == ')' {
|
|
@@ -685,7 +794,6 @@ func (p *Parser) comment() (err error) {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
}
|
|
}
|
|
|
|
|
|
func (p *Parser) token(lower bool) (str string, err error) {
|
|
func (p *Parser) token(lower bool) (str string, err error) {
|
|
@@ -706,7 +814,7 @@ func (p *Parser) token(lower bool) (str string, err error) {
|
|
_ = p.accept.WriteByte(p.ch)
|
|
_ = p.accept.WriteByte(p.ch)
|
|
once = true
|
|
once = true
|
|
} else if !once {
|
|
} else if !once {
|
|
- err = errors.New("invalid token")
|
|
|
|
|
|
+ err = p.newParseError(ErrorInvalidToken)
|
|
return
|
|
return
|
|
} else {
|
|
} else {
|
|
return
|
|
return
|
|
@@ -731,7 +839,7 @@ func (p *Parser) quotedString() (str string, err error) {
|
|
}()
|
|
}()
|
|
|
|
|
|
if p.ch != '"' {
|
|
if p.ch != '"' {
|
|
- err = errors.New("unexpected token")
|
|
|
|
|
|
+ err = p.newParseError(ErrorUnexpectedQuotedStrToken)
|
|
return
|
|
return
|
|
}
|
|
}
|
|
p.next()
|
|
p.next()
|
|
@@ -750,7 +858,7 @@ func (p *Parser) quotedString() (str string, err error) {
|
|
if (p.ch < 127 && p.ch > 32) || p.isWSP(p.ch) {
|
|
if (p.ch < 127 && p.ch > 32) || p.isWSP(p.ch) {
|
|
_ = p.accept.WriteByte(p.ch)
|
|
_ = p.accept.WriteByte(p.ch)
|
|
} else {
|
|
} else {
|
|
- err = errors.New("unexpected token")
|
|
|
|
|
|
+ err = p.newParseError(ErrorUnexpectedQuotedStrToken)
|
|
return
|
|
return
|
|
}
|
|
}
|
|
case 1:
|
|
case 1:
|
|
@@ -759,7 +867,7 @@ func (p *Parser) quotedString() (str string, err error) {
|
|
_ = p.accept.WriteByte(p.ch)
|
|
_ = p.accept.WriteByte(p.ch)
|
|
state = 0
|
|
state = 0
|
|
} else {
|
|
} else {
|
|
- err = errors.New("unexpected token")
|
|
|
|
|
|
+ err = p.newParseError(ErrorUnexpectedQuotedStrToken)
|
|
return
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -785,7 +893,7 @@ func (p *Parser) parameter() (attribute, value string, err error) {
|
|
if len(attribute) > 0 {
|
|
if len(attribute) > 0 {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
- return "", "", errors.New("expecting =")
|
|
|
|
|
|
+ return "", "", p.newParseError(ErrorParameterExpectingEquals)
|
|
}
|
|
}
|
|
p.next()
|
|
p.next()
|
|
if p.ch == '"' {
|
|
if p.ch == '"' {
|
|
@@ -845,7 +953,7 @@ func (p *Parser) mime(part *Part, cb string) (err error) {
|
|
len(part.Headers) > 0 &&
|
|
len(part.Headers) > 0 &&
|
|
part.Headers.Get("MIME-Version") == "" &&
|
|
part.Headers.Get("MIME-Version") == "" &&
|
|
err == nil {
|
|
err == nil {
|
|
- err = NotMime
|
|
|
|
|
|
+ err = NotMineErr
|
|
}
|
|
}
|
|
}()
|
|
}()
|
|
}
|
|
}
|
|
@@ -857,7 +965,7 @@ func (p *Parser) mime(part *Part, cb string) (err error) {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
} else if root {
|
|
} else if root {
|
|
- return errors.New("parse error, no header")
|
|
|
|
|
|
+ return p.newParseError(ErrorNoHeader)
|
|
}
|
|
}
|
|
if p.ch == '\n' && p.peek() == '\n' {
|
|
if p.ch == '\n' && p.peek() == '\n' {
|
|
p.next()
|
|
p.next()
|
|
@@ -1052,10 +1160,10 @@ func (p *Parser) Parse(buf []byte) error {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-// ParseError returns true if the type of error was a parse error
|
|
|
|
|
|
+// Error returns true if the type of error was a parse error
|
|
// Returns false if it was an io.EOF or the email was not mime, or exceeded maximum nodes
|
|
// Returns false if it was an io.EOF or the email was not mime, or exceeded maximum nodes
|
|
func (p *Parser) ParseError(err error) bool {
|
|
func (p *Parser) ParseError(err error) bool {
|
|
- if err != nil && err != io.EOF && err != NotMime && err != MaxNodesErr {
|
|
|
|
|
|
+ if err != nil && err != io.EOF && err != NotMineErr && err != MaxNodesErr {
|
|
return true
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
return false
|