10var errMissingCRLF = errors.New("missing crlf at end of message")
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 {
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)
22 nr, err := r.Read(buf)
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.
28 if p[0] == '.' && prevlast == '\r' && last == '\n' {
29 if _, err := w.Write([]byte{'.'}); err != nil {
33 // Look for the next newline, or end of buffer.
42 if _, err := w.Write(p[:n]); err != nil {
45 // Keep track of the last two bytes we've written.
47 prevlast, last = last, p[0]
49 prevlast, last = p[n-2], p[n-1]
56 } else if err != nil {
60 if prevlast != '\r' || last != '\n' {
63 if _, err := w.Write(dotcrlf); err != nil {
69var dotcrlf = []byte(".\r\n")
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 {
77 buf []byte // From previous read.
78 err error // Read error, for after r.buf is exhausted.
81// NewDataReader returns an initialized DataReader.
82func NewDataReader(r *bufio.Reader) *DataReader {
85 // Set up initial state to accept a message that is only "." and CRLF.
91// Read implements io.Reader.
92func (r *DataReader) Read(p []byte) (int, error) {
95 // Read until newline as long as it fits in the buffer.
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 {
104 } else if r.err == io.EOF {
105 // Mark EOF as bad for now. If we see the ending dotcrlf below, err becomes regular
107 r.err = io.ErrUnexpectedEOF
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) {
118 } else if r.buf[0] == '.' {
128 r.plast, r.last = r.last, r.buf[0]
130 r.plast, r.last = r.buf[n-2], r.buf[n-1]