1package smtp
2
3import (
4 "bufio"
5 "bytes"
6 "errors"
7 "io"
8)
9
10var errMissingCRLF = errors.New("missing crlf at end of message")
11
12// DataWrite reads data (a mail message) from r, and writes it to smtp
13// connection w with dot stuffing, as required by the SMTP data command.
14func DataWrite(w io.Writer, r io.Reader) error {
15 // ../rfc/5321:2003
16
17 var prevlast, last byte = '\r', '\n' // Start on a new line, so we insert a dot if the first byte is a dot.
18 // todo: at least for smtp submission we should probably set a max line length, eg 1000 octects including crlf. ../rfc/5321:3512
19 // todo: at least for smtp submission or a pedantic mode, we should refuse messages with bare \r or bare \n.
20 buf := make([]byte, 8*1024)
21 for {
22 nr, err := r.Read(buf)
23 if nr > 0 {
24 // Process buf by writing a line at a time, and checking if the next character
25 // after the line starts with a dot. Insert an extra dot if so.
26 p := buf[:nr]
27 for len(p) > 0 {
28 if p[0] == '.' && prevlast == '\r' && last == '\n' {
29 if _, err := w.Write([]byte{'.'}); err != nil {
30 return err
31 }
32 }
33 // Look for the next newline, or end of buffer.
34 n := 0
35 for n < len(p) {
36 c := p[n]
37 n++
38 if c == '\n' {
39 break
40 }
41 }
42 if _, err := w.Write(p[:n]); err != nil {
43 return err
44 }
45 // Keep track of the last two bytes we've written.
46 if n == 1 {
47 prevlast, last = last, p[0]
48 } else {
49 prevlast, last = p[n-2], p[n-1]
50 }
51 p = p[n:]
52 }
53 }
54 if err == io.EOF {
55 break
56 } else if err != nil {
57 return err
58 }
59 }
60 if prevlast != '\r' || last != '\n' {
61 return errMissingCRLF
62 }
63 if _, err := w.Write(dotcrlf); err != nil {
64 return err
65 }
66 return nil
67}
68
69var dotcrlf = []byte(".\r\n")
70
71// DataReader is an io.Reader that reads data from an SMTP DATA command, doing dot
72// unstuffing and returning io.EOF when a bare dot is received. Use NewDataReader.
73type DataReader struct {
74 // ../rfc/5321:2003
75 r *bufio.Reader
76 plast, last byte
77 buf []byte // From previous read.
78 err error // Read error, for after r.buf is exhausted.
79}
80
81// NewDataReader returns an initialized DataReader.
82func NewDataReader(r *bufio.Reader) *DataReader {
83 return &DataReader{
84 r: r,
85 // Set up initial state to accept a message that is only "." and CRLF.
86 plast: '\r',
87 last: '\n',
88 }
89}
90
91// Read implements io.Reader.
92func (r *DataReader) Read(p []byte) (int, error) {
93 wrote := 0
94 for len(p) > 0 {
95 // Read until newline as long as it fits in the buffer.
96 if len(r.buf) == 0 {
97 if r.err != nil {
98 break
99 }
100 // todo: set a max length, eg 1000 octets including crlf excluding potential leading dot. ../rfc/5321:3512
101 r.buf, r.err = r.r.ReadSlice('\n')
102 if r.err == bufio.ErrBufferFull {
103 r.err = nil
104 } else if r.err == io.EOF {
105 // Mark EOF as bad for now. If we see the ending dotcrlf below, err becomes regular
106 // io.EOF again.
107 r.err = io.ErrUnexpectedEOF
108 }
109 }
110 if len(r.buf) > 0 {
111 // We require crlf. A bare LF is not a line ending. ../rfc/5321:2032
112 // todo: we could return an error for a bare \n.
113 if r.plast == '\r' && r.last == '\n' {
114 if bytes.Equal(r.buf, dotcrlf) {
115 r.buf = nil
116 r.err = io.EOF
117 break
118 } else if r.buf[0] == '.' {
119 r.buf = r.buf[1:]
120 }
121 }
122 n := len(r.buf)
123 if n > len(p) {
124 n = len(p)
125 }
126 copy(p, r.buf[:n])
127 if n == 1 {
128 r.plast, r.last = r.last, r.buf[0]
129 } else if n > 1 {
130 r.plast, r.last = r.buf[n-2], r.buf[n-1]
131 }
132 p = p[n:]
133 r.buf = r.buf[n:]
134 wrote += n
135 }
136 }
137 return wrote, r.err
138}
139