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. ../rfc/5322:343
13 Has8bit bool // Whether a byte with the high/8bit has been read. So whether this is 8BITMIME instead of 7BIT.
14 Size int64
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.
26func (w *Writer) Write(buf []byte) (int, error) {
27 origtail := w.tail
28
29 if !w.HaveBody && len(buf) > 0 {
30 get := func(i int) byte {
31 if i < 0 {
32 return w.tail[3+i]
33 }
34 return buf[i]
35 }
36
37 for i, b := range buf {
38 if b == '\n' && (get(i-1) == '\n' || get(i-1) == '\r' && get(i-2) == '\n') {
39 w.HaveBody = true
40 break
41 }
42 }
43
44 n := len(buf)
45 if n > 3 {
46 n = 3
47 }
48 copy(w.tail[:], w.tail[n:])
49 copy(w.tail[3-n:], buf[len(buf)-n:])
50 }
51 if !w.Has8bit {
52 for _, b := range buf {
53 if b&0x80 != 0 {
54 w.Has8bit = true
55 break
56 }
57 }
58 }
59
60 wrote := 0
61 o := 0
62Top:
63 for o < len(buf) {
64 for i := o; i < len(buf); i++ {
65 if buf[i] == '\n' && (i > 0 && buf[i-1] != '\r' || i == 0 && origtail[2] != '\r') {
66 // Write buffer leading up to missing \r.
67 if i > o+1 {
68 n, err := w.writer.Write(buf[o:i])
69 if n > 0 {
70 wrote += n
71 w.Size += int64(n)
72 }
73 if err != nil {
74 return wrote, err
75 }
76 }
77 n, err := w.writer.Write([]byte{'\r', '\n'})
78 if n == 2 {
79 wrote += 1 // For only the newline.
80 w.Size += int64(2)
81 }
82 if err != nil {
83 return wrote, err
84 }
85 o = i + 1
86 continue Top
87 }
88 }
89 n, err := w.writer.Write(buf[o:])
90 if n > 0 {
91 wrote += n
92 w.Size += int64(n)
93 }
94 if err != nil {
95 return wrote, err
96 }
97 break
98 }
99 return wrote, nil
100}
101