mirror of
https://github.com/mjl-/mox.git
synced 2025-04-21 21:40:01 +03:00
write base64 message parts with 76 data bytes on a line instead of 78
As required by RFC 2045 (MIME). The 78 byte lines work in practice, except that SpamAssassin has rules that give messages with 78-byte lines spam points. Mentioned by kjetilho on irc.
This commit is contained in:
parent
00c8db98e6
commit
69d2699961
9 changed files with 16 additions and 13 deletions
dsn
imapserver
message
moxio
spf
webapisrv
webmail
|
@ -340,7 +340,7 @@ func (m *Message) Compose(log mlog.Log, smtputf8 bool) ([]byte, error) {
|
|||
data := base64.StdEncoding.EncodeToString(headers)
|
||||
for len(data) > 0 {
|
||||
line := data
|
||||
n := min(len(line), 78)
|
||||
n := min(len(line), 76) // ../rfc/2045:1372
|
||||
line, data = data[:n], data[n:]
|
||||
if _, err := origp.Write([]byte(line + "\r\n")); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -64,7 +64,7 @@ func TestCompressBreak(t *testing.T) {
|
|||
tcheck(t, err, "read random")
|
||||
text := base64.StdEncoding.EncodeToString(buf)
|
||||
for len(text) > 0 {
|
||||
n := min(78, len(text))
|
||||
n := min(76, len(text))
|
||||
msg += text[:n] + "\r\n"
|
||||
text = text[n:]
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ func (w *HeaderWriter) Add(separator string, texts ...string) {
|
|||
}
|
||||
for _, text := range texts {
|
||||
n := len(text)
|
||||
if w.nonfirst && w.lineLen > 1 && w.lineLen+len(separator)+n > 78 {
|
||||
if w.nonfirst && w.lineLen > 1 && w.lineLen+len(separator)+n > 76 {
|
||||
w.b.WriteString("\r\n\t")
|
||||
w.lineLen = 1
|
||||
} else if w.nonfirst && separator != "" {
|
||||
|
@ -45,7 +45,7 @@ func (w *HeaderWriter) Add(separator string, texts ...string) {
|
|||
func (w *HeaderWriter) AddWrap(buf []byte, text bool) {
|
||||
for len(buf) > 0 {
|
||||
line := buf
|
||||
n := 78 - w.lineLen
|
||||
n := 76 - w.lineLen
|
||||
if len(buf) > n {
|
||||
if text {
|
||||
if i := bytes.LastIndexAny(buf[:n], " \t"); i > 0 {
|
||||
|
|
|
@ -11,7 +11,9 @@ import (
|
|||
func NeedsQuotedPrintable(text string) bool {
|
||||
// ../rfc/2045:1025
|
||||
for _, line := range strings.Split(text, "\r\n") {
|
||||
if len(line) > 78 || strings.Contains(line, "\r") || strings.Contains(line, "\n") {
|
||||
// 78 should be fine too, qp itself has a requirement of 76 bytes on a line, but
|
||||
// using qp for anything longer than 76 is safer.
|
||||
if len(line) > 76 || strings.Contains(line, "\r") || strings.Contains(line, "\n") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ func (f closerFunc) Close() error {
|
|||
}
|
||||
|
||||
// Base64Writer turns a writer for data into one that writes base64 content on
|
||||
// \r\n separated lines of max 78+2 characters length.
|
||||
// \r\n separated lines of max 76+2 characters length.
|
||||
func Base64Writer(w io.Writer) io.WriteCloser {
|
||||
lw := &lineWrapper{w: w}
|
||||
bw := base64.NewEncoder(base64.StdEncoding, lw)
|
||||
|
@ -39,7 +39,8 @@ type lineWrapper struct {
|
|||
func (lw *lineWrapper) Write(buf []byte) (int, error) {
|
||||
wrote := 0
|
||||
for len(buf) > 0 {
|
||||
n := min(78-lw.n, len(buf))
|
||||
// base64 has max 76 data bytes on per line. ../rfc/2045:1372
|
||||
n := min(76-lw.n, len(buf))
|
||||
nn, err := lw.w.Write(buf[:n])
|
||||
if nn > 0 {
|
||||
wrote += nn
|
||||
|
@ -49,7 +50,7 @@ func (lw *lineWrapper) Write(buf []byte) (int, error) {
|
|||
return wrote, err
|
||||
}
|
||||
lw.n += nn
|
||||
if lw.n == 78 {
|
||||
if lw.n == 76 {
|
||||
_, err := lw.w.Write([]byte("\r\n"))
|
||||
if err != nil {
|
||||
return wrote, err
|
||||
|
|
|
@ -13,7 +13,7 @@ func TestBase64Writer(t *testing.T) {
|
|||
err = bw.Close()
|
||||
tcheckf(t, err, "close")
|
||||
s := sb.String()
|
||||
exp := "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nz\r\ng5MDEyMzQ1Njc4OQ==\r\n"
|
||||
exp := "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2\r\nNzg5MDEyMzQ1Njc4OQ==\r\n"
|
||||
if s != exp {
|
||||
t.Fatalf("base64writer, got %q, expected %q", s, exp)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ func TestReceived(t *testing.T) {
|
|||
Receiver: "z",
|
||||
Identity: ReceivedMailFrom,
|
||||
Mechanism: "+ip4:0.0.0.0/0",
|
||||
}, "Received-SPF: pass (c) client-ip=0.0.0.0; envelope-from=\"x@x\"; helo=y;\r\n\tproblem=\"a b\\\"\\\\\"; mechanism=\"+ip4:0.0.0.0/0\"; receiver=z; identity=mailfrom\r\n")
|
||||
}, "Received-SPF: pass (c) client-ip=0.0.0.0; envelope-from=\"x@x\"; helo=y;\r\n\tproblem=\"a b\\\"\\\\\"; mechanism=\"+ip4:0.0.0.0/0\"; receiver=z;\r\n\tidentity=mailfrom\r\n")
|
||||
|
||||
test(Received{
|
||||
Result: StatusPass,
|
||||
|
@ -35,5 +35,5 @@ func TestReceived(t *testing.T) {
|
|||
Helo: dns.IPDomain{IP: net.ParseIP("2001:db8::1")},
|
||||
Receiver: "z",
|
||||
Identity: ReceivedMailFrom,
|
||||
}, "Received-SPF: pass client-ip=0.0.0.0; envelope-from=\"x@x\"; helo=\"2001:db8::1\";\r\n\treceiver=z; identity=mailfrom\r\n")
|
||||
}, "Received-SPF: pass client-ip=0.0.0.0; envelope-from=\"x@x\";\r\n\thelo=\"2001:db8::1\"; receiver=z; identity=mailfrom\r\n")
|
||||
}
|
||||
|
|
|
@ -875,7 +875,7 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S
|
|||
|
||||
for len(base64Data) > 0 {
|
||||
line := base64Data
|
||||
n := min(len(line), 78)
|
||||
n := min(len(line), 76) // ../rfc/2045:1372
|
||||
line, base64Data = base64Data[:n], base64Data[n:]
|
||||
_, err := p.Write([]byte(line))
|
||||
xcheckf(err, "writing attachment")
|
||||
|
|
|
@ -831,7 +831,7 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
|
|||
|
||||
for len(base64Data) > 0 {
|
||||
line := base64Data
|
||||
n := min(len(line), 78)
|
||||
n := min(len(line), 76) // ../rfc/2045:1372
|
||||
line, base64Data = base64Data[:n], base64Data[n:]
|
||||
_, err := ap.Write(line)
|
||||
xcheckf(ctx, err, "writing attachment")
|
||||
|
|
Loading…
Reference in a new issue