1package message
2
3import (
4 "io"
5)
6
7// Writer is a write-through helper, collecting properties about the written
8// message and replacing bare \n line endings with \r\n.
9type Writer struct {
10 writer io.Writer
11
12 HaveBody bool // Body is optional in a message. ../rfc/5322:343
13 Has8bit bool // Whether a byte with the high/8bit has been read. So whether this needs SMTP 8BITMIME instead of 7BIT.
14 Size int64 // Number of bytes written, may be different from bytes read due to LF to CRLF conversion.
15
16 tail [3]byte // For detecting header/body-separating crlf.
17 // todo: should be parsing headers here, as we go
18}
19
20func NewWriter(w io.Writer) *Writer {
21 // Pretend we already saw \r\n, for handling empty header.
22 return &Writer{writer: w, tail: [3]byte{0, '\r', '\n'}}
23}
24
25// Write implements io.Writer, and writes buf as message to the Writer's underlying
26// io.Writer. It converts bare new lines (LF) to carriage returns with new lines
27// (CRLF).
28func (w *Writer) Write(buf []byte) (int, error) {
29 origtail := w.tail
30
31 if !w.HaveBody && len(buf) > 0 {
32 get := func(i int) byte {
33 if i < 0 {
34 return w.tail[3+i]
35 }
36 return buf[i]
37 }
38
39 for i, b := range buf {
40 if b == '\n' && (get(i-1) == '\n' || get(i-1) == '\r' && get(i-2) == '\n') {
41 w.HaveBody = true
42 break
43 }
44 }
45
46 n := len(buf)
47 if n > 3 {
48 n = 3
49 }
50 copy(w.tail[:], w.tail[n:])
51 copy(w.tail[3-n:], buf[len(buf)-n:])
52 }
53 if !w.Has8bit {
54 for _, b := range buf {
55 if b&0x80 != 0 {
56 w.Has8bit = true
57 break
58 }
59 }
60 }
61
62 wrote := 0
63 o := 0
64Top:
65 for o < len(buf) {
66 for i := o; i < len(buf); i++ {
67 if buf[i] == '\n' && (i > 0 && buf[i-1] != '\r' || i == 0 && origtail[2] != '\r') {
68 // Write buffer leading up to missing \r.
69 if i > o {
70 n, err := w.writer.Write(buf[o:i])
71 if n > 0 {
72 wrote += n
73 w.Size += int64(n)
74 }
75 if err != nil {
76 return wrote, err
77 }
78 }
79 n, err := w.writer.Write([]byte{'\r', '\n'})
80 if n == 2 {
81 wrote += 1 // For only the newline.
82 w.Size += int64(2)
83 }
84 if err != nil {
85 return wrote, err
86 }
87 o = i + 1
88 continue Top
89 }
90 }
91 n, err := w.writer.Write(buf[o:])
92 if n > 0 {
93 wrote += n
94 w.Size += int64(n)
95 }
96 if err != nil {
97 return wrote, err
98 }
99 break
100 }
101 return wrote, nil
102}
103