Refactor SMTP client code for better readability

The variable names in the code related to the I/O of the SMTP client have been clarified for improved readability and comprehension. For example, unclear variable names like `d` and `w` have been replaced with more meaningful names like `depth` and `writer`. The same naming improvements have also been applied to function parameters. This update aims to enhance code maintenance and simplify future development processes.
This commit is contained in:
Winni Neessen 2024-02-24 12:43:01 +01:00
parent 68e6284e4e
commit 263f6bb3de
Signed by: wneessen
GPG key ID: 5F3AF39B820C119D
3 changed files with 151 additions and 147 deletions

8
msg.go
View file

@ -933,9 +933,9 @@ func (m *Msg) applyMiddlewares(ms *Msg) *Msg {
// WriteTo writes the formated Msg into a give io.Writer and satisfies the io.WriteTo interface // WriteTo writes the formated Msg into a give io.Writer and satisfies the io.WriteTo interface
func (m *Msg) WriteTo(w io.Writer) (int64, error) { func (m *Msg) WriteTo(w io.Writer) (int64, error) {
mw := &msgWriter{w: w, c: m.charset, en: m.encoder} mw := &msgWriter{writer: w, charset: m.charset, encoder: m.encoder}
mw.writeMsg(m.applyMiddlewares(m)) mw.writeMsg(m.applyMiddlewares(m))
return mw.n, mw.err return mw.bytesWritten, mw.err
} }
// WriteToSkipMiddleware writes the formated Msg into a give io.Writer and satisfies // WriteToSkipMiddleware writes the formated Msg into a give io.Writer and satisfies
@ -950,10 +950,10 @@ func (m *Msg) WriteToSkipMiddleware(w io.Writer, mt MiddlewareType) (int64, erro
mwl = append(mwl, m.middlewares[i]) mwl = append(mwl, m.middlewares[i])
} }
m.middlewares = mwl m.middlewares = mwl
mw := &msgWriter{w: w, c: m.charset, en: m.encoder} mw := &msgWriter{writer: w, charset: m.charset, encoder: m.encoder}
mw.writeMsg(m.applyMiddlewares(m)) mw.writeMsg(m.applyMiddlewares(m))
m.middlewares = omwl m.middlewares = omwl
return mw.n, mw.err return mw.bytesWritten, mw.err
} }
// Write is an alias method to WriteTo due to compatibility reasons // Write is an alias method to WriteTo due to compatibility reasons

View file

@ -35,237 +35,241 @@ const DoubleNewLine = "\r\n\r\n"
// msgWriter handles the I/O to the io.WriteCloser of the SMTP client // msgWriter handles the I/O to the io.WriteCloser of the SMTP client
type msgWriter struct { type msgWriter struct {
c Charset bytesWritten int64
d int8 charset Charset
en mime.WordEncoder depth int8
err error encoder mime.WordEncoder
mpw [3]*multipart.Writer err error
n int64 multiPartWriter [3]*multipart.Writer
pw io.Writer partWriter io.Writer
w io.Writer writer io.Writer
} }
// Write implements the io.Writer interface for msgWriter // Write implements the io.Writer interface for msgWriter
func (mw *msgWriter) Write(p []byte) (int, error) { func (mw *msgWriter) Write(payload []byte) (int, error) {
if mw.err != nil { if mw.err != nil {
return 0, fmt.Errorf("failed to write due to previous error: %w", mw.err) return 0, fmt.Errorf("failed to write due to previous error: %w", mw.err)
} }
var n int var n int
n, mw.err = mw.w.Write(p) n, mw.err = mw.writer.Write(payload)
mw.n += int64(n) mw.bytesWritten += int64(n)
return n, mw.err return n, mw.err
} }
// writeMsg formats the message and sends it to its io.Writer // writeMsg formats the message and sends it to its io.Writer
func (mw *msgWriter) writeMsg(m *Msg) { func (mw *msgWriter) writeMsg(msg *Msg) {
m.addDefaultHeader() msg.addDefaultHeader()
m.checkUserAgent() msg.checkUserAgent()
mw.writeGenHeader(m) mw.writeGenHeader(msg)
mw.writePreformattedGenHeader(m) mw.writePreformattedGenHeader(msg)
// Set the FROM header (or envelope FROM if FROM is empty) // Set the FROM header (or envelope FROM if FROM is empty)
hf := true hasFrom := true
f, ok := m.addrHeader[HeaderFrom] from, ok := msg.addrHeader[HeaderFrom]
if !ok || (len(f) == 0 || f == nil) { if !ok || (len(from) == 0 || from == nil) {
f, ok = m.addrHeader[HeaderEnvelopeFrom] from, ok = msg.addrHeader[HeaderEnvelopeFrom]
if !ok || (len(f) == 0 || f == nil) { if !ok || (len(from) == 0 || from == nil) {
hf = false hasFrom = false
} }
} }
if hf && (len(f) > 0 && f[0] != nil) { if hasFrom && (len(from) > 0 && from[0] != nil) {
mw.writeHeader(Header(HeaderFrom), f[0].String()) mw.writeHeader(Header(HeaderFrom), from[0].String())
} }
// Set the rest of the address headers // Set the rest of the address headers
for _, t := range []AddrHeader{HeaderTo, HeaderCc} { for _, to := range []AddrHeader{HeaderTo, HeaderCc} {
if al, ok := m.addrHeader[t]; ok { if addresses, ok := msg.addrHeader[to]; ok {
var v []string var val []string
for _, a := range al { for _, addr := range addresses {
v = append(v, a.String()) val = append(val, addr.String())
} }
mw.writeHeader(Header(t), v...) mw.writeHeader(Header(to), val...)
} }
} }
if m.hasMixed() { if msg.hasMixed() {
mw.startMP("mixed", m.boundary) mw.startMP("mixed", msg.boundary)
mw.writeString(DoubleNewLine) mw.writeString(DoubleNewLine)
} }
if m.hasRelated() { if msg.hasRelated() {
mw.startMP("related", m.boundary) mw.startMP("related", msg.boundary)
mw.writeString(DoubleNewLine) mw.writeString(DoubleNewLine)
} }
if m.hasAlt() { if msg.hasAlt() {
mw.startMP(MIMEAlternative, m.boundary) mw.startMP(MIMEAlternative, msg.boundary)
mw.writeString(DoubleNewLine) mw.writeString(DoubleNewLine)
} }
if m.hasPGPType() { if msg.hasPGPType() {
switch m.pgptype { switch msg.pgptype {
case PGPEncrypt: case PGPEncrypt:
mw.startMP(`encrypted; protocol="application/pgp-encrypted"`, m.boundary) mw.startMP(`encrypted; protocol="application/pgp-encrypted"`,
msg.boundary)
case PGPSignature: case PGPSignature:
mw.startMP(`signed; protocol="application/pgp-signature";`, m.boundary) mw.startMP(`signed; protocol="application/pgp-signature";`,
msg.boundary)
default:
} }
mw.writeString(DoubleNewLine) mw.writeString(DoubleNewLine)
} }
for _, p := range m.parts { for _, part := range msg.parts {
if !p.del { if !part.del {
mw.writePart(p, m.charset) mw.writePart(part, msg.charset)
} }
} }
if m.hasAlt() { if msg.hasAlt() {
mw.stopMP() mw.stopMP()
} }
// Add embeds // Add embeds
mw.addFiles(m.embeds, false) mw.addFiles(msg.embeds, false)
if m.hasRelated() { if msg.hasRelated() {
mw.stopMP() mw.stopMP()
} }
// Add attachments // Add attachments
mw.addFiles(m.attachments, true) mw.addFiles(msg.attachments, true)
if m.hasMixed() { if msg.hasMixed() {
mw.stopMP() mw.stopMP()
} }
} }
// writeGenHeader writes out all generic headers to the msgWriter // writeGenHeader writes out all generic headers to the msgWriter
func (mw *msgWriter) writeGenHeader(m *Msg) { func (mw *msgWriter) writeGenHeader(msg *Msg) {
gk := make([]string, 0, len(m.genHeader)) keys := make([]string, 0, len(msg.genHeader))
for k := range m.genHeader { for key := range msg.genHeader {
gk = append(gk, string(k)) keys = append(keys, string(key))
} }
sort.Strings(gk) sort.Strings(keys)
for _, k := range gk { for _, key := range keys {
mw.writeHeader(Header(k), m.genHeader[Header(k)]...) mw.writeHeader(Header(key), msg.genHeader[Header(key)]...)
} }
} }
// writePreformatedHeader writes out all preformated generic headers to the msgWriter // writePreformatedHeader writes out all preformated generic headers to the msgWriter
func (mw *msgWriter) writePreformattedGenHeader(m *Msg) { func (mw *msgWriter) writePreformattedGenHeader(msg *Msg) {
for k, v := range m.preformHeader { for key, val := range msg.preformHeader {
mw.writeString(fmt.Sprintf("%s: %s%s", k, v, SingleNewLine)) mw.writeString(fmt.Sprintf("%s: %s%s", key, val, SingleNewLine))
} }
} }
// startMP writes a multipart beginning // startMP writes a multipart beginning
func (mw *msgWriter) startMP(mt MIMEType, b string) { func (mw *msgWriter) startMP(mimeType MIMEType, boundary string) {
mp := multipart.NewWriter(mw) multiPartWriter := multipart.NewWriter(mw)
if b != "" { if boundary != "" {
mw.err = mp.SetBoundary(b) mw.err = multiPartWriter.SetBoundary(boundary)
} }
ct := fmt.Sprintf("multipart/%s;\r\n boundary=%s", mt, mp.Boundary()) contentType := fmt.Sprintf("multipart/%s;\r\n boundary=%s", mimeType,
mw.mpw[mw.d] = mp multiPartWriter.Boundary())
mw.multiPartWriter[mw.depth] = multiPartWriter
if mw.d == 0 { if mw.depth == 0 {
mw.writeString(fmt.Sprintf("%s: %s", HeaderContentType, ct)) mw.writeString(fmt.Sprintf("%s: %s", HeaderContentType, contentType))
} }
if mw.d > 0 { if mw.depth > 0 {
mw.newPart(map[string][]string{"Content-Type": {ct}}) mw.newPart(map[string][]string{"Content-Type": {contentType}})
} }
mw.d++ mw.depth++
} }
// stopMP closes the multipart // stopMP closes the multipart
func (mw *msgWriter) stopMP() { func (mw *msgWriter) stopMP() {
if mw.d > 0 { if mw.depth > 0 {
mw.err = mw.mpw[mw.d-1].Close() mw.err = mw.multiPartWriter[mw.depth-1].Close()
mw.d-- mw.depth--
} }
} }
// addFiles adds the attachments/embeds file content to the mail body // addFiles adds the attachments/embeds file content to the mail body
func (mw *msgWriter) addFiles(fl []*File, a bool) { func (mw *msgWriter) addFiles(files []*File, isAttachment bool) {
for _, f := range fl { for _, file := range files {
e := EncodingB64 encoding := EncodingB64
if _, ok := f.getHeader(HeaderContentType); !ok { if _, ok := file.getHeader(HeaderContentType); !ok {
mt := mime.TypeByExtension(filepath.Ext(f.Name)) mimeType := mime.TypeByExtension(filepath.Ext(file.Name))
if mt == "" { if mimeType == "" {
mt = "application/octet-stream" mimeType = "application/octet-stream"
} }
if f.ContentType != "" { if file.ContentType != "" {
mt = string(f.ContentType) mimeType = string(file.ContentType)
} }
f.setHeader(HeaderContentType, fmt.Sprintf(`%s; name="%s"`, mt, file.setHeader(HeaderContentType, fmt.Sprintf(`%s; name="%s"`, mimeType,
mw.en.Encode(mw.c.String(), f.Name))) mw.encoder.Encode(mw.charset.String(), file.Name)))
} }
if _, ok := f.getHeader(HeaderContentTransferEnc); !ok { if _, ok := file.getHeader(HeaderContentTransferEnc); !ok {
if f.Enc != "" { if file.Enc != "" {
e = f.Enc encoding = file.Enc
} }
f.setHeader(HeaderContentTransferEnc, string(e)) file.setHeader(HeaderContentTransferEnc, string(encoding))
} }
if f.Desc != "" { if file.Desc != "" {
if _, ok := f.getHeader(HeaderContentDescription); !ok { if _, ok := file.getHeader(HeaderContentDescription); !ok {
f.setHeader(HeaderContentDescription, f.Desc) file.setHeader(HeaderContentDescription, file.Desc)
} }
} }
if _, ok := f.getHeader(HeaderContentDisposition); !ok { if _, ok := file.getHeader(HeaderContentDisposition); !ok {
d := "inline" disposition := "inline"
if a { if isAttachment {
d = "attachment" disposition = "attachment"
} }
f.setHeader(HeaderContentDisposition, fmt.Sprintf(`%s; filename="%s"`, d, file.setHeader(HeaderContentDisposition, fmt.Sprintf(`%s; filename="%s"`,
mw.en.Encode(mw.c.String(), f.Name))) disposition, mw.encoder.Encode(mw.charset.String(), file.Name)))
} }
if !a { if !isAttachment {
if _, ok := f.getHeader(HeaderContentID); !ok { if _, ok := file.getHeader(HeaderContentID); !ok {
f.setHeader(HeaderContentID, fmt.Sprintf("<%s>", f.Name)) file.setHeader(HeaderContentID, fmt.Sprintf("<%s>", file.Name))
} }
} }
if mw.d == 0 { if mw.depth == 0 {
for h, v := range f.Header { for header, val := range file.Header {
mw.writeHeader(Header(h), v...) mw.writeHeader(Header(header), val...)
} }
mw.writeString(SingleNewLine) mw.writeString(SingleNewLine)
} }
if mw.d > 0 { if mw.depth > 0 {
mw.newPart(f.Header) mw.newPart(file.Header)
} }
if mw.err == nil { if mw.err == nil {
mw.writeBody(f.Writer, e) mw.writeBody(file.Writer, encoding)
} }
} }
} }
// newPart creates a new MIME multipart io.Writer and sets the partwriter to it // newPart creates a new MIME multipart io.Writer and sets the partwriter to it
func (mw *msgWriter) newPart(h map[string][]string) { func (mw *msgWriter) newPart(header map[string][]string) {
mw.pw, mw.err = mw.mpw[mw.d-1].CreatePart(h) mw.partWriter, mw.err = mw.multiPartWriter[mw.depth-1].CreatePart(header)
} }
// writePart writes the corresponding part to the Msg body // writePart writes the corresponding part to the Msg body
func (mw *msgWriter) writePart(p *Part, cs Charset) { func (mw *msgWriter) writePart(part *Part, charset Charset) {
pcs := p.cset partCharset := part.cset
if pcs.String() == "" { if partCharset.String() == "" {
pcs = cs partCharset = charset
} }
ct := fmt.Sprintf("%s; charset=%s", p.ctype, pcs) contentType := fmt.Sprintf("%s; charset=%s", part.ctype, partCharset)
cte := p.enc.String() contentTransferEnc := part.enc.String()
if mw.d == 0 { if mw.depth == 0 {
mw.writeHeader(HeaderContentType, ct) mw.writeHeader(HeaderContentType, contentType)
mw.writeHeader(HeaderContentTransferEnc, cte) mw.writeHeader(HeaderContentTransferEnc, contentTransferEnc)
mw.writeString(SingleNewLine) mw.writeString(SingleNewLine)
} }
if mw.d > 0 { if mw.depth > 0 {
mh := textproto.MIMEHeader{} mimeHeader := textproto.MIMEHeader{}
if p.desc != "" { if part.desc != "" {
mh.Add(string(HeaderContentDescription), p.desc) mimeHeader.Add(string(HeaderContentDescription), part.desc)
} }
mh.Add(string(HeaderContentType), ct) mimeHeader.Add(string(HeaderContentType), contentType)
mh.Add(string(HeaderContentTransferEnc), cte) mimeHeader.Add(string(HeaderContentTransferEnc), contentTransferEnc)
mw.newPart(mh) mw.newPart(mimeHeader)
} }
mw.writeBody(p.w, p.enc) mw.writeBody(part.w, part.enc)
} }
// writeString writes a string into the msgWriter's io.Writer interface // writeString writes a string into the msgWriter's io.Writer interface
@ -274,24 +278,24 @@ func (mw *msgWriter) writeString(s string) {
return return
} }
var n int var n int
n, mw.err = io.WriteString(mw.w, s) n, mw.err = io.WriteString(mw.writer, s)
mw.n += int64(n) mw.bytesWritten += int64(n)
} }
// writeHeader writes a header into the msgWriter's io.Writer // writeHeader writes a header into the msgWriter's io.Writer
func (mw *msgWriter) writeHeader(k Header, vl ...string) { func (mw *msgWriter) writeHeader(key Header, values ...string) {
wbuf := bytes.Buffer{} wbuf := bytes.Buffer{}
cl := MaxHeaderLength - 2 cl := MaxHeaderLength - 2
wbuf.WriteString(string(k)) wbuf.WriteString(string(key))
cl -= len(k) cl -= len(key)
if len(vl) == 0 { if len(values) == 0 {
wbuf.WriteString(":\r\n") wbuf.WriteString(":\r\n")
return return
} }
wbuf.WriteString(": ") wbuf.WriteString(": ")
cl -= 2 cl -= 2
fs := strings.Join(vl, ", ") fs := strings.Join(values, ", ")
sfs := strings.Split(fs, " ") sfs := strings.Split(fs, " ")
for i, v := range sfs { for i, v := range sfs {
if cl-len(v) <= 1 { if cl-len(v) <= 1 {
@ -318,11 +322,11 @@ func (mw *msgWriter) writeBody(f func(io.Writer) (int64, error), e Encoding) {
var ew io.WriteCloser var ew io.WriteCloser
var n int64 var n int64
var err error var err error
if mw.d == 0 { if mw.depth == 0 {
w = mw.w w = mw.writer
} }
if mw.d > 0 { if mw.depth > 0 {
w = mw.pw w = mw.partWriter
} }
wbuf := bytes.Buffer{} wbuf := bytes.Buffer{}
lb := Base64LineBreaker{} lb := Base64LineBreaker{}
@ -342,8 +346,8 @@ func (mw *msgWriter) writeBody(f func(io.Writer) (int64, error), e Encoding) {
if err != nil && mw.err == nil { if err != nil && mw.err == nil {
mw.err = fmt.Errorf("bodyWriter io.Copy: %w", err) mw.err = fmt.Errorf("bodyWriter io.Copy: %w", err)
} }
if mw.d == 0 { if mw.depth == 0 {
mw.n += n mw.bytesWritten += n
} }
return return
default: default:
@ -369,7 +373,7 @@ func (mw *msgWriter) writeBody(f func(io.Writer) (int64, error), e Encoding) {
// 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
// bytes twice // bytes twice
if mw.d == 0 { if mw.depth == 0 {
mw.n += n mw.bytesWritten += n
} }
} }

View file

@ -28,7 +28,7 @@ func (bw *brokenWriter) Write([]byte) (int, error) {
// TestMsgWriter_Write tests the WriteTo() method of the msgWriter // TestMsgWriter_Write tests the WriteTo() method of the msgWriter
func TestMsgWriter_Write(t *testing.T) { func TestMsgWriter_Write(t *testing.T) {
bw := &brokenWriter{} bw := &brokenWriter{}
mw := &msgWriter{w: bw, c: CharsetUTF8, en: mime.QEncoding} mw := &msgWriter{writer: bw, charset: CharsetUTF8, encoder: mime.QEncoding}
_, err := mw.Write([]byte("test")) _, err := mw.Write([]byte("test"))
if err == nil { if err == nil {
t.Errorf("msgWriter WriteTo() with brokenWriter should fail, but didn't") t.Errorf("msgWriter WriteTo() with brokenWriter should fail, but didn't")
@ -55,7 +55,7 @@ func TestMsgWriter_writeMsg(t *testing.T) {
m.SetBodyString(TypeTextPlain, "This is the body") m.SetBodyString(TypeTextPlain, "This is the body")
m.AddAlternativeString(TypeTextHTML, "This is the alternative body") m.AddAlternativeString(TypeTextHTML, "This is the alternative body")
buf := bytes.Buffer{} buf := bytes.Buffer{}
mw := &msgWriter{w: &buf, c: CharsetUTF8, en: mime.QEncoding} mw := &msgWriter{writer: &buf, charset: CharsetUTF8, encoder: mime.QEncoding}
mw.writeMsg(m) mw.writeMsg(m)
ms := buf.String() ms := buf.String()
@ -134,7 +134,7 @@ func TestMsgWriter_writeMsg_PGP(t *testing.T) {
m.Subject("This is a subject") m.Subject("This is a subject")
m.SetBodyString(TypeTextPlain, "This is the body") m.SetBodyString(TypeTextPlain, "This is the body")
buf := bytes.Buffer{} buf := bytes.Buffer{}
mw := &msgWriter{w: &buf, c: CharsetUTF8, en: mime.QEncoding} mw := &msgWriter{writer: &buf, charset: CharsetUTF8, encoder: mime.QEncoding}
mw.writeMsg(m) mw.writeMsg(m)
ms := buf.String() ms := buf.String()
if !strings.Contains(ms, `encrypted; protocol="application/pgp-encrypted"`) { if !strings.Contains(ms, `encrypted; protocol="application/pgp-encrypted"`) {
@ -147,7 +147,7 @@ func TestMsgWriter_writeMsg_PGP(t *testing.T) {
m.Subject("This is a subject") m.Subject("This is a subject")
m.SetBodyString(TypeTextPlain, "This is the body") m.SetBodyString(TypeTextPlain, "This is the body")
buf = bytes.Buffer{} buf = bytes.Buffer{}
mw = &msgWriter{w: &buf, c: CharsetUTF8, en: mime.QEncoding} mw = &msgWriter{writer: &buf, charset: CharsetUTF8, encoder: mime.QEncoding}
mw.writeMsg(m) mw.writeMsg(m)
ms = buf.String() ms = buf.String()
if !strings.Contains(ms, `signed; protocol="application/pgp-signature"`) { if !strings.Contains(ms, `signed; protocol="application/pgp-signature"`) {