mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 16:33:47 +03:00
1f9b640d9a
mox was already strict in its "\r\n.\r\n" handling for end-of-message in an smtp transaction. due to a mostly unrelated bug, sequences of "\nX\n", including "\n.\n" were rejected with a "local processing error". the sequence "\r\n.\n" dropped the dot, not necessarily a big problem, this is unlikely to happen in a legimate transaction and the behaviour not unreasonable. we take this opportunity to reject all bare \r. we detect all slightly incorrect combinations of "\r\n.\r\n" with an error mentioning smtp smuggling, in part to appease the tools checking for it. smtp errors are 500 "bad syntax", and mention smtp smuggling.
102 lines
2.2 KiB
Go
102 lines
2.2 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.
|
|
|
|
tail [3]byte // For detecting header/body-separating crlf.
|
|
// 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) {
|
|
origtail := w.tail
|
|
|
|
if !w.HaveBody && len(buf) > 0 {
|
|
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
|
|
}
|
|
}
|
|
|
|
n := len(buf)
|
|
if n > 3 {
|
|
n = 3
|
|
}
|
|
copy(w.tail[:], w.tail[n:])
|
|
copy(w.tail[3-n:], buf[len(buf)-n:])
|
|
}
|
|
if !w.Has8bit {
|
|
for _, b := range buf {
|
|
if b&0x80 != 0 {
|
|
w.Has8bit = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
wrote := 0
|
|
o := 0
|
|
Top:
|
|
for o < len(buf) {
|
|
for i := o; i < len(buf); i++ {
|
|
if buf[i] == '\n' && (i > 0 && buf[i-1] != '\r' || i == 0 && origtail[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
|
|
}
|