diff --git a/dsn/dsn.go b/dsn/dsn.go index d769073..ca8873c 100644 --- a/dsn/dsn.go +++ b/dsn/dsn.go @@ -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 diff --git a/imapserver/compress_test.go b/imapserver/compress_test.go index b8ec941..fb41f70 100644 --- a/imapserver/compress_test.go +++ b/imapserver/compress_test.go @@ -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:] } diff --git a/message/headerwriter.go b/message/headerwriter.go index 89f050a..a234501 100644 --- a/message/headerwriter.go +++ b/message/headerwriter.go @@ -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 { diff --git a/message/qp.go b/message/qp.go index 768c33c..8121272 100644 --- a/message/qp.go +++ b/message/qp.go @@ -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 } } diff --git a/moxio/base64writer.go b/moxio/base64writer.go index 5ec31c7..66cb38b 100644 --- a/moxio/base64writer.go +++ b/moxio/base64writer.go @@ -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 diff --git a/moxio/base64writer_test.go b/moxio/base64writer_test.go index 64c0f40..5dc3810 100644 --- a/moxio/base64writer_test.go +++ b/moxio/base64writer_test.go @@ -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) } diff --git a/spf/received_test.go b/spf/received_test.go index 3383e75..9a44f7f 100644 --- a/spf/received_test.go +++ b/spf/received_test.go @@ -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") } diff --git a/webapisrv/server.go b/webapisrv/server.go index 5edc568..c0bef0f 100644 --- a/webapisrv/server.go +++ b/webapisrv/server.go @@ -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") diff --git a/webmail/api.go b/webmail/api.go index 5263a09..5f089e3 100644 --- a/webmail/api.go +++ b/webmail/api.go @@ -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")