data.go raw

   1  package smtp
   2  
   3  import (
   4  	"bufio"
   5  	"fmt"
   6  	"io"
   7  )
   8  
   9  type EnhancedCode [3]int
  10  
  11  // SMTPError specifies the error code, enhanced error code (if any) and
  12  // message returned by the server.
  13  type SMTPError struct {
  14  	Code         int
  15  	EnhancedCode EnhancedCode
  16  	Message      string
  17  }
  18  
  19  // NoEnhancedCode is used to indicate that enhanced error code should not be
  20  // included in response.
  21  //
  22  // Note that RFC 2034 requires an enhanced code to be included in all 2xx, 4xx
  23  // and 5xx responses. This constant is exported for use by extensions, you
  24  // should probably use EnhancedCodeNotSet instead.
  25  var NoEnhancedCode = EnhancedCode{-1, -1, -1}
  26  
  27  // EnhancedCodeNotSet is a nil value of EnhancedCode field in SMTPError, used
  28  // to indicate that backend failed to provide enhanced status code. X.0.0 will
  29  // be used (X is derived from error code).
  30  var EnhancedCodeNotSet = EnhancedCode{0, 0, 0}
  31  
  32  func (err *SMTPError) Error() string {
  33  	s := fmt.Sprintf("SMTP error %03d", err.Code)
  34  	if err.Message != "" {
  35  		s += ": " + err.Message
  36  	}
  37  	return s
  38  }
  39  
  40  func (err *SMTPError) Temporary() bool {
  41  	return err.Code/100 == 4
  42  }
  43  
  44  var ErrDataTooLarge = &SMTPError{
  45  	Code:         552,
  46  	EnhancedCode: EnhancedCode{5, 3, 4},
  47  	Message:      "Maximum message size exceeded",
  48  }
  49  
  50  type dataReader struct {
  51  	r     *bufio.Reader
  52  	state int
  53  
  54  	limited bool
  55  	n       int64 // Maximum bytes remaining
  56  }
  57  
  58  func newDataReader(c *Conn) *dataReader {
  59  	dr := &dataReader{
  60  		r: c.text.R,
  61  	}
  62  
  63  	if c.server.MaxMessageBytes > 0 {
  64  		dr.limited = true
  65  		dr.n = int64(c.server.MaxMessageBytes)
  66  	}
  67  
  68  	return dr
  69  }
  70  
  71  func (r *dataReader) Read(b []byte) (n int, err error) {
  72  	if r.limited {
  73  		if r.n <= 0 {
  74  			return 0, ErrDataTooLarge
  75  		}
  76  		if int64(len(b)) > r.n {
  77  			b = b[0:r.n]
  78  		}
  79  	}
  80  
  81  	// Code below is taken from net/textproto with only one modification to
  82  	// not rewrite CRLF -> LF.
  83  
  84  	// Run data through a simple state machine to
  85  	// elide leading dots and detect End-of-Data (<CR><LF>.<CR><LF>) line.
  86  	const (
  87  		stateBeginLine = iota // beginning of line; initial state; must be zero
  88  		stateDot              // read . at beginning of line
  89  		stateDotCR            // read .\r at beginning of line
  90  		stateCR               // read \r (possibly at end of line)
  91  		stateData             // reading data in middle of line
  92  		stateEOF              // reached .\r\n end marker line
  93  	)
  94  	for n < len(b) && r.state != stateEOF {
  95  		var c byte
  96  		c, err = r.r.ReadByte()
  97  		if err != nil {
  98  			if err == io.EOF {
  99  				err = io.ErrUnexpectedEOF
 100  			}
 101  			break
 102  		}
 103  		switch r.state {
 104  		case stateBeginLine:
 105  			if c == '.' {
 106  				r.state = stateDot
 107  				continue
 108  			}
 109  			if c == '\r' {
 110  				r.state = stateCR
 111  				break
 112  			}
 113  			r.state = stateData
 114  		case stateDot:
 115  			if c == '\r' {
 116  				r.state = stateDotCR
 117  				continue
 118  			}
 119  			r.state = stateData
 120  		case stateDotCR:
 121  			if c == '\n' {
 122  				r.state = stateEOF
 123  				continue
 124  			}
 125  			r.state = stateData
 126  		case stateCR:
 127  			if c == '\n' {
 128  				r.state = stateBeginLine
 129  				break
 130  			}
 131  			r.state = stateData
 132  		case stateData:
 133  			if c == '\r' {
 134  				r.state = stateCR
 135  			}
 136  		}
 137  		b[n] = c
 138  		n++
 139  	}
 140  	if err == nil && r.state == stateEOF {
 141  		err = io.EOF
 142  	}
 143  
 144  	if r.limited {
 145  		r.n -= int64(n)
 146  	}
 147  	return
 148  }
 149