mirror of
https://github.com/mjl-/mox.git
synced 2025-06-28 01:48:15 +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
@ -340,7 +340,7 @@ func (m *Message) Compose(log mlog.Log, smtputf8 bool) ([]byte, error) {
|
|||||||
data := base64.StdEncoding.EncodeToString(headers)
|
data := base64.StdEncoding.EncodeToString(headers)
|
||||||
for len(data) > 0 {
|
for len(data) > 0 {
|
||||||
line := data
|
line := data
|
||||||
n := min(len(line), 78)
|
n := min(len(line), 76) // ../rfc/2045:1372
|
||||||
line, data = data[:n], data[n:]
|
line, data = data[:n], data[n:]
|
||||||
if _, err := origp.Write([]byte(line + "\r\n")); err != nil {
|
if _, err := origp.Write([]byte(line + "\r\n")); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -64,7 +64,7 @@ func TestCompressBreak(t *testing.T) {
|
|||||||
tcheck(t, err, "read random")
|
tcheck(t, err, "read random")
|
||||||
text := base64.StdEncoding.EncodeToString(buf)
|
text := base64.StdEncoding.EncodeToString(buf)
|
||||||
for len(text) > 0 {
|
for len(text) > 0 {
|
||||||
n := min(78, len(text))
|
n := min(76, len(text))
|
||||||
msg += text[:n] + "\r\n"
|
msg += text[:n] + "\r\n"
|
||||||
text = text[n:]
|
text = text[n:]
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ func (w *HeaderWriter) Add(separator string, texts ...string) {
|
|||||||
}
|
}
|
||||||
for _, text := range texts {
|
for _, text := range texts {
|
||||||
n := len(text)
|
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.b.WriteString("\r\n\t")
|
||||||
w.lineLen = 1
|
w.lineLen = 1
|
||||||
} else if w.nonfirst && separator != "" {
|
} 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) {
|
func (w *HeaderWriter) AddWrap(buf []byte, text bool) {
|
||||||
for len(buf) > 0 {
|
for len(buf) > 0 {
|
||||||
line := buf
|
line := buf
|
||||||
n := 78 - w.lineLen
|
n := 76 - w.lineLen
|
||||||
if len(buf) > n {
|
if len(buf) > n {
|
||||||
if text {
|
if text {
|
||||||
if i := bytes.LastIndexAny(buf[:n], " \t"); i > 0 {
|
if i := bytes.LastIndexAny(buf[:n], " \t"); i > 0 {
|
||||||
|
@ -11,7 +11,9 @@ import (
|
|||||||
func NeedsQuotedPrintable(text string) bool {
|
func NeedsQuotedPrintable(text string) bool {
|
||||||
// ../rfc/2045:1025
|
// ../rfc/2045:1025
|
||||||
for _, line := range strings.Split(text, "\r\n") {
|
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
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ func (f closerFunc) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Base64Writer turns a writer for data into one that writes base64 content on
|
// 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 {
|
func Base64Writer(w io.Writer) io.WriteCloser {
|
||||||
lw := &lineWrapper{w: w}
|
lw := &lineWrapper{w: w}
|
||||||
bw := base64.NewEncoder(base64.StdEncoding, lw)
|
bw := base64.NewEncoder(base64.StdEncoding, lw)
|
||||||
@ -39,7 +39,8 @@ type lineWrapper struct {
|
|||||||
func (lw *lineWrapper) Write(buf []byte) (int, error) {
|
func (lw *lineWrapper) Write(buf []byte) (int, error) {
|
||||||
wrote := 0
|
wrote := 0
|
||||||
for len(buf) > 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])
|
nn, err := lw.w.Write(buf[:n])
|
||||||
if nn > 0 {
|
if nn > 0 {
|
||||||
wrote += nn
|
wrote += nn
|
||||||
@ -49,7 +50,7 @@ func (lw *lineWrapper) Write(buf []byte) (int, error) {
|
|||||||
return wrote, err
|
return wrote, err
|
||||||
}
|
}
|
||||||
lw.n += nn
|
lw.n += nn
|
||||||
if lw.n == 78 {
|
if lw.n == 76 {
|
||||||
_, err := lw.w.Write([]byte("\r\n"))
|
_, err := lw.w.Write([]byte("\r\n"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrote, err
|
return wrote, err
|
||||||
|
@ -13,7 +13,7 @@ func TestBase64Writer(t *testing.T) {
|
|||||||
err = bw.Close()
|
err = bw.Close()
|
||||||
tcheckf(t, err, "close")
|
tcheckf(t, err, "close")
|
||||||
s := sb.String()
|
s := sb.String()
|
||||||
exp := "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nz\r\ng5MDEyMzQ1Njc4OQ==\r\n"
|
exp := "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2\r\nNzg5MDEyMzQ1Njc4OQ==\r\n"
|
||||||
if s != exp {
|
if s != exp {
|
||||||
t.Fatalf("base64writer, got %q, expected %q", s, exp)
|
t.Fatalf("base64writer, got %q, expected %q", s, exp)
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ func TestReceived(t *testing.T) {
|
|||||||
Receiver: "z",
|
Receiver: "z",
|
||||||
Identity: ReceivedMailFrom,
|
Identity: ReceivedMailFrom,
|
||||||
Mechanism: "+ip4:0.0.0.0/0",
|
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{
|
test(Received{
|
||||||
Result: StatusPass,
|
Result: StatusPass,
|
||||||
@ -35,5 +35,5 @@ func TestReceived(t *testing.T) {
|
|||||||
Helo: dns.IPDomain{IP: net.ParseIP("2001:db8::1")},
|
Helo: dns.IPDomain{IP: net.ParseIP("2001:db8::1")},
|
||||||
Receiver: "z",
|
Receiver: "z",
|
||||||
Identity: ReceivedMailFrom,
|
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 {
|
for len(base64Data) > 0 {
|
||||||
line := base64Data
|
line := base64Data
|
||||||
n := min(len(line), 78)
|
n := min(len(line), 76) // ../rfc/2045:1372
|
||||||
line, base64Data = base64Data[:n], base64Data[n:]
|
line, base64Data = base64Data[:n], base64Data[n:]
|
||||||
_, err := p.Write([]byte(line))
|
_, err := p.Write([]byte(line))
|
||||||
xcheckf(err, "writing attachment")
|
xcheckf(err, "writing attachment")
|
||||||
|
@ -831,7 +831,7 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
|
|||||||
|
|
||||||
for len(base64Data) > 0 {
|
for len(base64Data) > 0 {
|
||||||
line := base64Data
|
line := base64Data
|
||||||
n := min(len(line), 78)
|
n := min(len(line), 76) // ../rfc/2045:1372
|
||||||
line, base64Data = base64Data[:n], base64Data[n:]
|
line, base64Data = base64Data[:n], base64Data[n:]
|
||||||
_, err := ap.Write(line)
|
_, err := ap.Write(line)
|
||||||
xcheckf(ctx, err, "writing attachment")
|
xcheckf(ctx, err, "writing attachment")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user