diff --git a/base64_writer.go b/base64_writer.go new file mode 100644 index 00000000..ff99696a --- /dev/null +++ b/base64_writer.go @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2022 Winni Neessen +// +// SPDX-License-Identifier: MIT + +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 +} diff --git a/msgwriter.go b/msgwriter.go index 78daec75..8a28ea18 100644 --- a/msgwriter.go +++ b/msgwriter.go @@ -22,6 +22,10 @@ import ( // RFC 2047 suggests 76 characters 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 const SingleNewLine = "\r\n" @@ -277,12 +281,14 @@ func (mw *msgWriter) writeBody(f func(io.Writer) (int64, error), e Encoding) { w = mw.pw } wbuf := bytes.Buffer{} + lb := Base64LineBreaker{} + lb.out = &wbuf switch e { case EncodingQP: ew = quotedprintable.NewWriter(&wbuf) case EncodingB64: - ew = base64.NewEncoder(base64.StdEncoding, &wbuf) + ew = base64.NewEncoder(base64.StdEncoding, &lb) case NoEncoding: _, mw.err = f(&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 = ew.Close() + mw.err = lb.Close() n, mw.err = io.Copy(w, &wbuf) // Since the part writer uses the WriteTo() method, we don't need to add the