mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 16:33:47 +03:00
ecf60568b4
message.Writer.Write() adds missing \r's, but the buffer of "last bytes written" was only being updated while writing the message headers, not while writing the body. so for Write()'s in the body section (depending on buffering), we were compensating based on the "last bytes written" as set during the last write in the header section. that could cause a spurious \r to be added when a Write starts with \n while the previous Write did properly end with \r. for issue #117, thanks haraldrudell for reporting and investigating
109 lines
2.6 KiB
Go
109 lines
2.6 KiB
Go
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.
|
|
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
|
|
// 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).
|
|
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 {
|
|
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
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update w.tail after having written. Regardless of error, writers can't expect
|
|
// subsequent writes to work again properly anyway.
|
|
defer func() {
|
|
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
|
|
}
|
|
return wrote, nil
|
|
}
|