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