Fixes a bug with attachments. Hopefully addresses #37 as well

By default, the encoding/base64 in Go does not add line breaks to its output.
This patch introduces the Base64LineBreaker which satisfies the io.WriteCloser interface
Attachments are now correctly broken up into maximum of 76 chars
This commit is contained in:
Winni Neessen 2022-08-14 12:06:26 +02:00
parent a3a143de45
commit 701cbbf399
Signed by: wneessen
GPG key ID: 5F3AF39B820C119D
2 changed files with 64 additions and 1 deletions

56
base64_writer.go Normal file
View file

@ -0,0 +1,56 @@
package mail
import "io"
// Base64LineBreaker is a io.WriteCloser that writes Base64 encoded data streams
// with line breaks at a given line length
type Base64LineBreaker struct {
line [MaxBodyLength]byte
used int
out io.Writer
}
var nl = []byte(SingleNewLine)
// Write writes the data stream and inserts a SingleNewLine when the maximum
// line length is reached
func (l *Base64LineBreaker) Write(b []byte) (n int, err error) {
if l.used+len(b) < MaxBodyLength {
copy(l.line[l.used:], b)
l.used += len(b)
return len(b), nil
}
n, err = l.out.Write(l.line[0:l.used])
if err != nil {
return
}
excess := MaxBodyLength - l.used
l.used = 0
n, err = l.out.Write(b[0:excess])
if err != nil {
return
}
n, err = l.out.Write(nl)
if err != nil {
return
}
return l.Write(b[excess:])
}
// Close closes the Base64LineBreaker and writes any access data that is still
// unwritten in memory
func (l *Base64LineBreaker) Close() (err error) {
if l.used > 0 {
_, err = l.out.Write(l.line[0:l.used])
if err != nil {
return
}
_, err = l.out.Write(nl)
}
return
}

View file

@ -22,6 +22,10 @@ import (
// RFC 2047 suggests 76 characters // RFC 2047 suggests 76 characters
const MaxHeaderLength = 76 const MaxHeaderLength = 76
// MaxBodyLength defines the maximum line length for the mail body
// RFC 2047 suggests 76 characters
const MaxBodyLength = 76
// SingleNewLine represents a new line that can be used by the msgWriter to issue a carriage return // SingleNewLine represents a new line that can be used by the msgWriter to issue a carriage return
const SingleNewLine = "\r\n" const SingleNewLine = "\r\n"
@ -277,12 +281,14 @@ func (mw *msgWriter) writeBody(f func(io.Writer) (int64, error), e Encoding) {
w = mw.pw w = mw.pw
} }
wbuf := bytes.Buffer{} wbuf := bytes.Buffer{}
lb := Base64LineBreaker{}
lb.out = &wbuf
switch e { switch e {
case EncodingQP: case EncodingQP:
ew = quotedprintable.NewWriter(&wbuf) ew = quotedprintable.NewWriter(&wbuf)
case EncodingB64: case EncodingB64:
ew = base64.NewEncoder(base64.StdEncoding, &wbuf) ew = base64.NewEncoder(base64.StdEncoding, &lb)
case NoEncoding: case NoEncoding:
_, mw.err = f(&wbuf) _, mw.err = f(&wbuf)
n, mw.err = io.Copy(w, &wbuf) n, mw.err = io.Copy(w, &wbuf)
@ -296,6 +302,7 @@ func (mw *msgWriter) writeBody(f func(io.Writer) (int64, error), e Encoding) {
_, mw.err = f(ew) _, mw.err = f(ew)
mw.err = ew.Close() mw.err = ew.Close()
mw.err = lb.Close()
n, mw.err = io.Copy(w, &wbuf) n, mw.err = io.Copy(w, &wbuf)
// Since the part writer uses the WriteTo() method, we don't need to add the // Since the part writer uses the WriteTo() method, we don't need to add the