From 701cbbf3992ddd68eba672e1c3e62bf6f7856065 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 14 Aug 2022 12:06:26 +0200 Subject: [PATCH] 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 --- base64_writer.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ msgwriter.go | 9 +++++++- 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 base64_writer.go diff --git a/base64_writer.go b/base64_writer.go new file mode 100644 index 0000000..01b439e --- /dev/null +++ b/base64_writer.go @@ -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 +} diff --git a/msgwriter.go b/msgwriter.go index 78daec7..8a28ea1 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