2023-01-30 16:27:06 +03:00
|
|
|
package message
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Writer is a write-through helper, collecting properties about the written
|
when accepting an incoming message, turn any bare newlines (without carriage return) into crlf
because that is what most of the code expects. we could work around having bare
lf, but it would complicate too much code.
currently, a message with bare lf is accepted (in smtpserver delivery,
imapserver append, etc), but when an imap session would try to fetch parsed
parts, that would fail because and even cause a imapserver panic (closing the
connection).
in message imports we would already convert bare lf to crlf (because it is
expected those messages are all lf-only-ending).
we store messages with crlf-ending instead of lf-ending so the imapserver has
all correct information at hand (line counts, byte counts).
found by using emclient with mox. it adds a message to the inbox that can have
mixed crlf and bare lf line endings in a few header fields (in some
localization, emclient authors explained how that happened, thanks!). we can
now convert those lines and read those messages over imap. emclient already
switched to all-crlf line endings in newer (development) versions.
2023-11-21 15:19:54 +03:00
|
|
|
// message and replacing bare \n line endings with \r\n.
|
2023-01-30 16:27:06 +03:00
|
|
|
type Writer struct {
|
2023-08-11 15:07:49 +03:00
|
|
|
writer io.Writer
|
|
|
|
|
2023-12-12 17:47:26 +03:00
|
|
|
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.
|
2023-08-11 15:07:49 +03:00
|
|
|
|
2024-02-08 14:33:19 +03:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2023-08-11 15:07:49 +03:00
|
|
|
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'}}
|
|
|
|
}
|
|
|
|
|
2023-12-12 17:47:26 +03:00
|
|
|
// 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) {
|
2024-02-08 14:33:19 +03:00
|
|
|
if !w.Has8bit {
|
|
|
|
for _, b := range buf {
|
|
|
|
if b >= 0x80 {
|
|
|
|
w.Has8bit = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
when accepting an incoming message, turn any bare newlines (without carriage return) into crlf
because that is what most of the code expects. we could work around having bare
lf, but it would complicate too much code.
currently, a message with bare lf is accepted (in smtpserver delivery,
imapserver append, etc), but when an imap session would try to fetch parsed
parts, that would fail because and even cause a imapserver panic (closing the
connection).
in message imports we would already convert bare lf to crlf (because it is
expected those messages are all lf-only-ending).
we store messages with crlf-ending instead of lf-ending so the imapserver has
all correct information at hand (line counts, byte counts).
found by using emclient with mox. it adds a message to the inbox that can have
mixed crlf and bare lf line endings in a few header fields (in some
localization, emclient authors explained how that happened, thanks!). we can
now convert those lines and read those messages over imap. emclient already
switched to all-crlf line endings in newer (development) versions.
2023-11-21 15:19:54 +03:00
|
|
|
|
2024-02-08 14:33:19 +03:00
|
|
|
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 {
|
when accepting an incoming message, turn any bare newlines (without carriage return) into crlf
because that is what most of the code expects. we could work around having bare
lf, but it would complicate too much code.
currently, a message with bare lf is accepted (in smtpserver delivery,
imapserver append, etc), but when an imap session would try to fetch parsed
parts, that would fail because and even cause a imapserver panic (closing the
connection).
in message imports we would already convert bare lf to crlf (because it is
expected those messages are all lf-only-ending).
we store messages with crlf-ending instead of lf-ending so the imapserver has
all correct information at hand (line counts, byte counts).
found by using emclient with mox. it adds a message to the inbox that can have
mixed crlf and bare lf line endings in a few header fields (in some
localization, emclient authors explained how that happened, thanks!). we can
now convert those lines and read those messages over imap. emclient already
switched to all-crlf line endings in newer (development) versions.
2023-11-21 15:19:54 +03:00
|
|
|
if b == '\n' && (get(i-1) == '\n' || get(i-1) == '\r' && get(i-2) == '\n') {
|
2023-08-11 15:07:49 +03:00
|
|
|
w.HaveBody = true
|
2023-01-30 16:27:06 +03:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2024-02-08 14:33:19 +03:00
|
|
|
}
|
2023-01-30 16:27:06 +03:00
|
|
|
|
2024-02-08 14:33:19 +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:])
|
2024-02-08 14:33:19 +03:00
|
|
|
}()
|
when accepting an incoming message, turn any bare newlines (without carriage return) into crlf
because that is what most of the code expects. we could work around having bare
lf, but it would complicate too much code.
currently, a message with bare lf is accepted (in smtpserver delivery,
imapserver append, etc), but when an imap session would try to fetch parsed
parts, that would fail because and even cause a imapserver panic (closing the
connection).
in message imports we would already convert bare lf to crlf (because it is
expected those messages are all lf-only-ending).
we store messages with crlf-ending instead of lf-ending so the imapserver has
all correct information at hand (line counts, byte counts).
found by using emclient with mox. it adds a message to the inbox that can have
mixed crlf and bare lf line endings in a few header fields (in some
localization, emclient authors explained how that happened, thanks!). we can
now convert those lines and read those messages over imap. emclient already
switched to all-crlf line endings in newer (development) versions.
2023-11-21 15:19:54 +03:00
|
|
|
|
|
|
|
wrote := 0
|
|
|
|
o := 0
|
|
|
|
Top:
|
|
|
|
for o < len(buf) {
|
2024-02-08 14:33:19 +03:00
|
|
|
// Look for bare newline. If present, write up to that position while adding the
|
|
|
|
// missing carriage return. Then start the loop again.
|
when accepting an incoming message, turn any bare newlines (without carriage return) into crlf
because that is what most of the code expects. we could work around having bare
lf, but it would complicate too much code.
currently, a message with bare lf is accepted (in smtpserver delivery,
imapserver append, etc), but when an imap session would try to fetch parsed
parts, that would fail because and even cause a imapserver panic (closing the
connection).
in message imports we would already convert bare lf to crlf (because it is
expected those messages are all lf-only-ending).
we store messages with crlf-ending instead of lf-ending so the imapserver has
all correct information at hand (line counts, byte counts).
found by using emclient with mox. it adds a message to the inbox that can have
mixed crlf and bare lf line endings in a few header fields (in some
localization, emclient authors explained how that happened, thanks!). we can
now convert those lines and read those messages over imap. emclient already
switched to all-crlf line endings in newer (development) versions.
2023-11-21 15:19:54 +03:00
|
|
|
for i := o; i < len(buf); i++ {
|
2024-02-08 14:33:19 +03:00
|
|
|
if buf[i] == '\n' && (i > 0 && buf[i-1] != '\r' || i == 0 && w.tail[2] != '\r') {
|
when accepting an incoming message, turn any bare newlines (without carriage return) into crlf
because that is what most of the code expects. we could work around having bare
lf, but it would complicate too much code.
currently, a message with bare lf is accepted (in smtpserver delivery,
imapserver append, etc), but when an imap session would try to fetch parsed
parts, that would fail because and even cause a imapserver panic (closing the
connection).
in message imports we would already convert bare lf to crlf (because it is
expected those messages are all lf-only-ending).
we store messages with crlf-ending instead of lf-ending so the imapserver has
all correct information at hand (line counts, byte counts).
found by using emclient with mox. it adds a message to the inbox that can have
mixed crlf and bare lf line endings in a few header fields (in some
localization, emclient authors explained how that happened, thanks!). we can
now convert those lines and read those messages over imap. emclient already
switched to all-crlf line endings in newer (development) versions.
2023-11-21 15:19:54 +03:00
|
|
|
// Write buffer leading up to missing \r.
|
2024-01-01 20:30:31 +03:00
|
|
|
if i > o {
|
when accepting an incoming message, turn any bare newlines (without carriage return) into crlf
because that is what most of the code expects. we could work around having bare
lf, but it would complicate too much code.
currently, a message with bare lf is accepted (in smtpserver delivery,
imapserver append, etc), but when an imap session would try to fetch parsed
parts, that would fail because and even cause a imapserver panic (closing the
connection).
in message imports we would already convert bare lf to crlf (because it is
expected those messages are all lf-only-ending).
we store messages with crlf-ending instead of lf-ending so the imapserver has
all correct information at hand (line counts, byte counts).
found by using emclient with mox. it adds a message to the inbox that can have
mixed crlf and bare lf line endings in a few header fields (in some
localization, emclient authors explained how that happened, thanks!). we can
now convert those lines and read those messages over imap. emclient already
switched to all-crlf line endings in newer (development) versions.
2023-11-21 15:19:54 +03:00
|
|
|
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
|
|
|
}
|
when accepting an incoming message, turn any bare newlines (without carriage return) into crlf
because that is what most of the code expects. we could work around having bare
lf, but it would complicate too much code.
currently, a message with bare lf is accepted (in smtpserver delivery,
imapserver append, etc), but when an imap session would try to fetch parsed
parts, that would fail because and even cause a imapserver panic (closing the
connection).
in message imports we would already convert bare lf to crlf (because it is
expected those messages are all lf-only-ending).
we store messages with crlf-ending instead of lf-ending so the imapserver has
all correct information at hand (line counts, byte counts).
found by using emclient with mox. it adds a message to the inbox that can have
mixed crlf and bare lf line endings in a few header fields (in some
localization, emclient authors explained how that happened, thanks!). we can
now convert those lines and read those messages over imap. emclient already
switched to all-crlf line endings in newer (development) versions.
2023-11-21 15:19:54 +03:00
|
|
|
return wrote, nil
|
2023-01-30 16:27:06 +03:00
|
|
|
}
|