mox/message/writer.go

110 lines
2.6 KiB
Go
Raw Permalink Normal View History

2023-01-30 16:27:06 +03:00
package message
import (
"io"
)
// Writer is a write-through helper, collecting properties about the written
// message and replacing bare \n line endings with \r\n.
2023-01-30 16:27:06 +03:00
type Writer struct {
writer io.Writer
HaveBody bool // Body is optional in a message. ../rfc/5322:343
Has8bit bool // Whether a byte with the high/8bit has been read. So whether this needs SMTP 8BITMIME instead of 7BIT.
Size int64 // Number of bytes written, may be different from bytes read due to LF to CRLF conversion.
// For detecting header/body-separating crlf and fixing up bare lf. These are the
// incoming bytes, not the fixed up bytes. So CRs may be missing from tail.
tail [3]byte
2023-01-30 16:27:06 +03:00
// todo: should be parsing headers here, as we go
}
func NewWriter(w io.Writer) *Writer {
// Pretend we already saw \r\n, for handling empty header.
return &Writer{writer: w, tail: [3]byte{0, '\r', '\n'}}
}
// Write implements io.Writer, and writes buf as message to the Writer's underlying
// io.Writer. It converts bare new lines (LF) to carriage returns with new lines
// (CRLF).
2023-01-30 16:27:06 +03:00
func (w *Writer) Write(buf []byte) (int, error) {
if !w.Has8bit {
for _, b := range buf {
if b >= 0x80 {
w.Has8bit = true
break
}
}
}
if !w.HaveBody {
2023-01-30 16:27:06 +03:00
get := func(i int) byte {
if i < 0 {
return w.tail[3+i]
}
return buf[i]
}
for i, b := range buf {
if b == '\n' && (get(i-1) == '\n' || get(i-1) == '\r' && get(i-2) == '\n') {
w.HaveBody = true
2023-01-30 16:27:06 +03:00
break
}
}
}
2023-01-30 16:27:06 +03:00
// Update w.tail after having written. Regardless of error, writers can't expect
// subsequent writes to work again properly anyway.
defer func() {
2023-01-30 16:27:06 +03:00
n := len(buf)
if n > 3 {
n = 3
}
copy(w.tail[:], w.tail[n:])
copy(w.tail[3-n:], buf[len(buf)-n:])
}()
wrote := 0
o := 0
Top:
for o < len(buf) {
// Look for bare newline. If present, write up to that position while adding the
// missing carriage return. Then start the loop again.
for i := o; i < len(buf); i++ {
if buf[i] == '\n' && (i > 0 && buf[i-1] != '\r' || i == 0 && w.tail[2] != '\r') {
// Write buffer leading up to missing \r.
if i > o {
n, err := w.writer.Write(buf[o:i])
if n > 0 {
wrote += n
w.Size += int64(n)
}
if err != nil {
return wrote, err
}
}
n, err := w.writer.Write([]byte{'\r', '\n'})
if n == 2 {
wrote += 1 // For only the newline.
w.Size += int64(2)
}
if err != nil {
return wrote, err
}
o = i + 1
continue Top
}
}
n, err := w.writer.Write(buf[o:])
if n > 0 {
wrote += n
w.Size += int64(n)
}
if err != nil {
return wrote, err
}
break
2023-01-30 16:27:06 +03:00
}
return wrote, nil
2023-01-30 16:27:06 +03:00
}