148 lines
3.0 KiB
Go
148 lines
3.0 KiB
Go
package smtp
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
)
|
|
|
|
type EnhancedCode [3]int
|
|
|
|
// SMTPError specifies the error code, enhanced error code (if any) and
|
|
// message returned by the server.
|
|
type SMTPError struct {
|
|
Code int
|
|
EnhancedCode EnhancedCode
|
|
Message string
|
|
}
|
|
|
|
// NoEnhancedCode is used to indicate that enhanced error code should not be
|
|
// included in response.
|
|
//
|
|
// Note that RFC 2034 requires an enhanced code to be included in all 2xx, 4xx
|
|
// and 5xx responses. This constant is exported for use by extensions, you
|
|
// should probably use EnhancedCodeNotSet instead.
|
|
var NoEnhancedCode = EnhancedCode{-1, -1, -1}
|
|
|
|
// EnhancedCodeNotSet is a nil value of EnhancedCode field in SMTPError, used
|
|
// to indicate that backend failed to provide enhanced status code. X.0.0 will
|
|
// be used (X is derived from error code).
|
|
var EnhancedCodeNotSet = EnhancedCode{0, 0, 0}
|
|
|
|
func (err *SMTPError) Error() string {
|
|
return err.Message
|
|
}
|
|
|
|
func (err *SMTPError) Temporary() bool {
|
|
return err.Code/100 == 4
|
|
}
|
|
|
|
var ErrDataTooLarge = &SMTPError{
|
|
Code: 552,
|
|
EnhancedCode: EnhancedCode{5, 3, 4},
|
|
Message: "Maximum message size exceeded",
|
|
}
|
|
|
|
type dataReader struct {
|
|
r *bufio.Reader
|
|
state int
|
|
|
|
limited bool
|
|
n int64 // Maximum bytes remaining
|
|
}
|
|
|
|
func newDataReader(c *Conn) *dataReader {
|
|
dr := &dataReader{
|
|
r: c.text.R,
|
|
}
|
|
|
|
if c.server.MaxMessageBytes > 0 {
|
|
dr.limited = true
|
|
dr.n = int64(c.server.MaxMessageBytes)
|
|
}
|
|
|
|
return dr
|
|
}
|
|
|
|
func (r *dataReader) Read(b []byte) (n int, err error) {
|
|
if r.limited {
|
|
if r.n <= 0 {
|
|
return 0, ErrDataTooLarge
|
|
}
|
|
if int64(len(b)) > r.n {
|
|
b = b[0:r.n]
|
|
}
|
|
}
|
|
|
|
// Code below is taken from net/textproto with only one modification to
|
|
// not rewrite CRLF -> LF.
|
|
|
|
// Run data through a simple state machine to
|
|
// elide leading dots and detect ending .\r\n line.
|
|
const (
|
|
stateBeginLine = iota // beginning of line; initial state; must be zero
|
|
stateDot // read . at beginning of line
|
|
stateDotCR // read .\r at beginning of line
|
|
stateCR // read \r (possibly at end of line)
|
|
stateData // reading data in middle of line
|
|
stateEOF // reached .\r\n end marker line
|
|
)
|
|
for n < len(b) && r.state != stateEOF {
|
|
var c byte
|
|
c, err = r.r.ReadByte()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
err = io.ErrUnexpectedEOF
|
|
}
|
|
break
|
|
}
|
|
switch r.state {
|
|
case stateBeginLine:
|
|
if c == '.' {
|
|
r.state = stateDot
|
|
continue
|
|
}
|
|
r.state = stateData
|
|
case stateDot:
|
|
if c == '\r' {
|
|
r.state = stateDotCR
|
|
continue
|
|
}
|
|
if c == '\n' {
|
|
r.state = stateEOF
|
|
continue
|
|
}
|
|
|
|
r.state = stateData
|
|
case stateDotCR:
|
|
if c == '\n' {
|
|
r.state = stateEOF
|
|
continue
|
|
}
|
|
r.state = stateData
|
|
case stateCR:
|
|
if c == '\n' {
|
|
r.state = stateBeginLine
|
|
break
|
|
}
|
|
r.state = stateData
|
|
case stateData:
|
|
if c == '\r' {
|
|
r.state = stateCR
|
|
}
|
|
if c == '\n' {
|
|
r.state = stateBeginLine
|
|
}
|
|
}
|
|
b[n] = c
|
|
n++
|
|
}
|
|
if err == nil && r.state == stateEOF {
|
|
err = io.EOF
|
|
}
|
|
|
|
if r.limited {
|
|
r.n -= int64(n)
|
|
}
|
|
return
|
|
}
|