From e8739b88b0c511fe3a82f31fde3b141ad36e44b4 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 19:44:41 +0200 Subject: [PATCH 01/60] Enhance SMTP AUTH comments and error descriptions Extended documentation for each SMTP AUTH type including security considerations, relevant specifications, and the context for usage. Updated error descriptions for consistency and clarity. This enhances readability and provides better guidance for developers. --- auth.go | 81 +++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 64 insertions(+), 17 deletions(-) diff --git a/auth.go b/auth.go index f1dad86..aae1060 100644 --- a/auth.go +++ b/auth.go @@ -9,66 +9,113 @@ import "errors" // SMTPAuthType represents a string to any SMTP AUTH type type SMTPAuthType string -// Supported SMTP AUTH types const ( - // SMTPAuthCramMD5 is the "CRAM-MD5" SASL authentication mechanism as described in RFC 4954 + // SMTPAuthCramMD5 is the "CRAM-MD5" SASL authentication mechanism as described in RFC 4954. + // https://datatracker.ietf.org/doc/rfc4954/ + // + // CRAM-MD5 is not secure by modern standards. The vulnerabilities of MD5 and the lack of + // advanced security features make it inappropriate for protecting sensitive communications + // today. + // + // It was recommended to deprecate the standard in 20 November 2008. As an alternative it + // recommends e.g. SCRAM or SASL Plain protected by TLS instead. + // https://datatracker.ietf.org/doc/html/draft-ietf-sasl-crammd5-to-historic-00.html SMTPAuthCramMD5 SMTPAuthType = "CRAM-MD5" - // SMTPAuthLogin is the "LOGIN" SASL authentication mechanism + // SMTPAuthLogin is the "LOGIN" SASL authentication mechanism. This authentication mechanism + // does not have an official RFC that could be followed. There is a spec by Microsoft and an + // IETF draft. The IETF draft is more lax than the MS spec, therefore we follow the I-D, which + // automatically matches the MS spec. + // https://msopenspecs.azureedge.net/files/MS-XLOGIN/%5bMS-XLOGIN%5d.pdf + // https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00 + // + // Since the "LOGIN" SASL authentication mechansim transmits the username and password in + // plaintext over the internet connection, we only allow this mechanism over a TLS secured + // connection. SMTPAuthLogin SMTPAuthType = "LOGIN" // SMTPAuthNoAuth is equivalent to performing no authentication at all. It is a convenience // option and should not be used. Instead, for mail servers that do no support/require - // authentication, the Client should not be used with the WithSMTPAuth option + // authentication, the Client should not be passed the WithSMTPAuth option at all. SMTPAuthNoAuth SMTPAuthType = "" - // SMTPAuthPlain is the "PLAIN" authentication mechanism as described in RFC 4616 + // SMTPAuthPlain is the "PLAIN" authentication mechanism as described in RFC 4616. + // https://datatracker.ietf.org/doc/rfc4616/ + // + // Since the "PLAIN" SASL authentication mechansim transmits the username and password in + // plaintext over the internet connection, we only allow this mechanism over a TLS secured + // connection. SMTPAuthPlain SMTPAuthType = "PLAIN" // SMTPAuthXOAUTH2 is the "XOAUTH2" SASL authentication mechanism. // https://developers.google.com/gmail/imap/xoauth2-protocol SMTPAuthXOAUTH2 SMTPAuthType = "XOAUTH2" - // SMTPAuthSCRAMSHA1 represents the SCRAM-SHA-1 SMTP authentication mechanism + // SMTPAuthSCRAMSHA1 is the "SCRAM-SHA-1" SASL authentication mechanism as described in RFC 5802. // https://datatracker.ietf.org/doc/html/rfc5802 + // + // SCRAM-SHA-1 is still considered secure for certain applications, particularly when used as part + // of a challenge-response authentication mechanism (as we use it). However, it is generally + // recommended to prefer stronger alternatives like SCRAM-SHA-256(-PLUS), as SHA-1 has known + // vulnerabilities in other contexts, although it remains effective in HMAC constructions. SMTPAuthSCRAMSHA1 SMTPAuthType = "SCRAM-SHA-1" - // SMTPAuthSCRAMSHA1PLUS represents the "SCRAM-SHA-1-PLUS" authentication mechanism for SMTP. + // SMTPAuthSCRAMSHA1PLUS is the "SCRAM-SHA-1-PLUS" SASL authentication mechanism as described in RFC 5802. // https://datatracker.ietf.org/doc/html/rfc5802 + // + // SCRAM-SHA-X-PLUS authentication require TLS channel bindings to protect against MitM attacks and + // to guarantee that the integrity of the transport layer is preserved throughout the authentication + // process. Therefore we only allow this mechansim over a TLS secured connection. + // + // SCRAM-SHA-1-PLUS is still considered secure for certain applications, particularly when used as part + // of a challenge-response authentication mechanism (as we use it). However, it is generally + // recommended to prefer stronger alternatives like SCRAM-SHA-256(-PLUS), as SHA-1 has known + // vulnerabilities in other contexts, although it remains effective in HMAC constructions. SMTPAuthSCRAMSHA1PLUS SMTPAuthType = "SCRAM-SHA-1-PLUS" - // SMTPAuthSCRAMSHA256 represents the SCRAM-SHA-256 authentication mechanism for SMTP. + // SMTPAuthSCRAMSHA256 is the "SCRAM-SHA-256" SASL authentication mechanism as described in RFC 7677. // https://datatracker.ietf.org/doc/html/rfc7677 SMTPAuthSCRAMSHA256 SMTPAuthType = "SCRAM-SHA-256" - // SMTPAuthSCRAMSHA256PLUS represents the "SCRAM-SHA-256-PLUS" SMTP AUTH type. + // SMTPAuthSCRAMSHA256PLUS is the "SCRAM-SHA-256-PLUS" SASL authentication mechanism as described in RFC 7677. // https://datatracker.ietf.org/doc/html/rfc7677 + // + // SCRAM-SHA-X-PLUS authentication require TLS channel bindings to protect against MitM attacks and + // to guarantee that the integrity of the transport layer is preserved throughout the authentication + // process. Therefore we only allow this mechansim over a TLS secured connection. SMTPAuthSCRAMSHA256PLUS SMTPAuthType = "SCRAM-SHA-256-PLUS" ) // SMTP Auth related static errors var ( - // ErrPlainAuthNotSupported should be used if the target server does not support the "PLAIN" schema + // ErrPlainAuthNotSupported is returned when the server does not support the "PLAIN" SMTP + // authentication type. ErrPlainAuthNotSupported = errors.New("server does not support SMTP AUTH type: PLAIN") - // ErrLoginAuthNotSupported should be used if the target server does not support the "LOGIN" schema + // ErrLoginAuthNotSupported is returned when the server does not support the "LOGIN" SMTP + // authentication type. ErrLoginAuthNotSupported = errors.New("server does not support SMTP AUTH type: LOGIN") - // ErrCramMD5AuthNotSupported should be used if the target server does not support the "CRAM-MD5" schema + // ErrCramMD5AuthNotSupported is returned when the server does not support the "CRAM-MD5" SMTP + // authentication type. ErrCramMD5AuthNotSupported = errors.New("server does not support SMTP AUTH type: CRAM-MD5") - // ErrXOauth2AuthNotSupported should be used if the target server does not support the "XOAUTH2" schema + // ErrXOauth2AuthNotSupported is returned when the server does not support the "XOAUTH2" schema. ErrXOauth2AuthNotSupported = errors.New("server does not support SMTP AUTH type: XOAUTH2") - // ErrSCRAMSHA1AuthNotSupported should be used if the target server does not support the "SCRAM-SHA-1" schema + // ErrSCRAMSHA1AuthNotSupported is returned when the server does not support the "SCRAM-SHA-1" SMTP + // authentication type. ErrSCRAMSHA1AuthNotSupported = errors.New("server does not support SMTP AUTH type: SCRAM-SHA-1") - // ErrSCRAMSHA1PLUSAuthNotSupported should be used if the target server does not support the "SCRAM-SHA-1-PLUS" schema + // ErrSCRAMSHA1PLUSAuthNotSupported is returned when the server does not support the "SCRAM-SHA-1-PLUS" SMTP + // authentication type. ErrSCRAMSHA1PLUSAuthNotSupported = errors.New("server does not support SMTP AUTH type: SCRAM-SHA-1-PLUS") - // ErrSCRAMSHA256AuthNotSupported should be used if the target server does not support the "SCRAM-SHA-256" schema + // ErrSCRAMSHA256AuthNotSupported is returned when the server does not support the "SCRAM-SHA-256" SMTP + // authentication type. ErrSCRAMSHA256AuthNotSupported = errors.New("server does not support SMTP AUTH type: SCRAM-SHA-256") - // ErrSCRAMSHA256PLUSAuthNotSupported should be used if the target server does not support the "SCRAM-SHA-256-PLUS" schema + // ErrSCRAMSHA256PLUSAuthNotSupported is returned when the server does not support the "SCRAM-SHA-256-PLUS" SMTP + // authentication type. ErrSCRAMSHA256PLUSAuthNotSupported = errors.New("server does not support SMTP AUTH type: SCRAM-SHA-256-PLUS") ) From ea90352ef47a6c4bc9172efdb5d2fb1db9712b6d Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 19:45:37 +0200 Subject: [PATCH 02/60] Refactor SMTPAuthType comment for clarity Updated the comment for SMTPAuthType to more clearly explain its purpose as a type wrapper for SMTP authentication mechanisms. This improves code readability and helps future developers understand the type's function. --- auth.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/auth.go b/auth.go index aae1060..4eadd11 100644 --- a/auth.go +++ b/auth.go @@ -6,7 +6,8 @@ package mail import "errors" -// SMTPAuthType represents a string to any SMTP AUTH type +// SMTPAuthType is a type wrapper for a string type. It represents the type of SMTP authentication +// mechanism to be used. type SMTPAuthType string const ( From 6a9c8bb56bdcba34a911063e6a39f73e7432976a Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 19:50:10 +0200 Subject: [PATCH 03/60] Refactor documentation and comments for clarity Streamlined comments and documentation in `b64linebreaker.go` for better readability and consistency. Improved descriptions of the Base64LineBreaker and its methods to ensure clarity on functionality. --- b64linebreaker.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/b64linebreaker.go b/b64linebreaker.go index 088b38e..8f82cda 100644 --- a/b64linebreaker.go +++ b/b64linebreaker.go @@ -9,21 +9,25 @@ import ( "io" ) -// ErrNoOutWriter is an error message that should be used if a Base64LineBreaker has no out io.Writer set +// newlineBytes is a byte slice representation of the SingleNewLine constant used for line breaking +// in encoding processes. +var newlineBytes = []byte(SingleNewLine) + +// ErrNoOutWriter is the error message returned when no io.Writer is set for Base64LineBreaker. const ErrNoOutWriter = "no io.Writer set for Base64LineBreaker" -// Base64LineBreaker is a io.WriteCloser that writes Base64 encoded data streams -// with line breaks at a given line length +// Base64LineBreaker is used to handle base64 encoding with the insertion of new lines after a certain +// number of characters. +// +// It satisfies the io.WriteCloser interface. type Base64LineBreaker struct { line [MaxBodyLength]byte used int out io.Writer } -var newlineBytes = []byte(SingleNewLine) - -// Write writes the data stream and inserts a SingleNewLine when the maximum -// line length is reached +// Write writes data to the Base64LineBreaker, ensuring lines do not exceed MaxBodyLength. +// It handles continuation if data length exceeds the limit and writes new lines accordingly. func (l *Base64LineBreaker) Write(data []byte) (numBytes int, err error) { if l.out == nil { err = errors.New(ErrNoOutWriter) @@ -55,8 +59,7 @@ func (l *Base64LineBreaker) Write(data []byte) (numBytes int, err error) { return l.Write(data[excess:]) } -// Close closes the Base64LineBreaker and writes any access data that is still -// unwritten in memory +// Close finalizes the Base64LineBreaker, writing any remaining buffered data and appending a newline. func (l *Base64LineBreaker) Close() (err error) { if l.used > 0 { _, err = l.out.Write(l.line[0:l.used]) From 59e91eb9361feb09f9a44936bc57dd3b54402a84 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 20:00:49 +0200 Subject: [PATCH 04/60] Update RFC URLs to use html versions Changed the URLs for RFC 4954 and RFC 4616 from plain text to HTML formats for improved readability and consistency. This adjustment does not affect the functionality but enhances the documentation quality. --- auth.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auth.go b/auth.go index 4eadd11..110e45e 100644 --- a/auth.go +++ b/auth.go @@ -12,7 +12,7 @@ type SMTPAuthType string const ( // SMTPAuthCramMD5 is the "CRAM-MD5" SASL authentication mechanism as described in RFC 4954. - // https://datatracker.ietf.org/doc/rfc4954/ + // https://datatracker.ietf.org/doc/html/rfc4954/ // // CRAM-MD5 is not secure by modern standards. The vulnerabilities of MD5 and the lack of // advanced security features make it inappropriate for protecting sensitive communications @@ -41,7 +41,7 @@ const ( SMTPAuthNoAuth SMTPAuthType = "" // SMTPAuthPlain is the "PLAIN" authentication mechanism as described in RFC 4616. - // https://datatracker.ietf.org/doc/rfc4616/ + // https://datatracker.ietf.org/doc/html/rfc4616/ // // Since the "PLAIN" SASL authentication mechansim transmits the username and password in // plaintext over the internet connection, we only allow this mechanism over a TLS secured From 92c411454b184c9c76210045aaa15f07a2b40635 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 20:13:13 +0200 Subject: [PATCH 05/60] Enhance comments with detailed explanations and links Improved comments for better clarity by detailing the purpose of each constant and type, and included relevant RFC links for deeper context. These changes aim to help developers quickly understand the code without needing to cross-reference external documents. --- client.go | 75 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/client.go b/client.go index 4d1b0b1..247ca71 100644 --- a/client.go +++ b/client.go @@ -19,67 +19,74 @@ import ( "github.com/wneessen/go-mail/smtp" ) -// Defaults const ( - // DefaultPort is the default connection port to the SMTP server + // DefaultPort is the default connection port to the SMTP server. DefaultPort = 25 - // DefaultPortSSL is the default connection port for SSL/TLS to the SMTP server + // DefaultPortSSL is the default connection port for SSL/TLS to the SMTP server. DefaultPortSSL = 465 - // DefaultPortTLS is the default connection port for STARTTLS to the SMTP server + // DefaultPortTLS is the default connection port for STARTTLS to the SMTP server. DefaultPortTLS = 587 - // DefaultTimeout is the default connection timeout + // DefaultTimeout is the default connection timeout. DefaultTimeout = time.Second * 15 - // DefaultTLSPolicy is the default STARTTLS policy + // DefaultTLSPolicy specifies the default TLS policy for connections. DefaultTLSPolicy = TLSMandatory - // DefaultTLSMinVersion is the minimum TLS version required for the connection - // Nowadays TLS1.2 should be the sane default + // DefaultTLSMinVersion defines the minimum TLS version to be used for secure connections. + // Nowadays TLS 1.2 is assumed be a sane default. DefaultTLSMinVersion = tls.VersionTLS12 ) -// DSNMailReturnOption is a type to define which MAIL RET option is used when a DSN -// is requested -type DSNMailReturnOption string +type ( -// DSNRcptNotifyOption is a type to define which RCPT NOTIFY option is used when a DSN -// is requested -type DSNRcptNotifyOption string + // DSNMailReturnOption is a type wrapper for a string and specifies the type of return content requested + // in a Delivery Status Notification (DSN). + // https://datatracker.ietf.org/doc/html/rfc1891/ + DSNMailReturnOption string + + // DSNRcptNotifyOption is a type wrapper for a string and specifies the notification options for a + // recipient in DSNs. + // https://datatracker.ietf.org/doc/html/rfc1891/ + DSNRcptNotifyOption string +) const ( - // DSNMailReturnHeadersOnly requests that only the headers of the message be returned. - // See: https://www.rfc-editor.org/rfc/rfc1891#section-5.3 + + // DSNMailReturnHeadersOnly requests that only the message headers of the mail message are returned in + // a DSN (Delivery Status Notification). + // https://datatracker.ietf.org/doc/html/rfc1891#section-5.3 DSNMailReturnHeadersOnly DSNMailReturnOption = "HDRS" - // DSNMailReturnFull requests that the entire message be returned in any "failed" - // delivery status notification issued for this recipient - // See: https://www.rfc-editor.org/rfc/rfc1891#section-5.3 + // DSNMailReturnFull requests that the entire mail message is returned in any failed DSN + // (Delivery Status Notification) issued for this recipient. + // https://datatracker.ietf.org/doc/html/rfc1891/#section-5.3 DSNMailReturnFull DSNMailReturnOption = "FULL" - // DSNRcptNotifyNever requests that a DSN not be returned to the sender under - // any conditions. - // See: https://www.rfc-editor.org/rfc/rfc1891#section-5.1 + // DSNRcptNotifyNever indicates that no DSN (Delivery Status Notifications) should be sent for the + // recipient under any condition. + // https://datatracker.ietf.org/doc/html/rfc1891/#section-5.1 DSNRcptNotifyNever DSNRcptNotifyOption = "NEVER" - // DSNRcptNotifySuccess requests that a DSN be issued on successful delivery - // See: https://www.rfc-editor.org/rfc/rfc1891#section-5.1 + // DSNRcptNotifySuccess indicates that the sender requests a DSN (Delivery Status Notification) if the + // message is successfully delivered. + // https://datatracker.ietf.org/doc/html/rfc1891/#section-5.1 DSNRcptNotifySuccess DSNRcptNotifyOption = "SUCCESS" - // DSNRcptNotifyFailure requests that a DSN be issued on delivery failure - // See: https://www.rfc-editor.org/rfc/rfc1891#section-5.1 + // DSNRcptNotifyFailure requests that a DSN (Delivery Status Notification) is issued if delivery of + // a message fails. + // https://datatracker.ietf.org/doc/html/rfc1891/#section-5.1 DSNRcptNotifyFailure DSNRcptNotifyOption = "FAILURE" - // DSNRcptNotifyDelay indicates the sender's willingness to receive - // "delayed" DSNs. Delayed DSNs may be issued if delivery of a message has - // been delayed for an unusual amount of time (as determined by the MTA at - // which the message is delayed), but the final delivery status (whether - // successful or failure) cannot be determined. The absence of the DELAY - // keyword in a NOTIFY parameter requests that a "delayed" DSN NOT be - // issued under any conditions. - // See: https://www.rfc-editor.org/rfc/rfc1891#section-5.1 + // DSNRcptNotifyDelay indicates the sender's willingness to receive "delayed" DSNs. + // + // Delayed DSNs may be issued if delivery of a message has been delayed for an unusual amount of time + // (as determined by the MTA at which the message is delayed), but the final delivery status (whether + // successful or failure) cannot be determined. The absence of the DELAY keyword in a NOTIFY parameter + // requests that a "delayed" DSN NOT be issued under any conditions. + // https://datatracker.ietf.org/doc/html/rfc1891/#section-5.1 DSNRcptNotifyDelay DSNRcptNotifyOption = "DELAY" ) From ef3da39840564fc9dc940920a1dc7d334bf2740e Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 20:29:14 +0200 Subject: [PATCH 06/60] Refactor DSN field in Client structure Renamed `dsn` field to `requestDSN` in Client structure for clarity and consistency. Adjusted associated methods and tests to reflect this change, improving code readability and maintainability. --- client.go | 143 +++++++++++++++++++++++++------------------------ client_test.go | 4 +- 2 files changed, 75 insertions(+), 72 deletions(-) diff --git a/client.go b/client.go index 247ca71..6885d90 100644 --- a/client.go +++ b/client.go @@ -40,19 +40,6 @@ const ( DefaultTLSMinVersion = tls.VersionTLS12 ) -type ( - - // DSNMailReturnOption is a type wrapper for a string and specifies the type of return content requested - // in a Delivery Status Notification (DSN). - // https://datatracker.ietf.org/doc/html/rfc1891/ - DSNMailReturnOption string - - // DSNRcptNotifyOption is a type wrapper for a string and specifies the notification options for a - // recipient in DSNs. - // https://datatracker.ietf.org/doc/html/rfc1891/ - DSNRcptNotifyOption string -) - const ( // DSNMailReturnHeadersOnly requests that only the message headers of the mail message are returned in @@ -90,82 +77,98 @@ const ( DSNRcptNotifyDelay DSNRcptNotifyOption = "DELAY" ) -// DialContextFunc is a type to define custom DialContext function. -type DialContextFunc func(ctx context.Context, network, address string) (net.Conn, error) +type ( -// Client is the SMTP client struct -type Client struct { - // Timeout for the SMTP server connection - connTimeout time.Duration + // DialContextFunc defines a function type for establishing a network connection using context, network + // type, and address. It is used to specify custom DialContext function. + // + // By default we use net.Dial or tls.Dial respectively. + DialContextFunc func(ctx context.Context, network, address string) (net.Conn, error) - // dialContextFunc is a custom DialContext function to dial target SMTP server - dialContextFunc DialContextFunc + // DSNMailReturnOption is a type wrapper for a string and specifies the type of return content requested + // in a Delivery Status Notification (DSN). + // https://datatracker.ietf.org/doc/html/rfc1891/ + DSNMailReturnOption string - // dsn indicates that we want to use DSN for the Client - dsn bool + // DSNRcptNotifyOption is a type wrapper for a string and specifies the notification options for a + // recipient in DSNs. + // https://datatracker.ietf.org/doc/html/rfc1891/ + DSNRcptNotifyOption string - // dsnmrtype defines the DSNMailReturnOption in case DSN is enabled - dsnmrtype DSNMailReturnOption + // Option is a function type that modifies the configuration or behavior of a Client instance. + Option func(*Client) error - // dsnrntype defines the DSNRcptNotifyOption in case DSN is enabled - dsnrntype []string + // Client is the go-mail client that is responsible for connecting and interacting with an SMTP server. + Client struct { + // connTimeout specifies timeout for the connection to the SMTP server. + connTimeout time.Duration - // fallbackPort is used as an alternative port number in case the primary port is unavailable or - // fails to bind. - fallbackPort int + // dialContextFunc is the DialContextFunc that is used by the Client to connect to the SMTP server. + dialContextFunc DialContextFunc - // HELO/EHLO string for the greeting the target SMTP server - helo string + // dsnmrtype defines the DSNMailReturnOption in case DSN is enabled + dsnmrtype DSNMailReturnOption - // Hostname of the target SMTP server to connect to - host string + // dsnrntype defines the DSNRcptNotifyOption in case DSN is enabled + dsnrntype []string - // isEncrypted indicates if a Client connection is encrypted or not - isEncrypted bool + // fallbackPort is used as an alternative port number in case the primary port is unavailable or + // fails to bind. + fallbackPort int - // logger is a logger that implements the log.Logger interface - logger log.Logger + // HELO/EHLO string for the greeting the target SMTP server + helo string - // mutex is used to synchronize access to shared resources, ensuring that only one goroutine can - // modify them at a time. - mutex sync.RWMutex + // Hostname of the target SMTP server to connect to + host string - // noNoop indicates the Noop is to be skipped - noNoop bool + // isEncrypted indicates if a Client connection is encrypted or not + isEncrypted bool - // pass is the corresponding SMTP AUTH password - pass string + // logger is a logger that implements the log.Logger interface + logger log.Logger - // port specifies the network port number on which the server listens for incoming connections. - port int + // mutex is used to synchronize access to shared resources, ensuring that only one goroutine can + // modify them at a time. + mutex sync.RWMutex - // smtpAuth is a pointer to smtp.Auth - smtpAuth smtp.Auth + // noNoop indicates the Noop is to be skipped + noNoop bool - // smtpAuthType represents the authentication type for SMTP AUTH - smtpAuthType SMTPAuthType + // pass is the corresponding SMTP AUTH password + pass string - // smtpClient is the smtp.Client that is set up when using the Dial*() methods - smtpClient *smtp.Client + // port specifies the network port number on which the server listens for incoming connections. + port int - // tlspolicy sets the client to use the provided TLSPolicy for the STARTTLS protocol - tlspolicy TLSPolicy + // requestDSN indicates that we want to use DSN for the Client + requestDSN bool - // tlsconfig represents the tls.Config setting for the STARTTLS connection - tlsconfig *tls.Config + // smtpAuth is a pointer to smtp.Auth + smtpAuth smtp.Auth - // useDebugLog enables the debug logging on the SMTP client - useDebugLog bool + // smtpAuthType represents the authentication type for SMTP AUTH + smtpAuthType SMTPAuthType - // user is the SMTP AUTH username - user string + // smtpClient is the smtp.Client that is set up when using the Dial*() methods + smtpClient *smtp.Client - // Use SSL for the connection - useSSL bool -} + // tlspolicy sets the client to use the provided TLSPolicy for the STARTTLS protocol + tlspolicy TLSPolicy -// Option returns a function that can be used for grouping Client options -type Option func(*Client) error + // tlsconfig represents the tls.Config setting for the STARTTLS connection + tlsconfig *tls.Config + + // useDebugLog enables the debug logging on the SMTP client + useDebugLog bool + + // user is the SMTP AUTH username + user string + + // Use SSL for the connection + useSSL bool + } +) var ( // ErrInvalidPort should be used if a port is specified that is not valid @@ -394,7 +397,7 @@ func WithPassword(password string) Option { // and DSNRcptNotifyFailure func WithDSN() Option { return func(c *Client) error { - c.dsn = true + c.requestDSN = true c.dsnmrtype = DSNMailReturnFull c.dsnrntype = []string{string(DSNRcptNotifyFailure), string(DSNRcptNotifySuccess)} return nil @@ -414,7 +417,7 @@ func WithDSNMailReturnType(option DSNMailReturnOption) Option { return ErrInvalidDSNMailReturnOption } - c.dsn = true + c.requestDSN = true c.dsnmrtype = option return nil } @@ -448,7 +451,7 @@ func WithDSNRcptNotifyType(opts ...DSNRcptNotifyOption) Option { return ErrInvalidDSNRcptNotifyCombination } - c.dsn = true + c.requestDSN = true c.dsnrntype = rcptOpts return nil } @@ -870,7 +873,7 @@ func (c *Client) sendSingleMsg(message *Msg) error { } } - if c.dsn { + if c.requestDSN { if c.dsnmrtype != "" { c.smtpClient.SetDSNMailReturnOption(string(c.dsnmrtype)) } diff --git a/client_test.go b/client_test.go index b8d77ed..7c6f00e 100644 --- a/client_test.go +++ b/client_test.go @@ -483,8 +483,8 @@ func TestWithDSN(t *testing.T) { t.Errorf("failed to create new client: %s", err) return } - if !c.dsn { - t.Errorf("WithDSN failed. c.dsn expected to be: %t, got: %t", true, c.dsn) + if !c.requestDSN { + t.Errorf("WithDSN failed. c.requestDSN expected to be: %t, got: %t", true, c.requestDSN) } if c.dsnmrtype != DSNMailReturnFull { t.Errorf("WithDSN failed. c.dsnmrtype expected to be: %s, got: %s", DSNMailReturnFull, From f7c12d412be96c881cce32d5f746e2c426a87bd6 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 20:30:43 +0200 Subject: [PATCH 07/60] Rename dsnmrtype to dsnReturnType in client.go Refactor variable names for consistency. The `dsnmrtype` variable has been renamed to `dsnReturnType` across the client and test files to improve code readability and maintain uniformity. --- client.go | 12 ++++++------ client_test.go | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/client.go b/client.go index 6885d90..48d36dd 100644 --- a/client.go +++ b/client.go @@ -106,8 +106,8 @@ type ( // dialContextFunc is the DialContextFunc that is used by the Client to connect to the SMTP server. dialContextFunc DialContextFunc - // dsnmrtype defines the DSNMailReturnOption in case DSN is enabled - dsnmrtype DSNMailReturnOption + // dsnReturnType defines the DSNMailReturnOption in case DSN is enabled + dsnReturnType DSNMailReturnOption // dsnrntype defines the DSNRcptNotifyOption in case DSN is enabled dsnrntype []string @@ -398,7 +398,7 @@ func WithPassword(password string) Option { func WithDSN() Option { return func(c *Client) error { c.requestDSN = true - c.dsnmrtype = DSNMailReturnFull + c.dsnReturnType = DSNMailReturnFull c.dsnrntype = []string{string(DSNRcptNotifyFailure), string(DSNRcptNotifySuccess)} return nil } @@ -418,7 +418,7 @@ func WithDSNMailReturnType(option DSNMailReturnOption) Option { } c.requestDSN = true - c.dsnmrtype = option + c.dsnReturnType = option return nil } } @@ -874,8 +874,8 @@ func (c *Client) sendSingleMsg(message *Msg) error { } if c.requestDSN { - if c.dsnmrtype != "" { - c.smtpClient.SetDSNMailReturnOption(string(c.dsnmrtype)) + if c.dsnReturnType != "" { + c.smtpClient.SetDSNMailReturnOption(string(c.dsnReturnType)) } } if err = c.smtpClient.Mail(from); err != nil { diff --git a/client_test.go b/client_test.go index 7c6f00e..47ffab8 100644 --- a/client_test.go +++ b/client_test.go @@ -486,9 +486,9 @@ func TestWithDSN(t *testing.T) { if !c.requestDSN { t.Errorf("WithDSN failed. c.requestDSN expected to be: %t, got: %t", true, c.requestDSN) } - if c.dsnmrtype != DSNMailReturnFull { - t.Errorf("WithDSN failed. c.dsnmrtype expected to be: %s, got: %s", DSNMailReturnFull, - c.dsnmrtype) + if c.dsnReturnType != DSNMailReturnFull { + t.Errorf("WithDSN failed. c.dsnReturnType expected to be: %s, got: %s", DSNMailReturnFull, + c.dsnReturnType) } if c.dsnrntype[0] != string(DSNRcptNotifyFailure) { t.Errorf("WithDSN failed. c.dsnrntype[0] expected to be: %s, got: %s", DSNRcptNotifyFailure, @@ -519,8 +519,8 @@ func TestWithDSNMailReturnType(t *testing.T) { t.Errorf("failed to create new client: %s", err) return } - if string(c.dsnmrtype) != tt.want { - t.Errorf("WithDSNMailReturnType failed. Expected %s, got: %s", tt.want, string(c.dsnmrtype)) + if string(c.dsnReturnType) != tt.want { + t.Errorf("WithDSNMailReturnType failed. Expected %s, got: %s", tt.want, string(c.dsnReturnType)) } }) } From aab04672f8c78fe782a7d9e52f8eeac78bbbc90b Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 20:39:14 +0200 Subject: [PATCH 08/60] Rename DSN type variable for clarity Renamed the variable from `dsnrntype` to `dsnRcptNotifyType` to improve code readability and ensure clarity regarding its purpose. Also updated corresponding comments and test cases to reflect this change. --- client.go | 22 +++++++++++++--------- client_test.go | 18 +++++++++--------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/client.go b/client.go index 48d36dd..45ccf0c 100644 --- a/client.go +++ b/client.go @@ -106,20 +106,24 @@ type ( // dialContextFunc is the DialContextFunc that is used by the Client to connect to the SMTP server. dialContextFunc DialContextFunc - // dsnReturnType defines the DSNMailReturnOption in case DSN is enabled - dsnReturnType DSNMailReturnOption + // dsnRcptNotifyType represents the different types of notifications for DSN (Delivery Status Notifications) + // receipts. + dsnRcptNotifyType []string - // dsnrntype defines the DSNRcptNotifyOption in case DSN is enabled - dsnrntype []string + // dsnReturnType specifies the type of Delivery Status Notification (DSN) that should be requested for an + // email. + dsnReturnType DSNMailReturnOption // fallbackPort is used as an alternative port number in case the primary port is unavailable or // fails to bind. + // + // The fallbackPort is only used in combination with SetTLSPortPolicy and SetSSLPort correspondingly. fallbackPort int - // HELO/EHLO string for the greeting the target SMTP server + // helo is the hostname used in the HELO/EHLO greeting, that is sent to the target SMTP server. helo string - // Hostname of the target SMTP server to connect to + // host is the hostname of the SMTP server we are connecting to. host string // isEncrypted indicates if a Client connection is encrypted or not @@ -399,7 +403,7 @@ func WithDSN() Option { return func(c *Client) error { c.requestDSN = true c.dsnReturnType = DSNMailReturnFull - c.dsnrntype = []string{string(DSNRcptNotifyFailure), string(DSNRcptNotifySuccess)} + c.dsnRcptNotifyType = []string{string(DSNRcptNotifyFailure), string(DSNRcptNotifySuccess)} return nil } } @@ -452,7 +456,7 @@ func WithDSNRcptNotifyType(opts ...DSNRcptNotifyOption) Option { } c.requestDSN = true - c.dsnrntype = rcptOpts + c.dsnRcptNotifyType = rcptOpts return nil } } @@ -892,7 +896,7 @@ func (c *Client) sendSingleMsg(message *Msg) error { rcptSendErr := &SendError{affectedMsg: message} rcptSendErr.errlist = make([]error, 0) rcptSendErr.rcpt = make([]string, 0) - rcptNotifyOpt := strings.Join(c.dsnrntype, ",") + rcptNotifyOpt := strings.Join(c.dsnRcptNotifyType, ",") c.smtpClient.SetDSNRcptNotifyOption(rcptNotifyOpt) for _, rcpt := range rcpts { if err = c.smtpClient.Rcpt(rcpt); err != nil { diff --git a/client_test.go b/client_test.go index 47ffab8..1539426 100644 --- a/client_test.go +++ b/client_test.go @@ -490,13 +490,13 @@ func TestWithDSN(t *testing.T) { t.Errorf("WithDSN failed. c.dsnReturnType expected to be: %s, got: %s", DSNMailReturnFull, c.dsnReturnType) } - if c.dsnrntype[0] != string(DSNRcptNotifyFailure) { - t.Errorf("WithDSN failed. c.dsnrntype[0] expected to be: %s, got: %s", DSNRcptNotifyFailure, - c.dsnrntype[0]) + if c.dsnRcptNotifyType[0] != string(DSNRcptNotifyFailure) { + t.Errorf("WithDSN failed. c.dsnRcptNotifyType[0] expected to be: %s, got: %s", DSNRcptNotifyFailure, + c.dsnRcptNotifyType[0]) } - if c.dsnrntype[1] != string(DSNRcptNotifySuccess) { - t.Errorf("WithDSN failed. c.dsnrntype[1] expected to be: %s, got: %s", DSNRcptNotifySuccess, - c.dsnrntype[1]) + if c.dsnRcptNotifyType[1] != string(DSNRcptNotifySuccess) { + t.Errorf("WithDSN failed. c.dsnRcptNotifyType[1] expected to be: %s, got: %s", DSNRcptNotifySuccess, + c.dsnRcptNotifyType[1]) } } @@ -547,11 +547,11 @@ func TestWithDSNRcptNotifyType(t *testing.T) { t.Errorf("failed to create new client: %s", err) return } - if len(c.dsnrntype) <= 0 && !tt.sf { + if len(c.dsnRcptNotifyType) <= 0 && !tt.sf { t.Errorf("WithDSNRcptNotifyType failed. Expected at least one DSNRNType but got none") } - if !tt.sf && c.dsnrntype[0] != tt.want { - t.Errorf("WithDSNRcptNotifyType failed. Expected %s, got: %s", tt.want, c.dsnrntype[0]) + if !tt.sf && c.dsnRcptNotifyType[0] != tt.want { + t.Errorf("WithDSNRcptNotifyType failed. Expected %s, got: %s", tt.want, c.dsnRcptNotifyType[0]) } }) } From 6cd3cfd2f7c95d277f42dd2cf63ed6e0b6fa925d Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 20:57:51 +0200 Subject: [PATCH 09/60] Refactor comments for clarity and consistency Rephrase comments to enhance clarity and maintain consistent style. Improved explanations for fields such as `smtpAuth`, `helo`, and `noNoop`, and standardize grammar and format across all comments. This helps in better understanding the code and its functionality. --- client.go | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/client.go b/client.go index 45ccf0c..3be7d2e 100644 --- a/client.go +++ b/client.go @@ -121,55 +121,66 @@ type ( fallbackPort int // helo is the hostname used in the HELO/EHLO greeting, that is sent to the target SMTP server. + // + // helo might be different as host. This can be useful in a shared-hosting scenario. helo string // host is the hostname of the SMTP server we are connecting to. host string - // isEncrypted indicates if a Client connection is encrypted or not + // isEncrypted indicates wether the Client connection is encrypted or not. isEncrypted bool - // logger is a logger that implements the log.Logger interface + // logger is a logger that satisfies the log.Logger interface. logger log.Logger // mutex is used to synchronize access to shared resources, ensuring that only one goroutine can // modify them at a time. mutex sync.RWMutex - // noNoop indicates the Noop is to be skipped + // noNoop indicates that the Client should skip the "NOOP" command during the dial. + // + // This is useful for servers which delay potentially unwanted clients when they perform commands + // other than AUTH. noNoop bool - // pass is the corresponding SMTP AUTH password + // pass represents a password or a secret token used for the SMTP authentication. pass string - // port specifies the network port number on which the server listens for incoming connections. + // port specifies the network port that is used to establish the connection with the SMTP server. port int - // requestDSN indicates that we want to use DSN for the Client + // requestDSN indicates wether we want to request DSN (Delivery Status Notifications). requestDSN bool - // smtpAuth is a pointer to smtp.Auth + // smtpAuth is the authentication type that is used to authenticate the user with SMTP server. It + // satisfies the smtp.Auth interface. + // + // Unless you plan to write you own custom authentication method, it is advised to not set this manually. + // You should use one of go-mail's SMTPAuthType, instead. smtpAuth smtp.Auth - // smtpAuthType represents the authentication type for SMTP AUTH + // smtpAuthType specifies the authentication type to be used for SMTP authentication. smtpAuthType SMTPAuthType - // smtpClient is the smtp.Client that is set up when using the Dial*() methods + // smtpClient is an instance of smtp.Client used for handling the communication with the SMTP server. smtpClient *smtp.Client - // tlspolicy sets the client to use the provided TLSPolicy for the STARTTLS protocol + // tlspolicy defines the TLSPolicy configuration the Client uses for the STARTTLS protocol. + // https://datatracker.ietf.org/doc/html/rfc3207#section-2 tlspolicy TLSPolicy - // tlsconfig represents the tls.Config setting for the STARTTLS connection + // tlsconfig is a pointer to tls.Config that specifies the TLS configuration for the STARTTLS communication. tlsconfig *tls.Config - // useDebugLog enables the debug logging on the SMTP client + // useDebugLog indicates whether debug level logging is enabled for the Client. useDebugLog bool - // user is the SMTP AUTH username + // user represents a username used for the SMTP authentication. user string - // Use SSL for the connection + // useSSL indicates whether to use SSL/TLS encryption for network communication. + // https://datatracker.ietf.org/doc/html/rfc8314 useSSL bool } ) From 779a3f3942cc1e93ec3924b98a171de2491b9535 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 21:12:05 +0200 Subject: [PATCH 10/60] Refactor error comments for clarity Updated error comments to provide clearer and more descriptive explanations. Enhanced readability by elaborating on the conditions that result in each error, giving developers better context. No functional changes to the code were made. --- client.go | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/client.go b/client.go index 3be7d2e..48f4040 100644 --- a/client.go +++ b/client.go @@ -186,43 +186,41 @@ type ( ) var ( - // ErrInvalidPort should be used if a port is specified that is not valid + // ErrInvalidPort is returned when the specified port for the SMTP connection is not valid ErrInvalidPort = errors.New("invalid port number") - // ErrInvalidTimeout should be used if a timeout is set that is zero or negative + // ErrInvalidTimeout is returned when the specified timeout is zero or negative. ErrInvalidTimeout = errors.New("timeout cannot be zero or negative") - // ErrInvalidHELO should be used if an empty HELO sting is provided + // ErrInvalidHELO is returned when the HELO/EHLO value is invalid due to being empty. ErrInvalidHELO = errors.New("invalid HELO/EHLO value - must not be empty") - // ErrInvalidTLSConfig should be used if an empty tls.Config is provided + // ErrInvalidTLSConfig is returned when the provided TLS configuration is invalid or nil. ErrInvalidTLSConfig = errors.New("invalid TLS config") - // ErrNoHostname should be used if a Client has no hostname set + // ErrNoHostname is returned when the hostname for the client is not provided or empty. ErrNoHostname = errors.New("hostname for client cannot be empty") - // ErrDeadlineExtendFailed should be used if the extension of the connection deadline fails + // ErrDeadlineExtendFailed is returned when an attempt to extend the connection deadline fails. ErrDeadlineExtendFailed = errors.New("connection deadline extension failed") - // ErrNoActiveConnection should be used when a method is used that requies a server connection - // but is not yet connected + // ErrNoActiveConnection indicates that there is no active connection to the SMTP server. ErrNoActiveConnection = errors.New("not connected to SMTP server") - // ErrServerNoUnencoded should be used when 8BIT encoding is selected for a message, but - // the server does not offer 8BITMIME mode + // ErrServerNoUnencoded indicates that the server does not support 8BITMIME for unencoded 8-bit messages. ErrServerNoUnencoded = errors.New("message is 8bit unencoded, but server does not support 8BITMIME") - // ErrInvalidDSNMailReturnOption should be used when an invalid option is provided for the - // DSNMailReturnOption in WithDSN + // ErrInvalidDSNMailReturnOption is returned when an invalid DSNMailReturnOption is provided as argument + // to the WithDSN Option. ErrInvalidDSNMailReturnOption = errors.New("DSN mail return option can only be HDRS or FULL") - // ErrInvalidDSNRcptNotifyOption should be used when an invalid option is provided for the - // DSNRcptNotifyOption in WithDSN + // ErrInvalidDSNRcptNotifyOption is returned when an invalid DSNRcptNotifyOption is provided as argument + // to the WithDSN Option. ErrInvalidDSNRcptNotifyOption = errors.New("DSN rcpt notify option can only be: NEVER, " + "SUCCESS, FAILURE or DELAY") - // ErrInvalidDSNRcptNotifyCombination should be used when an invalid option is provided for the - // DSNRcptNotifyOption in WithDSN + // ErrInvalidDSNRcptNotifyCombination is returned when an invalid combination of DSNRcptNotifyOption is + // provided as argument to the WithDSN Option. ErrInvalidDSNRcptNotifyCombination = errors.New("DSN rcpt notify option NEVER cannot be " + "combined with any of SUCCESS, FAILURE or DELAY") ) From a34f400a05794b4cdecf63ee8d5285217cc6aa5f Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 21:26:29 +0200 Subject: [PATCH 11/60] Improve client option documentation for clarity and validation Enhanced the documentation for NewClient and related option functions to provide clearer descriptions. Added validation details for WithPort and WithTimeout, and improved explanations for SSL/TLS settings. --- client.go | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/client.go b/client.go index 48f4040..5662e22 100644 --- a/client.go +++ b/client.go @@ -225,7 +225,11 @@ var ( "combined with any of SUCCESS, FAILURE or DELAY") ) -// NewClient returns a new Session client object +// NewClient creates a new Client instance with the provided host and optional configuration Option functions. +// It initializes default values for connection timeout, port, TLS settings, and HELO/EHLO hostname. +// Option functions, if provided, override default values. +// +// Returns an error if critical defaults are unset. func NewClient(host string, opts ...Option) (*Client, error) { c := &Client{ connTimeout: DefaultTimeout, @@ -258,7 +262,8 @@ func NewClient(host string, opts ...Option) (*Client, error) { return c, nil } -// WithPort overrides the default connection port +// WithPort sets the port number for the Client and overrides the default port. It validates the port number to +// ensure it is between 1 and 65535. An error is returned if the provided port number is invalid. func WithPort(port int) Option { return func(c *Client) error { if port < 1 || port > 65535 { @@ -269,7 +274,8 @@ func WithPort(port int) Option { } } -// WithTimeout overrides the default connection timeout +// WithTimeout sets the connection timeout for the Client to the provided duration and overrides the default +// timeout. An error is returned if the provided timeout is invalid. func WithTimeout(timeout time.Duration) Option { return func(c *Client) error { if timeout <= 0 { @@ -280,7 +286,7 @@ func WithTimeout(timeout time.Duration) Option { } } -// WithSSL tells the client to use a SSL/TLS connection +// WithSSL enables implicit SSL/TLS for the Client. func WithSSL() Option { return func(c *Client) error { c.useSSL = true @@ -288,16 +294,16 @@ func WithSSL() Option { } } -// WithSSLPort tells the Client wether or not to use SSL and fallback. +// WithSSLPort enables implicit SSL/TLS with an optional fallback for the Client. // The correct port is automatically set. // -// Port 465 is used when SSL set (true). -// Port 25 is used when SSL is unset (false). -// When the SSL connection fails and fb is set to true, -// the client will attempt to connect on port 25 using plaintext. +// If this option is used with NewClient, the default port 25 will be overriden +// with port 465. If fallback is set to true and the SSL/TLS connection fails, +// the Client will attempt to connect on port 25 using plaintext. // -// Note: If a different port has already been set otherwise, the port-choosing -// and fallback automatism will be skipped. +// Note: If a different port has already been set otherwise using WithPort, the +// selected port has higher precedence and is used to establish the SSL/TLS +// connection. In this case the authmatic fallback mechanism is skipped at all. func WithSSLPort(fallback bool) Option { return func(c *Client) error { c.SetSSLPort(true, fallback) From 3e5c93a4189a9a5ee25c978bf9bd4075e10b5e33 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 21:38:39 +0200 Subject: [PATCH 12/60] Refine and clarify SSL and debug logging comments Revised the comments for `WithSSLPort`, `WithDebugLog`, `WithLogger`, and `WithHELO` options to improve readability and provide clearer explanations. Added caution about potential data protection issues when using debug logging and specified defaults where applicable. --- client.go | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/client.go b/client.go index 5662e22..731a73d 100644 --- a/client.go +++ b/client.go @@ -294,16 +294,16 @@ func WithSSL() Option { } } -// WithSSLPort enables implicit SSL/TLS with an optional fallback for the Client. -// The correct port is automatically set. +// WithSSLPort enables implicit SSL/TLS with an optional fallback for the Client. The correct port is +// automatically set. // -// If this option is used with NewClient, the default port 25 will be overriden -// with port 465. If fallback is set to true and the SSL/TLS connection fails, -// the Client will attempt to connect on port 25 using plaintext. +// If this option is used with NewClient, the default port 25 will be overriden with port 465. If fallback +// is set to true and the SSL/TLS connection fails, the Client will attempt to connect on port 25 using +// using an unencrypted connection. // -// Note: If a different port has already been set otherwise using WithPort, the -// selected port has higher precedence and is used to establish the SSL/TLS -// connection. In this case the authmatic fallback mechanism is skipped at all. +// Note: If a different port has already been set otherwise using WithPort, the selected port has higher +// precedence and is used to establish the SSL/TLS connection. In this case the authmatic fallback +// mechanism is skipped at all. func WithSSLPort(fallback bool) Option { return func(c *Client) error { c.SetSSLPort(true, fallback) @@ -311,8 +311,12 @@ func WithSSLPort(fallback bool) Option { } } -// WithDebugLog tells the client to log incoming and outgoing messages of the SMTP client -// to StdErr +// WithDebugLog neables debug logging for the Client. The debug logger will log incoming and outgoing +// communication between the Client and the server to os.StdErr. +// +// Note: The SMTP communication might include unencrypted authentication data, depending if you are +// using SMTP authentication and the type of authentication mechanism. This could pose a data +// protection problem. Use debug logging with care. func WithDebugLog() Option { return func(c *Client) error { c.useDebugLog = true @@ -320,7 +324,10 @@ func WithDebugLog() Option { } } -// WithLogger overrides the default log.Logger that is used for debug logging +// WithLogger defines a custom logger for the Client. The logger has to satisfy the log.Logger +// interface and is only used when debug logging is enabled on the Client. +// +// By default we use log.Stdlog. func WithLogger(logger log.Logger) Option { return func(c *Client) error { c.logger = logger @@ -328,7 +335,9 @@ func WithLogger(logger log.Logger) Option { } } -// WithHELO tells the client to use the provided string as HELO/EHLO greeting host +// WithHELO sets the HELO/EHLO string used for the the Client. +// +// By default we use os.Hostname to identify the HELO/EHLO string. func WithHELO(helo string) Option { return func(c *Client) error { if helo == "" { From bae0ac6cdeb2f01d6ee3e795ec737158e21e8795 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 21:45:12 +0200 Subject: [PATCH 13/60] Refactor TLS policy comments for clarity Updated comments for WithTLSPolicy and WithTLSPortPolicy to provide clearer explanations. Improved readability and emphasized recommended best practices for SMTP TLS connections. --- client.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/client.go b/client.go index 731a73d..200f0c5 100644 --- a/client.go +++ b/client.go @@ -348,10 +348,10 @@ func WithHELO(helo string) Option { } } -// WithTLSPolicy tells the client to use the provided TLSPolicy +// WithTLSPolicy sets the TLSPolicy of the Client and overrides the DefaultTLSPolicy // -// Note: To follow best-practices for SMTP TLS connections, it is recommended -// to use WithTLSPortPolicy instead. +// Note: To follow best-practices for SMTP TLS connections, it is recommended to use +// WithTLSPortPolicy instead. func WithTLSPolicy(policy TLSPolicy) Option { return func(c *Client) error { c.tlspolicy = policy @@ -359,16 +359,16 @@ func WithTLSPolicy(policy TLSPolicy) Option { } } -// WithTLSPortPolicy tells the client to use the provided TLSPolicy, -// The correct port is automatically set. +// WithTLSPortPolicy enables explicit TLS via STARTTLS for the Client using the provided TLSPolicy. The +// correct port is automatically set. // -// Port 587 is used for TLSMandatory and TLSOpportunistic. -// If the connection fails with TLSOpportunistic, -// a plaintext connection is attempted on port 25 as a fallback. -// NoTLS will allways use port 25. +// If TLSMandatory or TLSOpportunistic are provided as TLSPolicy, port 587 will be used for the connection. +// If the connection fails with TLSOpportunistic, the Client will attempt to connect on port 25 using +// using an unencrypted connection as a fallback. If NoTLS is provided, the Client will always use port 25. // -// Note: If a different port has already been set otherwise, the port-choosing -// and fallback automatism will be skipped. +// Note: If a different port has already been set otherwise using WithPort, the selected port has higher +// precedence and is used to establish the SSL/TLS connection. In this case the authmatic fallback +// mechanism is skipped at all. func WithTLSPortPolicy(policy TLSPolicy) Option { return func(c *Client) error { c.SetTLSPortPolicy(policy) From eeaee3f60a609a402e8e05e79767fadc00ef4eb2 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 21:57:38 +0200 Subject: [PATCH 14/60] Update and expand documentation for client configuration options Revised comments provide clearer guidance on the usage of various client configuration functions. Additional details and external references are included for better understanding and error handling. --- client.go | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/client.go b/client.go index 200f0c5..aafed72 100644 --- a/client.go +++ b/client.go @@ -376,7 +376,8 @@ func WithTLSPortPolicy(policy TLSPolicy) Option { } } -// WithTLSConfig tells the client to use the provided *tls.Config +// WithTLSConfig sets the tls.Config for the Client and overrides the default. An error is returned +// if the provided tls.Config is invalid. func WithTLSConfig(tlsconfig *tls.Config) Option { return func(c *Client) error { if tlsconfig == nil { @@ -387,7 +388,7 @@ func WithTLSConfig(tlsconfig *tls.Config) Option { } } -// WithSMTPAuth tells the client to use the provided SMTPAuthType for authentication +// WithSMTPAuth configures the Client to use the specified SMTPAuthType for the SMTP authentication. func WithSMTPAuth(authtype SMTPAuthType) Option { return func(c *Client) error { c.smtpAuthType = authtype @@ -395,7 +396,8 @@ func WithSMTPAuth(authtype SMTPAuthType) Option { } } -// WithSMTPAuthCustom tells the client to use the provided smtp.Auth for SMTP authentication +// WithSMTPAuthCustom sets a custom SMTP authentication mechanism for the client instance. The provided +// authentication mechanism has to satisfy the smtp.Auth interface. func WithSMTPAuthCustom(smtpAuth smtp.Auth) Option { return func(c *Client) error { c.smtpAuth = smtpAuth @@ -403,7 +405,7 @@ func WithSMTPAuthCustom(smtpAuth smtp.Auth) Option { } } -// WithUsername tells the client to use the provided string as username for authentication +// WithUsername sets the username, the Client will use for the SMTP authentication. func WithUsername(username string) Option { return func(c *Client) error { c.user = username @@ -411,7 +413,7 @@ func WithUsername(username string) Option { } } -// WithPassword tells the client to use the provided string as password/secret for authentication +// WithPassword sets the password, the Client will use for the SMTP authentication. func WithPassword(password string) Option { return func(c *Client) error { c.pass = password @@ -419,10 +421,12 @@ func WithPassword(password string) Option { } } -// WithDSN enables the Client to request DSNs (if the server supports it) -// as described in the RFC 1891 and set defaults for DSNMailReturnOption -// to DSNMailReturnFull and DSNRcptNotifyOption to DSNRcptNotifySuccess -// and DSNRcptNotifyFailure +// WithDSN enables DSN (Delivery Status Notifications) for the Client as described in the RFC 1891. DSN +// only work if the server supports them. +// https://datatracker.ietf.org/doc/html/rfc1891 +// +// By default we set DSNMailReturnOption to DSNMailReturnFull and DSNRcptNotifyOption to DSNRcptNotifySuccess +// and DSNRcptNotifyFailure. func WithDSN() Option { return func(c *Client) error { c.requestDSN = true @@ -432,10 +436,11 @@ func WithDSN() Option { } } -// WithDSNMailReturnType enables the Client to request DSNs (if the server supports it) -// as described in the RFC 1891 and set the MAIL FROM Return option type to the -// given DSNMailReturnOption -// See: https://www.rfc-editor.org/rfc/rfc1891 +// WithDSNMailReturnType enables DSN (Delivery Status Notifications) for the Client as described in the +// RFC 1891. DSN only work if the server supports them. +// https://datatracker.ietf.org/doc/html/rfc1891 +// +// It will set the DSNMailReturnOption to the provided value. func WithDSNMailReturnType(option DSNMailReturnOption) Option { return func(c *Client) error { switch option { @@ -451,9 +456,11 @@ func WithDSNMailReturnType(option DSNMailReturnOption) Option { } } -// WithDSNRcptNotifyType enables the Client to request DSNs as described in the RFC 1891 -// and sets the RCPT TO notify options to the given list of DSNRcptNotifyOption -// See: https://www.rfc-editor.org/rfc/rfc1891 +// WithDSNRcptNotifyType enables DSN (Delivery Status Notifications) for the Client as described in the +// RFC 1891. DSN only work if the server supports them. +// https://datatracker.ietf.org/doc/html/rfc1891 +// +// It will set the DSNRcptNotifyOption to the provided values. func WithDSNRcptNotifyType(opts ...DSNRcptNotifyOption) Option { return func(c *Client) error { var rcptOpts []string From d900f5403e6b83d8af5d25cb197bab33629793b5 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 22:23:18 +0200 Subject: [PATCH 15/60] Refine method documentation in client.go Correct typos and enhance clarity in method descriptions. Provide additional context for default values and behavior, and specify consequences for security settings in client configurations. --- client.go | 71 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/client.go b/client.go index aafed72..b63b58d 100644 --- a/client.go +++ b/client.go @@ -311,7 +311,7 @@ func WithSSLPort(fallback bool) Option { } } -// WithDebugLog neables debug logging for the Client. The debug logger will log incoming and outgoing +// WithDebugLog enables debug logging for the Client. The debug logger will log incoming and outgoing // communication between the Client and the server to os.StdErr. // // Note: The SMTP communication might include unencrypted authentication data, depending if you are @@ -492,8 +492,10 @@ func WithDSNRcptNotifyType(opts ...DSNRcptNotifyOption) Option { } } -// WithoutNoop disables the Client Noop check during connections. This is primarily for servers which delay responses -// to SMTP commands that are not the AUTH command. For example Microsoft Exchange's Tarpit. +// WithoutNoop indicates that the Client should skip the "NOOP" command during the dial. +// +// This is useful for servers which delay potentially unwanted clients when they perform commands +// other than AUTH. For example Microsoft Exchange's Tarpit. func WithoutNoop() Option { return func(c *Client) error { c.noNoop = true @@ -501,7 +503,8 @@ func WithoutNoop() Option { } } -// WithDialContextFunc overrides the default DialContext for connecting SMTP server +// WithDialContextFunc sets the provided DialContextFunc as DialContext and overrides the default DialContext for +// connecting to the SMTP server func WithDialContextFunc(dialCtxFunc DialContextFunc) Option { return func(c *Client) error { c.dialContextFunc = dialCtxFunc @@ -509,34 +512,35 @@ func WithDialContextFunc(dialCtxFunc DialContextFunc) Option { } } -// TLSPolicy returns the currently set TLSPolicy as string +// TLSPolicy returns the TLSPolicy that is currently set on the Client as string func (c *Client) TLSPolicy() string { return c.tlspolicy.String() } -// ServerAddr returns the currently set combination of hostname and port +// ServerAddr returns the server address that is currently set on the Client in the format "host:port". func (c *Client) ServerAddr() string { return fmt.Sprintf("%s:%d", c.host, c.port) } -// SetTLSPolicy overrides the current TLSPolicy with the given TLSPolicy value +// SetTLSPolicy sets or overrides the TLSPolicy that is currently set on the Client with the given +// TLSPolicy. // -// Note: To follow best-practices for SMTP TLS connections, it is recommended -// to use SetTLSPortPolicy instead. +// Note: To follow best-practices for SMTP TLS connections, it is recommended to use SetTLSPortPolicy +// instead. func (c *Client) SetTLSPolicy(policy TLSPolicy) { c.tlspolicy = policy } -// SetTLSPortPolicy overrides the current TLSPolicy with the given TLSPolicy -// value. The correct port is automatically set. +// SetTLSPortPolicy sets or overrides the TLSPolicy that is currently set on the Client with the given +// TLSPolicy. The correct port is automatically set. // -// Port 587 is used for TLSMandatory and TLSOpportunistic. -// If the connection fails with TLSOpportunistic, a plaintext connection is -// attempted on port 25 as a fallback. -// NoTLS will allways use port 25. +// If TLSMandatory or TLSOpportunistic are provided as TLSPolicy, port 587 will be used for the connection. +// If the connection fails with TLSOpportunistic, the Client will attempt to connect on port 25 using +// using an unencrypted connection as a fallback. If NoTLS is provided, the Client will always use port 25. // -// Note: If a different port has already been set otherwise, the port-choosing -// and fallback automatism will be skipped. +// Note: If a different port has already been set otherwise using WithPort, the selected port has higher +// precedence and is used to establish the SSL/TLS connection. In this case the authmatic fallback +// mechanism is skipped at all. func (c *Client) SetTLSPortPolicy(policy TLSPolicy) { if c.port == DefaultPort { c.port = DefaultPortTLS @@ -552,21 +556,21 @@ func (c *Client) SetTLSPortPolicy(policy TLSPolicy) { c.tlspolicy = policy } -// SetSSL tells the Client wether to use SSL or not +// SetSSL sets or overrides wether the Client should use implicit SSL/TLS. func (c *Client) SetSSL(ssl bool) { c.useSSL = ssl } -// SetSSLPort tells the Client wether or not to use SSL and fallback. -// The correct port is automatically set. +// SetSSLPort sets or overrides wether the Client should use implicit SSL/TLS with optional fallback. The +// correct port is automatically set. // -// Port 465 is used when SSL set (true). -// Port 25 is used when SSL is unset (false). -// When the SSL connection fails and fb is set to true, -// the client will attempt to connect on port 25 using plaintext. +// If ssl is set to true, the default port 25 will be overriden with port 465. If fallback is set to true +// and the SSL/TLS connection fails, the Client will attempt to connect on port 25 using using an +// unencrypted connection. // -// Note: If a different port has already been set otherwise, the port-choosing -// and fallback automatism will be skipped. +// Note: If a different port has already been set otherwise using WithPort, the selected port has higher +// precedence and is used to establish the SSL/TLS connection. In this case the authmatic fallback +// mechanism is skipped at all. func (c *Client) SetSSLPort(ssl bool, fallback bool) { if c.port == DefaultPort { if ssl { @@ -582,7 +586,12 @@ func (c *Client) SetSSLPort(ssl bool, fallback bool) { c.useSSL = ssl } -// SetDebugLog tells the Client whether debug logging is enabled or not +// SetDebugLog sets or overrides wether the Client is using debug logging. The debug logger will log +// incoming and outgoing communication between the Client and the server to os.StdErr. +// +// Note: The SMTP communication might include unencrypted authentication data, depending if you are +// using SMTP authentication and the type of authentication mechanism. This could pose a data +// protection problem. Use debug logging with care. func (c *Client) SetDebugLog(val bool) { c.useDebugLog = val if c.smtpClient != nil { @@ -590,7 +599,10 @@ func (c *Client) SetDebugLog(val bool) { } } -// SetLogger tells the Client which log.Logger to use +// SetLogger sets of overrides the custom logger currently set for the Client. The logger has to satisfy +// the log.Logger interface and is only used when debug logging is enabled on the Client. +// +// By default we use log.Stdlog. func (c *Client) SetLogger(logger log.Logger) { c.logger = logger if c.smtpClient != nil { @@ -598,7 +610,8 @@ func (c *Client) SetLogger(logger log.Logger) { } } -// SetTLSConfig overrides the current *tls.Config with the given *tls.Config value +// SetTLSConfig sets or overrides the tls.Config that is currently set for the Client with the given value. +// An error is returned if the provided tls.Config is invalid. func (c *Client) SetTLSConfig(tlsconfig *tls.Config) error { c.mutex.Lock() defer c.mutex.Unlock() From 972a3c51c76091fe80015ccda4b235545f00f5bd Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 22:33:42 +0200 Subject: [PATCH 16/60] Add custom SMTP authentication type support Introduce the SMTPAuthCustom type to represent user-defined SMTP authentication mechanisms. Updated relevant functions in client.go to handle the new type appropriately and made sure the client distinguishes between built-in and custom authentication methods. --- auth.go | 6 ++++++ client.go | 16 ++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/auth.go b/auth.go index 110e45e..fef7881 100644 --- a/auth.go +++ b/auth.go @@ -23,6 +23,12 @@ const ( // https://datatracker.ietf.org/doc/html/draft-ietf-sasl-crammd5-to-historic-00.html SMTPAuthCramMD5 SMTPAuthType = "CRAM-MD5" + // SMTPAuthCustom is a custom SMTP AUTH mechanism provided by the user. If a user provides + // a custom smtp.Auth function to the Client, the Client will its smtpAuthType to this type. + // + // Do not use this SMTPAuthType without setting a custom smtp.Auth function on the Client. + SMTPAuthCustom SMTPAuthType = "CUSTOM" + // SMTPAuthLogin is the "LOGIN" SASL authentication mechanism. This authentication mechanism // does not have an official RFC that could be followed. There is a spec by Microsoft and an // IETF draft. The IETF draft is more lax than the MS spec, therefore we follow the I-D, which diff --git a/client.go b/client.go index b63b58d..5332cda 100644 --- a/client.go +++ b/client.go @@ -396,11 +396,12 @@ func WithSMTPAuth(authtype SMTPAuthType) Option { } } -// WithSMTPAuthCustom sets a custom SMTP authentication mechanism for the client instance. The provided +// WithSMTPAuthCustom sets a custom SMTP authentication mechanism for the Client. The provided // authentication mechanism has to satisfy the smtp.Auth interface. func WithSMTPAuthCustom(smtpAuth smtp.Auth) Option { return func(c *Client) error { c.smtpAuth = smtpAuth + c.smtpAuthType = SMTPAuthCustom return nil } } @@ -623,25 +624,28 @@ func (c *Client) SetTLSConfig(tlsconfig *tls.Config) error { return nil } -// SetUsername overrides the current username string with the given value +// SetUsername sets or overrides the username, the Client will use for the SMTP authentication. func (c *Client) SetUsername(username string) { c.user = username } -// SetPassword overrides the current password string with the given value +// SetPassword sets or overrides the password, the Client will use for the SMTP authentication. func (c *Client) SetPassword(password string) { c.pass = password } -// SetSMTPAuth overrides the current SMTP AUTH type setting with the given value +// SetSMTPAuth sets or overrides the SMTPAuthType that is currently set on the Client for the SMTP +// authentication. func (c *Client) SetSMTPAuth(authtype SMTPAuthType) { c.smtpAuthType = authtype c.smtpAuth = nil } -// SetSMTPAuthCustom overrides the current SMTP AUTH setting with the given custom smtp.Auth +// SetSMTPAuthCustom sets or overrides the custom SMTP authentication mechanism currently set for +// the Client. The provided authentication mechanism has to satisfy the smtp.Auth interface. func (c *Client) SetSMTPAuthCustom(smtpAuth smtp.Auth) { c.smtpAuth = smtpAuth + c.smtpAuthType = SMTPAuthCustom } // setDefaultHelo retrieves the current hostname and sets it as HELO/EHLO hostname @@ -827,7 +831,7 @@ func (c *Client) auth() error { if err := c.checkConn(); err != nil { return fmt.Errorf("failed to authenticate: %w", err) } - if c.smtpAuth == nil && c.smtpAuthType != "" { + if c.smtpAuth == nil && c.smtpAuthType != SMTPAuthCustom { hasSMTPAuth, smtpAuthType := c.smtpClient.Extension("AUTH") if !hasSMTPAuth { return fmt.Errorf("server does not support SMTP AUTH") From 8942b08424e441fa6079583c5495fda4711268a9 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 22:36:28 +0200 Subject: [PATCH 17/60] Add validation for custom SMTP auth type in client tests Previously, only the presence of the SMTP auth method was checked, but not its type. This additional validation ensures that the SMTP auth type is correctly set to custom, thereby improving test accuracy. --- client_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client_test.go b/client_test.go index 1539426..d8dc87f 100644 --- a/client_test.go +++ b/client_test.go @@ -602,6 +602,10 @@ func TestSetSMTPAuthCustom(t *testing.T) { if c.smtpAuth == nil { t.Errorf("failed to set custom SMTP auth method. SMTP Auth method is empty") } + if c.smtpAuthType != SMTPAuthCustom { + t.Errorf("failed to set custom SMTP auth method. SMTP Auth type is not custom: %s", + c.smtpAuthType) + } p, _, err := c.smtpAuth.Start(&si) if err != nil { t.Errorf("SMTP Auth Start() method returned error: %s", err) From dfdadc5da235b0270152c20351c2cacf9491222e Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 22:38:18 +0200 Subject: [PATCH 18/60] Refactor client.go: Move functions to new locations Reordered several functions within client.go for better code organization and readability. This change involves moving `setDefaultHelo`, `checkConn`, `serverFallbackAddr`, and `tls` functions to new locations without altering their implementations. --- client.go | 150 +++++++++++++++++++++++++++--------------------------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/client.go b/client.go index 5332cda..48d6731 100644 --- a/client.go +++ b/client.go @@ -648,16 +648,6 @@ func (c *Client) SetSMTPAuthCustom(smtpAuth smtp.Auth) { c.smtpAuthType = SMTPAuthCustom } -// setDefaultHelo retrieves the current hostname and sets it as HELO/EHLO hostname -func (c *Client) setDefaultHelo() error { - hostname, err := os.Hostname() - if err != nil { - return fmt.Errorf("failed to read local hostname: %w", err) - } - c.helo = hostname - return nil -} - // DialWithContext establishes a connection to the SMTP server with a given context.Context func (c *Client) DialWithContext(dialCtx context.Context) error { c.mutex.Lock() @@ -761,71 +751,6 @@ func (c *Client) DialAndSendWithContext(ctx context.Context, messages ...*Msg) e return nil } -// checkConn makes sure that a required server connection is available and extends the -// connection deadline -func (c *Client) checkConn() error { - if !c.smtpClient.HasConnection() { - return ErrNoActiveConnection - } - - if !c.noNoop { - if err := c.smtpClient.Noop(); err != nil { - return ErrNoActiveConnection - } - } - - if err := c.smtpClient.UpdateDeadline(c.connTimeout); err != nil { - return ErrDeadlineExtendFailed - } - return nil -} - -// serverFallbackAddr returns the currently set combination of hostname -// and fallback port. -func (c *Client) serverFallbackAddr() string { - return fmt.Sprintf("%s:%d", c.host, c.fallbackPort) -} - -// tls tries to make sure that the STARTTLS requirements are satisfied -func (c *Client) tls() error { - if !c.smtpClient.HasConnection() { - return ErrNoActiveConnection - } - if !c.useSSL && c.tlspolicy != NoTLS { - hasStartTLS := false - extension, _ := c.smtpClient.Extension("STARTTLS") - if c.tlspolicy == TLSMandatory { - hasStartTLS = true - if !extension { - return fmt.Errorf("STARTTLS mode set to: %q, but target host does not support STARTTLS", - c.tlspolicy) - } - } - if c.tlspolicy == TLSOpportunistic { - if extension { - hasStartTLS = true - } - } - if hasStartTLS { - if err := c.smtpClient.StartTLS(c.tlsconfig); err != nil { - return err - } - } - tlsConnState, err := c.smtpClient.GetTLSConnectionState() - if err != nil { - switch { - case errors.Is(err, smtp.ErrNonTLSConnection): - c.isEncrypted = false - return nil - default: - return fmt.Errorf("failed to get TLS connection state: %w", err) - } - } - c.isEncrypted = tlsConnState.HandshakeComplete - } - return nil -} - // auth will try to perform SMTP AUTH if requested func (c *Client) auth() error { if err := c.checkConn(); err != nil { @@ -998,3 +923,78 @@ func (c *Client) sendSingleMsg(message *Msg) error { } return nil } + +// checkConn makes sure that a required server connection is available and extends the +// connection deadline +func (c *Client) checkConn() error { + if !c.smtpClient.HasConnection() { + return ErrNoActiveConnection + } + + if !c.noNoop { + if err := c.smtpClient.Noop(); err != nil { + return ErrNoActiveConnection + } + } + + if err := c.smtpClient.UpdateDeadline(c.connTimeout); err != nil { + return ErrDeadlineExtendFailed + } + return nil +} + +// serverFallbackAddr returns the currently set combination of hostname +// and fallback port. +func (c *Client) serverFallbackAddr() string { + return fmt.Sprintf("%s:%d", c.host, c.fallbackPort) +} + +// setDefaultHelo retrieves the current hostname and sets it as HELO/EHLO hostname +func (c *Client) setDefaultHelo() error { + hostname, err := os.Hostname() + if err != nil { + return fmt.Errorf("failed to read local hostname: %w", err) + } + c.helo = hostname + return nil +} + +// tls tries to make sure that the STARTTLS requirements are satisfied +func (c *Client) tls() error { + if !c.smtpClient.HasConnection() { + return ErrNoActiveConnection + } + if !c.useSSL && c.tlspolicy != NoTLS { + hasStartTLS := false + extension, _ := c.smtpClient.Extension("STARTTLS") + if c.tlspolicy == TLSMandatory { + hasStartTLS = true + if !extension { + return fmt.Errorf("STARTTLS mode set to: %q, but target host does not support STARTTLS", + c.tlspolicy) + } + } + if c.tlspolicy == TLSOpportunistic { + if extension { + hasStartTLS = true + } + } + if hasStartTLS { + if err := c.smtpClient.StartTLS(c.tlsconfig); err != nil { + return err + } + } + tlsConnState, err := c.smtpClient.GetTLSConnectionState() + if err != nil { + switch { + case errors.Is(err, smtp.ErrNonTLSConnection): + c.isEncrypted = false + return nil + default: + return fmt.Errorf("failed to get TLS connection state: %w", err) + } + } + c.isEncrypted = tlsConnState.HandshakeComplete + } + return nil +} From adcb8ac41dede831a53691e71167a0b867288a01 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 23:15:01 +0200 Subject: [PATCH 19/60] Fix connection handling and improve thread-safety in SMTP client Reset connections to nil after Close, add RLock in HasConnection, and refine Close logic to handle already closed connections gracefully. Enhanced DialWithContext documentation and added tests for double-close scenarios to ensure robustness. --- client.go | 17 ++++++++++++++--- client_test.go | 43 ++++++++++++++++++++++++++++++++----------- smtp/smtp.go | 7 ++++++- 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/client.go b/client.go index 48d6731..787fafa 100644 --- a/client.go +++ b/client.go @@ -648,7 +648,17 @@ func (c *Client) SetSMTPAuthCustom(smtpAuth smtp.Auth) { c.smtpAuthType = SMTPAuthCustom } -// DialWithContext establishes a connection to the SMTP server with a given context.Context +// DialWithContext establishes a connection to the server using the provided context.Context. +// +// Before connecting to the server, the function will add a deadline of the Client's timeout +// to the provided context.Context. +// +// After dialing the DialContextFunc defined in the Client and successfully establishing the +// connection to the SMTP server, it will send the HELO/EHLO SMTP command followed by the +// optional STARTTLS and SMTP AUTH commands. It will also attach the log.Logger in case +// debug logging is enabled on the Client. +// +// From this point in time the Client has an active (cancelable) connection to the SMTP server. func (c *Client) DialWithContext(dialCtx context.Context) error { c.mutex.Lock() defer c.mutex.Unlock() @@ -707,8 +717,9 @@ func (c *Client) DialWithContext(dialCtx context.Context) error { // Close closes the Client connection func (c *Client) Close() error { - if err := c.checkConn(); err != nil { - return err + // If the connection is already closed, we considered this a no-op and disregard any error. + if !c.smtpClient.HasConnection() { + return nil } if err := c.smtpClient.Quit(); err != nil { return fmt.Errorf("failed to close SMTP client: %w", err) diff --git a/client_test.go b/client_test.go index d8dc87f..c767e75 100644 --- a/client_test.go +++ b/client_test.go @@ -617,6 +617,32 @@ func TestSetSMTPAuthCustom(t *testing.T) { } } +// TestClient_Close_double tests if a close on an already closed connection causes an error. +func TestClient_Close_double(t *testing.T) { + c, err := getTestConnection(true) + if err != nil { + t.Skipf("failed to create test client: %s. Skipping tests", err) + } + ctx := context.Background() + if err = c.DialWithContext(ctx); err != nil { + t.Errorf("failed to dial with context: %s", err) + return + } + if c.smtpClient == nil { + t.Errorf("DialWithContext didn't fail but no SMTP client found.") + return + } + if !c.smtpClient.HasConnection() { + t.Errorf("DialWithContext didn't fail but no connection found.") + } + if err = c.Close(); err != nil { + t.Errorf("failed to close connection: %s", err) + } + if err = c.Close(); err != nil { + t.Errorf("failed 2nd close connection: %s", err) + } +} + // TestClient_DialWithContext tests the DialWithContext method for the Client object func TestClient_DialWithContext(t *testing.T) { c, err := getTestConnection(true) @@ -2391,7 +2417,6 @@ func TestXOAuth2OK_faker(t *testing.T) { "250 8BITMIME", "250 OK", "235 2.7.0 Accepted", - "250 OK", "221 OK", } var wrote strings.Builder @@ -2412,10 +2437,10 @@ func TestXOAuth2OK_faker(t *testing.T) { if err != nil { t.Fatalf("unable to create new client: %v", err) } - if err := c.DialWithContext(context.Background()); err != nil { + if err = c.DialWithContext(context.Background()); err != nil { t.Fatalf("unexpected dial error: %v", err) } - if err := c.Close(); err != nil { + if err = c.Close(); err != nil { t.Fatalf("disconnect from test server failed: %v", err) } if !strings.Contains(wrote.String(), "AUTH XOAUTH2 dXNlcj11c2VyAWF1dGg9QmVhcmVyIHRva2VuAQE=\r\n") { @@ -2430,7 +2455,6 @@ func TestXOAuth2Unsupported_faker(t *testing.T) { "250-AUTH LOGIN PLAIN", "250 8BITMIME", "250 OK", - "250 OK", "221 OK", } var wrote strings.Builder @@ -2449,18 +2473,18 @@ func TestXOAuth2Unsupported_faker(t *testing.T) { if err != nil { t.Fatalf("unable to create new client: %v", err) } - if err := c.DialWithContext(context.Background()); err == nil { + if err = c.DialWithContext(context.Background()); err == nil { t.Fatal("expected dial error got nil") } else { if !errors.Is(err, ErrXOauth2AuthNotSupported) { t.Fatalf("expected %v; got %v", ErrXOauth2AuthNotSupported, err) } } - if err := c.Close(); err != nil { + if err = c.Close(); err != nil { t.Fatalf("disconnect from test server failed: %v", err) } client := strings.Split(wrote.String(), "\r\n") - if len(client) != 5 { + if len(client) != 4 { t.Fatalf("unexpected number of client requests got %d; want 5", len(client)) } if !strings.HasPrefix(client[0], "EHLO") { @@ -2469,10 +2493,7 @@ func TestXOAuth2Unsupported_faker(t *testing.T) { if client[1] != "NOOP" { t.Fatalf("expected NOOP, got %q", client[1]) } - if client[2] != "NOOP" { - t.Fatalf("expected NOOP, got %q", client[2]) - } - if client[3] != "QUIT" { + if client[2] != "QUIT" { t.Fatalf("expected QUIT, got %q", client[3]) } } diff --git a/smtp/smtp.go b/smtp/smtp.go index f9961c9..ce163a2 100644 --- a/smtp/smtp.go +++ b/smtp/smtp.go @@ -516,6 +516,8 @@ func (c *Client) Quit() error { } c.mutex.Lock() err = c.Text.Close() + c.Text = nil + c.conn = nil c.mutex.Unlock() return err @@ -555,7 +557,10 @@ func (c *Client) SetDSNRcptNotifyOption(d string) { // HasConnection checks if the client has an active connection. // Returns true if the `conn` field is not nil, indicating an active connection. func (c *Client) HasConnection() bool { - return c.conn != nil + c.mutex.RLock() + conn := c.conn + c.mutex.RUnlock() + return conn != nil } func (c *Client) UpdateDeadline(timeout time.Duration) error { From 48b469faf726d0d44f00547b278277595c6b5444 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 23:23:16 +0200 Subject: [PATCH 20/60] Refactor SMTP client comment and function documentation Updated function comments across client.go to improve clarity and consistency. Added missing details on error handling and context usage for `DialAndSendWithContext` and ensured all functions contain relevant, detailed descriptions. --- client.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/client.go b/client.go index 787fafa..6ea1cbb 100644 --- a/client.go +++ b/client.go @@ -715,9 +715,9 @@ func (c *Client) DialWithContext(dialCtx context.Context) error { return nil } -// Close closes the Client connection +// Close terminates the connection to the SMTP server, returning an error if the disconnection fails. +// If the connection is already closed, we considered this a no-op and disregard any error. func (c *Client) Close() error { - // If the connection is already closed, we considered this a no-op and disregard any error. if !c.smtpClient.HasConnection() { return nil } @@ -728,7 +728,7 @@ func (c *Client) Close() error { return nil } -// Reset sends the RSET command to the SMTP client +// Reset sends an SMTP RSET command to reset the state of the current SMTP session. func (c *Client) Reset() error { if err := c.checkConn(); err != nil { return err @@ -740,19 +740,24 @@ func (c *Client) Reset() error { return nil } -// DialAndSend establishes a connection to the SMTP server with a -// default context.Background and sends the mail +// DialAndSend establishes a connection to the server and sends out the provided Msg. It will call +// DialAndSendWithContext with an empty Context.Background func (c *Client) DialAndSend(messages ...*Msg) error { ctx := context.Background() return c.DialAndSendWithContext(ctx, messages...) } -// DialAndSendWithContext establishes a connection to the SMTP server with a -// custom context and sends the mail +// DialAndSendWithContext establishes a connection to the SMTP server using DialWithContext using the +// provided context.Context, then sends out the given Msg. After successful delivery the Client +// will close the connection to the server. func (c *Client) DialAndSendWithContext(ctx context.Context, messages ...*Msg) error { if err := c.DialWithContext(ctx); err != nil { return fmt.Errorf("dial failed: %w", err) } + defer func() { + _ = c.Close() + }() + if err := c.Send(messages...); err != nil { return fmt.Errorf("send failed: %w", err) } From fbbf17acd06144be2244643f5b71b7140876dace Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 4 Oct 2024 23:25:43 +0200 Subject: [PATCH 21/60] Refactor comments for clarity in client.go Simplify comments in `client.go` to improve code documentation. Ensure comments are more descriptive and provide context for the functions they describe, enhancing code readability and maintainability. --- client.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/client.go b/client.go index 6ea1cbb..d196a24 100644 --- a/client.go +++ b/client.go @@ -767,7 +767,9 @@ func (c *Client) DialAndSendWithContext(ctx context.Context, messages ...*Msg) e return nil } -// auth will try to perform SMTP AUTH if requested +// auth attempts to authenticate the client using SMTP AUTH mechanisms. It checks the connection, determines +// the supported authentication methods, and applies the appropriate authentication type. Returns an error if +// authentication fails. func (c *Client) auth() error { if err := c.checkConn(); err != nil { return fmt.Errorf("failed to authenticate: %w", err) @@ -940,8 +942,7 @@ func (c *Client) sendSingleMsg(message *Msg) error { return nil } -// checkConn makes sure that a required server connection is available and extends the -// connection deadline +// checkConn makes sure that a required server connection is available and extends the connection deadline func (c *Client) checkConn() error { if !c.smtpClient.HasConnection() { return ErrNoActiveConnection @@ -959,13 +960,12 @@ func (c *Client) checkConn() error { return nil } -// serverFallbackAddr returns the currently set combination of hostname -// and fallback port. +// serverFallbackAddr returns the currently set combination of hostname and fallback port. func (c *Client) serverFallbackAddr() string { return fmt.Sprintf("%s:%d", c.host, c.fallbackPort) } -// setDefaultHelo retrieves the current hostname and sets it as HELO/EHLO hostname +// setDefaultHelo sets the HELO/EHLO hostname to the local machine's hostname. func (c *Client) setDefaultHelo() error { hostname, err := os.Hostname() if err != nil { @@ -975,7 +975,8 @@ func (c *Client) setDefaultHelo() error { return nil } -// tls tries to make sure that the STARTTLS requirements are satisfied +// tls establishes a TLS connection based on the client's TLS policy and configuration. +// Returns an error if no active connection exists or if a TLS error occurs. func (c *Client) tls() error { if !c.smtpClient.HasConnection() { return ErrNoActiveConnection From 91639436843a757e0a35a04c2523438e956ce3da Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 10:15:43 +0200 Subject: [PATCH 22/60] Add isConnected flag to track active connection state Introduced the isConnected boolean flag in the Client struct to clearly indicate whether there is an active connection. Updated relevant methods to set this flag accordingly, ensuring consistent state management across the Client's lifecycle. --- smtp/smtp.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/smtp/smtp.go b/smtp/smtp.go index ce163a2..444b203 100644 --- a/smtp/smtp.go +++ b/smtp/smtp.go @@ -67,6 +67,9 @@ type Client struct { // helloError is the error from the hello helloError error + // isConnected indicates if the Client has an active connection + isConnected bool + // localName is the name to use in HELO/EHLO localName string // the name to use in HELO/EHLO @@ -113,6 +116,7 @@ func NewClient(conn net.Conn, host string) (*Client, error) { } c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"} _, c.tls = conn.(*tls.Conn) + c.isConnected = true return c, nil } @@ -121,6 +125,7 @@ func NewClient(conn net.Conn, host string) (*Client, error) { func (c *Client) Close() error { c.mutex.Lock() err := c.Text.Close() + c.isConnected = false c.mutex.Unlock() return err } @@ -516,8 +521,7 @@ func (c *Client) Quit() error { } c.mutex.Lock() err = c.Text.Close() - c.Text = nil - c.conn = nil + c.isConnected = false c.mutex.Unlock() return err @@ -558,11 +562,12 @@ func (c *Client) SetDSNRcptNotifyOption(d string) { // Returns true if the `conn` field is not nil, indicating an active connection. func (c *Client) HasConnection() bool { c.mutex.RLock() - conn := c.conn + isConn := c.isConnected c.mutex.RUnlock() - return conn != nil + return isConn } +// UpdateDeadline sets a new deadline on the SMTP connection with the specified timeout duration. func (c *Client) UpdateDeadline(timeout time.Duration) error { c.mutex.Lock() if err := c.conn.SetDeadline(time.Now().Add(timeout)); err != nil { From 159c1bf850e02d787cfd04021fe2e917d7dec9cb Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 10:55:25 +0200 Subject: [PATCH 23/60] Add tests for new tls and connection handling methods This commit introduces tests for various TLS-related methods such as GetTLSConnectionState, HasConnection, SetDSNMailReturnOption, SetDSNRcptNotifyOption, and UpdateDeadline. It also modifies the error handling logic in smtp.go to include new error types and improves the mutex handling in UpdateDeadline. --- smtp/smtp.go | 21 ++- smtp/smtp_test.go | 352 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 367 insertions(+), 6 deletions(-) diff --git a/smtp/smtp.go b/smtp/smtp.go index 444b203..7c39996 100644 --- a/smtp/smtp.go +++ b/smtp/smtp.go @@ -36,7 +36,15 @@ import ( "github.com/wneessen/go-mail/log" ) -var ErrNonTLSConnection = errors.New("connection is not using TLS") +var ( + + // ErrNonTLSConnection is returned when an attempt is made to retrieve TLS state on a non-TLS connection. + ErrNonTLSConnection = errors.New("connection is not using TLS") + + // ErrNoConnection is returned when attempting to perform an operation that requires an established + // connection but none exists. + ErrNoConnection = errors.New("connection is not established") +) // A Client represents a client connection to an SMTP server. type Client struct { @@ -570,10 +578,10 @@ func (c *Client) HasConnection() bool { // UpdateDeadline sets a new deadline on the SMTP connection with the specified timeout duration. func (c *Client) UpdateDeadline(timeout time.Duration) error { c.mutex.Lock() + defer c.mutex.Unlock() if err := c.conn.SetDeadline(time.Now().Add(timeout)); err != nil { return fmt.Errorf("smtp: failed to update deadline: %w", err) } - c.mutex.Unlock() return nil } @@ -583,17 +591,18 @@ func (c *Client) GetTLSConnectionState() (*tls.ConnectionState, error) { c.mutex.RLock() defer c.mutex.RUnlock() + if !c.isConnected { + return nil, ErrNoConnection + + } if !c.tls { return nil, ErrNonTLSConnection } - if c.conn == nil { - return nil, errors.New("smtp: connection is not established") - } if conn, ok := c.conn.(*tls.Conn); ok { cstate := conn.ConnectionState() return &cstate, nil } - return nil, errors.New("smtp: connection is not a TLS connection") + return nil, errors.New("unable to retrieve TLS connection state") } // debugLog checks if the debug flag is set and if so logs the provided message to diff --git a/smtp/smtp_test.go b/smtp/smtp_test.go index 451a349..7b53963 100644 --- a/smtp/smtp_test.go +++ b/smtp/smtp_test.go @@ -1640,6 +1640,356 @@ func TestTLSConnState(t *testing.T) { <-serverDone } +func TestClient_GetTLSConnectionState(t *testing.T) { + ln := newLocalListener(t) + defer func() { + _ = ln.Close() + }() + clientDone := make(chan bool) + serverDone := make(chan bool) + go func() { + defer close(serverDone) + c, err := ln.Accept() + if err != nil { + t.Errorf("Server accept: %v", err) + return + } + defer func() { + _ = c.Close() + }() + if err := serverHandle(c, t); err != nil { + t.Errorf("server error: %v", err) + } + }() + go func() { + defer close(clientDone) + c, err := Dial(ln.Addr().String()) + if err != nil { + t.Errorf("Client dial: %v", err) + return + } + defer func() { + _ = c.Quit() + }() + cfg := &tls.Config{ServerName: "example.com"} + testHookStartTLS(cfg) // set the RootCAs + if err := c.StartTLS(cfg); err != nil { + t.Errorf("StartTLS: %v", err) + return + } + cs, err := c.GetTLSConnectionState() + if err != nil { + t.Errorf("failed to get TLSConnectionState: %s", err) + return + } + if cs.Version == 0 || !cs.HandshakeComplete { + t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs) + } + }() + <-clientDone + <-serverDone +} + +func TestClient_GetTLSConnectionState_noTLS(t *testing.T) { + ln := newLocalListener(t) + defer func() { + _ = ln.Close() + }() + clientDone := make(chan bool) + serverDone := make(chan bool) + go func() { + defer close(serverDone) + c, err := ln.Accept() + if err != nil { + t.Errorf("Server accept: %v", err) + return + } + defer func() { + _ = c.Close() + }() + if err := serverHandle(c, t); err != nil { + t.Errorf("server error: %v", err) + } + }() + go func() { + defer close(clientDone) + c, err := Dial(ln.Addr().String()) + if err != nil { + t.Errorf("Client dial: %v", err) + return + } + defer func() { + _ = c.Quit() + }() + _, err = c.GetTLSConnectionState() + if err == nil { + t.Error("GetTLSConnectionState: expected error; got nil") + return + } + }() + <-clientDone + <-serverDone +} + +func TestClient_GetTLSConnectionState_noConn(t *testing.T) { + ln := newLocalListener(t) + defer func() { + _ = ln.Close() + }() + clientDone := make(chan bool) + serverDone := make(chan bool) + go func() { + defer close(serverDone) + c, err := ln.Accept() + if err != nil { + t.Errorf("Server accept: %v", err) + return + } + defer func() { + _ = c.Close() + }() + if err := serverHandle(c, t); err != nil { + t.Errorf("server error: %v", err) + } + }() + go func() { + defer close(clientDone) + c, err := Dial(ln.Addr().String()) + if err != nil { + t.Errorf("Client dial: %v", err) + return + } + _ = c.Close() + _, err = c.GetTLSConnectionState() + if err == nil { + t.Error("GetTLSConnectionState: expected error; got nil") + return + } + }() + <-clientDone + <-serverDone +} + +func TestClient_GetTLSConnectionState_unableErr(t *testing.T) { + ln := newLocalListener(t) + defer func() { + _ = ln.Close() + }() + clientDone := make(chan bool) + serverDone := make(chan bool) + go func() { + defer close(serverDone) + c, err := ln.Accept() + if err != nil { + t.Errorf("Server accept: %v", err) + return + } + defer func() { + _ = c.Close() + }() + if err := serverHandle(c, t); err != nil { + t.Errorf("server error: %v", err) + } + }() + go func() { + defer close(clientDone) + c, err := Dial(ln.Addr().String()) + if err != nil { + t.Errorf("Client dial: %v", err) + return + } + defer func() { + _ = c.Quit() + }() + c.tls = true + _, err = c.GetTLSConnectionState() + if err == nil { + t.Error("GetTLSConnectionState: expected error; got nil") + return + } + }() + <-clientDone + <-serverDone +} +func TestClient_HasConnection(t *testing.T) { + ln := newLocalListener(t) + defer func() { + _ = ln.Close() + }() + clientDone := make(chan bool) + serverDone := make(chan bool) + go func() { + defer close(serverDone) + c, err := ln.Accept() + if err != nil { + t.Errorf("Server accept: %v", err) + return + } + defer func() { + _ = c.Close() + }() + if err := serverHandle(c, t); err != nil { + t.Errorf("server error: %v", err) + } + }() + go func() { + defer close(clientDone) + c, err := Dial(ln.Addr().String()) + if err != nil { + t.Errorf("Client dial: %v", err) + return + } + cfg := &tls.Config{ServerName: "example.com"} + testHookStartTLS(cfg) // set the RootCAs + if err := c.StartTLS(cfg); err != nil { + t.Errorf("StartTLS: %v", err) + return + } + if !c.HasConnection() { + t.Error("HasConnection: expected true; got false") + return + } + if err = c.Quit(); err != nil { + t.Errorf("closing connection failed: %s", err) + return + } + if c.HasConnection() { + t.Error("HasConnection: expected false; got true") + } + }() + <-clientDone + <-serverDone +} + +func TestClient_SetDSNMailReturnOption(t *testing.T) { + ln := newLocalListener(t) + defer func() { + _ = ln.Close() + }() + clientDone := make(chan bool) + serverDone := make(chan bool) + go func() { + defer close(serverDone) + c, err := ln.Accept() + if err != nil { + t.Errorf("Server accept: %v", err) + return + } + defer func() { + _ = c.Close() + }() + if err := serverHandle(c, t); err != nil { + t.Errorf("server error: %v", err) + } + }() + go func() { + defer close(clientDone) + c, err := Dial(ln.Addr().String()) + if err != nil { + t.Errorf("Client dial: %v", err) + return + } + defer func() { + _ = c.Quit() + }() + c.SetDSNMailReturnOption("foo") + if c.dsnmrtype != "foo" { + t.Errorf("SetDSNMailReturnOption: expected %s; got %s", "foo", c.dsnrntype) + } + }() + <-clientDone + <-serverDone +} + +func TestClient_SetDSNRcptNotifyOption(t *testing.T) { + ln := newLocalListener(t) + defer func() { + _ = ln.Close() + }() + clientDone := make(chan bool) + serverDone := make(chan bool) + go func() { + defer close(serverDone) + c, err := ln.Accept() + if err != nil { + t.Errorf("Server accept: %v", err) + return + } + defer func() { + _ = c.Close() + }() + if err := serverHandle(c, t); err != nil { + t.Errorf("server error: %v", err) + } + }() + go func() { + defer close(clientDone) + c, err := Dial(ln.Addr().String()) + if err != nil { + t.Errorf("Client dial: %v", err) + return + } + defer func() { + _ = c.Quit() + }() + c.SetDSNRcptNotifyOption("foo") + if c.dsnrntype != "foo" { + t.Errorf("SetDSNMailReturnOption: expected %s; got %s", "foo", c.dsnrntype) + } + }() + <-clientDone + <-serverDone +} + +func TestClient_UpdateDeadline(t *testing.T) { + ln := newLocalListener(t) + defer func() { + _ = ln.Close() + }() + clientDone := make(chan bool) + serverDone := make(chan bool) + go func() { + defer close(serverDone) + c, err := ln.Accept() + if err != nil { + t.Errorf("Server accept: %v", err) + return + } + defer func() { + _ = c.Close() + }() + if err = serverHandle(c, t); err != nil { + t.Errorf("server error: %v", err) + } + }() + go func() { + defer close(clientDone) + c, err := Dial(ln.Addr().String()) + if err != nil { + t.Errorf("Client dial: %v", err) + return + } + defer func() { + _ = c.Close() + }() + if !c.HasConnection() { + t.Error("HasConnection: expected true; got false") + return + } + if err = c.UpdateDeadline(time.Millisecond * 20); err != nil { + t.Errorf("failed to update deadline: %s", err) + return + } + time.Sleep(time.Millisecond * 50) + if !c.HasConnection() { + t.Error("HasConnection: expected true; got false") + return + } + }() + <-clientDone + <-serverDone +} + func newLocalListener(t *testing.T) net.Listener { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { @@ -1685,6 +2035,8 @@ func serverHandle(c net.Conn, t *testing.T) error { } config := &tls.Config{Certificates: []tls.Certificate{keypair}} return tf(config) + case "QUIT": + return nil default: t.Fatalf("unrecognized command: %q", s.Text()) } From fa3c6f956edcfd974f64b157d50f2e9426ce4175 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 11:02:26 +0200 Subject: [PATCH 24/60] Update Send function documentation for better clarity Enhanced the documentation of the `Send` function to explicitly describe the behavior when the Client has no active connection and the handling of multiple message transmissions. This ensures developers understand the error handling mechanism and the association of `SendError` with each message. --- client_119.go | 5 ++++- client_120.go | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/client_119.go b/client_119.go index 7de5d59..24a0118 100644 --- a/client_119.go +++ b/client_119.go @@ -9,7 +9,10 @@ package mail import "errors" -// Send sends out the mail message +// Send attempts to send one or more Msg using the Client connection to the SMTP server. +// If the Client has no active connection to the server, Send will fail with an error. For each of the +// provided Msg it will associate a SendError to the Msg in case there of a transmission or delivery +// error. func (c *Client) Send(messages ...*Msg) error { if err := c.checkConn(); err != nil { return &SendError{Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err)} diff --git a/client_120.go b/client_120.go index 4f82aa7..c6049eb 100644 --- a/client_120.go +++ b/client_120.go @@ -11,7 +11,10 @@ import ( "errors" ) -// Send sends out the mail message +// Send attempts to send one or more Msg using the Client connection to the SMTP server. +// If the Client has no active connection to the server, Send will fail with an error. For each of the +// provided Msg it will associate a SendError to the Msg in case there of a transmission or delivery +// error. func (c *Client) Send(messages ...*Msg) (returnErr error) { if err := c.checkConn(); err != nil { returnErr = &SendError{Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err)} From 869e8db6c566ccf55dde2f1932ddebcf6b50feb7 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 11:06:46 +0200 Subject: [PATCH 25/60] Improve package documentation and description Expand the package mail description to highlight ease of use, reliance on the Go Standard Library, and added functionalities. Clarify the role of VERSION in the user agent string. --- doc.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/doc.go b/doc.go index 831a57c..de16f5d 100644 --- a/doc.go +++ b/doc.go @@ -2,8 +2,13 @@ // // SPDX-License-Identifier: MIT -// Package mail provides a simple and easy way to send mails with Go +// Package mail provides an easy to use interface for formating and sending mails. go-mail follows idiomatic Go style +// and best practice. It has a small dependency footprint by mainly relying on the Go Standard Library and the Go +// extended packages. It combines a lot of functionality from the standard library to give easy and convenient access +// to mail and SMTP related tasks. It works like a programatic email client and provides lots of methods and +// functionalities you would consider standard in a MUA. package mail -// VERSION is used in the default user agent string +// VERSION indicates the current version of the package. It is also attached to the default user +// agent string. const VERSION = "0.4.4" From 5653df373ba399d244765ac54456b430e4c56c33 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 11:11:17 +0200 Subject: [PATCH 26/60] Add periods to end of go doc comments Ensure all go doc comments in eml.go have consistent punctuation by adding periods to the end of each comment. This improves code readability and maintains uniformity in the documentation style. --- eml.go | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/eml.go b/eml.go index 7e705f6..b57ad3c 100644 --- a/eml.go +++ b/eml.go @@ -18,14 +18,13 @@ import ( "strings" ) -// EMLToMsgFromString will parse a given EML string and returns a pre-filled Msg pointer +// EMLToMsgFromString will parse a given EML string and returns a pre-filled Msg pointer. func EMLToMsgFromString(emlString string) (*Msg, error) { eb := bytes.NewBufferString(emlString) return EMLToMsgFromReader(eb) } -// EMLToMsgFromReader will parse a reader that holds EML content and returns a pre-filled -// Msg pointer +// EMLToMsgFromReader will parse a reader that holds EML content and returns a pre-filled Msg pointer. func EMLToMsgFromReader(reader io.Reader) (*Msg, error) { msg := &Msg{ addrHeader: make(map[AddrHeader][]*netmail.Address), @@ -46,8 +45,7 @@ func EMLToMsgFromReader(reader io.Reader) (*Msg, error) { return msg, nil } -// EMLToMsgFromFile will open and parse a .eml file at a provided file path and returns a -// pre-filled Msg pointer +// EMLToMsgFromFile will open and parse a .eml file at a provided file path and returns a pre-filled Msg pointer. func EMLToMsgFromFile(filePath string) (*Msg, error) { msg := &Msg{ addrHeader: make(map[AddrHeader][]*netmail.Address), @@ -68,7 +66,7 @@ func EMLToMsgFromFile(filePath string) (*Msg, error) { return msg, nil } -// parseEML parses the EML's headers and body and inserts the parsed values into the Msg +// parseEML parses the EML's headers and body and inserts the parsed values into the Msg. func parseEML(parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error { if err := parseEMLHeaders(&parsedMsg.Header, msg); err != nil { return fmt.Errorf("failed to parse EML headers: %w", err) @@ -79,7 +77,7 @@ func parseEML(parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error return nil } -// readEML opens an EML file and uses net/mail to parse the header and body +// readEML opens an EML file and uses net/mail to parse the header and body. func readEML(filePath string) (*netmail.Message, *bytes.Buffer, error) { fileHandle, err := os.Open(filePath) if err != nil { @@ -91,7 +89,7 @@ func readEML(filePath string) (*netmail.Message, *bytes.Buffer, error) { return readEMLFromReader(fileHandle) } -// readEMLFromReader uses net/mail to parse the header and body from a given io.Reader +// readEMLFromReader uses net/mail to parse the header and body from a given io.Reader. func readEMLFromReader(reader io.Reader) (*netmail.Message, *bytes.Buffer, error) { parsedMsg, err := netmail.ReadMessage(reader) if err != nil { @@ -106,8 +104,8 @@ func readEMLFromReader(reader io.Reader) (*netmail.Message, *bytes.Buffer, error return parsedMsg, &buf, nil } -// parseEMLHeaders will check the EML headers for the most common headers and set the -// according settings in the Msg +// parseEMLHeaders will check the EML headers for the most common headers and set the according settings +// in the Msg. func parseEMLHeaders(mailHeader *netmail.Header, msg *Msg) error { commonHeaders := []Header{ HeaderContentType, HeaderImportance, HeaderInReplyTo, HeaderListUnsubscribe, @@ -175,7 +173,7 @@ func parseEMLHeaders(mailHeader *netmail.Header, msg *Msg) error { return nil } -// parseEMLBodyParts parses the body of a EML based on the different content types and encodings +// parseEMLBodyParts parses the body of a EML based on the different content types and encodings. func parseEMLBodyParts(parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error { // Extract the transfer encoding of the body mediatype, params, err := mime.ParseMediaType(parsedMsg.Header.Get(HeaderContentType.String())) @@ -212,10 +210,11 @@ func parseEMLBodyParts(parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *M return nil } -// parseEMLBodyPlain parses the mail body of plain type mails +// parseEMLBodyPlain parses the mail body of plain type mails. func parseEMLBodyPlain(mediatype string, parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error { contentTransferEnc := parsedMsg.Header.Get(HeaderContentTransferEnc.String()) - // According to RFC2045, if no Content-Transfer-Encoding is set, we can imply 7bit US-ASCII encoding + // If no Content-Transfer-Encoding is set, we can imply 7bit US-ASCII encoding + // https://datatracker.ietf.org/doc/html/rfc2045#section-6.1 if contentTransferEnc == "" || strings.EqualFold(contentTransferEnc, EncodingUSASCII.String()) { msg.SetEncoding(EncodingUSASCII) msg.SetBodyString(ContentType(mediatype), bodybuf.String()) @@ -349,7 +348,7 @@ ReadNextPart: return nil } -// parseEMLEncoding parses and determines the encoding of the message +// parseEMLEncoding parses and determines the encoding of the message. func parseEMLEncoding(mailHeader *netmail.Header, msg *Msg) { if value := mailHeader.Get(HeaderContentTransferEnc.String()); value != "" { switch { @@ -363,7 +362,7 @@ func parseEMLEncoding(mailHeader *netmail.Header, msg *Msg) { } } -// parseEMLContentTypeCharset parses and determines the charset and content type of the message +// parseEMLContentTypeCharset parses and determines the charset and content type of the message. func parseEMLContentTypeCharset(mailHeader *netmail.Header, msg *Msg) { if value := mailHeader.Get(HeaderContentType.String()); value != "" { contentType, optional := parseMultiPartHeader(value) @@ -377,7 +376,7 @@ func parseEMLContentTypeCharset(mailHeader *netmail.Header, msg *Msg) { } } -// handleEMLMultiPartBase64Encoding sets the content body of a base64 encoded Part +// handleEMLMultiPartBase64Encoding sets the content body of a base64 encoded Part. func handleEMLMultiPartBase64Encoding(multiPartData []byte, part *Part) error { part.SetEncoding(EncodingB64) content, err := base64.StdEncoding.DecodeString(string(multiPartData)) @@ -388,8 +387,7 @@ func handleEMLMultiPartBase64Encoding(multiPartData []byte, part *Part) error { return nil } -// parseMultiPartHeader parses a multipart header and returns the value and optional parts as -// separate map +// parseMultiPartHeader parses a multipart header and returns the value and optional parts as separate map. func parseMultiPartHeader(multiPartHeader string) (header string, optional map[string]string) { optional = make(map[string]string) headerSplit := strings.SplitN(multiPartHeader, ";", 2) @@ -404,7 +402,7 @@ func parseMultiPartHeader(multiPartHeader string) (header string, optional map[s return } -// parseEMLAttachmentEmbed parses a multipart that is an attachment or embed +// parseEMLAttachmentEmbed parses a multipart that is an attachment or embed. func parseEMLAttachmentEmbed(contentDisposition []string, multiPart *multipart.Part, msg *Msg) error { cdType, optional := parseMultiPartHeader(contentDisposition[0]) filename := "generic.attachment" From ecd0bff5ad17cbd1f2ed138fcbcc63da47c87cf1 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 11:23:44 +0200 Subject: [PATCH 27/60] Update comments for better clarity and add RFC references Revised the comments to provide more detailed descriptions and context for each type and constant. Additionally, included relevant RFC document references where applicable to improve understanding of encoding and MIME types. --- encoding.go | 76 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/encoding.go b/encoding.go index 47213da..1adce33 100644 --- a/encoding.go +++ b/encoding.go @@ -4,37 +4,41 @@ package mail -// Charset represents a character set for the encoding +// Charset is a type wrapper for a string representing different character encodings. type Charset string -// ContentType represents a content type for the Msg +// ContentType is a type wrapper for a string and represents the MIME type of the content being handled. type ContentType string -// Encoding represents a MIME encoding scheme like quoted-printable or Base64. +// Encoding is a type wrapper for a string and represents the type of encoding used for email messages +// and/or parts. type Encoding string -// MIMEVersion represents the MIME version for the mail +// MIMEVersion is a type wrapper for a string nad represents the MIME version used in email messages. type MIMEVersion string -// MIMEType represents the MIME type for the mail +// MIMEType is a type wrapper for a string and represents the MIME type for the Msg content or parts. type MIMEType string -// List of supported encodings const ( // EncodingB64 represents the Base64 encoding as specified in RFC 2045. + // https://datatracker.ietf.org/doc/html/rfc2045#section-6.8 EncodingB64 Encoding = "base64" // EncodingQP represents the "quoted-printable" encoding as specified in RFC 2045. + // https://datatracker.ietf.org/doc/html/rfc2045#section-6.7 EncodingQP Encoding = "quoted-printable" // EncodingUSASCII represents encoding with only US-ASCII characters (aka 7Bit) + // https://datatracker.ietf.org/doc/html/rfc2045#section-2.7 EncodingUSASCII Encoding = "7bit" - // NoEncoding avoids any character encoding (except of the mail headers) + // NoEncoding represents 8-bit encoding for email messages as specified in RFC 6152. + // https://datatracker.ietf.org/doc/html/rfc2045#section-2.8 + // https://datatracker.ietf.org/doc/html/rfc6152 NoEncoding Encoding = "8bit" ) -// List of common charsets const ( // CharsetUTF7 represents the "UTF-7" charset CharsetUTF7 Charset = "UTF-7" @@ -133,42 +137,60 @@ const ( CharsetGBK Charset = "GBK" ) -// List of MIME versions -const ( - // MIME10 is the MIME Version 1.0 - MIME10 MIMEVersion = "1.0" -) +// MIME10 represents the MIME version "1.0" used in email messages. +const MIME10 MIMEVersion = "1.0" -// List of common content types const ( - TypeAppOctetStream ContentType = "application/octet-stream" + // TypeAppOctetStream represents the MIME type for arbitrary binary data. + TypeAppOctetStream ContentType = "application/octet-stream" + + // TypeMultipartAlternative represents the MIME type for a message body that can contain multiple alternative + // formats. TypeMultipartAlternative ContentType = "multipart/alternative" - TypeMultipartMixed ContentType = "multipart/mixed" - TypeMultipartRelated ContentType = "multipart/related" - TypePGPSignature ContentType = "application/pgp-signature" - TypePGPEncrypted ContentType = "application/pgp-encrypted" - TypeTextHTML ContentType = "text/html" - TypeTextPlain ContentType = "text/plain" + + // TypeMultipartMixed represents the MIME type for a multipart message containing different parts. + TypeMultipartMixed ContentType = "multipart/mixed" + + // TypeMultipartRelated represents the MIME type for a multipart message where each part is a related file + // or resource. + TypeMultipartRelated ContentType = "multipart/related" + + // TypePGPSignature represents the MIME type for PGP signed messages. + TypePGPSignature ContentType = "application/pgp-signature" + + // TypePGPEncrypted represents the MIME type for PGP encrypted messages. + TypePGPEncrypted ContentType = "application/pgp-encrypted" + + // TypeTextHTML represents the MIME type for HTML text content. + TypeTextHTML ContentType = "text/html" + + // TypeTextPlain represents the MIME type for plain text content. + TypeTextPlain ContentType = "text/plain" ) -// List of MIMETypes const ( + // MIMEAlternative MIMEType represents a MIME multipart/alternative type, used for emails with multiple versions. MIMEAlternative MIMEType = "alternative" - MIMEMixed MIMEType = "mixed" - MIMERelated MIMEType = "related" + + // MIMEMixed MIMEType represents a MIME multipart/mixed type used for emails containing different types of content. + MIMEMixed MIMEType = "mixed" + + // MIMERelated MIMEType represents a MIME multipart/related type, used for emails with related content entities. + MIMERelated MIMEType = "related" ) -// String is a standard method to convert an Charset into a printable format +// String satisfies the fmt.Stringer interface for the Charset type. It converts a Charset into a printable format. func (c Charset) String() string { return string(c) } -// String is a standard method to convert an ContentType into a printable format +// String satisfies the fmt.Stringer interface for the ContentType type. It converts a ContentType into a printable +// format. func (c ContentType) String() string { return string(c) } -// String is a standard method to convert an Encoding into a printable format +// String satisfies the fmt.Stringer interface for the Encoding type. It converts an Encoding into a printable format. func (e Encoding) String() string { return string(e) } From a0a7f74121c040a4453d57061d1e3e130fee77a2 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 11:42:21 +0200 Subject: [PATCH 28/60] Refactor file.go comments for clarity and detail Improved comments for better readability and understanding. Enhanced descriptions for File, FileOption, and various methods, providing more context and precision. Added notes on default behaviors and specific use cases for methods where applicable. --- file.go | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/file.go b/file.go index 45e142a..77e516c 100644 --- a/file.go +++ b/file.go @@ -9,10 +9,11 @@ import ( "net/textproto" ) -// FileOption returns a function that can be used for grouping File options +// FileOption is a function type used to modify properties of a File type FileOption func(*File) -// File is an attachment or embedded file of the Msg +// File represents a file with properties like content type, description, encoding, headers, name, and +// writer function. This can either be an attachment or an embedded file for a Msg. type File struct { ContentType ContentType Desc string @@ -22,32 +23,35 @@ type File struct { Writer func(w io.Writer) (int64, error) } -// WithFileContentID sets the Content-ID header for the File +// WithFileContentID sets the "Content-ID" header in the File's MIME headers to the specified id. func WithFileContentID(id string) FileOption { return func(f *File) { f.Header.Set(HeaderContentID.String(), id) } } -// WithFileName sets the filename of the File +// WithFileName sets the name of a File to the provided value. func WithFileName(name string) FileOption { return func(f *File) { f.Name = name } } -// WithFileDescription sets an optional file description of the File that will be -// added as Content-Description part +// WithFileDescription sets an optional file description for the File. The description is used in the +// Content-Description header of the MIME output. func WithFileDescription(description string) FileOption { return func(f *File) { f.Desc = description } } -// WithFileEncoding sets the encoding of the File. By default we should always use -// Base64 encoding but there might be exceptions, where this might come handy. -// Please note that quoted-printable should never be used for attachments/embeds. If this -// is provided as argument, the function will automatically override back to Base64 +// WithFileEncoding sets the encoding type for a file. +// +// By default one should always use Base64 encoding for attachments and embeds, but there might be exceptions in +// which this might come handy. +// +// Note: that quoted-printable must never be used for attachments or embeds. If EncodingQP is provided as encoding +// to this method, it will be automatically overwritten with EncodingB64. func WithFileEncoding(encoding Encoding) FileOption { return func(f *File) { if encoding == EncodingQP { @@ -58,23 +62,23 @@ func WithFileEncoding(encoding Encoding) FileOption { } // WithFileContentType sets the content type of the File. -// By default go-mail will try to guess the file type and its corresponding -// content type and fall back to application/octet-stream if the file type -// could not be guessed. In some cases, however, it might be needed to force -// this to a specific type. For such situations this override method can -// be used +// +// By default we will try to guess the file type and its corresponding content type and fall back to +// application/octet-stream if the file type, if no matching type could be guessed. This FileOption can +// be used to override this type, in case a specific type is required. func WithFileContentType(contentType ContentType) FileOption { return func(f *File) { f.ContentType = contentType } } -// setHeader sets header fields to a File +// setHeader sets the value of a given MIME header field for the File. func (f *File) setHeader(header Header, value string) { f.Header.Set(string(header), value) } -// getHeader return header fields of a File +// getHeader retrieves the value of the specified MIME header field. It returns the header value and a boolean +// indicating whether the header was present or not. func (f *File) getHeader(header Header) (string, bool) { v := f.Header.Get(string(header)) return v, v != "" From 476130d6e3f20c4bafefca5ff9b495d73c382fe1 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 11:43:50 +0200 Subject: [PATCH 29/60] Fumpt files to make golangci-lint happy --- smtp/smtp.go | 1 - smtp/smtp_test.go | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/smtp/smtp.go b/smtp/smtp.go index 7c39996..d713f8c 100644 --- a/smtp/smtp.go +++ b/smtp/smtp.go @@ -593,7 +593,6 @@ func (c *Client) GetTLSConnectionState() (*tls.ConnectionState, error) { if !c.isConnected { return nil, ErrNoConnection - } if !c.tls { return nil, ErrNonTLSConnection diff --git a/smtp/smtp_test.go b/smtp/smtp_test.go index 7b53963..4fd32eb 100644 --- a/smtp/smtp_test.go +++ b/smtp/smtp_test.go @@ -1811,6 +1811,7 @@ func TestClient_GetTLSConnectionState_unableErr(t *testing.T) { <-clientDone <-serverDone } + func TestClient_HasConnection(t *testing.T) { ln := newLocalListener(t) defer func() { From 94f47d43696733f1fb455b4d692d63f13d4a6202 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 11:47:15 +0200 Subject: [PATCH 30/60] Update crypto and text libraries Upgraded golang.org/x/crypto to v0.28.0 and golang.org/x/text to v0.19.0. These updates improve security and compatibility with recent changes in Go modules. Ensure to run `go mod tidy` to clean up any unused dependencies. --- go.mod | 4 ++-- go.sum | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 1dcef3a..4fb64a8 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,6 @@ module github.com/wneessen/go-mail go 1.16 require ( - golang.org/x/crypto v0.27.0 - golang.org/x/text v0.18.0 + golang.org/x/crypto v0.28.0 + golang.org/x/text v0.19.0 ) diff --git a/go.sum b/go.sum index 78b6dba..8e6bffc 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -37,7 +37,7 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -46,7 +46,7 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -55,8 +55,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From 493f8fc65762531f9cb6dcef1c8975968a0e1535 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 11:54:37 +0200 Subject: [PATCH 31/60] Add periods to charset comments Updated the comment lines for various charset constants to include ending periods for consistency and better readability. This change enhances code documentation quality without altering any functional behavior. --- encoding.go | 64 ++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/encoding.go b/encoding.go index 1adce33..669c694 100644 --- a/encoding.go +++ b/encoding.go @@ -40,100 +40,100 @@ const ( ) const ( - // CharsetUTF7 represents the "UTF-7" charset + // CharsetUTF7 represents the "UTF-7" charset. CharsetUTF7 Charset = "UTF-7" - // CharsetUTF8 represents the "UTF-8" charset + // CharsetUTF8 represents the "UTF-8" charset. CharsetUTF8 Charset = "UTF-8" - // CharsetASCII represents the "US-ASCII" charset + // CharsetASCII represents the "US-ASCII" charset. CharsetASCII Charset = "US-ASCII" - // CharsetISO88591 represents the "ISO-8859-1" charset + // CharsetISO88591 represents the "ISO-8859-1" charset. CharsetISO88591 Charset = "ISO-8859-1" - // CharsetISO88592 represents the "ISO-8859-2" charset + // CharsetISO88592 represents the "ISO-8859-2" charset. CharsetISO88592 Charset = "ISO-8859-2" - // CharsetISO88593 represents the "ISO-8859-3" charset + // CharsetISO88593 represents the "ISO-8859-3" charset. CharsetISO88593 Charset = "ISO-8859-3" - // CharsetISO88594 represents the "ISO-8859-4" charset + // CharsetISO88594 represents the "ISO-8859-4" charset. CharsetISO88594 Charset = "ISO-8859-4" - // CharsetISO88595 represents the "ISO-8859-5" charset + // CharsetISO88595 represents the "ISO-8859-5" charset. CharsetISO88595 Charset = "ISO-8859-5" - // CharsetISO88596 represents the "ISO-8859-6" charset + // CharsetISO88596 represents the "ISO-8859-6" charset. CharsetISO88596 Charset = "ISO-8859-6" - // CharsetISO88597 represents the "ISO-8859-7" charset + // CharsetISO88597 represents the "ISO-8859-7" charset. CharsetISO88597 Charset = "ISO-8859-7" - // CharsetISO88599 represents the "ISO-8859-9" charset + // CharsetISO88599 represents the "ISO-8859-9" charset. CharsetISO88599 Charset = "ISO-8859-9" - // CharsetISO885913 represents the "ISO-8859-13" charset + // CharsetISO885913 represents the "ISO-8859-13" charset. CharsetISO885913 Charset = "ISO-8859-13" - // CharsetISO885914 represents the "ISO-8859-14" charset + // CharsetISO885914 represents the "ISO-8859-14" charset. CharsetISO885914 Charset = "ISO-8859-14" - // CharsetISO885915 represents the "ISO-8859-15" charset + // CharsetISO885915 represents the "ISO-8859-15" charset. CharsetISO885915 Charset = "ISO-8859-15" - // CharsetISO885916 represents the "ISO-8859-16" charset + // CharsetISO885916 represents the "ISO-8859-16" charset. CharsetISO885916 Charset = "ISO-8859-16" - // CharsetISO2022JP represents the "ISO-2022-JP" charset + // CharsetISO2022JP represents the "ISO-2022-JP" charset. CharsetISO2022JP Charset = "ISO-2022-JP" - // CharsetISO2022KR represents the "ISO-2022-KR" charset + // CharsetISO2022KR represents the "ISO-2022-KR" charset. CharsetISO2022KR Charset = "ISO-2022-KR" - // CharsetWindows1250 represents the "windows-1250" charset + // CharsetWindows1250 represents the "windows-1250" charset. CharsetWindows1250 Charset = "windows-1250" - // CharsetWindows1251 represents the "windows-1251" charset + // CharsetWindows1251 represents the "windows-1251" charset. CharsetWindows1251 Charset = "windows-1251" - // CharsetWindows1252 represents the "windows-1252" charset + // CharsetWindows1252 represents the "windows-1252" charset. CharsetWindows1252 Charset = "windows-1252" - // CharsetWindows1255 represents the "windows-1255" charset + // CharsetWindows1255 represents the "windows-1255" charset. CharsetWindows1255 Charset = "windows-1255" - // CharsetWindows1256 represents the "windows-1256" charset + // CharsetWindows1256 represents the "windows-1256" charset. CharsetWindows1256 Charset = "windows-1256" - // CharsetKOI8R represents the "KOI8-R" charset + // CharsetKOI8R represents the "KOI8-R" charset. CharsetKOI8R Charset = "KOI8-R" - // CharsetKOI8U represents the "KOI8-U" charset + // CharsetKOI8U represents the "KOI8-U" charset. CharsetKOI8U Charset = "KOI8-U" - // CharsetBig5 represents the "Big5" charset + // CharsetBig5 represents the "Big5" charset. CharsetBig5 Charset = "Big5" - // CharsetGB18030 represents the "GB18030" charset + // CharsetGB18030 represents the "GB18030" charset. CharsetGB18030 Charset = "GB18030" - // CharsetGB2312 represents the "GB2312" charset + // CharsetGB2312 represents the "GB2312" charset. CharsetGB2312 Charset = "GB2312" - // CharsetTIS620 represents the "TIS-620" charset + // CharsetTIS620 represents the "TIS-620" charset. CharsetTIS620 Charset = "TIS-620" - // CharsetEUCKR represents the "EUC-KR" charset + // CharsetEUCKR represents the "EUC-KR" charset. CharsetEUCKR Charset = "EUC-KR" - // CharsetShiftJIS represents the "Shift_JIS" charset + // CharsetShiftJIS represents the "Shift_JIS" charset. CharsetShiftJIS Charset = "Shift_JIS" - // CharsetUnknown represents the "Unknown" charset + // CharsetUnknown represents the "Unknown" charset. CharsetUnknown Charset = "Unknown" - // CharsetGBK represents the "GBK" charset + // CharsetGBK represents the "GBK" charset. CharsetGBK Charset = "GBK" ) From 96466facdd9fd5953793f8f10787a4dc88b8c109 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 12:00:10 +0200 Subject: [PATCH 32/60] Refactor and document header types and importance levels. Updated type declarations for headers and importance to clarify their roles in the Msg package. Added detailed inline comments and RFC links for better documentation and understanding. Enhanced string representation methods to explicitly handle importance levels and header types. --- header.go | 105 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 58 insertions(+), 47 deletions(-) diff --git a/header.go b/header.go index 9191b7e..d96271a 100644 --- a/header.go +++ b/header.go @@ -4,129 +4,137 @@ package mail -// Header represents a generic mail header field name +// Header is a type wrapper for a string and represents email header fields in a Msg. type Header string -// AddrHeader represents a address related mail Header field name +// AddrHeader is a type wrapper for a string and represents email address headers fields in a Msg. type AddrHeader string -// Importance represents a Importance/Priority value string +// Importance is a type wrapper for an int and represents the level of importance or priority for a Msg. type Importance int -// List of common generic header field names const ( - // HeaderContentDescription is the "Content-Description" header + // HeaderContentDescription is the "Content-Description" header. HeaderContentDescription Header = "Content-Description" - // HeaderContentDisposition is the "Content-Disposition" header + // HeaderContentDisposition is the "Content-Disposition" header. HeaderContentDisposition Header = "Content-Disposition" - // HeaderContentID is the "Content-ID" header + // HeaderContentID is the "Content-ID" header. HeaderContentID Header = "Content-ID" - // HeaderContentLang is the "Content-Language" header + // HeaderContentLang is the "Content-Language" header. HeaderContentLang Header = "Content-Language" - // HeaderContentLocation is the "Content-Location" header (RFC 2110) + // HeaderContentLocation is the "Content-Location" header (RFC 2110). + // https://datatracker.ietf.org/doc/html/rfc2110#section-4.3 HeaderContentLocation Header = "Content-Location" - // HeaderContentTransferEnc is the "Content-Transfer-Encoding" header + // HeaderContentTransferEnc is the "Content-Transfer-Encoding" header. HeaderContentTransferEnc Header = "Content-Transfer-Encoding" - // HeaderContentType is the "Content-Type" header + // HeaderContentType is the "Content-Type" header. HeaderContentType Header = "Content-Type" - // HeaderDate represents the "Date" field - // See: https://www.rfc-editor.org/rfc/rfc822#section-5.1 + // HeaderDate represents the "Date" field. + // https://datatracker.ietf.org/doc/html/rfc822#section-5.1 HeaderDate Header = "Date" - // HeaderDispositionNotificationTo is the MDN header as described in RFC8098 - // See: https://www.rfc-editor.org/rfc/rfc8098.html#section-2.1 + // HeaderDispositionNotificationTo is the MDN header as described in RFC 8098. + // https://datatracker.ietf.org/doc/html/rfc8098#section-2.1 HeaderDispositionNotificationTo Header = "Disposition-Notification-To" - // HeaderImportance represents the "Importance" field + // HeaderImportance represents the "Importance" field. HeaderImportance Header = "Importance" - // HeaderInReplyTo represents the "In-Reply-To" field + // HeaderInReplyTo represents the "In-Reply-To" field. HeaderInReplyTo Header = "In-Reply-To" - // HeaderListUnsubscribe is the "List-Unsubscribe" header field + // HeaderListUnsubscribe is the "List-Unsubscribe" header field. HeaderListUnsubscribe Header = "List-Unsubscribe" - // HeaderListUnsubscribePost is the "List-Unsubscribe-Post" header field + // HeaderListUnsubscribePost is the "List-Unsubscribe-Post" header field. HeaderListUnsubscribePost Header = "List-Unsubscribe-Post" - // HeaderMessageID represents the "Message-ID" field for message identification - // See: https://www.rfc-editor.org/rfc/rfc1036#section-2.1.5 + // HeaderMessageID represents the "Message-ID" field for message identification. + // https://datatracker.ietf.org/doc/html/rfc1036#section-2.1.5 HeaderMessageID Header = "Message-ID" - // HeaderMIMEVersion represents the "MIME-Version" field as per RFC 2045 - // See: https://datatracker.ietf.org/doc/html/rfc2045#section-4 + // HeaderMIMEVersion represents the "MIME-Version" field as per RFC 2045. + // https://datatracker.ietf.org/doc/html/rfc2045#section-4 HeaderMIMEVersion Header = "MIME-Version" - // HeaderOrganization is the "Organization" header field + // HeaderOrganization is the "Organization" header field. HeaderOrganization Header = "Organization" - // HeaderPrecedence is the "Precedence" header field + // HeaderPrecedence is the "Precedence" header field. HeaderPrecedence Header = "Precedence" - // HeaderPriority represents the "Priority" field + // HeaderPriority represents the "Priority" field. HeaderPriority Header = "Priority" - // HeaderReferences is the "References" header field + // HeaderReferences is the "References" header field. HeaderReferences Header = "References" - // HeaderReplyTo is the "Reply-To" header field + // HeaderReplyTo is the "Reply-To" header field. HeaderReplyTo Header = "Reply-To" - // HeaderSubject is the "Subject" header field + // HeaderSubject is the "Subject" header field. HeaderSubject Header = "Subject" - // HeaderUserAgent is the "User-Agent" header field + // HeaderUserAgent is the "User-Agent" header field. HeaderUserAgent Header = "User-Agent" - // HeaderXAutoResponseSuppress is the "X-Auto-Response-Suppress" header field + // HeaderXAutoResponseSuppress is the "X-Auto-Response-Suppress" header field. HeaderXAutoResponseSuppress Header = "X-Auto-Response-Suppress" - // HeaderXMailer is the "X-Mailer" header field + // HeaderXMailer is the "X-Mailer" header field. HeaderXMailer Header = "X-Mailer" - // HeaderXMSMailPriority is the "X-MSMail-Priority" header field + // HeaderXMSMailPriority is the "X-MSMail-Priority" header field. HeaderXMSMailPriority Header = "X-MSMail-Priority" - // HeaderXPriority is the "X-Priority" header field + // HeaderXPriority is the "X-Priority" header field. HeaderXPriority Header = "X-Priority" ) -// List of common address header field names const ( - // HeaderBcc is the "Blind Carbon Copy" header field + // HeaderBcc is the "Blind Carbon Copy" header field. HeaderBcc AddrHeader = "Bcc" - // HeaderCc is the "Carbon Copy" header field + // HeaderCc is the "Carbon Copy" header field. HeaderCc AddrHeader = "Cc" - // HeaderEnvelopeFrom is the envelope FROM header field - // It's not included in the mail body but only used by the Client for the envelope + // HeaderEnvelopeFrom is the envelope FROM header field. It is not included in the mail body but only used by + // the Client for the envelope. HeaderEnvelopeFrom AddrHeader = "EnvelopeFrom" - // HeaderFrom is the "From" header field + // HeaderFrom is the "From" header field. HeaderFrom AddrHeader = "From" - // HeaderTo is the "Receipient" header field + // HeaderTo is the "Receipient" header field. HeaderTo AddrHeader = "To" ) -// List of Importance values const ( + // ImportanceLow indicates a low level of importance or priority in a Msg. ImportanceLow Importance = iota + + // ImportanceNormal indicates a standard level of importance or priority for a Msg. ImportanceNormal + + // ImportanceHigh indicates a high level of importance or priority in a Msg. ImportanceHigh + + // ImportanceNonUrgent indicates a non-urgent level of importance or priority in a Msg. ImportanceNonUrgent + + // ImportanceUrgent indicates an urgent level of importance or priority in a Msg. ImportanceUrgent ) -// NumString returns the importance number string based on the Importance +// NumString returns a numerical string representation of the Importance, mapping ImportanceHigh and +// ImportanceUrgent to "1" and others to "0". func (i Importance) NumString() string { switch i { case ImportanceNonUrgent: @@ -142,7 +150,8 @@ func (i Importance) NumString() string { } } -// XPrioString returns the X-Priority number string based on the Importance +// XPrioString returns the X-Priority string representation of the Importance, mapping ImportanceHigh and +// ImportanceUrgent to "1" and others to "5". func (i Importance) XPrioString() string { switch i { case ImportanceNonUrgent: @@ -158,7 +167,8 @@ func (i Importance) XPrioString() string { } } -// String returns the importance string based on the Importance +// String satisfies the fmt.Stringer interface for the Importance type and returns the string representation of the +// Importance level. func (i Importance) String() string { switch i { case ImportanceNonUrgent: @@ -174,12 +184,13 @@ func (i Importance) String() string { } } -// String returns the header string based on the given Header +// String satisfies the fmt.Stringer interface for the Header type and returns the string representation of the Header. func (h Header) String() string { return string(h) } -// String returns the address header string based on the given AddrHeader +// String satisfies the fmt.Stringer interface for the AddrHeader type and returns the string representation of the +// AddrHeader. func (a AddrHeader) String() string { return string(a) } From a820ba3cee708bd1057e39dabcc2347e2fd1ec9f Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 12:14:00 +0200 Subject: [PATCH 33/60] Correct typo in README.md Fixed a typo in the README where `io.WriteTo` was incorrectly spelled instead of `io.WriterTo`. This ensures the documentation correctly reflects the interfaces implemented by the Message object. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c200c7..3a67d0d 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Here are some highlights of go-mail's featureset: * [X] Support sending mails via a local sendmail command * [X] Support for requestng MDNs (RFC 8098) and DSNs (RFC 1891) * [X] DKIM signature support via [go-mail-middlware](https://github.com/wneessen/go-mail-middleware) -* [X] Message object satisfies `io.WriteTo` and `io.Reader` interfaces +* [X] Message object satisfies `io.WriterTo` and `io.Reader` interfaces * [X] Support for Go's `html/template` and `text/template` (as message body, alternative part or attachment/emebed) * [X] Output to file support which allows storing mail messages as e. g. `.eml` files to disk to open them in a MUA * [X] Debug logging of SMTP traffic From cd4c0194dc24aafd8c97d0155a6ee67d355598e9 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 12:15:01 +0200 Subject: [PATCH 34/60] Refactor comments for clarity and detail Enhanced the descriptive comments for error variables, constants, and types to provide clearer and more detailed explanations. This improves code readability and ensures that the purpose and usage of different elements are better understood by developers. --- msg.go | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/msg.go b/msg.go index b66bffe..5b92cc1 100644 --- a/msg.go +++ b/msg.go @@ -24,51 +24,60 @@ import ( ) var ( - // ErrNoFromAddress should be used when a FROM address is requrested but not set + // ErrNoFromAddress indicates that the FROM address is not set, which is required. ErrNoFromAddress = errors.New("no FROM address set") - // ErrNoRcptAddresses should be used when the list of RCPTs is empty + // ErrNoRcptAddresses indicates that no recipient addresses have been set. ErrNoRcptAddresses = errors.New("no recipient addresses set") ) const ( - // errTplExecuteFailed is issued when the template execution was not successful + // errTplExecuteFailed indicates that the execution of a template has failed, including the underlying error. errTplExecuteFailed = "failed to execute template: %w" - // errTplPointerNil is issued when a template pointer is expected but it is nil + // errTplPointerNil indicates that a template pointer is nil, which prevents further template execution or + // processing. errTplPointerNil = "template pointer is nil" - // errParseMailAddr is used when a mail address could not be validated + // errParseMailAddr indicates that parsing of a mail address has failed, including the problematic address + // and error. errParseMailAddr = "failed to parse mail address %q: %w" ) const ( - // NoPGP indicates that a message should not be treated as PGP encrypted - // or signed and is the default value for a message + // NoPGP indicates that a message should not be treated as PGP encrypted or signed and is the default value + // for a message NoPGP PGPType = iota - // PGPEncrypt indicates that a message should be treated as PGP encrypted - // This works closely together with the corresponding go-mail-middleware + // PGPEncrypt indicates that a message should be treated as PGP encrypted. This works closely together with + // the corresponding go-mail-middleware. PGPEncrypt - // PGPSignature indicates that a message should be treated as PGP signed - // This works closely together with the corresponding go-mail-middleware + // PGPSignature indicates that a message should be treated as PGP signed. This works closely together with + // the corresponding go-mail-middleware. PGPSignature ) -// MiddlewareType is the type description of the Middleware and needs to be returned -// in the Middleware interface by the Type method +// MiddlewareType is a type wrapper for a string. It describes the type of the Middleware and needs to be +// returned by the Middleware.Type method to satisfy the Middleware interface. type MiddlewareType string -// Middleware is an interface to define a function to apply to Msg before sending +// Middleware represents the interface for modifying or handling email messages. A Middleware allows the user to +// alter a Msg before it is finally processed. Multiple Middleware can be applied to a Msg. +// +// Type returns a unique MiddlewareType. It describes the type of Middleware and makes sure that +// a Middleware is only applied once. +// Handle performs all the processing to the Msg. It always needs to return a Msg back. type Middleware interface { Handle(*Msg) *Msg Type() MiddlewareType } -// PGPType is a type alias for a int representing a type of PGP encryption -// or signature +// PGPType is a type wrapper for an int, representing a type of PGP encryption or signature. type PGPType int -// Msg is the mail message struct +// Msg represents an email message with various headers, attachments, and encoding settings. +// +// The Msg is the central part of go-mail. It provided a lot of methods that you would expect in a mail +// user agent (MUA). Msg satisfies the io.WriterTo and io.Reader interfaces. type Msg struct { // addrHeader is a slice of strings that the different mail AddrHeader fields addrHeader map[AddrHeader][]*mail.Address From 682f7a6ca5cb43cfb3d1a8e0bbdeb7c57534fd4e Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 12:35:15 +0200 Subject: [PATCH 35/60] Clarify Msg struct field comments Updated comments for all fields in the Msg struct to provide clearer and more detailed explanations. This includes specifying the data type for each field and the role they play within the Msg struct, making the code easier to understand and maintain. --- msg.go | 49 +++++++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/msg.go b/msg.go index 5b92cc1..118c7b3 100644 --- a/msg.go +++ b/msg.go @@ -79,55 +79,68 @@ type PGPType int // The Msg is the central part of go-mail. It provided a lot of methods that you would expect in a mail // user agent (MUA). Msg satisfies the io.WriterTo and io.Reader interfaces. type Msg struct { - // addrHeader is a slice of strings that the different mail AddrHeader fields + // addrHeader holds a mapping between AddrHeader keys and their corresponding slices of mail.Address pointers. addrHeader map[AddrHeader][]*mail.Address - // attachments represent the different attachment File of the Msg + // attachments holds a list of File pointers that represent files either as attachments or embeds files in + // a Msg. attachments []*File - // boundary is the MIME content boundary + // boundary represents the delimiter for separating parts in a multipart message. boundary string - // charset represents the charset of the mail (defaults to UTF-8) + // charset represents the Charset of the Msg. + // + // By default we set CharsetUTF8 for a Msg unless overridden by a corresponding MsgOption. charset Charset - // embeds represent the different embedded File of the Msg + // embeds contains a slice of File pointers representing the embedded files in a Msg. embeds []*File - // encoder represents a mime.WordEncoder from the std lib + // encoder is a mime.WordEncoder used to encode strings (such as email headers) using a specified + // Encoding. encoder mime.WordEncoder - // encoding represents the message encoding (the encoder will be a corresponding WordEncoder) + // encoding specifies the type of Encoding used for email messages and/or parts. encoding Encoding - // genHeader is a slice of strings that the different generic mail Header fields + // genHeader is a map where the keys are email headers (of type Header) and the values are slices of strings + // representing header values. genHeader map[Header][]string - // isDelivered signals if a message has been delivered or not + // isDelivered indicates wether the Msg has been delivered. isDelivered bool - // middlewares is the list of middlewares to apply to the Msg before sending in FIFO order + // middlewares is a slice of Middleware used for modifying or handling messages before they are processed. + // + // middlewares are processed in FIFO order. middlewares []Middleware - // mimever represents the MIME version + // mimever represents the MIME version used in a Msg. mimever MIMEVersion - // parts represent the different parts of the Msg + // parts is a slice that holds pointers to Part structures, which represent different parts of a Msg. parts []*Part - // preformHeader is a slice of strings that the different generic mail Header fields - // of which content is already preformated and will not be affected by the automatic line - // breaks + // preformHeader maps Header types to their already preformatted string values. + // + // Preformatted Header values will not be affected by automatic line breaks. preformHeader map[Header]string // pgptype indicates that a message has a PGPType assigned and therefore will generate - // different Content-Type settings in the msgWriter + // different Content-Type settings in the msgWriter. pgptype PGPType - // sendError holds the SendError in case a Msg could not be delivered during the Client.Send operation + // sendError represents an error encountered during the process of sending a Msg during the + // Client.Send operation. + // + // sendError will hold an error of type SendError. sendError error - // noDefaultUserAgent indicates whether the default User Agent will be excluded for the Msg when it's sent. + // noDefaultUserAgent indicates whether the default User-Agent will be omitted for the Msg when it is + // being sent. + // + // This can be useful in scenarios where headers are conditionally passed based on receipt - i. e. SMTP proxies. noDefaultUserAgent bool } From c186cba2c22cad99400e801a8dd09b092befdf43 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 12:48:09 +0200 Subject: [PATCH 36/60] Refactor MsgOption comments for clarity Revised the comments for MsgOption functions to provide clearer explanations of their purpose and usage. Added detailed descriptions for options such as WithMIMEVersion and WithBoundary to clarify their contexts and constraints. --- msg.go | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/msg.go b/msg.go index 118c7b3..4c547ca 100644 --- a/msg.go +++ b/msg.go @@ -144,13 +144,14 @@ type Msg struct { noDefaultUserAgent bool } -// SendmailPath is the default system path to the sendmail binary +// SendmailPath is the default system path to the sendmail binary - at least on standard Unix-like OS. const SendmailPath = "/usr/sbin/sendmail" -// MsgOption returns a function that can be used for grouping Msg options +// MsgOption is a function type that modifies a Msg instance during its creation or initialization. type MsgOption func(*Msg) -// NewMsg returns a new Msg pointer +// NewMsg creates a new email message with optional MsgOption functions that customize various aspects of the +// message. func NewMsg(opts ...MsgOption) *Msg { msg := &Msg{ addrHeader: make(map[AddrHeader][]*mail.Address), @@ -161,7 +162,7 @@ func NewMsg(opts ...MsgOption) *Msg { mimever: MIME10, } - // Override defaults with optionally provided MsgOption functions + // Override defaults with optionally provided MsgOption functions. for _, option := range opts { if option == nil { continue @@ -175,49 +176,63 @@ func NewMsg(opts ...MsgOption) *Msg { return msg } -// WithCharset overrides the default message charset +// WithCharset sets the Charset type for a Msg during its creation or initialization. func WithCharset(c Charset) MsgOption { return func(m *Msg) { m.charset = c } } -// WithEncoding overrides the default message encoding +// WithEncoding sets the Encoding type for a Msg during its creation or initialization. func WithEncoding(e Encoding) MsgOption { return func(m *Msg) { m.encoding = e } } -// WithMIMEVersion overrides the default MIME version +// WithMIMEVersion sets the MIMEVersion type for a Msg during its creation or initialization. +// +// Note that in the context of email, MIME Version 1.0 is the only officially standardized and supported +// version. While MIME has been updated and extended over time (via various RFCs), these updates and extensions +// do not introduce new MIME versions; they refine or add features within the framework of MIME 1.0. +// Therefore there should be no reason to ever use this MsgOption. +// https://datatracker.ietf.org/doc/html/rfc1521 +// https://datatracker.ietf.org/doc/html/rfc2045 +// https://datatracker.ietf.org/doc/html/rfc2049 func WithMIMEVersion(mv MIMEVersion) MsgOption { return func(m *Msg) { m.mimever = mv } } -// WithBoundary overrides the default MIME boundary +// WithBoundary sets the boundary of a Msg to the provided string value during its creation or initialization. +// +// Note that by default we create random MIME boundaries. This should only be used if a specific boundary is +// required. func WithBoundary(b string) MsgOption { return func(m *Msg) { m.boundary = b } } -// WithMiddleware add the given middleware in the end of the list of the client middlewares +// WithMiddleware adds the given Middleware to the end of the list of the Client middlewares slice. Middleware +// are processed in FIFO order. func WithMiddleware(mw Middleware) MsgOption { return func(m *Msg) { m.middlewares = append(m.middlewares, mw) } } -// WithPGPType overrides the default PGPType of the message +// WithPGPType sets the PGP type for the Msg during its creation or initialization, determining the encryption or +// signature method. func WithPGPType(pt PGPType) MsgOption { return func(m *Msg) { m.pgptype = pt } } -// WithNoDefaultUserAgent configures the Msg to not use the default User Agent +// WithNoDefaultUserAgent disables the inclusion of a default User-Agent header in the Msg during its creation or +// initialization. func WithNoDefaultUserAgent() MsgOption { return func(m *Msg) { m.noDefaultUserAgent = true From 78e285778298b4d5fbd7c62ea89ca38706f99610 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 13:39:21 +0200 Subject: [PATCH 37/60] Update method comments to include additional context and RFC references Enhanced the comments for various methods in `msg.go`, `client.go`, `auth.go`, and `encoding.go` to provide more detailed explanations, context, and relevant RFC references. This improves the clarity and maintainability of the code by providing developers with a deeper understanding of each method's purpose and usage. --- auth.go | 20 +++++++++++----- client.go | 19 ++++++++++++--- encoding.go | 5 ++++ msg.go | 66 +++++++++++++++++++++++++++++++++-------------------- 4 files changed, 76 insertions(+), 34 deletions(-) diff --git a/auth.go b/auth.go index fef7881..e175a12 100644 --- a/auth.go +++ b/auth.go @@ -20,6 +20,7 @@ const ( // // It was recommended to deprecate the standard in 20 November 2008. As an alternative it // recommends e.g. SCRAM or SASL Plain protected by TLS instead. + // // https://datatracker.ietf.org/doc/html/draft-ietf-sasl-crammd5-to-historic-00.html SMTPAuthCramMD5 SMTPAuthType = "CRAM-MD5" @@ -33,12 +34,14 @@ const ( // does not have an official RFC that could be followed. There is a spec by Microsoft and an // IETF draft. The IETF draft is more lax than the MS spec, therefore we follow the I-D, which // automatically matches the MS spec. - // https://msopenspecs.azureedge.net/files/MS-XLOGIN/%5bMS-XLOGIN%5d.pdf - // https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00 // // Since the "LOGIN" SASL authentication mechansim transmits the username and password in // plaintext over the internet connection, we only allow this mechanism over a TLS secured // connection. + // + // https://msopenspecs.azureedge.net/files/MS-XLOGIN/%5bMS-XLOGIN%5d.pdf + // + // https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00 SMTPAuthLogin SMTPAuthType = "LOGIN" // SMTPAuthNoAuth is equivalent to performing no authentication at all. It is a convenience @@ -47,11 +50,12 @@ const ( SMTPAuthNoAuth SMTPAuthType = "" // SMTPAuthPlain is the "PLAIN" authentication mechanism as described in RFC 4616. - // https://datatracker.ietf.org/doc/html/rfc4616/ // // Since the "PLAIN" SASL authentication mechansim transmits the username and password in // plaintext over the internet connection, we only allow this mechanism over a TLS secured // connection. + // + // https://datatracker.ietf.org/doc/html/rfc4616/ SMTPAuthPlain SMTPAuthType = "PLAIN" // SMTPAuthXOAUTH2 is the "XOAUTH2" SASL authentication mechanism. @@ -59,16 +63,16 @@ const ( SMTPAuthXOAUTH2 SMTPAuthType = "XOAUTH2" // SMTPAuthSCRAMSHA1 is the "SCRAM-SHA-1" SASL authentication mechanism as described in RFC 5802. - // https://datatracker.ietf.org/doc/html/rfc5802 // // SCRAM-SHA-1 is still considered secure for certain applications, particularly when used as part // of a challenge-response authentication mechanism (as we use it). However, it is generally // recommended to prefer stronger alternatives like SCRAM-SHA-256(-PLUS), as SHA-1 has known // vulnerabilities in other contexts, although it remains effective in HMAC constructions. + // + // https://datatracker.ietf.org/doc/html/rfc5802 SMTPAuthSCRAMSHA1 SMTPAuthType = "SCRAM-SHA-1" // SMTPAuthSCRAMSHA1PLUS is the "SCRAM-SHA-1-PLUS" SASL authentication mechanism as described in RFC 5802. - // https://datatracker.ietf.org/doc/html/rfc5802 // // SCRAM-SHA-X-PLUS authentication require TLS channel bindings to protect against MitM attacks and // to guarantee that the integrity of the transport layer is preserved throughout the authentication @@ -78,18 +82,22 @@ const ( // of a challenge-response authentication mechanism (as we use it). However, it is generally // recommended to prefer stronger alternatives like SCRAM-SHA-256(-PLUS), as SHA-1 has known // vulnerabilities in other contexts, although it remains effective in HMAC constructions. + // + // https://datatracker.ietf.org/doc/html/rfc5802 SMTPAuthSCRAMSHA1PLUS SMTPAuthType = "SCRAM-SHA-1-PLUS" // SMTPAuthSCRAMSHA256 is the "SCRAM-SHA-256" SASL authentication mechanism as described in RFC 7677. + // // https://datatracker.ietf.org/doc/html/rfc7677 SMTPAuthSCRAMSHA256 SMTPAuthType = "SCRAM-SHA-256" // SMTPAuthSCRAMSHA256PLUS is the "SCRAM-SHA-256-PLUS" SASL authentication mechanism as described in RFC 7677. - // https://datatracker.ietf.org/doc/html/rfc7677 // // SCRAM-SHA-X-PLUS authentication require TLS channel bindings to protect against MitM attacks and // to guarantee that the integrity of the transport layer is preserved throughout the authentication // process. Therefore we only allow this mechansim over a TLS secured connection. + // + // https://datatracker.ietf.org/doc/html/rfc7677 SMTPAuthSCRAMSHA256PLUS SMTPAuthType = "SCRAM-SHA-256-PLUS" ) diff --git a/client.go b/client.go index d196a24..26a8bb5 100644 --- a/client.go +++ b/client.go @@ -44,26 +44,31 @@ const ( // DSNMailReturnHeadersOnly requests that only the message headers of the mail message are returned in // a DSN (Delivery Status Notification). + // // https://datatracker.ietf.org/doc/html/rfc1891#section-5.3 DSNMailReturnHeadersOnly DSNMailReturnOption = "HDRS" // DSNMailReturnFull requests that the entire mail message is returned in any failed DSN // (Delivery Status Notification) issued for this recipient. + // // https://datatracker.ietf.org/doc/html/rfc1891/#section-5.3 DSNMailReturnFull DSNMailReturnOption = "FULL" // DSNRcptNotifyNever indicates that no DSN (Delivery Status Notifications) should be sent for the // recipient under any condition. + // // https://datatracker.ietf.org/doc/html/rfc1891/#section-5.1 DSNRcptNotifyNever DSNRcptNotifyOption = "NEVER" // DSNRcptNotifySuccess indicates that the sender requests a DSN (Delivery Status Notification) if the // message is successfully delivered. + // // https://datatracker.ietf.org/doc/html/rfc1891/#section-5.1 DSNRcptNotifySuccess DSNRcptNotifyOption = "SUCCESS" // DSNRcptNotifyFailure requests that a DSN (Delivery Status Notification) is issued if delivery of // a message fails. + // // https://datatracker.ietf.org/doc/html/rfc1891/#section-5.1 DSNRcptNotifyFailure DSNRcptNotifyOption = "FAILURE" @@ -73,6 +78,7 @@ const ( // (as determined by the MTA at which the message is delayed), but the final delivery status (whether // successful or failure) cannot be determined. The absence of the DELAY keyword in a NOTIFY parameter // requests that a "delayed" DSN NOT be issued under any conditions. + // // https://datatracker.ietf.org/doc/html/rfc1891/#section-5.1 DSNRcptNotifyDelay DSNRcptNotifyOption = "DELAY" ) @@ -87,11 +93,13 @@ type ( // DSNMailReturnOption is a type wrapper for a string and specifies the type of return content requested // in a Delivery Status Notification (DSN). + // // https://datatracker.ietf.org/doc/html/rfc1891/ DSNMailReturnOption string // DSNRcptNotifyOption is a type wrapper for a string and specifies the notification options for a // recipient in DSNs. + // // https://datatracker.ietf.org/doc/html/rfc1891/ DSNRcptNotifyOption string @@ -167,6 +175,7 @@ type ( smtpClient *smtp.Client // tlspolicy defines the TLSPolicy configuration the Client uses for the STARTTLS protocol. + // // https://datatracker.ietf.org/doc/html/rfc3207#section-2 tlspolicy TLSPolicy @@ -180,6 +189,7 @@ type ( user string // useSSL indicates whether to use SSL/TLS encryption for network communication. + // // https://datatracker.ietf.org/doc/html/rfc8314 useSSL bool } @@ -424,10 +434,11 @@ func WithPassword(password string) Option { // WithDSN enables DSN (Delivery Status Notifications) for the Client as described in the RFC 1891. DSN // only work if the server supports them. -// https://datatracker.ietf.org/doc/html/rfc1891 // // By default we set DSNMailReturnOption to DSNMailReturnFull and DSNRcptNotifyOption to DSNRcptNotifySuccess // and DSNRcptNotifyFailure. +// +// https://datatracker.ietf.org/doc/html/rfc1891 func WithDSN() Option { return func(c *Client) error { c.requestDSN = true @@ -439,9 +450,10 @@ func WithDSN() Option { // WithDSNMailReturnType enables DSN (Delivery Status Notifications) for the Client as described in the // RFC 1891. DSN only work if the server supports them. -// https://datatracker.ietf.org/doc/html/rfc1891 // // It will set the DSNMailReturnOption to the provided value. +// +// https://datatracker.ietf.org/doc/html/rfc1891 func WithDSNMailReturnType(option DSNMailReturnOption) Option { return func(c *Client) error { switch option { @@ -459,9 +471,10 @@ func WithDSNMailReturnType(option DSNMailReturnOption) Option { // WithDSNRcptNotifyType enables DSN (Delivery Status Notifications) for the Client as described in the // RFC 1891. DSN only work if the server supports them. -// https://datatracker.ietf.org/doc/html/rfc1891 // // It will set the DSNRcptNotifyOption to the provided values. +// +// https://datatracker.ietf.org/doc/html/rfc1891 func WithDSNRcptNotifyType(opts ...DSNRcptNotifyOption) Option { return func(c *Client) error { var rcptOpts []string diff --git a/encoding.go b/encoding.go index 669c694..2b559c4 100644 --- a/encoding.go +++ b/encoding.go @@ -22,19 +22,24 @@ type MIMEType string const ( // EncodingB64 represents the Base64 encoding as specified in RFC 2045. + // // https://datatracker.ietf.org/doc/html/rfc2045#section-6.8 EncodingB64 Encoding = "base64" // EncodingQP represents the "quoted-printable" encoding as specified in RFC 2045. + // // https://datatracker.ietf.org/doc/html/rfc2045#section-6.7 EncodingQP Encoding = "quoted-printable" // EncodingUSASCII represents encoding with only US-ASCII characters (aka 7Bit) + // // https://datatracker.ietf.org/doc/html/rfc2045#section-2.7 EncodingUSASCII Encoding = "7bit" // NoEncoding represents 8-bit encoding for email messages as specified in RFC 6152. + // // https://datatracker.ietf.org/doc/html/rfc2045#section-2.8 + // // https://datatracker.ietf.org/doc/html/rfc6152 NoEncoding Encoding = "8bit" ) diff --git a/msg.go b/msg.go index 4c547ca..995e93f 100644 --- a/msg.go +++ b/msg.go @@ -197,7 +197,9 @@ func WithEncoding(e Encoding) MsgOption { // do not introduce new MIME versions; they refine or add features within the framework of MIME 1.0. // Therefore there should be no reason to ever use this MsgOption. // https://datatracker.ietf.org/doc/html/rfc1521 +// // https://datatracker.ietf.org/doc/html/rfc2045 +// // https://datatracker.ietf.org/doc/html/rfc2049 func WithMIMEVersion(mv MIMEVersion) MsgOption { return func(m *Msg) { @@ -239,52 +241,68 @@ func WithNoDefaultUserAgent() MsgOption { } } -// SetCharset sets the encoding charset of the Msg +// SetCharset sets or overrides the currently set encoding charset of the Msg. func (m *Msg) SetCharset(c Charset) { m.charset = c } -// SetEncoding sets the encoding of the Msg +// SetEncoding sets or overrides the currently set Encoding of the Msg. func (m *Msg) SetEncoding(e Encoding) { m.encoding = e m.setEncoder() } -// SetBoundary sets the boundary of the Msg +// SetBoundary sets or overrides the currently set boundary of the Msg. +// +// Note that by default we create random MIME boundaries. This should only be used if a specific boundary is +// required. func (m *Msg) SetBoundary(b string) { m.boundary = b } -// SetMIMEVersion sets the MIME version of the Msg +// SetMIMEVersion sets or overrides the currently set MIME version of the Msg. +// +// Note that in the context of email, MIME Version 1.0 is the only officially standardized and supported +// version. While MIME has been updated and extended over time (via various RFCs), these updates and extensions +// do not introduce new MIME versions; they refine or add features within the framework of MIME 1.0. +// Therefore there should be no reason to ever use this MsgOption. +// +// https://datatracker.ietf.org/doc/html/rfc1521 +// +// https://datatracker.ietf.org/doc/html/rfc2045 +// +// https://datatracker.ietf.org/doc/html/rfc2049 func (m *Msg) SetMIMEVersion(mv MIMEVersion) { m.mimever = mv } -// SetPGPType sets the PGPType of the Msg +// SetPGPType sets or overrides the currently set PGP type for the Msg, determining the encryption or +// signature method. func (m *Msg) SetPGPType(t PGPType) { m.pgptype = t } -// Encoding returns the currently set encoding of the Msg +// Encoding returns the currently set Encoding of the Msg as string. func (m *Msg) Encoding() string { return m.encoding.String() } -// Charset returns the currently set charset of the Msg +// Charset returns the currently set Charset of the Msg as string. func (m *Msg) Charset() string { return m.charset.String() } -// SetHeader sets a generic header field of the Msg -// For adding address headers like "To:" or "From", see SetAddrHeader +// SetHeader sets a generic header field of the Msg. // -// Deprecated: This method only exists for compatibility reason. Please use SetGenHeader instead +// Deprecated: This method only exists for compatibility reason. Please use SetGenHeader instead. +// For adding address headers like "To:" or "From", use SetAddrHeader instead. func (m *Msg) SetHeader(header Header, values ...string) { m.SetGenHeader(header, values...) } -// SetGenHeader sets a generic header field of the Msg -// For adding address headers like "To:" or "From", see SetAddrHeader +// SetGenHeader sets a generic header field of the Msg to the provided list of values. +// +// Note: for adding email address related headers (like "To:" or "From") use SetAddrHeader instead. func (m *Msg) SetGenHeader(header Header, values ...string) { if m.genHeader == nil { m.genHeader = make(map[Header][]string) @@ -295,26 +313,24 @@ func (m *Msg) SetGenHeader(header Header, values ...string) { m.genHeader[header] = values } -// SetHeaderPreformatted sets a generic header field of the Msg which content is -// already preformated. +// SetHeaderPreformatted sets a generic header field of the Msg, which content is already preformatted. // -// Deprecated: This method only exists for compatibility reason. Please use -// SetGenHeaderPreformatted instead +// Deprecated: This method only exists for compatibility reason. Please use SetGenHeaderPreformatted instead. func (m *Msg) SetHeaderPreformatted(header Header, value string) { m.SetGenHeaderPreformatted(header, value) } -// SetGenHeaderPreformatted sets a generic header field of the Msg which content is -// already preformated. +// SetGenHeaderPreformatted sets a generic header field of the Msg which content is already preformated. // -// This method does not take a slice of values but only a single value. This is -// due to the fact, that we do not perform any content alteration and expect the -// user has already done so +// This method does not take a slice of values but only a single value. The reason for this is that we do not +// perform any content alteration on these kind of headers and expect the user to have already taken care of +// any kind of formatting required for the header. // -// **Please note:** This method should be used only as a last resort. Since the -// user is respondible for the formating of the message header, go-mail cannot -// guarantee the fully compliance with the RFC 2822. It is recommended to use -// SetGenHeader instead. +// Note: This method should be used only as a last resort. Since the user is respondible for the formatting of +// the message header, we cannot guarantee any compliance with the RFC 2822. It is advised to use SetGenHeader +// instead. +// +// https://datatracker.ietf.org/doc/html/rfc2822 func (m *Msg) SetGenHeaderPreformatted(header Header, value string) { if m.preformHeader == nil { m.preformHeader = make(map[Header]string) From 1dcdad9da141e91ca8557f1480abb59e39d14006 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 13:56:47 +0200 Subject: [PATCH 38/60] Enhance address header methods with detailed documentation Updated comments in `header.go` and `msg.go` to provide more detailed explanations and references to RFC 5322. This improves clarity on how headers are set and utilized, and the conditions under which they operate. --- header.go | 7 +++++-- msg.go | 26 +++++++++++++++++++++----- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/header.go b/header.go index d96271a..2b25cd2 100644 --- a/header.go +++ b/header.go @@ -105,8 +105,11 @@ const ( // HeaderCc is the "Carbon Copy" header field. HeaderCc AddrHeader = "Cc" - // HeaderEnvelopeFrom is the envelope FROM header field. It is not included in the mail body but only used by - // the Client for the envelope. + // HeaderEnvelopeFrom is the envelope FROM header field. + // + // It is generally not included in the mail body but only used by the Client for the communication with the + // SMTP server. If the Msg has no "FROM" address set in the mail body, the msgWriter will try to use the + // envelope from address, if this has been set for the Msg. HeaderEnvelopeFrom AddrHeader = "EnvelopeFrom" // HeaderFrom is the "From" header field. diff --git a/msg.go b/msg.go index 995e93f..7e15192 100644 --- a/msg.go +++ b/msg.go @@ -338,7 +338,13 @@ func (m *Msg) SetGenHeaderPreformatted(header Header, value string) { m.preformHeader[header] = value } -// SetAddrHeader sets an address related header field of the Msg +// SetAddrHeader sets the specified AddrHeader for the Msg to the given values. +// +// Addresses are parsed according to RFC 5322. If parsing of ANY of the provided values fails, +// and error is returned. If you cannot guarantee that all provided values are valid, you can +// use SetAddrHeaderIgnoreInvalid instead, which will skip any parsing error silently. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.4 func (m *Msg) SetAddrHeader(header AddrHeader, values ...string) error { if m.addrHeader == nil { m.addrHeader = make(map[AddrHeader][]*mail.Address) @@ -362,8 +368,12 @@ func (m *Msg) SetAddrHeader(header AddrHeader, values ...string) error { return nil } -// SetAddrHeaderIgnoreInvalid sets an address related header field of the Msg and ignores invalid address -// in the validation process +// SetAddrHeaderIgnoreInvalid sets the specified AddrHeader for the Msg to the given values. +// +// Addresses are parsed according to RFC 5322. If parsing of ANY of the provided values fails, +// the error is ignored and the address omiitted from the address list. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.4 func (m *Msg) SetAddrHeaderIgnoreInvalid(header AddrHeader, values ...string) { var addresses []*mail.Address for _, addrVal := range values { @@ -376,8 +386,14 @@ func (m *Msg) SetAddrHeaderIgnoreInvalid(header AddrHeader, values ...string) { m.addrHeader[header] = addresses } -// EnvelopeFrom takes and validates a given mail address and sets it as envelope "FROM" -// addrHeader of the Msg +// EnvelopeFrom sets the envelope from address for the Msg. +// +// The HeaderEnvelopeFrom address is generally not included in the mail body but only used by the Client for the +// communication with the SMTP server. If the Msg has no "FROM" address set in the mail body, the msgWriter will +// try to use the envelope from address, if this has been set for the Msg. The provided address is validated +// according to RFC 5322 and will return an error if the validation fails. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.4 func (m *Msg) EnvelopeFrom(from string) error { return m.SetAddrHeader(HeaderEnvelopeFrom, from) } From 3d5435c138ec4a8e98c1e916f50c01ee33cd26e6 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 13:59:24 +0200 Subject: [PATCH 39/60] Update EnvelopeFromFormat documentation in msg.go Expanded the documentation for the EnvelopeFromFormat method to clarify its purpose, usage, and compliance with RFC 5322. Added details on how the envelope from address is used in SMTP communication and validation requirements. --- msg.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/msg.go b/msg.go index 7e15192..1eb90f2 100644 --- a/msg.go +++ b/msg.go @@ -398,8 +398,14 @@ func (m *Msg) EnvelopeFrom(from string) error { return m.SetAddrHeader(HeaderEnvelopeFrom, from) } -// EnvelopeFromFormat takes a name and address, formats them RFC5322 compliant and stores them as -// the envelope FROM address header field +// EnvelopeFromFormat sets the provided name and mail address as HeaderEnvelopeFrom for the Msg. +// +// The HeaderEnvelopeFrom address is generally not included in the mail body but only used by the Client for the +// communication with the SMTP server. If the Msg has no "FROM" address set in the mail body, the msgWriter will +// try to use the envelope from address, if this has been set for the Msg. The provided name and address adre +// validated according to RFC 5322 and will return an error if the validation fails. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.4 func (m *Msg) EnvelopeFromFormat(name, addr string) error { return m.SetAddrHeader(HeaderEnvelopeFrom, fmt.Sprintf(`"%s" <%s>`, name, addr)) } From c520925457fdd6be78d4ba848cd123277b0dbd84 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 19:11:16 +0200 Subject: [PATCH 40/60] Enhance documentation for email address methods Detailed doc comments have been added to various methods handling "From", "To", "CC", "BCC", and "Reply-To" email addresses within the Msg class. The new comments follow RFC 5322 standards and provide explicit descriptions of the functionality and validation rules for each method. This improves code readability and maintainability. Additionally, moved the `addAddr` function to a more appropriate position within the file. --- msg.go | 214 ++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 174 insertions(+), 40 deletions(-) diff --git a/msg.go b/msg.go index 1eb90f2..7e391ba 100644 --- a/msg.go +++ b/msg.go @@ -410,102 +410,229 @@ func (m *Msg) EnvelopeFromFormat(name, addr string) error { return m.SetAddrHeader(HeaderEnvelopeFrom, fmt.Sprintf(`"%s" <%s>`, name, addr)) } -// From takes and validates a given mail address and sets it as "From" genHeader of the Msg +// From sets the "FROM" address in the mail body for the Msg. +// +// The "FROM" address is included in the mail body and indicates the sender of the message to the recipient. +// This address is visible in the email client and is typically displayed to the recipient. If the "FROM" address +// is not set, the msgWriter may attempt to use the envelope from address (if available) for sending. The provided +// address is validated according to RFC 5322 and will return an error if the validation fails. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2 func (m *Msg) From(from string) error { return m.SetAddrHeader(HeaderFrom, from) } -// FromFormat takes a name and address, formats them RFC5322 compliant and stores them as -// the From address header field +// FromFormat sets the provided name and mail address as the "FROM" address in the mail body for the Msg. +// +// The "FROM" address is included in the mail body and indicates the sender of the message to the recipient, +// and is visible in the email client. If the "FROM" address is not explicitly set, the msgWriter may use +// the envelope from address (if provided) when sending the message. The provided name and address are +// validated according to RFC 5322 and will return an error if the validation fails. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2 func (m *Msg) FromFormat(name, addr string) error { return m.SetAddrHeader(HeaderFrom, fmt.Sprintf(`"%s" <%s>`, name, addr)) } -// To takes and validates a given mail address list sets the To: addresses of the Msg +// To sets one or more "TO" addresses in the mail body for the Msg. +// +// The "TO" address specifies the primary recipient(s) of the message and is included in the mail body. +// This address is visible to the recipient and any other recipients of the message. Multiple "TO" addresses +// can be set by passing them as variadic arguments to this method. Each provided address is validated +// according to RFC 5322, and an error will be returned if ANY validation fails. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) To(rcpts ...string) error { return m.SetAddrHeader(HeaderTo, rcpts...) } -// AddTo adds an additional address to the To address header field +// AddTo adds a single "TO" address to the existing list of recipients in the mail body for the Msg. +// +// This method allows you to add a single recipient to the "TO" field without replacing any previously set +// "TO" addresses. The "TO" address specifies the primary recipient(s) of the message and is visible in the mail +// client. The provided address is validated according to RFC 5322, and an error will be returned if the +// validation fails. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) AddTo(rcpt string) error { return m.addAddr(HeaderTo, rcpt) } -// AddToFormat takes a name and address, formats them RFC5322 compliant and stores them as -// as additional To address header field +// AddToFormat adds a single "TO" address with the provided name and email to the existing list of recipients +// in the mail body for the Msg. +// +// This method allows you to add a recipient's name and email address to the "TO" field without replacing any +// previously set "TO" addresses. The "TO" address specifies the primary recipient(s) of the message and is +// visible in the mail client. The provided name and address are validated according to RFC 5322, and an error +// will be returned if the validation fails. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) AddToFormat(name, addr string) error { return m.addAddr(HeaderTo, fmt.Sprintf(`"%s" <%s>`, name, addr)) } -// ToIgnoreInvalid takes and validates a given mail address list sets the To: addresses of the Msg -// Any provided address that is not RFC5322 compliant, will be ignored +// ToIgnoreInvalid sets one or more "TO" addresses in the mail body for the Msg, ignoring any invalid addresses. +// +// This method allows you to add multiple "TO" recipients to the message body. Unlike the standard `To` method, +// any invalid addresses are ignored, and no error is returned for those addresses. Valid addresses will still be +// included in the "TO" field, which is visible in the recipient's mail client. Use this method with caution if +// address validation is critical. Invalid addresses are determined according to RFC 5322. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) ToIgnoreInvalid(rcpts ...string) { m.SetAddrHeaderIgnoreInvalid(HeaderTo, rcpts...) } -// ToFromString takes and validates a given string of comma separted -// mail address and sets them as To: addresses of the Msg +// ToFromString takes a string of comma-separated email addresses, validates each, and sets them as the +// "TO" addresses for the Msg. +// +// This method allows you to pass a single string containing multiple email addresses separated by commas. +// Each address is validated according to RFC 5322 and set as a recipient in the "TO" field. If any validation +// fails, an error will be returned. The addresses are visible in the mail body and displayed to recipients in +// the mail client. Any "TO" address applied previously will be overwritten. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) ToFromString(rcpts string) error { return m.To(strings.Split(rcpts, ",")...) } -// Cc takes and validates a given mail address list sets the Cc: addresses of the Msg +// Cc sets one or more "CC" (carbon copy) addresses in the mail body for the Msg. +// +// The "CC" address specifies secondary recipient(s) of the message, and is included in the mail body. +// These addresses are visible to all recipients, including those listed in the "TO" and other "CC" fields. +// Multiple "CC" addresses can be set by passing them as variadic arguments to this method. Each provided +// address is validated according to RFC 5322, and an error will be returned if ANY validation fails. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) Cc(rcpts ...string) error { return m.SetAddrHeader(HeaderCc, rcpts...) } -// AddCc adds an additional address to the Cc address header field +// AddCc adds a single "CC" (carbon copy) address to the existing list of "CC" recipients in the mail body +// for the Msg. +// +// This method allows you to add a single recipient to the "CC" field without replacing any previously set "CC" +// addresses. The "CC" address specifies secondary recipient(s) and is visible to all recipients, including those +// in the "TO" field. The provided address is validated according to RFC 5322, and an error will be returned if +// the validation fails. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) AddCc(rcpt string) error { return m.addAddr(HeaderCc, rcpt) } -// AddCcFormat takes a name and address, formats them RFC5322 compliant and stores them as -// as additional Cc address header field +// AddCcFormat adds a single "CC" (carbon copy) address with the provided name and email to the existing list +// of "CC" recipients in the mail body for the Msg. +// +// This method allows you to add a recipient's name and email address to the "CC" field without replacing any +// previously set "CC" addresses. The "CC" address specifies secondary recipient(s) and is visible to all +// recipients, including those in the "TO" field. The provided name and address are validated according to +// RFC 5322, and an error will be returned if the validation fails. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) AddCcFormat(name, addr string) error { return m.addAddr(HeaderCc, fmt.Sprintf(`"%s" <%s>`, name, addr)) } -// CcIgnoreInvalid takes and validates a given mail address list sets the Cc: addresses of the Msg -// Any provided address that is not RFC5322 compliant, will be ignored +// CcIgnoreInvalid sets one or more "CC" (carbon copy) addresses in the mail body for the Msg, ignoring any +// invalid addresses. +// +// This method allows you to add multiple "CC" recipients to the message body. Unlike the standard `Cc` method, +// any invalid addresses are ignored, and no error is returned for those addresses. Valid addresses will still +// be included in the "CC" field, which is visible to all recipients in the mail client. Use this method with +// caution if address validation is critical, as invalid addresses are determined according to RFC 5322. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) CcIgnoreInvalid(rcpts ...string) { m.SetAddrHeaderIgnoreInvalid(HeaderCc, rcpts...) } -// CcFromString takes and validates a given string of comma separted -// mail address and sets them as Cc: addresses of the Msg +// CcFromString takes a string of comma-separated email addresses, validates each, and sets them as the "CC" +// addresses for the Msg. +// +// This method allows you to pass a single string containing multiple email addresses separated by commas. +// Each address is validated according to RFC 5322 and set as a recipient in the "CC" field. If any validation +// fails, an error will be returned. The addresses are visible in the mail body and displayed to recipients +// in the mail client. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) CcFromString(rcpts string) error { return m.Cc(strings.Split(rcpts, ",")...) } -// Bcc takes and validates a given mail address list sets the Bcc: addresses of the Msg +// Bcc sets one or more "BCC" (blind carbon copy) addresses in the mail body for the Msg. +// +// The "BCC" address specifies recipient(s) of the message who will receive a copy without other recipients +// being aware of it. These addresses are not visible in the mail body or to any other recipients, ensuring +// the privacy of BCC'd recipients. Multiple "BCC" addresses can be set by passing them as variadic arguments +// to this method. Each provided address is validated according to RFC 5322, and an error will be returned +// if ANY validation fails. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) Bcc(rcpts ...string) error { return m.SetAddrHeader(HeaderBcc, rcpts...) } -// AddBcc adds an additional address to the Bcc address header field +// AddBcc adds a single "BCC" (blind carbon copy) address to the existing list of "BCC" recipients in the mail +// body for the Msg. +// +// This method allows you to add a single recipient to the "BCC" field without replacing any previously set +// "BCC" addresses. The "BCC" address specifies recipient(s) of the message who will receive a copy without other +// recipients being aware of it. The provided address is validated according to RFC 5322, and an error will be +// returned if the validation fails. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) AddBcc(rcpt string) error { return m.addAddr(HeaderBcc, rcpt) } -// AddBccFormat takes a name and address, formats them RFC5322 compliant and stores them as -// as additional Bcc address header field +// AddBccFormat adds a single "BCC" (blind carbon copy) address with the provided name and email to the existing +// list of "BCC" recipients in the mail body for the Msg. +// +// This method allows you to add a recipient's name and email address to the "BCC" field without replacing +// any previously set "BCC" addresses. The "BCC" address specifies recipient(s) of the message who will receive +// a copy without other recipients being aware of it. The provided name and address are validated according to +// RFC 5322, and an error will be returned if the validation fails. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) AddBccFormat(name, addr string) error { return m.addAddr(HeaderBcc, fmt.Sprintf(`"%s" <%s>`, name, addr)) } -// BccIgnoreInvalid takes and validates a given mail address list sets the Bcc: addresses of the Msg -// Any provided address that is not RFC5322 compliant, will be ignored +// BccIgnoreInvalid sets one or more "BCC" (blind carbon copy) addresses in the mail body for the Msg, +// ignoring any invalid addresses. +// +// This method allows you to add multiple "BCC" recipients to the message body. Unlike the standard `Bcc` +// method, any invalid addresses are ignored, and no error is returned for those addresses. Valid addresses +// will still be included in the "BCC" field, which ensures the privacy of the BCC'd recipients. Use this method +// with caution if address validation is critical, as invalid addresses are determined according to RFC 5322. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) BccIgnoreInvalid(rcpts ...string) { m.SetAddrHeaderIgnoreInvalid(HeaderBcc, rcpts...) } -// BccFromString takes and validates a given string of comma separted -// mail address and sets them as Bcc: addresses of the Msg +// BccFromString takes a string of comma-separated email addresses, validates each, and sets them as the "BCC" +// addresses for the Msg. +// +// This method allows you to pass a single string containing multiple email addresses separated by commas. +// Each address is validated according to RFC 5322 and set as a recipient in the "BCC" field. If any validation +// fails, an error will be returned. The addresses are not visible in the mail body and ensure the privacy of +// BCC'd recipients. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) BccFromString(rcpts string) error { return m.Bcc(strings.Split(rcpts, ",")...) } -// ReplyTo takes and validates a given mail address and sets it as "Reply-To" addrHeader of the Msg +// ReplyTo sets the "Reply-To" address for the Msg, specifying where replies should be sent. +// +// This method takes a single email address as input and attempts to parse it. If the address is valid, it sets +// the "Reply-To" header in the message. The "Reply-To" address can be different from the "From" address, +// allowing the sender to specify an alternate address for responses. If the provided address cannot be parsed, +// an error will be returned, indicating the parsing failure. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2 func (m *Msg) ReplyTo(addr string) error { replyTo, err := mail.ParseAddress(addr) if err != nil { @@ -515,22 +642,19 @@ func (m *Msg) ReplyTo(addr string) error { return nil } -// ReplyToFormat takes a name and address, formats them RFC5322 compliant and stores them as -// the Reply-To header field +// ReplyToFormat sets the "Reply-To" address for the Msg using the provided name and email address, specifying +// where replies should be sent. +// +// This method formats the name and email address into a single "Reply-To" header. If the formatted address is valid, +// it sets the "Reply-To" header in the message. This allows the sender to specify a display name along with the +// reply address, providing clarity for recipients. If the constructed address cannot be parsed, an error will +// be returned, indicating the parsing failure. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2 func (m *Msg) ReplyToFormat(name, addr string) error { return m.ReplyTo(fmt.Sprintf(`"%s" <%s>`, name, addr)) } -// addAddr adds an additional address to the given addrHeader of the Msg -func (m *Msg) addAddr(header AddrHeader, addr string) error { - var addresses []string - for _, address := range m.addrHeader[header] { - addresses = append(addresses, address.String()) - } - addresses = append(addresses, addr) - return m.SetAddrHeader(header, addresses...) -} - // Subject sets the "Subject" header field of the Msg func (m *Msg) Subject(subj string) { m.SetGenHeader(HeaderSubject, subj) @@ -1230,6 +1354,16 @@ func (m *Msg) SendError() error { return m.sendError } +// addAddr adds an additional address to the given addrHeader of the Msg +func (m *Msg) addAddr(header AddrHeader, addr string) error { + var addresses []string + for _, address := range m.addrHeader[header] { + addresses = append(addresses, address.String()) + } + addresses = append(addresses, addr) + return m.SetAddrHeader(header, addresses...) +} + // encodeString encodes a string based on the configured message encoder and the corresponding // charset for the Msg func (m *Msg) encodeString(str string) string { From b37f8995dab89d1f5aa5ba7c6b0cac855f873cb4 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 19:34:37 +0200 Subject: [PATCH 41/60] Update Msg documentation for clarity and RFC compliance Enhanced the documentation for several Msg methods to provide clearer explanations and include relevant RFC references. This includes improved descriptions of functionality, parameter details, return values, and links to pertinent RFC sections. --- msg.go | 210 ++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 179 insertions(+), 31 deletions(-) diff --git a/msg.go b/msg.go index 7e391ba..5d29233 100644 --- a/msg.go +++ b/msg.go @@ -655,12 +655,26 @@ func (m *Msg) ReplyToFormat(name, addr string) error { return m.ReplyTo(fmt.Sprintf(`"%s" <%s>`, name, addr)) } -// Subject sets the "Subject" header field of the Msg +// Subject sets the "Subject" header for the Msg, specifying the topic of the message. +// +// This method takes a single string as input and sets it as the "Subject" of the email. The subject line provides +// a brief summary of the content of the message, allowing recipients to quickly understand its purpose. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.5 func (m *Msg) Subject(subj string) { m.SetGenHeader(HeaderSubject, subj) } -// SetMessageID generates a random message id for the mail +// SetMessageID generates and sets a unique "Message-ID" header for the Msg. +// +// This method creates a "Message-ID" string using the current process ID, random numbers, and the hostname +// of the machine. The generated ID helps uniquely identify the message in email systems, facilitating tracking +// and preventing duplication. If the hostname cannot be retrieved, it defaults to "localhost.localdomain". +// +// The generated Message-ID follows the format +// "". +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.4 func (m *Msg) SetMessageID() { hostname, err := os.Hostname() if err != nil { @@ -675,8 +689,14 @@ func (m *Msg) SetMessageID() { m.SetMessageIDWithValue(messageID) } -// GetMessageID returns the message ID of the Msg as string value. If no message ID -// is set, an empty string will be returned +// GetMessageID retrieves the "Message-ID" header from the Msg. +// +// This method checks if a "Message-ID" has been set in the message's generated headers. If a valid "Message-ID" +// exists in the Msg, it returns the first occurrence of the header. If the "Message-ID" has not been set or +// is empty, it returns an empty string. This allows other components to access the unique identifier for the +// message, which is useful for tracking and referencing in email systems. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.4 func (m *Msg) GetMessageID() string { if msgidheader, ok := m.genHeader[HeaderMessageID]; ok { if len(msgidheader) > 0 { @@ -686,32 +706,67 @@ func (m *Msg) GetMessageID() string { return "" } -// SetMessageIDWithValue sets the message id for the mail +// SetMessageIDWithValue sets the "Message-ID" header for the Msg using the provided messageID string. +// +// This method formats the input messageID by enclosing it in angle brackets ("<>") and sets it as the "Message-ID" +// header in the message. The "Message-ID" is a unique identifier for the email, helping email clients and servers +// to track and reference the message. There are no validations performed on the input messageID, so it should +// be in a suitable format for use as a Message-ID. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.4 func (m *Msg) SetMessageIDWithValue(messageID string) { m.SetGenHeader(HeaderMessageID, fmt.Sprintf("<%s>", messageID)) } -// SetBulk sets the "Precedence: bulk" and "X-Auto-Response-Suppress: All" genHeaders which are -// recommended for automated mails like OOO replies -// See: https://www.rfc-editor.org/rfc/rfc2076#section-3.9 -// See also: https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcmail/ced68690-498a-4567-9d14-5c01f974d8b1#Appendix_A_Target_51 +// SetBulk sets the "Precedence: bulk" and "X-Auto-Response-Suppress: All" headers for the Msg, +// which are recommended for automated emails such as out-of-office replies. +// +// The "Precedence: bulk" header indicates that the message is a bulk email, and the "X-Auto-Response-Suppress: All" +// header instructs mail servers and clients to suppress automatic responses to this message. +// This is particularly useful for reducing unnecessary replies to automated notifications or replies. +// For further details, refer to RFC 2076, Section 3.9, and Microsoft's documentation on +// handling automated emails. +// +// https://www.rfc-editor.org/rfc/rfc2076#section-3.9 +// +// https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcmail/ced68690-498a-4567-9d14-5c01f974d8b1#Appendix_A_Target_51 func (m *Msg) SetBulk() { m.SetGenHeader(HeaderPrecedence, "bulk") m.SetGenHeader(HeaderXAutoResponseSuppress, "All") } -// SetDate sets the Date genHeader field to the current time in a valid format +// SetDate sets the "Date" header for the Msg to the current time in a valid RFC 1123 format. +// +// This method retrieves the current time and formats it according to RFC 1123, ensuring that the "Date" +// header is compliant with email standards. The "Date" header indicates when the message was created, +// providing recipients with context for the timing of the email. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.3 func (m *Msg) SetDate() { now := time.Now().Format(time.RFC1123Z) m.SetGenHeader(HeaderDate, now) } -// SetDateWithValue sets the Date genHeader field to the provided time in a valid format +// SetDateWithValue sets the "Date" header for the Msg using the provided time value in a valid RFC 1123 format. +// +// This method takes a `time.Time` value as input and formats it according to RFC 1123, ensuring that the "Date" +// header is compliant with email standards. The "Date" header indicates when the message was created, +// providing recipients with context for the timing of the email. This allows for setting a custom date +// rather than using the current time. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.3 func (m *Msg) SetDateWithValue(timeVal time.Time) { m.SetGenHeader(HeaderDate, timeVal.Format(time.RFC1123Z)) } -// SetImportance sets the Msg Importance/Priority header to given Importance +// SetImportance sets the "Importance" and "Priority" headers for the Msg to the specified Importance level. +// +// This method adjusts the email's importance based on the provided Importance value. If the importance level +// is set to `ImportanceNormal`, no headers are modified. Otherwise, it sets the "Importance", "Priority", +// "X-Priority", and "X-MSMail-Priority" headers accordingly, providing email clients with information on +// how to prioritize the message. This allows the sender to indicate the significance of the email to recipients. +// +// https://datatracker.ietf.org/doc/html/rfc2156 func (m *Msg) SetImportance(importance Importance) { if importance == ImportanceNormal { return @@ -722,26 +777,48 @@ func (m *Msg) SetImportance(importance Importance) { m.SetGenHeader(HeaderXMSMailPriority, importance.NumString()) } -// SetOrganization sets the provided string as Organization header for the Msg +// SetOrganization sets the "Organization" header for the Msg to the specified organization string. +// +// This method allows you to specify the organization associated with the email sender. The "Organization" +// header provides recipients with information about the organization that is sending the message. +// This can help establish context and credibility for the email communication. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.4 func (m *Msg) SetOrganization(org string) { m.SetGenHeader(HeaderOrganization, org) } -// SetUserAgent sets the User-Agent/X-Mailer header for the Msg +// SetUserAgent sets the "User-Agent" and "X-Mailer" headers for the Msg to the specified user agent string. +// +// This method allows you to specify the user agent or mailer software used to send the email. +// The "User-Agent" and "X-Mailer" headers provide recipients with information about the email client +// or application that generated the message. This can be useful for identifying the source of the email, +// particularly for troubleshooting or filtering purposes. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.7 func (m *Msg) SetUserAgent(userAgent string) { m.SetGenHeader(HeaderUserAgent, userAgent) m.SetGenHeader(HeaderXMailer, userAgent) } -// IsDelivered will return true if the Msg has been successfully delivered +// IsDelivered indicates whether the Msg has been delivered. +// +// This method checks the internal state of the message to determine if it has been successfully +// delivered. It returns true if the message is marked as delivered and false otherwise. +// This can be useful for tracking the status of the email communication. func (m *Msg) IsDelivered() bool { return m.isDelivered } -// RequestMDNTo adds the Disposition-Notification-To header to request a MDN from the receiving end -// as described in RFC8098. It allows to provide a list recipient addresses. -// Address validation is performed -// See: https://www.rfc-editor.org/rfc/rfc8098.html +// RequestMDNTo adds the "Disposition-Notification-To" header to the Msg to request a Message Disposition +// Notification (MDN) from the receiving end, as specified in RFC 8098. +// +// This method allows you to provide a list of recipient addresses to receive the MDN. +// Each address is validated according to RFC 5322 standards. If ANY address is invalid, an error +// will be returned indicating the parsing failure. If the "Disposition-Notification-To" header +// is already set, it will be updated with the new list of addresses. +// +// https://datatracker.ietf.org/doc/html/rfc8098 func (m *Msg) RequestMDNTo(rcpts ...string) error { var addresses []string for _, addrVal := range rcpts { @@ -757,15 +834,26 @@ func (m *Msg) RequestMDNTo(rcpts ...string) error { return nil } -// RequestMDNToFormat adds the Disposition-Notification-To header to request a MDN from the receiving end -// as described in RFC8098. It allows to provide a recipient address with name and address and will format -// accordingly. Address validation is performed -// See: https://www.rfc-editor.org/rfc/rfc8098.html +// RequestMDNToFormat adds the "Disposition-Notification-To" header to the Msg to request a Message Disposition +// Notification (MDN) from the receiving end, as specified in RFC 8098. +// +// This method allows you to provide a recipient address along with a name, formatting it appropriately. +// Address validation is performed according to RFC 5322 standards. If the provided address is invalid, +// an error will be returned. This method internally calls RequestMDNTo to handle the actual setting of the header. +// +// https://datatracker.ietf.org/doc/html/rfc8098 func (m *Msg) RequestMDNToFormat(name, addr string) error { return m.RequestMDNTo(fmt.Sprintf(`%s <%s>`, name, addr)) } -// RequestMDNAddTo adds an additional recipient to the recipient list of the MDN +// RequestMDNAddTo adds an additional recipient to the "Disposition-Notification-To" header for the Msg. +// +// This method allows you to append a new recipient address to the existing list of recipients for the +// MDN. The provided address is validated according to RFC 5322 standards. If the address is invalid, +// an error will be returned indicating the parsing failure. If the "Disposition-Notification-To" +// header is already set, the new recipient will be added to the existing list. +// +// https://datatracker.ietf.org/doc/html/rfc8098 func (m *Msg) RequestMDNAddTo(rcpt string) error { address, err := mail.ParseAddress(rcpt) if err != nil { @@ -780,14 +868,35 @@ func (m *Msg) RequestMDNAddTo(rcpt string) error { return nil } -// RequestMDNAddToFormat adds an additional formated recipient to the recipient list of the MDN +// RequestMDNAddToFormat adds an additional formatted recipient to the "Disposition-Notification-To" +// header for the Msg. +// +// This method allows you to specify a recipient address along with a name, formatting it appropriately +// before adding it to the existing list of recipients for the MDN. The formatted address is validated +// according to RFC 5322 standards. If the provided address is invalid, an error will be returned. +// This method internally calls RequestMDNAddTo to handle the actual addition of the recipient. +// +// https://datatracker.ietf.org/doc/html/rfc8098 func (m *Msg) RequestMDNAddToFormat(name, addr string) error { return m.RequestMDNAddTo(fmt.Sprintf(`"%s" <%s>`, name, addr)) } -// GetSender returns the currently set envelope FROM address. If no envelope FROM is set it will use -// the first mail body FROM address. If useFullAddr is true, it will return the full address string -// including the address name, if set +// GetSender returns the currently set envelope "FROM" address for the Msg. If no envelope +// "FROM" address is set, it will use the first "FROM" address from the mail body. If the +// useFullAddr parameter is true, it will return the full address string, including the name +// if it is set. +// +// If neither the envelope "FROM" nor the body "FROM" addresses are available, it will return +// an error indicating that no "FROM" address is present. +// +// Parameters: +// - useFullAddr: A boolean indicating whether to return the full address string (including +// the name) or just the email address. +// +// Returns: +// - The sender's address as a string and an error if applicable. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2 func (m *Msg) GetSender(useFullAddr bool) (string, error) { from, ok := m.addrHeader[HeaderEnvelopeFrom] if !ok || len(from) == 0 { @@ -802,7 +911,18 @@ func (m *Msg) GetSender(useFullAddr bool) (string, error) { return from[0].Address, nil } -// GetRecipients returns a list of the currently set TO/CC/BCC addresses. +// GetRecipients returns a list of the currently set "TO", "CC", and "BCC" addresses for the Msg. +// +// This method aggregates recipients from the "TO", "CC", and "BCC" headers and returns them as a +// slice of strings. If no recipients are found in these headers, it will return an error indicating +// that no recipient addresses are present. +// +// Returns: +// - A slice of strings containing the recipients' addresses and an error if applicable. +// - If there are no recipient addresses set, it will return an error indicating no recipient +// addresses are available. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) GetRecipients() ([]string, error) { var rcpts []string for _, addressType := range []AddrHeader{HeaderTo, HeaderCc, HeaderBcc} { @@ -820,12 +940,40 @@ func (m *Msg) GetRecipients() ([]string, error) { return rcpts, nil } -// GetAddrHeader returns the content of the requested address header of the Msg +// GetAddrHeader returns the content of the requested address header for the Msg. +// +// This method retrieves the addresses associated with the specified address header. It returns a +// slice of pointers to mail.Address structures representing the addresses found in the header. +// If the requested header does not exist or contains no addresses, it will return nil. +// +// Parameters: +// - header: The AddrHeader enum value indicating which address header to retrieve (e.g., "TO", +// "CC", "BCC", etc.). +// +// Returns: +// - A slice of pointers to mail.Address structures containing the addresses from the specified +// header. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6 func (m *Msg) GetAddrHeader(header AddrHeader) []*mail.Address { return m.addrHeader[header] } -// GetAddrHeaderString returns the address string of the requested address header of the Msg +// GetAddrHeaderString returns the address strings of the requested address header for the Msg. +// +// This method retrieves the addresses associated with the specified address header and returns them +// as a slice of strings. Each address is formatted as a string, which includes both the name (if +// available) and the email address. If the requested header does not exist or contains no addresses, +// it will return an empty slice. +// +// Parameters: +// - header: The AddrHeader enum value indicating which address header to retrieve (e.g., "TO", +// "CC", "BCC", etc.). +// +// Returns: +// - A slice of strings containing the formatted addresses from the specified header. +// +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6 func (m *Msg) GetAddrHeaderString(header AddrHeader) []string { var addresses []string for _, mh := range m.addrHeader[header] { From 4890d9130b1b7b674fe7ed723a2b49c8b6454c9d Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 20:06:51 +0200 Subject: [PATCH 42/60] Expand docstrings for MsgOption functions and methods. This commit enhances the docstrings for the MsgOption functions and related methods in msg.go, providing extensive explanations, parameters, and references. It helps users understand the functionality, usage, and context of each function, improving code readability and usability. --- msg.go | 415 +++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 318 insertions(+), 97 deletions(-) diff --git a/msg.go b/msg.go index 5d29233..af0e1a2 100644 --- a/msg.go +++ b/msg.go @@ -150,8 +150,22 @@ const SendmailPath = "/usr/sbin/sendmail" // MsgOption is a function type that modifies a Msg instance during its creation or initialization. type MsgOption func(*Msg) -// NewMsg creates a new email message with optional MsgOption functions that customize various aspects of the -// message. +// NewMsg creates a new email message with optional MsgOption functions that customize various aspects +// of the message. +// +// This function initializes a new Msg instance with default values for address headers, character set, +// encoding, general headers, and MIME version. It then applies any provided MsgOption functions to +// customize the message according to the user's needs. If an option is nil, it will be ignored. +// After applying the options, the function sets the appropriate MIME WordEncoder for the message. +// +// Parameters: +// - opts: A variadic list of MsgOption functions that can be used to customize the Msg instance. +// +// Returns: +// - A pointer to the newly created Msg instance. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5321 func NewMsg(opts ...MsgOption) *Msg { msg := &Msg{ addrHeader: make(map[AddrHeader][]*mail.Address), @@ -177,64 +191,140 @@ func NewMsg(opts ...MsgOption) *Msg { } // WithCharset sets the Charset type for a Msg during its creation or initialization. -func WithCharset(c Charset) MsgOption { +// +// This MsgOption function allows you to specify the character set to be used in the email message. +// The charset defines how the text in the message is encoded and interpreted by the email client. +// This option should be called when creating a new Msg instance to ensure that the desired charset +// is set correctly. +// +// Parameters: +// - charset: The Charset value that specifies the desired character set for the Msg. +// +// Returns: +// - A MsgOption function that can be used to customize the Msg instance. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2047#section-5 +func WithCharset(charset Charset) MsgOption { return func(m *Msg) { - m.charset = c + m.charset = charset } } // WithEncoding sets the Encoding type for a Msg during its creation or initialization. -func WithEncoding(e Encoding) MsgOption { +// +// This MsgOption function allows you to specify the encoding type to be used in the email message. +// The encoding defines how the message content is encoded, which affects how it is transmitted +// and decoded by email clients. This option should be called when creating a new Msg instance to +// ensure that the desired encoding is set correctly. +// +// Parameters: +// - encoding: The Encoding value that specifies the desired encoding type for the Msg. +// +// Returns: +// - A MsgOption function that can be used to customize the Msg instance. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2047#section-6 +func WithEncoding(encoding Encoding) MsgOption { return func(m *Msg) { - m.encoding = e + m.encoding = encoding } } // WithMIMEVersion sets the MIMEVersion type for a Msg during its creation or initialization. // -// Note that in the context of email, MIME Version 1.0 is the only officially standardized and supported -// version. While MIME has been updated and extended over time (via various RFCs), these updates and extensions -// do not introduce new MIME versions; they refine or add features within the framework of MIME 1.0. -// Therefore there should be no reason to ever use this MsgOption. -// https://datatracker.ietf.org/doc/html/rfc1521 +// Note that in the context of email, MIME Version 1.0 is the only officially standardized and +// supported version. While MIME has been updated and extended over time via various RFCs, these +// updates and extensions do not introduce new MIME versions; they refine or add features within +// the framework of MIME 1.0. Therefore, there should be no reason to ever use this MsgOption. // -// https://datatracker.ietf.org/doc/html/rfc2045 +// Parameters: +// - version: The MIMEVersion value that specifies the desired MIME version for the Msg. // -// https://datatracker.ietf.org/doc/html/rfc2049 -func WithMIMEVersion(mv MIMEVersion) MsgOption { - return func(m *Msg) { - m.mimever = mv - } -} - -// WithBoundary sets the boundary of a Msg to the provided string value during its creation or initialization. +// Returns: +// - A MsgOption function that can be used to customize the Msg instance. // -// Note that by default we create random MIME boundaries. This should only be used if a specific boundary is -// required. -func WithBoundary(b string) MsgOption { +// References: +// - https://datatracker.ietf.org/doc/html/rfc1521 +// - https://datatracker.ietf.org/doc/html/rfc2045 +// - https://datatracker.ietf.org/doc/html/rfc2049 +func WithMIMEVersion(version MIMEVersion) MsgOption { return func(m *Msg) { - m.boundary = b + m.mimever = version } } -// WithMiddleware adds the given Middleware to the end of the list of the Client middlewares slice. Middleware -// are processed in FIFO order. -func WithMiddleware(mw Middleware) MsgOption { - return func(m *Msg) { - m.middlewares = append(m.middlewares, mw) - } -} - -// WithPGPType sets the PGP type for the Msg during its creation or initialization, determining the encryption or -// signature method. -func WithPGPType(pt PGPType) MsgOption { - return func(m *Msg) { - m.pgptype = pt - } -} - -// WithNoDefaultUserAgent disables the inclusion of a default User-Agent header in the Msg during its creation or +// WithBoundary sets the boundary of a Msg to the provided string value during its creation or // initialization. +// +// Note that by default, random MIME boundaries are created. This option should only be used if +// a specific boundary is required for the email message. Using a predefined boundary can be +// helpful when constructing multipart messages with specific formatting or content separation. +// +// Parameters: +// - boundary: The string value that specifies the desired boundary for the Msg. +// +// Returns: +// - A MsgOption function that can be used to customize the Msg instance. +func WithBoundary(boundary string) MsgOption { + return func(m *Msg) { + m.boundary = boundary + } +} + +// WithMiddleware adds the given Middleware to the end of the list of the Client middlewares slice. +// Middleware are processed in FIFO order. +// +// This MsgOption function allows you to specify custom middleware that will be applied during the +// message handling process. Middleware can be used to modify the message, perform logging, or +// implement additional functionality as the message flows through the system. Each middleware +// is executed in the order it was added. +// +// Parameters: +// - middleware: The Middleware to be added to the list for processing. +// +// Returns: +// - A MsgOption function that can be used to customize the Msg instance. +func WithMiddleware(middleware Middleware) MsgOption { + return func(m *Msg) { + m.middlewares = append(m.middlewares, middleware) + } +} + +// WithPGPType sets the PGP type for the Msg during its creation or initialization, determining +// the encryption or signature method. +// +// This MsgOption function allows you to specify the PGP (Pretty Good Privacy) type to be used +// for securing the message. The chosen PGP type influences how the message is encrypted or +// signed, ensuring confidentiality and integrity of the content. This option should be called +// when creating a new Msg instance to set the desired PGP type appropriately. +// +// Parameters: +// - pgptype: The PGPType value that specifies the desired PGP type for the Msg. +// +// Returns: +// - A MsgOption function that can be used to customize the Msg instance. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc4880 +func WithPGPType(pgptype PGPType) MsgOption { + return func(m *Msg) { + m.pgptype = pgptype + } +} + +// WithNoDefaultUserAgent disables the inclusion of a default User-Agent header in the Msg during +// its creation or initialization. +// +// This MsgOption function allows you to customize the Msg instance by omitting the default +// User-Agent header, which is typically included to provide information about the software +// sending the email. This option can be useful when you want to have more control over the +// headers included in the message, such as when sending from a custom application or for +// privacy reasons. +// +// Returns: +// - A MsgOption function that can be used to customize the Msg instance. func WithNoDefaultUserAgent() MsgOption { return func(m *Msg) { m.noDefaultUserAgent = true @@ -242,67 +332,150 @@ func WithNoDefaultUserAgent() MsgOption { } // SetCharset sets or overrides the currently set encoding charset of the Msg. -func (m *Msg) SetCharset(c Charset) { - m.charset = c +// +// This method allows you to specify a character set for the email message. The charset is +// important for ensuring that the content of the message is correctly interpreted by +// mail clients. Common charset values include UTF-8, ISO-8859-1, and others. If a charset +// is not explicitly set, CharsetUTF8 is used as default. +// +// Parameters: +// - charset: The Charset value to set for the Msg, determining the encoding used for the message content. +func (m *Msg) SetCharset(charset Charset) { + m.charset = charset } // SetEncoding sets or overrides the currently set Encoding of the Msg. -func (m *Msg) SetEncoding(e Encoding) { - m.encoding = e +// +// This method allows you to specify the encoding type for the email message. The encoding +// determines how the message content is represented and can affect the size and compatibility +// of the email. Common encoding types include Base64 and Quoted-Printable. Setting a new +// encoding may also adjust how the message content is processed and transmitted. +// +// Parameters: +// - encoding: The Encoding value to set for the Msg, determining the method used to encode the +// message content. +func (m *Msg) SetEncoding(encoding Encoding) { + m.encoding = encoding m.setEncoder() } // SetBoundary sets or overrides the currently set boundary of the Msg. // -// Note that by default we create random MIME boundaries. This should only be used if a specific boundary is -// required. -func (m *Msg) SetBoundary(b string) { - m.boundary = b +// This method allows you to specify a custom boundary string for the MIME message. The +// boundary is used to separate different parts of the message, especially when dealing +// with multipart messages. By default, the Msg generates random MIME boundaries. This +// function should only be used if you have a specific boundary requirement for the +// message. Ensure that the boundary value does not conflict with any content within the +// message to avoid parsing errors. +// +// Parameters: +// - boundary: The string value representing the boundary to set for the Msg, used in +// multipart messages to delimit different sections. +func (m *Msg) SetBoundary(boundary string) { + m.boundary = boundary } // SetMIMEVersion sets or overrides the currently set MIME version of the Msg. // -// Note that in the context of email, MIME Version 1.0 is the only officially standardized and supported -// version. While MIME has been updated and extended over time (via various RFCs), these updates and extensions -// do not introduce new MIME versions; they refine or add features within the framework of MIME 1.0. -// Therefore there should be no reason to ever use this MsgOption. +// In the context of email, MIME Version 1.0 is the only officially standardized and +// supported version. Although MIME has been updated and extended over time through +// various RFCs, these updates do not introduce new MIME versions; they refine or add +// features within the framework of MIME 1.0. Therefore, there is generally no need to +// use this function to set a different MIME version. // -// https://datatracker.ietf.org/doc/html/rfc1521 +// Parameters: +// - version: The MIMEVersion value to set for the Msg, which determines the MIME +// version used in the email message. // -// https://datatracker.ietf.org/doc/html/rfc2045 -// -// https://datatracker.ietf.org/doc/html/rfc2049 -func (m *Msg) SetMIMEVersion(mv MIMEVersion) { - m.mimever = mv +// References: +// - https://datatracker.ietf.org/doc/html/rfc1521 +// - https://datatracker.ietf.org/doc/html/rfc2045 +// - https://datatracker.ietf.org/doc/html/rfc2049 +func (m *Msg) SetMIMEVersion(version MIMEVersion) { + m.mimever = version } -// SetPGPType sets or overrides the currently set PGP type for the Msg, determining the encryption or -// signature method. -func (m *Msg) SetPGPType(t PGPType) { - m.pgptype = t +// SetPGPType sets or overrides the currently set PGP type for the Msg, determining the +// encryption or signature method. +// +// This method allows you to specify the PGP type that will be used when encrypting or +// signing the message. Different PGP types correspond to various encryption and signing +// algorithms, and selecting the appropriate type is essential for ensuring the security +// and integrity of the message content. +// +// Parameters: +// - pgptype: The PGPType value to set for the Msg, which determines the encryption +// or signature method used for the email message. +func (m *Msg) SetPGPType(pgptype PGPType) { + m.pgptype = pgptype } -// Encoding returns the currently set Encoding of the Msg as string. +// Encoding returns the currently set Encoding of the Msg as a string. +// +// This method retrieves the encoding type that is currently applied to the message. The +// encoding type determines how the message content is encoded for transmission. Common +// encoding types include quoted-printable and base64, and the returned string will reflect +// the specific encoding method in use. +// +// Returns: +// - A string representation of the current Encoding of the Msg. func (m *Msg) Encoding() string { return m.encoding.String() } -// Charset returns the currently set Charset of the Msg as string. +// Charset returns the currently set Charset of the Msg as a string. +// +// This method retrieves the character set that is currently applied to the message. The +// charset defines the encoding for the text content of the message, ensuring that +// characters are displayed correctly across different email clients and platforms. The +// returned string will reflect the specific charset in use, such as UTF-8 or ISO-8859-1. +// +// Returns: +// - A string representation of the current Charset of the Msg. func (m *Msg) Charset() string { return m.charset.String() } // SetHeader sets a generic header field of the Msg. // -// Deprecated: This method only exists for compatibility reason. Please use SetGenHeader instead. -// For adding address headers like "To:" or "From", use SetAddrHeader instead. +// Deprecated: This method only exists for compatibility reasons. Please use SetGenHeader +// instead. For adding address headers like "To:" or "From", use SetAddrHeader instead. +// +// This method allows you to set a header field for the message, providing the header name +// and its corresponding values. However, it is recommended to utilize the newer methods +// for better clarity and functionality. Using SetGenHeader or SetAddrHeader is preferred +// for more specific header types, ensuring proper handling of the message headers. +// +// Parameters: +// - header: The header field to set in the Msg. +// - values: One or more string values to associate with the header field. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3 +// - https://datatracker.ietf.org/doc/html/rfc2047 func (m *Msg) SetHeader(header Header, values ...string) { m.SetGenHeader(header, values...) } // SetGenHeader sets a generic header field of the Msg to the provided list of values. // -// Note: for adding email address related headers (like "To:" or "From") use SetAddrHeader instead. +// This method is intended for setting generic headers in the email message. It takes a +// header name and a variadic list of string values, encoding them as necessary before +// storing them in the message's internal header map. +// +// Note: For adding email address-related headers (like "To:", "From", "Cc", etc.), +// use SetAddrHeader instead to ensure proper formatting and validation. +// +// Parameters: +// - header: The header field to set in the Msg. +// - values: One or more string values to associate with the header field. +// +// This method ensures that all values are appropriately encoded for email transmission, +// adhering to the necessary standards. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3 +// - https://datatracker.ietf.org/doc/html/rfc2047 func (m *Msg) SetGenHeader(header Header, values ...string) { if m.genHeader == nil { m.genHeader = make(map[Header][]string) @@ -315,22 +488,36 @@ func (m *Msg) SetGenHeader(header Header, values ...string) { // SetHeaderPreformatted sets a generic header field of the Msg, which content is already preformatted. // -// Deprecated: This method only exists for compatibility reason. Please use SetGenHeaderPreformatted instead. +// Deprecated: This method only exists for compatibility reasons. Please use +// SetGenHeaderPreformatted instead for setting preformatted generic header fields. +// +// Parameters: +// - header: The header field to set in the Msg. +// - value: The preformatted string value to associate with the header field. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3 +// - https://datatracker.ietf.org/doc/html/rfc2047 func (m *Msg) SetHeaderPreformatted(header Header, value string) { m.SetGenHeaderPreformatted(header, value) } -// SetGenHeaderPreformatted sets a generic header field of the Msg which content is already preformated. +// SetGenHeaderPreformatted sets a generic header field of the Msg which content is already preformatted. // // This method does not take a slice of values but only a single value. The reason for this is that we do not -// perform any content alteration on these kind of headers and expect the user to have already taken care of +// perform any content alteration on these kinds of headers and expect the user to have already taken care of // any kind of formatting required for the header. // -// Note: This method should be used only as a last resort. Since the user is respondible for the formatting of -// the message header, we cannot guarantee any compliance with the RFC 2822. It is advised to use SetGenHeader -// instead. +// Note: This method should be used only as a last resort. Since the user is responsible for the formatting of +// the message header, we cannot guarantee any compliance with RFC 2822. It is advised to use SetGenHeader +// instead for general header fields. // -// https://datatracker.ietf.org/doc/html/rfc2822 +// Parameters: +// - header: The header field to set in the Msg. +// - value: The preformatted string value to associate with the header field. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2822 func (m *Msg) SetGenHeaderPreformatted(header Header, value string) { if m.preformHeader == nil { m.preformHeader = make(map[Header]string) @@ -340,11 +527,21 @@ func (m *Msg) SetGenHeaderPreformatted(header Header, value string) { // SetAddrHeader sets the specified AddrHeader for the Msg to the given values. // -// Addresses are parsed according to RFC 5322. If parsing of ANY of the provided values fails, -// and error is returned. If you cannot guarantee that all provided values are valid, you can -// use SetAddrHeaderIgnoreInvalid instead, which will skip any parsing error silently. +// Addresses are parsed according to RFC 5322. If parsing any of the provided values fails, +// an error is returned. If you cannot guarantee that all provided values are valid, you can +// use SetAddrHeaderIgnoreInvalid instead, which will silently skip any parsing errors. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.4 +// This method allows you to set address-related headers for the message, ensuring that the +// provided addresses are properly formatted and parsed. Using this method helps maintain the +// integrity of the email addresses within the message. +// +// Parameters: +// - header: The AddrHeader to set in the Msg (e.g., "From", "To", "Cc", "Bcc"). +// - values: One or more string values representing the email addresses to associate with +// the specified header. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.4 func (m *Msg) SetAddrHeader(header AddrHeader, values ...string) error { if m.addrHeader == nil { m.addrHeader = make(map[AddrHeader][]*mail.Address) @@ -370,10 +567,19 @@ func (m *Msg) SetAddrHeader(header AddrHeader, values ...string) error { // SetAddrHeaderIgnoreInvalid sets the specified AddrHeader for the Msg to the given values. // -// Addresses are parsed according to RFC 5322. If parsing of ANY of the provided values fails, -// the error is ignored and the address omiitted from the address list. +// Addresses are parsed according to RFC 5322. If parsing of any of the provided values fails, +// the error is ignored and the address is omitted from the address list. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.4 +// This method allows for setting address headers while ignoring invalid addresses. It is useful +// in scenarios where you want to ensure that only valid addresses are included without halting +// execution due to parsing errors. +// +// Parameters: +// - header: The AddrHeader field to set in the Msg. +// - values: One or more string values representing email addresses. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.4 func (m *Msg) SetAddrHeaderIgnoreInvalid(header AddrHeader, values ...string) { var addresses []*mail.Address for _, addrVal := range values { @@ -388,36 +594,51 @@ func (m *Msg) SetAddrHeaderIgnoreInvalid(header AddrHeader, values ...string) { // EnvelopeFrom sets the envelope from address for the Msg. // -// The HeaderEnvelopeFrom address is generally not included in the mail body but only used by the Client for the -// communication with the SMTP server. If the Msg has no "FROM" address set in the mail body, the msgWriter will -// try to use the envelope from address, if this has been set for the Msg. The provided address is validated -// according to RFC 5322 and will return an error if the validation fails. +// The HeaderEnvelopeFrom address is generally not included in the mail body but only used by the +// Client for communication with the SMTP server. If the Msg has no "FROM" address set in the +// mail body, the msgWriter will try to use the envelope from address if it has been set for the Msg. +// The provided address is validated according to RFC 5322 and will return an error if the validation fails. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.4 +// Parameters: +// - from: The envelope from address to set in the Msg. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.4 func (m *Msg) EnvelopeFrom(from string) error { return m.SetAddrHeader(HeaderEnvelopeFrom, from) } // EnvelopeFromFormat sets the provided name and mail address as HeaderEnvelopeFrom for the Msg. // -// The HeaderEnvelopeFrom address is generally not included in the mail body but only used by the Client for the -// communication with the SMTP server. If the Msg has no "FROM" address set in the mail body, the msgWriter will -// try to use the envelope from address, if this has been set for the Msg. The provided name and address adre -// validated according to RFC 5322 and will return an error if the validation fails. +// The HeaderEnvelopeFrom address is generally not included in the mail body but only used by the +// Client for communication with the SMTP server. If the Msg has no "FROM" address set in the mail +// body, the msgWriter will try to use the envelope from address if it has been set for the Msg. +// The provided name and address are validated according to RFC 5322 and will return an error if +// the validation fails. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.4 +// Parameters: +// - name: The name to associate with the envelope from address. +// - addr: The mail address to set as the envelope from address. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.4 func (m *Msg) EnvelopeFromFormat(name, addr string) error { return m.SetAddrHeader(HeaderEnvelopeFrom, fmt.Sprintf(`"%s" <%s>`, name, addr)) } // From sets the "FROM" address in the mail body for the Msg. // -// The "FROM" address is included in the mail body and indicates the sender of the message to the recipient. -// This address is visible in the email client and is typically displayed to the recipient. If the "FROM" address -// is not set, the msgWriter may attempt to use the envelope from address (if available) for sending. The provided -// address is validated according to RFC 5322 and will return an error if the validation fails. +// The "FROM" address is included in the mail body and indicates the sender of the message to +// the recipient. This address is visible in the email client and is typically displayed to the +// recipient. If the "FROM" address is not set, the msgWriter may attempt to use the envelope +// from address (if available) for sending. The provided address is validated according to RFC +// 5322 and will return an error if the validation fails. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2 +// Parameters: +// - from: The "FROM" address to set in the mail body. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2 func (m *Msg) From(from string) error { return m.SetAddrHeader(HeaderFrom, from) } From 864c5932083f2712d653d8655622b23fc9bcf388 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sat, 5 Oct 2024 20:29:33 +0200 Subject: [PATCH 43/60] Add parameters and references to method comments in msg.go Enhanced method documentation by adding parameter descriptions and reference links. This improves the clarity and usability of the code, ensuring that users understand the purpose and usage of each method. --- msg.go | 153 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 120 insertions(+), 33 deletions(-) diff --git a/msg.go b/msg.go index af0e1a2..2f1a614 100644 --- a/msg.go +++ b/msg.go @@ -645,12 +645,18 @@ func (m *Msg) From(from string) error { // FromFormat sets the provided name and mail address as the "FROM" address in the mail body for the Msg. // -// The "FROM" address is included in the mail body and indicates the sender of the message to the recipient, -// and is visible in the email client. If the "FROM" address is not explicitly set, the msgWriter may use -// the envelope from address (if provided) when sending the message. The provided name and address are -// validated according to RFC 5322 and will return an error if the validation fails. +// The "FROM" address is included in the mail body and indicates the sender of the message to +// the recipient, and is visible in the email client. If the "FROM" address is not explicitly +// set, the msgWriter may use the envelope from address (if provided) when sending the message. +// The provided name and address are validated according to RFC 5322 and will return an error +// if the validation fails. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2 +// Parameters: +// - name: The name of the sender to include in the "FROM" address. +// - addr: The email address of the sender to include in the "FROM" address. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2 func (m *Msg) FromFormat(name, addr string) error { return m.SetAddrHeader(HeaderFrom, fmt.Sprintf(`"%s" <%s>`, name, addr)) } @@ -662,7 +668,11 @@ func (m *Msg) FromFormat(name, addr string) error { // can be set by passing them as variadic arguments to this method. Each provided address is validated // according to RFC 5322, and an error will be returned if ANY validation fails. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 +// Parameters: +// - rcpts: One or more recipient email addresses to include in the "TO" field. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) To(rcpts ...string) error { return m.SetAddrHeader(HeaderTo, rcpts...) } @@ -674,7 +684,11 @@ func (m *Msg) To(rcpts ...string) error { // client. The provided address is validated according to RFC 5322, and an error will be returned if the // validation fails. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 +// Parameters: +// - rcpt: The recipient email address to add to the "TO" field. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) AddTo(rcpt string) error { return m.addAddr(HeaderTo, rcpt) } @@ -687,7 +701,12 @@ func (m *Msg) AddTo(rcpt string) error { // visible in the mail client. The provided name and address are validated according to RFC 5322, and an error // will be returned if the validation fails. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 +// Parameters: +// - name: The name of the recipient to add to the "TO" field. +// - addr: The email address of the recipient to add to the "TO" field. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) AddToFormat(name, addr string) error { return m.addAddr(HeaderTo, fmt.Sprintf(`"%s" <%s>`, name, addr)) } @@ -699,7 +718,11 @@ func (m *Msg) AddToFormat(name, addr string) error { // included in the "TO" field, which is visible in the recipient's mail client. Use this method with caution if // address validation is critical. Invalid addresses are determined according to RFC 5322. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 +// Parameters: +// - rcpts: One or more recipient addresses to add to the "TO" field. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) ToIgnoreInvalid(rcpts ...string) { m.SetAddrHeaderIgnoreInvalid(HeaderTo, rcpts...) } @@ -712,7 +735,11 @@ func (m *Msg) ToIgnoreInvalid(rcpts ...string) { // fails, an error will be returned. The addresses are visible in the mail body and displayed to recipients in // the mail client. Any "TO" address applied previously will be overwritten. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 +// Parameters: +// - rcpts: A string containing multiple recipient addresses separated by commas. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) ToFromString(rcpts string) error { return m.To(strings.Split(rcpts, ",")...) } @@ -724,7 +751,11 @@ func (m *Msg) ToFromString(rcpts string) error { // Multiple "CC" addresses can be set by passing them as variadic arguments to this method. Each provided // address is validated according to RFC 5322, and an error will be returned if ANY validation fails. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 +// Parameters: +// - rcpts: One or more recipient addresses to be included in the "CC" field. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) Cc(rcpts ...string) error { return m.SetAddrHeader(HeaderCc, rcpts...) } @@ -737,7 +768,11 @@ func (m *Msg) Cc(rcpts ...string) error { // in the "TO" field. The provided address is validated according to RFC 5322, and an error will be returned if // the validation fails. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 +// Parameters: +// - rcpt: The recipient address to be added to the "CC" field. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) AddCc(rcpt string) error { return m.addAddr(HeaderCc, rcpt) } @@ -750,7 +785,12 @@ func (m *Msg) AddCc(rcpt string) error { // recipients, including those in the "TO" field. The provided name and address are validated according to // RFC 5322, and an error will be returned if the validation fails. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 +// Parameters: +// - name: The name of the recipient to be added to the "CC" field. +// - addr: The email address of the recipient to be added to the "CC" field. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) AddCcFormat(name, addr string) error { return m.addAddr(HeaderCc, fmt.Sprintf(`"%s" <%s>`, name, addr)) } @@ -763,7 +803,11 @@ func (m *Msg) AddCcFormat(name, addr string) error { // be included in the "CC" field, which is visible to all recipients in the mail client. Use this method with // caution if address validation is critical, as invalid addresses are determined according to RFC 5322. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 +// Parameters: +// - rcpts: One or more recipient email addresses to be added to the "CC" field. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) CcIgnoreInvalid(rcpts ...string) { m.SetAddrHeaderIgnoreInvalid(HeaderCc, rcpts...) } @@ -776,7 +820,11 @@ func (m *Msg) CcIgnoreInvalid(rcpts ...string) { // fails, an error will be returned. The addresses are visible in the mail body and displayed to recipients // in the mail client. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 +// Parameters: +// - rcpts: A string containing multiple email addresses separated by commas. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) CcFromString(rcpts string) error { return m.Cc(strings.Split(rcpts, ",")...) } @@ -789,7 +837,11 @@ func (m *Msg) CcFromString(rcpts string) error { // to this method. Each provided address is validated according to RFC 5322, and an error will be returned // if ANY validation fails. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 +// Parameters: +// - rcpts: One or more string values representing the BCC addresses to set in the Msg. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) Bcc(rcpts ...string) error { return m.SetAddrHeader(HeaderBcc, rcpts...) } @@ -802,7 +854,11 @@ func (m *Msg) Bcc(rcpts ...string) error { // recipients being aware of it. The provided address is validated according to RFC 5322, and an error will be // returned if the validation fails. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 +// Parameters: +// - rcpt: The BCC address to add to the existing list of recipients in the Msg. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) AddBcc(rcpt string) error { return m.addAddr(HeaderBcc, rcpt) } @@ -815,7 +871,12 @@ func (m *Msg) AddBcc(rcpt string) error { // a copy without other recipients being aware of it. The provided name and address are validated according to // RFC 5322, and an error will be returned if the validation fails. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 +// Parameters: +// - name: The name of the recipient to add to the BCC field. +// - addr: The email address of the recipient to add to the BCC field. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) AddBccFormat(name, addr string) error { return m.addAddr(HeaderBcc, fmt.Sprintf(`"%s" <%s>`, name, addr)) } @@ -828,7 +889,11 @@ func (m *Msg) AddBccFormat(name, addr string) error { // will still be included in the "BCC" field, which ensures the privacy of the BCC'd recipients. Use this method // with caution if address validation is critical, as invalid addresses are determined according to RFC 5322. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 +// Parameters: +// - rcpts: One or more string values representing the BCC email addresses to set. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) BccIgnoreInvalid(rcpts ...string) { m.SetAddrHeaderIgnoreInvalid(HeaderBcc, rcpts...) } @@ -841,7 +906,11 @@ func (m *Msg) BccIgnoreInvalid(rcpts ...string) { // fails, an error will be returned. The addresses are not visible in the mail body and ensure the privacy of // BCC'd recipients. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 +// Parameters: +// - rcpts: A string of comma-separated email addresses to set as BCC recipients. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) BccFromString(rcpts string) error { return m.Bcc(strings.Split(rcpts, ",")...) } @@ -853,7 +922,11 @@ func (m *Msg) BccFromString(rcpts string) error { // allowing the sender to specify an alternate address for responses. If the provided address cannot be parsed, // an error will be returned, indicating the parsing failure. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2 +// Parameters: +// - addr: The email address to set as the "Reply-To" address. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2 func (m *Msg) ReplyTo(addr string) error { replyTo, err := mail.ParseAddress(addr) if err != nil { @@ -871,7 +944,12 @@ func (m *Msg) ReplyTo(addr string) error { // reply address, providing clarity for recipients. If the constructed address cannot be parsed, an error will // be returned, indicating the parsing failure. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2 +// Parameters: +// - name: The display name associated with the reply address. +// - addr: The email address to set as the "Reply-To" address. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2 func (m *Msg) ReplyToFormat(name, addr string) error { return m.ReplyTo(fmt.Sprintf(`"%s" <%s>`, name, addr)) } @@ -881,7 +959,11 @@ func (m *Msg) ReplyToFormat(name, addr string) error { // This method takes a single string as input and sets it as the "Subject" of the email. The subject line provides // a brief summary of the content of the message, allowing recipients to quickly understand its purpose. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.5 +// Parameters: +// - subj: The subject line of the email. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.5 func (m *Msg) Subject(subj string) { m.SetGenHeader(HeaderSubject, subj) } @@ -895,7 +977,8 @@ func (m *Msg) Subject(subj string) { // The generated Message-ID follows the format // "". // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.4 +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.4 func (m *Msg) SetMessageID() { hostname, err := os.Hostname() if err != nil { @@ -917,7 +1000,8 @@ func (m *Msg) SetMessageID() { // is empty, it returns an empty string. This allows other components to access the unique identifier for the // message, which is useful for tracking and referencing in email systems. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.4 +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.4 func (m *Msg) GetMessageID() string { if msgidheader, ok := m.genHeader[HeaderMessageID]; ok { if len(msgidheader) > 0 { @@ -934,7 +1018,8 @@ func (m *Msg) GetMessageID() string { // to track and reference the message. There are no validations performed on the input messageID, so it should // be in a suitable format for use as a Message-ID. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.4 +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.4 func (m *Msg) SetMessageIDWithValue(messageID string) { m.SetGenHeader(HeaderMessageID, fmt.Sprintf("<%s>", messageID)) } @@ -945,12 +1030,10 @@ func (m *Msg) SetMessageIDWithValue(messageID string) { // The "Precedence: bulk" header indicates that the message is a bulk email, and the "X-Auto-Response-Suppress: All" // header instructs mail servers and clients to suppress automatic responses to this message. // This is particularly useful for reducing unnecessary replies to automated notifications or replies. -// For further details, refer to RFC 2076, Section 3.9, and Microsoft's documentation on -// handling automated emails. // -// https://www.rfc-editor.org/rfc/rfc2076#section-3.9 -// -// https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcmail/ced68690-498a-4567-9d14-5c01f974d8b1#Appendix_A_Target_51 +// References: +// - https://www.rfc-editor.org/rfc/rfc2076#section-3.9 +// - https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcmail/ced68690-498a-4567-9d14-5c01f974d8b1#Appendix_A_Target_51 func (m *Msg) SetBulk() { m.SetGenHeader(HeaderPrecedence, "bulk") m.SetGenHeader(HeaderXAutoResponseSuppress, "All") @@ -962,7 +1045,9 @@ func (m *Msg) SetBulk() { // header is compliant with email standards. The "Date" header indicates when the message was created, // providing recipients with context for the timing of the email. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.3 +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.3 +// - https://datatracker.ietf.org/doc/html/rfc1123 func (m *Msg) SetDate() { now := time.Now().Format(time.RFC1123Z) m.SetGenHeader(HeaderDate, now) @@ -975,7 +1060,9 @@ func (m *Msg) SetDate() { // providing recipients with context for the timing of the email. This allows for setting a custom date // rather than using the current time. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.3 +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.3 +// - https://datatracker.ietf.org/doc/html/rfc1123 func (m *Msg) SetDateWithValue(timeVal time.Time) { m.SetGenHeader(HeaderDate, timeVal.Format(time.RFC1123Z)) } From eafb9cb17ef8c8035ae73e43139c51d13edf10ae Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 6 Oct 2024 00:25:15 +0200 Subject: [PATCH 44/60] Add detailed parameter and return descriptions to msg.go Enhanced function documentation by adding detailed descriptions of parameters and return values for various methods. This clarifies expected inputs and outputs, improving code readability and maintainability. --- msg.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/msg.go b/msg.go index 2f1a614..9da94b8 100644 --- a/msg.go +++ b/msg.go @@ -48,9 +48,11 @@ const ( // NoPGP indicates that a message should not be treated as PGP encrypted or signed and is the default value // for a message NoPGP PGPType = iota + // PGPEncrypt indicates that a message should be treated as PGP encrypted. This works closely together with // the corresponding go-mail-middleware. PGPEncrypt + // PGPSignature indicates that a message should be treated as PGP signed. This works closely together with // the corresponding go-mail-middleware. PGPSignature @@ -1018,6 +1020,9 @@ func (m *Msg) GetMessageID() string { // to track and reference the message. There are no validations performed on the input messageID, so it should // be in a suitable format for use as a Message-ID. // +// Parameters: +// - messageID: The string to set as the "Message-ID" in the message header. +// // References: // - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.4 func (m *Msg) SetMessageIDWithValue(messageID string) { @@ -1060,6 +1065,9 @@ func (m *Msg) SetDate() { // providing recipients with context for the timing of the email. This allows for setting a custom date // rather than using the current time. // +// Parameters: +// - timeVal: The time value used to set the "Date" header. +// // References: // - https://datatracker.ietf.org/doc/html/rfc5322#section-3.3 // - https://datatracker.ietf.org/doc/html/rfc1123 @@ -1074,7 +1082,11 @@ func (m *Msg) SetDateWithValue(timeVal time.Time) { // "X-Priority", and "X-MSMail-Priority" headers accordingly, providing email clients with information on // how to prioritize the message. This allows the sender to indicate the significance of the email to recipients. // -// https://datatracker.ietf.org/doc/html/rfc2156 +// Parameters: +// - importance: The Importance value that determines the priority of the email message. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2156 func (m *Msg) SetImportance(importance Importance) { if importance == ImportanceNormal { return @@ -1091,7 +1103,11 @@ func (m *Msg) SetImportance(importance Importance) { // header provides recipients with information about the organization that is sending the message. // This can help establish context and credibility for the email communication. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.4 +// Parameters: +// - org: The name of the organization to be set in the "Organization" header. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.4 func (m *Msg) SetOrganization(org string) { m.SetGenHeader(HeaderOrganization, org) } @@ -1103,7 +1119,11 @@ func (m *Msg) SetOrganization(org string) { // or application that generated the message. This can be useful for identifying the source of the email, // particularly for troubleshooting or filtering purposes. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.7 +// Parameters: +// - userAgent: The user agent or mailer software to be set in the "User-Agent" and "X-Mailer" headers. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.7 func (m *Msg) SetUserAgent(userAgent string) { m.SetGenHeader(HeaderUserAgent, userAgent) m.SetGenHeader(HeaderXMailer, userAgent) @@ -1114,6 +1134,9 @@ func (m *Msg) SetUserAgent(userAgent string) { // This method checks the internal state of the message to determine if it has been successfully // delivered. It returns true if the message is marked as delivered and false otherwise. // This can be useful for tracking the status of the email communication. +// +// Returns: +// - A boolean value indicating the delivery status of the message (true if delivered, false otherwise). func (m *Msg) IsDelivered() bool { return m.isDelivered } @@ -1126,7 +1149,11 @@ func (m *Msg) IsDelivered() bool { // will be returned indicating the parsing failure. If the "Disposition-Notification-To" header // is already set, it will be updated with the new list of addresses. // -// https://datatracker.ietf.org/doc/html/rfc8098 +// Parameters: +// - rcpts: One or more recipient email addresses to request the MDN from. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc8098 func (m *Msg) RequestMDNTo(rcpts ...string) error { var addresses []string for _, addrVal := range rcpts { @@ -1149,7 +1176,12 @@ func (m *Msg) RequestMDNTo(rcpts ...string) error { // Address validation is performed according to RFC 5322 standards. If the provided address is invalid, // an error will be returned. This method internally calls RequestMDNTo to handle the actual setting of the header. // -// https://datatracker.ietf.org/doc/html/rfc8098 +// Parameters: +// - name: The name of the recipient for the MDN request. +// - addr: The email address of the recipient for the MDN request. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc8098 func (m *Msg) RequestMDNToFormat(name, addr string) error { return m.RequestMDNTo(fmt.Sprintf(`%s <%s>`, name, addr)) } @@ -1161,7 +1193,11 @@ func (m *Msg) RequestMDNToFormat(name, addr string) error { // an error will be returned indicating the parsing failure. If the "Disposition-Notification-To" // header is already set, the new recipient will be added to the existing list. // -// https://datatracker.ietf.org/doc/html/rfc8098 +// Parameters: +// - rcpt: The recipient email address to add to the "Disposition-Notification-To" header. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc8098 func (m *Msg) RequestMDNAddTo(rcpt string) error { address, err := mail.ParseAddress(rcpt) if err != nil { @@ -1184,7 +1220,12 @@ func (m *Msg) RequestMDNAddTo(rcpt string) error { // according to RFC 5322 standards. If the provided address is invalid, an error will be returned. // This method internally calls RequestMDNAddTo to handle the actual addition of the recipient. // -// https://datatracker.ietf.org/doc/html/rfc8098 +// Parameters: +// - name: The name of the recipient to add to the "Disposition-Notification-To" header. +// - addr: The email address of the recipient to add to the "Disposition-Notification-To" header. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc8098 func (m *Msg) RequestMDNAddToFormat(name, addr string) error { return m.RequestMDNAddTo(fmt.Sprintf(`"%s" <%s>`, name, addr)) } @@ -1204,7 +1245,8 @@ func (m *Msg) RequestMDNAddToFormat(name, addr string) error { // Returns: // - The sender's address as a string and an error if applicable. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2 +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2 func (m *Msg) GetSender(useFullAddr bool) (string, error) { from, ok := m.addrHeader[HeaderEnvelopeFrom] if !ok || len(from) == 0 { @@ -1230,7 +1272,8 @@ func (m *Msg) GetSender(useFullAddr bool) (string, error) { // - If there are no recipient addresses set, it will return an error indicating no recipient // addresses are available. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) GetRecipients() ([]string, error) { var rcpts []string for _, addressType := range []AddrHeader{HeaderTo, HeaderCc, HeaderBcc} { @@ -1262,7 +1305,8 @@ func (m *Msg) GetRecipients() ([]string, error) { // - A slice of pointers to mail.Address structures containing the addresses from the specified // header. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6 +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6 func (m *Msg) GetAddrHeader(header AddrHeader) []*mail.Address { return m.addrHeader[header] } @@ -1281,7 +1325,8 @@ func (m *Msg) GetAddrHeader(header AddrHeader) []*mail.Address { // Returns: // - A slice of strings containing the formatted addresses from the specified header. // -// https://datatracker.ietf.org/doc/html/rfc5322#section-3.6 +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6 func (m *Msg) GetAddrHeaderString(header AddrHeader) []string { var addresses []string for _, mh := range m.addrHeader[header] { From 01278ccb30ae247ca7e6914eee70b66d3460d917 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 6 Oct 2024 00:49:09 +0200 Subject: [PATCH 45/60] Document message methods Added detailed comments to various methods in `msg.go` for clarity. Each method now includes a declaration, a detailed description, returned values, parameters, and relevant RFC references. This enhances code readability and maintainability. --- msg.go | 250 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 229 insertions(+), 21 deletions(-) diff --git a/msg.go b/msg.go index 9da94b8..7dac30d 100644 --- a/msg.go +++ b/msg.go @@ -1335,67 +1335,179 @@ func (m *Msg) GetAddrHeaderString(header AddrHeader) []string { return addresses } -// GetFrom returns the content of the From address header of the Msg +// GetFrom returns the content of the "From" address header of the Msg. +// +// This method retrieves the list of email addresses set in the "From" header of the message. +// It returns a slice of pointers to `mail.Address` objects representing the sender(s) of the email. +// +// Returns: +// - A slice of `*mail.Address` containing the "From" header addresses. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2 func (m *Msg) GetFrom() []*mail.Address { return m.GetAddrHeader(HeaderFrom) } -// GetFromString returns the content of the From address header of the Msg as string slice +// GetFromString returns the content of the "From" address header of the Msg as a string slice. +// +// This method retrieves the list of email addresses set in the "From" header of the message +// and returns them as a slice of strings, with each entry representing a formatted email address. +// +// Returns: +// - A slice of strings containing the "From" header addresses. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2 func (m *Msg) GetFromString() []string { return m.GetAddrHeaderString(HeaderFrom) } -// GetTo returns the content of the To address header of the Msg +// GetTo returns the content of the "To" address header of the Msg. +// +// This method retrieves the list of email addresses set in the "To" header of the message. +// It returns a slice of pointers to `mail.Address` objects representing the primary recipient(s) of the email. +// +// Returns: +// - A slice of `*mail.Address` containing the "To" header addresses. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) GetTo() []*mail.Address { return m.GetAddrHeader(HeaderTo) } -// GetToString returns the content of the To address header of the Msg as string slice +// GetToString returns the content of the "To" address header of the Msg as a string slice. +// +// This method retrieves the list of email addresses set in the "To" header of the message +// and returns them as a slice of strings, with each entry representing a formatted email address. +// +// Returns: +// - A slice of strings containing the "To" header addresses. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) GetToString() []string { return m.GetAddrHeaderString(HeaderTo) } -// GetCc returns the content of the Cc address header of the Msg +// GetCc returns the content of the "Cc" address header of the Msg. +// +// This method retrieves the list of email addresses set in the "Cc" (carbon copy) header of the message. +// It returns a slice of pointers to `mail.Address` objects representing the secondary recipient(s) of the email. +// +// Returns: +// - A slice of `*mail.Address` containing the "Cc" header addresses. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) GetCc() []*mail.Address { return m.GetAddrHeader(HeaderCc) } -// GetCcString returns the content of the Cc address header of the Msg as string slice +// GetCcString returns the content of the "Cc" address header of the Msg as a string slice. +// +// This method retrieves the list of email addresses set in the "Cc" (carbon copy) header of the message +// and returns them as a slice of strings, with each entry representing a formatted email address. +// +// Returns: +// - A slice of strings containing the "Cc" header addresses. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) GetCcString() []string { return m.GetAddrHeaderString(HeaderCc) } -// GetBcc returns the content of the Bcc address header of the Msg +// GetBcc returns the content of the "Bcc" address header of the Msg. +// +// This method retrieves the list of email addresses set in the "Bcc" (blind carbon copy) header of the message. +// It returns a slice of pointers to `mail.Address` objects representing the Bcc recipient(s) of the email. +// +// Returns: +// - A slice of `*mail.Address` containing the "Bcc" header addresses. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) GetBcc() []*mail.Address { return m.GetAddrHeader(HeaderBcc) } -// GetBccString returns the content of the Bcc address header of the Msg as string slice +// GetBccString returns the content of the "Bcc" address header of the Msg as a string slice. +// +// This method retrieves the list of email addresses set in the "Bcc" (blind carbon copy) header of the message +// and returns them as a slice of strings, with each entry representing a formatted email address. +// +// Returns: +// - A slice of strings containing the "Bcc" header addresses. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3 func (m *Msg) GetBccString() []string { return m.GetAddrHeaderString(HeaderBcc) } -// GetGenHeader returns the content of the requested generic header of the Msg +// GetGenHeader returns the content of the requested generic header of the Msg. +// +// This method retrieves the list of string values associated with the specified generic header of the message. +// It returns a slice of strings representing the header's values. +// +// Parameters: +// - header: The Header field whose values are being retrieved. +// +// Returns: +// - A slice of strings containing the values of the specified generic header. func (m *Msg) GetGenHeader(header Header) []string { return m.genHeader[header] } -// GetParts returns the message parts of the Msg +// GetParts returns the message parts of the Msg. +// +// This method retrieves the list of parts that make up the email message. Each part may represent +// a different section of the email, such as a plain text body, HTML body, or attachments. +// +// Returns: +// - A slice of Part pointers representing the message parts of the email. func (m *Msg) GetParts() []*Part { return m.parts } -// GetAttachments returns the attachments of the Msg +// GetAttachments returns the attachments of the Msg. +// +// This method retrieves the list of files that have been attached to the email message. +// Each attachment includes details about the file, such as its name, content type, and data. +// +// Returns: +// - A slice of File pointers representing the attachments of the email. func (m *Msg) GetAttachments() []*File { return m.attachments } -// GetBoundary returns the boundary of the Msg +// GetBoundary returns the boundary of the Msg. +// +// This method retrieves the MIME boundary that is used to separate different parts of the message, +// particularly in multipart emails. The boundary helps to differentiate between various sections +// such as plain text, HTML content, and attachments. +// +// Returns: +// - A string representing the boundary of the message. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2046#section-5.1.1 func (m *Msg) GetBoundary() string { return m.boundary } // SetAttachments sets the attachments of the message. +// +// This method allows you to specify the attachments for the message by providing a slice of File pointers. +// Each file represents an attachment that will be included in the email. +// +// Parameters: +// - files: A slice of pointers to File structures representing the attachments to set for the message. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) SetAttachments(files []*File) { m.attachments = files } @@ -1407,33 +1519,83 @@ func (m *Msg) SetAttachements(files []*File) { m.SetAttachments(files) } -// UnsetAllAttachments unset the attachments of the message. +// UnsetAllAttachments unsets the attachments of the message. +// +// This method removes all attachments from the message by setting the attachments to nil, effectively +// clearing any previously set attachments. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) UnsetAllAttachments() { m.attachments = nil } -// GetEmbeds returns the embeds of the Msg +// GetEmbeds returns the embedded files of the Msg. +// +// This method retrieves the list of files that have been embedded in the message. Embeds are typically +// images or other media files that are referenced directly in the content of the email, such as inline +// images in HTML emails. +// +// Returns: +// - A slice of pointers to File structures representing the embedded files in the message. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) GetEmbeds() []*File { return m.embeds } -// SetEmbeds sets the embeds of the message. +// SetEmbeds sets the embedded files of the message. +// +// This method allows you to specify the files to be embedded in the message by providing a slice of File pointers. +// Embedded files, such as images or media, are typically used for inline content in HTML emails. +// +// Parameters: +// - files: A slice of pointers to File structures representing the embedded files to set for the message. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) SetEmbeds(files []*File) { m.embeds = files } -// UnsetAllEmbeds unset the embeds of the message. +// UnsetAllEmbeds unsets the embedded files of the message. +// +// This method removes all embedded files from the message by setting the embeds to nil, effectively +// clearing any previously set embedded files. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) UnsetAllEmbeds() { m.embeds = nil } -// UnsetAllParts unset the embeds and attachments of the message. +// UnsetAllParts unsets the embeds and attachments of the message. +// +// This method removes all embedded files and attachments from the message by unsetting both the +// embeds and attachments, effectively clearing all previously set message parts. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) UnsetAllParts() { m.UnsetAllAttachments() m.UnsetAllEmbeds() } // SetBodyString sets the body of the message. +// +// This method sets the body of the message using the provided content type and string content. The body can +// be set as plain text, HTML, or other formats based on the specified content type. Optional part settings +// can be passed through PartOption to customize the message body further. +// +// Parameters: +// - contentType: The ContentType of the body (e.g., plain text, HTML). +// - content: The string content to set as the body of the message. +// - opts: Optional parameters for customizing the body part. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2045 +// - https://datatracker.ietf.org/doc/html/rfc2046 func (m *Msg) SetBodyString(contentType ContentType, content string, opts ...PartOption) { buffer := bytes.NewBufferString(content) writeFunc := writeFuncFromBuffer(buffer) @@ -1441,6 +1603,20 @@ func (m *Msg) SetBodyString(contentType ContentType, content string, opts ...Par } // SetBodyWriter sets the body of the message. +// +// This method sets the body of the message using a write function, allowing content to be written +// directly to the body. The content type determines the format (e.g., plain text, HTML). +// Optional part settings can be provided via PartOption to customize the body further. +// +// Parameters: +// - contentType: The ContentType of the body (e.g., plain text, HTML). +// - writeFunc: A function that writes content to an io.Writer and returns the number of bytes written +// and an error, if any. +// - opts: Optional parameters for customizing the body part. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2045 +// - https://datatracker.ietf.org/doc/html/rfc2046 func (m *Msg) SetBodyWriter( contentType ContentType, writeFunc func(io.Writer) (int64, error), opts ...PartOption, @@ -1450,8 +1626,24 @@ func (m *Msg) SetBodyWriter( m.parts = []*Part{p} } -// SetBodyHTMLTemplate sets the body of the message from a given html/template.Template pointer -// The content type will be set to text/html automatically +// SetBodyHTMLTemplate sets the body of the message from a given html/template.Template pointer. +// +// This method sets the body of the message using the provided HTML template and data. The content type +// will be set to "text/html" automatically. The method executes the template with the provided data +// and writes the output to the message body. If the template is nil or fails to execute, an error will +// be returned. +// +// Parameters: +// - tpl: A pointer to the html/template.Template to be used for the message body. +// - data: The data to populate the template. +// - opts: Optional parameters for customizing the body part. +// +// Returns: +// - An error if the template is nil or fails to execute, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2045 +// - https://datatracker.ietf.org/doc/html/rfc2046 func (m *Msg) SetBodyHTMLTemplate(tpl *ht.Template, data interface{}, opts ...PartOption) error { if tpl == nil { return errors.New(errTplPointerNil) @@ -1465,8 +1657,24 @@ func (m *Msg) SetBodyHTMLTemplate(tpl *ht.Template, data interface{}, opts ...Pa return nil } -// SetBodyTextTemplate sets the body of the message from a given text/template.Template pointer -// The content type will be set to text/plain automatically +// SetBodyTextTemplate sets the body of the message from a given text/template.Template pointer. +// +// This method sets the body of the message using the provided text template and data. The content type +// will be set to "text/plain" automatically. The method executes the template with the provided data +// and writes the output to the message body. If the template is nil or fails to execute, an error will +// be returned. +// +// Parameters: +// - tpl: A pointer to the text/template.Template to be used for the message body. +// - data: The data to populate the template. +// - opts: Optional parameters for customizing the body part. +// +// Returns: +// - An error if the template is nil or fails to execute, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2045 +// - https://datatracker.ietf.org/doc/html/rfc2046 func (m *Msg) SetBodyTextTemplate(tpl *tt.Template, data interface{}, opts ...PartOption) error { if tpl == nil { return errors.New(errTplPointerNil) From b4197a136e930f86882f4d47337b08c3e452f27a Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 6 Oct 2024 12:04:27 +0200 Subject: [PATCH 46/60] Enhance documentation for message methods Expanded docstrings for methods in msg.go to provide detailed explanations, parameters, and return values. This improves clarity and helps developers understand usage and behavior more effectively. --- msg.go | 768 ++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 681 insertions(+), 87 deletions(-) diff --git a/msg.go b/msg.go index 7dac30d..fc0bb57 100644 --- a/msg.go +++ b/msg.go @@ -1689,13 +1689,40 @@ func (m *Msg) SetBodyTextTemplate(tpl *tt.Template, data interface{}, opts ...Pa } // AddAlternativeString sets the alternative body of the message. +// +// This method adds an alternative representation of the message body using the specified content type +// and string content. This is typically used to provide both plain text and HTML versions of the email. +// Optional part settings can be provided via PartOption to further customize the message. +// +// Parameters: +// - contentType: The content type of the alternative body (e.g., plain text, HTML). +// - content: The string content to set as the alternative body. +// - opts: Optional parameters for customizing the alternative body part. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2045 +// - https://datatracker.ietf.org/doc/html/rfc2046 func (m *Msg) AddAlternativeString(contentType ContentType, content string, opts ...PartOption) { buffer := bytes.NewBufferString(content) writeFunc := writeFuncFromBuffer(buffer) m.AddAlternativeWriter(contentType, writeFunc, opts...) } -// AddAlternativeWriter sets the body of the message. +// AddAlternativeWriter sets the alternative body of the message. +// +// This method adds an alternative representation of the message body using a write function, allowing +// content to be written directly to the body. This is typically used to provide different formats, such +// as plain text and HTML. Optional part settings can be provided via PartOption to customize the message part. +// +// Parameters: +// - contentType: The content type of the alternative body (e.g., plain text, HTML). +// - writeFunc: A function that writes content to an io.Writer and returns the number of bytes written and +// an error, if any. +// - opts: Optional parameters for customizing the alternative body part. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2045 +// - https://datatracker.ietf.org/doc/html/rfc2046 func (m *Msg) AddAlternativeWriter( contentType ContentType, writeFunc func(io.Writer) (int64, error), opts ...PartOption, @@ -1705,8 +1732,23 @@ func (m *Msg) AddAlternativeWriter( m.parts = append(m.parts, part) } -// AddAlternativeHTMLTemplate sets the alternative body of the message to a html/template.Template output -// The content type will be set to text/html automatically +// AddAlternativeHTMLTemplate sets the alternative body of the message to an html/template.Template output. +// +// The content type will be set to "text/html" automatically. This method executes the provided HTML template +// with the given data and adds the result as an alternative version of the message body. If the template +// is nil or fails to execute, an error will be returned. +// +// Parameters: +// - tpl: A pointer to the html/template.Template to be used for the alternative body. +// - data: The data to populate the template. +// - opts: Optional parameters for customizing the alternative body part. +// +// Returns: +// - An error if the template is nil or fails to execute, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2045 +// - https://datatracker.ietf.org/doc/html/rfc2046 func (m *Msg) AddAlternativeHTMLTemplate(tpl *ht.Template, data interface{}, opts ...PartOption) error { if tpl == nil { return errors.New(errTplPointerNil) @@ -1720,8 +1762,23 @@ func (m *Msg) AddAlternativeHTMLTemplate(tpl *ht.Template, data interface{}, opt return nil } -// AddAlternativeTextTemplate sets the alternative body of the message to a text/template.Template output -// The content type will be set to text/plain automatically +// AddAlternativeTextTemplate sets the alternative body of the message to a text/template.Template output. +// +// The content type will be set to "text/plain" automatically. This method executes the provided text template +// with the given data and adds the result as an alternative version of the message body. If the template +// is nil or fails to execute, an error will be returned. +// +// Parameters: +// - tpl: A pointer to the text/template.Template to be used for the alternative body. +// - data: The data to populate the template. +// - opts: Optional parameters for customizing the alternative body part. +// +// Returns: +// - An error if the template is nil or fails to execute, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2045 +// - https://datatracker.ietf.org/doc/html/rfc2046 func (m *Msg) AddAlternativeTextTemplate(tpl *tt.Template, data interface{}, opts ...PartOption) error { if tpl == nil { return errors.New(errTplPointerNil) @@ -1735,7 +1792,18 @@ func (m *Msg) AddAlternativeTextTemplate(tpl *tt.Template, data interface{}, opt return nil } -// AttachFile adds an attachment File to the Msg +// AttachFile adds an attachment File to the Msg. +// +// This method attaches a file to the message by specifying the file name. The file is retrieved from the +// filesystem and added to the list of attachments. Optional FileOption parameters can be provided to customize +// the attachment, such as setting its content type or encoding. +// +// Parameters: +// - name: The name of the file to be attached. +// - opts: Optional parameters for customizing the attachment. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) AttachFile(name string, opts ...FileOption) { file := fileFromFS(name) if file == nil { @@ -1744,12 +1812,22 @@ func (m *Msg) AttachFile(name string, opts ...FileOption) { m.attachments = m.appendFile(m.attachments, file, opts...) } -// AttachReader adds an attachment File via io.Reader to the Msg +// AttachReader adds an attachment File via io.Reader to the Msg. // -// CAVEAT: For AttachReader to work it has to read all data of the io.Reader -// into memory first, so it can seek through it. Using larger amounts of -// data on the io.Reader should be avoided. For such, it is recommended to -// either use AttachFile or AttachReadSeeker instead +// This method allows you to attach a file to the message using an io.Reader. It reads all data from the +// io.Reader into memory before attaching the file, which may not be suitable for large data sources. +// For larger files, it is recommended to use AttachFile or AttachReadSeeker instead. +// +// Parameters: +// - name: The name of the file to be attached. +// - reader: The io.Reader providing the file data to be attached. +// - opts: Optional parameters for customizing the attachment. +// +// Returns: +// - An error if the file could not be read from the io.Reader, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) AttachReader(name string, reader io.Reader, opts ...FileOption) error { file, err := fileFromReader(name, reader) if err != nil { @@ -1759,13 +1837,41 @@ func (m *Msg) AttachReader(name string, reader io.Reader, opts ...FileOption) er return nil } -// AttachReadSeeker adds an attachment File via io.ReadSeeker to the Msg +// AttachReadSeeker adds an attachment File via io.ReadSeeker to the Msg. +// +// This method allows you to attach a file to the message using an io.ReadSeeker, which is more efficient +// for larger files compared to AttachReader, as it allows for seeking through the data without needing +// to load the entire content into memory. +// +// Parameters: +// - name: The name of the file to be attached. +// - reader: The io.ReadSeeker providing the file data to be attached. +// - opts: Optional parameters for customizing the attachment. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) AttachReadSeeker(name string, reader io.ReadSeeker, opts ...FileOption) { file := fileFromReadSeeker(name, reader) m.attachments = m.appendFile(m.attachments, file, opts...) } -// AttachHTMLTemplate adds the output of a html/template.Template pointer as File attachment to the Msg +// AttachHTMLTemplate adds the output of a html/template.Template pointer as a File attachment to the Msg. +// +// This method allows you to attach the rendered output of an HTML template as a file to the message. +// The template is executed with the provided data, and its output is attached as a file. If the template +// fails to execute, an error will be returned. +// +// Parameters: +// - name: The name of the file to be attached. +// - tpl: A pointer to the html/template.Template to be executed for the attachment. +// - data: The data to populate the template. +// - opts: Optional parameters for customizing the attachment. +// +// Returns: +// - An error if the template fails to execute or cannot be attached, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) AttachHTMLTemplate( name string, tpl *ht.Template, data interface{}, opts ...FileOption, ) error { @@ -1777,7 +1883,23 @@ func (m *Msg) AttachHTMLTemplate( return nil } -// AttachTextTemplate adds the output of a text/template.Template pointer as File attachment to the Msg +// AttachTextTemplate adds the output of a text/template.Template pointer as a File attachment to the Msg. +// +// This method allows you to attach the rendered output of a text template as a file to the message. +// The template is executed with the provided data, and its output is attached as a file. If the template +// fails to execute, an error will be returned. +// +// Parameters: +// - name: The name of the file to be attached. +// - tpl: A pointer to the text/template.Template to be executed for the attachment. +// - data: The data to populate the template. +// - opts: Optional parameters for customizing the attachment. +// +// Returns: +// - An error if the template fails to execute or cannot be attached, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) AttachTextTemplate( name string, tpl *tt.Template, data interface{}, opts ...FileOption, ) error { @@ -1789,7 +1911,22 @@ func (m *Msg) AttachTextTemplate( return nil } -// AttachFromEmbedFS adds an attachment File from an embed.FS to the Msg +// AttachFromEmbedFS adds an attachment File from an embed.FS to the Msg. +// +// This method allows you to attach a file from an embedded filesystem (embed.FS) to the message. +// The file is retrieved from the provided embed.FS and attached to the email. If the embedded filesystem +// is nil or the file cannot be retrieved, an error will be returned. +// +// Parameters: +// - name: The name of the file to be attached. +// - fs: A pointer to the embed.FS from which the file will be retrieved. +// - opts: Optional parameters for customizing the attachment. +// +// Returns: +// - An error if the embed.FS is nil or the file cannot be retrieved, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) AttachFromEmbedFS(name string, fs *embed.FS, opts ...FileOption) error { if fs == nil { return fmt.Errorf("embed.FS must not be nil") @@ -1802,7 +1939,18 @@ func (m *Msg) AttachFromEmbedFS(name string, fs *embed.FS, opts ...FileOption) e return nil } -// EmbedFile adds an embedded File to the Msg +// EmbedFile adds an embedded File to the Msg. +// +// This method embeds a file from the filesystem directly into the email message. The embedded file, +// typically an image or media file, can be referenced within the email's content (such as inline in HTML). +// If the file is not found or cannot be loaded, it will not be added. +// +// Parameters: +// - name: The name of the file to be embedded. +// - opts: Optional parameters for customizing the embedded file. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) EmbedFile(name string, opts ...FileOption) { file := fileFromFS(name) if file == nil { @@ -1811,12 +1959,22 @@ func (m *Msg) EmbedFile(name string, opts ...FileOption) { m.embeds = m.appendFile(m.embeds, file, opts...) } -// EmbedReader adds an embedded File from an io.Reader to the Msg +// EmbedReader adds an embedded File from an io.Reader to the Msg. // -// CAVEAT: For EmbedReader to work it has to read all data of the io.Reader -// into memory first, so it can seek through it. Using larger amounts of -// data on the io.Reader should be avoided. For such, it is recommended to -// either use EmbedFile or EmbedReadSeeker instead +// This method embeds a file into the email message by reading its content from an io.Reader. +// It reads all data into memory before embedding the file, which may not be efficient for large data sources. +// For larger files, it is recommended to use EmbedFile or EmbedReadSeeker instead. +// +// Parameters: +// - name: The name of the file to be embedded. +// - reader: The io.Reader providing the file data to be embedded. +// - opts: Optional parameters for customizing the embedded file. +// +// Returns: +// - An error if the file could not be read from the io.Reader, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) EmbedReader(name string, reader io.Reader, opts ...FileOption) error { file, err := fileFromReader(name, reader) if err != nil { @@ -1826,13 +1984,41 @@ func (m *Msg) EmbedReader(name string, reader io.Reader, opts ...FileOption) err return nil } -// EmbedReadSeeker adds an embedded File from an io.ReadSeeker to the Msg +// EmbedReadSeeker adds an embedded File from an io.ReadSeeker to the Msg. +// +// This method embeds a file into the email message by reading its content from an io.ReadSeeker. +// Using io.ReadSeeker allows for more efficient handling of large files since it can seek through the data +// without loading the entire content into memory. +// +// Parameters: +// - name: The name of the file to be embedded. +// - reader: The io.ReadSeeker providing the file data to be embedded. +// - opts: Optional parameters for customizing the embedded file. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) EmbedReadSeeker(name string, reader io.ReadSeeker, opts ...FileOption) { file := fileFromReadSeeker(name, reader) m.embeds = m.appendFile(m.embeds, file, opts...) } -// EmbedHTMLTemplate adds the output of a html/template.Template pointer as embedded File to the Msg +// EmbedHTMLTemplate adds the output of a html/template.Template pointer as an embedded File to the Msg. +// +// This method embeds the rendered output of an HTML template into the email message. The template is +// executed with the provided data, and its output is embedded as a file. If the template fails to execute, +// an error will be returned. +// +// Parameters: +// - name: The name of the embedded file. +// - tpl: A pointer to the html/template.Template to be executed for the embedded content. +// - data: The data to populate the template. +// - opts: Optional parameters for customizing the embedded file. +// +// Returns: +// - An error if the template fails to execute or cannot be embedded, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) EmbedHTMLTemplate( name string, tpl *ht.Template, data interface{}, opts ...FileOption, ) error { @@ -1844,7 +2030,23 @@ func (m *Msg) EmbedHTMLTemplate( return nil } -// EmbedTextTemplate adds the output of a text/template.Template pointer as embedded File to the Msg +// EmbedTextTemplate adds the output of a text/template.Template pointer as an embedded File to the Msg. +// +// This method embeds the rendered output of a text template into the email message. The template is +// executed with the provided data, and its output is embedded as a file. If the template fails to execute, +// an error will be returned. +// +// Parameters: +// - name: The name of the embedded file. +// - tpl: A pointer to the text/template.Template to be executed for the embedded content. +// - data: The data to populate the template. +// - opts: Optional parameters for customizing the embedded file. +// +// Returns: +// - An error if the template fails to execute or cannot be embedded, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) EmbedTextTemplate( name string, tpl *tt.Template, data interface{}, opts ...FileOption, ) error { @@ -1856,7 +2058,21 @@ func (m *Msg) EmbedTextTemplate( return nil } -// EmbedFromEmbedFS adds an embedded File from an embed.FS to the Msg +// EmbedFromEmbedFS adds an embedded File from an embed.FS to the Msg. +// +// This method embeds a file from an embedded filesystem (embed.FS) into the email message. If the +// embedded filesystem is nil or the file cannot be retrieved, an error will be returned. +// +// Parameters: +// - name: The name of the file to be embedded. +// - fs: A pointer to the embed.FS from which the file will be retrieved. +// - opts: Optional parameters for customizing the embedded file. +// +// Returns: +// - An error if the embed.FS is nil or the file cannot be retrieved, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func (m *Msg) EmbedFromEmbedFS(name string, fs *embed.FS, opts ...FileOption) error { if fs == nil { return fmt.Errorf("embed.FS must not be nil") @@ -1869,8 +2085,14 @@ func (m *Msg) EmbedFromEmbedFS(name string, fs *embed.FS, opts ...FileOption) er return nil } -// Reset resets all headers, body parts and attachments/embeds of the Msg -// It leaves already set encodings, charsets, boundaries, etc. as is +// Reset resets all headers, body parts, attachments, and embeds of the Msg. +// +// This method clears all address headers, attachments, embeds, generic headers, and body parts of the message. +// However, it preserves the existing encoding, charset, boundary, and other message-level settings. +// Use this method to reset the message content while keeping certain configurations intact. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322 func (m *Msg) Reset() { m.addrHeader = make(map[AddrHeader][]*mail.Address) m.attachments = nil @@ -1879,7 +2101,17 @@ func (m *Msg) Reset() { m.parts = nil } -// ApplyMiddlewares apply the list of middlewares to a Msg +// ApplyMiddlewares applies the list of middlewares to a Msg. +// +// This method sequentially applies each middleware function in the list to the message (in FIFO order). +// The middleware functions can modify the message, such as adding headers or altering its content. +// The message is passed through each middleware in order, and the modified message is returned. +// +// Parameters: +// - msg: The Msg object to which the middlewares will be applied. +// +// Returns: +// - The modified Msg after all middleware functions have been applied. func (m *Msg) applyMiddlewares(msg *Msg) *Msg { for _, middleware := range m.middlewares { msg = middleware.Handle(msg) @@ -1887,15 +2119,44 @@ func (m *Msg) applyMiddlewares(msg *Msg) *Msg { return msg } -// WriteTo writes the formated Msg into a give io.Writer and satisfies the io.WriteTo interface +// WriteTo writes the formatted Msg into the given io.Writer and satisfies the io.WriterTo interface. +// +// This method writes the email message, including its headers, body, and attachments, to the provided +// io.Writer. It applies any middlewares to the message before writing it. The total number of bytes +// written and any error encountered during the writing process are returned. +// +// Parameters: +// - writer: The io.Writer to which the formatted message will be written. +// +// Returns: +// - The total number of bytes written. +// - An error if any occurred during the writing process, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322 func (m *Msg) WriteTo(writer io.Writer) (int64, error) { mw := &msgWriter{writer: writer, charset: m.charset, encoder: m.encoder} mw.writeMsg(m.applyMiddlewares(m)) return mw.bytesWritten, mw.err } -// WriteToSkipMiddleware writes the formated Msg into a give io.Writer and satisfies -// the io.WriteTo interface but will skip the given Middleware +// WriteToSkipMiddleware writes the formatted Msg into the given io.Writer, but skips the specified +// middleware type. +// +// This method writes the email message to the provided io.Writer after applying all middlewares, +// except for the specified middleware type, which will be skipped. It temporarily removes the +// middleware of the given type, writes the message, and then restores the original middleware list. +// +// Parameters: +// - writer: The io.Writer to which the formatted message will be written. +// - middleWareType: The MiddlewareType that should be skipped during the writing process. +// +// Returns: +// - The total number of bytes written. +// - An error if any occurred during the writing process, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322 func (m *Msg) WriteToSkipMiddleware(writer io.Writer, middleWareType MiddlewareType) (int64, error) { var origMiddlewares, middlewares []Middleware origMiddlewares = m.middlewares @@ -1912,30 +2173,39 @@ func (m *Msg) WriteToSkipMiddleware(writer io.Writer, middleWareType MiddlewareT return mw.bytesWritten, mw.err } -// Write is an alias method to WriteTo due to compatibility reasons +// Write is an alias method to WriteTo for compatibility reasons. +// +// This method provides a backward-compatible way to write the formatted Msg to the provided io.Writer +// by calling the WriteTo method. It writes the email message, including headers, body, and attachments, +// to the io.Writer and returns the number of bytes written and any error encountered. +// +// Parameters: +// - writer: The io.Writer to which the formatted message will be written. +// +// Returns: +// - The total number of bytes written. +// - An error if any occurred during the writing process, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322 func (m *Msg) Write(writer io.Writer) (int64, error) { return m.WriteTo(writer) } -// appendFile adds a File to the Msg (as attachment or embed) -func (m *Msg) appendFile(files []*File, file *File, opts ...FileOption) []*File { - // Override defaults with optionally provided FileOption functions - for _, opt := range opts { - if opt == nil { - continue - } - opt(file) - } - - if files == nil { - return []*File{file} - } - - return append(files, file) -} - -// WriteToFile stores the Msg as file on disk. It will try to create the given filename -// Already existing files will be overwritten +// WriteToFile stores the Msg as a file on disk. It will try to create the given filename, +// and if the file already exists, it will be overwritten. +// +// This method writes the email message, including its headers, body, and attachments, to a file on disk. +// If the file cannot be created or an error occurs during writing, an error is returned. +// +// Parameters: +// - name: The name of the file to be created or overwritten. +// +// Returns: +// - An error if the file cannot be created or if writing to the file fails, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322 func (m *Msg) WriteToFile(name string) error { file, err := os.Create(name) if err != nil { @@ -1949,22 +2219,58 @@ func (m *Msg) WriteToFile(name string) error { return file.Close() } -// WriteToSendmail returns WriteToSendmailWithCommand with a default sendmail path +// WriteToSendmail returns WriteToSendmailWithCommand with a default sendmail path. +// +// This method sends the email message using the default sendmail path. It calls WriteToSendmailWithCommand +// using the standard SendmailPath. If sending via sendmail fails, an error is returned. +// +// Returns: +// - An error if sending the message via sendmail fails, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5321 func (m *Msg) WriteToSendmail() error { return m.WriteToSendmailWithCommand(SendmailPath) } // WriteToSendmailWithCommand returns WriteToSendmailWithContext with a default timeout -// of 5 seconds and a given sendmail path +// of 5 seconds and a given sendmail path. +// +// This method sends the email message using the provided sendmail path, with a default timeout of 5 seconds. +// It creates a context with the specified timeout and then calls WriteToSendmailWithContext to send the message. +// +// Parameters: +// - sendmailPath: The path to the sendmail executable to be used for sending the message. +// +// Returns: +// - An error if sending the message via sendmail fails, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5321 func (m *Msg) WriteToSendmailWithCommand(sendmailPath string) error { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() return m.WriteToSendmailWithContext(ctx, sendmailPath) } -// WriteToSendmailWithContext opens an pipe to the local sendmail binary and tries to send the -// mail though that. It takes a context.Context, the path to the sendmail binary and additional -// arguments for the sendmail binary as parameters +// WriteToSendmailWithContext opens a pipe to the local sendmail binary and tries to send the +// email through it. It takes a context.Context, the path to the sendmail binary, and additional +// arguments for the sendmail binary as parameters. +// +// This method establishes a pipe to the sendmail executable using the provided context and arguments. +// It writes the email message to the sendmail process via STDIN. If any errors occur during the +// communication with the sendmail binary, they will be captured and returned. +// +// Parameters: +// - ctx: The context to control the timeout and cancellation of the sendmail process. +// - sendmailPath: The path to the sendmail executable. +// - args: Additional arguments for the sendmail binary. +// +// Returns: +// - An error if sending the message via sendmail fails, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5321 func (m *Msg) WriteToSendmailWithContext(ctx context.Context, sendmailPath string, args ...string) error { cmdCtx := exec.CommandContext(ctx, sendmailPath) cmdCtx.Args = append(cmdCtx.Args, "-oi", "-t") @@ -2017,10 +2323,19 @@ func (m *Msg) WriteToSendmailWithContext(ctx context.Context, sendmailPath strin // NewReader returns a Reader type that satisfies the io.Reader interface. // -// IMPORTANT: when creating a new Reader, the current state of the Msg is taken, as -// basis for the Reader. If you perform changes on Msg after creating the Reader, these -// changes will not be reflected in the Reader. You will have to use Msg.UpdateReader -// first to update the Reader's buffer with the current Msg content +// This method creates a new Reader for the Msg, capturing the current state of the message. +// Any subsequent changes made to the Msg after creating the Reader will not be reflected in the Reader's buffer. +// To reflect these changes in the Reader, you must call Msg.UpdateReader to update the Reader's content with +// the current state of the Msg. +// +// Returns: +// - A pointer to a Reader, which allows the Msg to be read as a stream of bytes. +// +// IMPORTANT: Any changes made to the Msg after creating the Reader will not be reflected in the Reader unless +// Msg.UpdateReader is called. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322 func (m *Msg) NewReader() *Reader { reader := &Reader{} buffer := bytes.Buffer{} @@ -2032,8 +2347,17 @@ func (m *Msg) NewReader() *Reader { return reader } -// UpdateReader will update a Reader with the content of the given Msg and reset the -// Reader position to the start +// UpdateReader updates a Reader with the current content of the Msg and resets the +// Reader's position to the start. +// +// This method rewrites the content of the provided Reader to reflect any changes made to the Msg. +// It resets the Reader's position to the beginning and updates the buffer with the latest message content. +// +// Parameters: +// - reader: A pointer to the Reader that will be updated with the Msg's current content. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322 func (m *Msg) UpdateReader(reader *Reader) { buffer := bytes.Buffer{} _, err := m.Write(&buffer) @@ -2042,14 +2366,27 @@ func (m *Msg) UpdateReader(reader *Reader) { reader.err = err } -// HasSendError returns true if the Msg experienced an error during the message delivery and the -// sendError field of the Msg is not nil +// HasSendError returns true if the Msg experienced an error during message delivery +// and the sendError field of the Msg is not nil. +// +// This method checks whether the message has encountered a delivery error by verifying if the +// sendError field is populated. +// +// Returns: +// - A boolean value indicating whether a send error occurred (true if an error is present). func (m *Msg) HasSendError() bool { return m.sendError != nil } -// SendErrorIsTemp returns true if the Msg experienced an error during the message delivery and the -// corresponding error was of temporary nature and should be retried later +// SendErrorIsTemp returns true if the Msg experienced a delivery error, and the corresponding +// error was of a temporary nature, meaning it can be retried later. +// +// This method checks whether the encountered sendError is a temporary error that can be retried. +// It uses the errors.As function to determine if the error is of type SendError and checks if +// the error is marked as temporary. +// +// Returns: +// - A boolean value indicating whether the send error is temporary (true if the error is temporary). func (m *Msg) SendErrorIsTemp() bool { var err *SendError if errors.As(m.sendError, &err) && err != nil { @@ -2058,12 +2395,32 @@ func (m *Msg) SendErrorIsTemp() bool { return false } -// SendError returns the sendError field of the Msg +// SendError returns the sendError field of the Msg. +// +// This method retrieves the error that occurred during the message delivery process, if any. +// It returns the sendError field, which holds the error encountered during sending. +// +// Returns: +// - The error encountered during message delivery, or nil if no error occurred. func (m *Msg) SendError() error { return m.sendError } -// addAddr adds an additional address to the given addrHeader of the Msg +// addAddr adds an additional address to the given addrHeader of the Msg. +// +// This method appends an email address to the specified address header (such as "To", "Cc", or "Bcc") +// without overwriting existing addresses. It first collects the current addresses in the header, then +// adds the new address and updates the header. +// +// Parameters: +// - header: The AddrHeader (e.g., HeaderTo, HeaderCc) to which the address will be added. +// - addr: The email address to add to the specified header. +// +// Returns: +// - An error if the address cannot be added, otherwise nil. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322 func (m *Msg) addAddr(header AddrHeader, addr string) error { var addresses []string for _, address := range m.addrHeader[header] { @@ -2073,13 +2430,69 @@ func (m *Msg) addAddr(header AddrHeader, addr string) error { return m.SetAddrHeader(header, addresses...) } +// appendFile adds a File to the Msg, either as an attachment or an embed. +// +// This method appends a File to the list of files (attachments or embeds) for the message. It applies +// optional FileOption functions to customize the file properties before adding it. If no files are +// already present, a new list is created. +// +// Parameters: +// - files: The current list of files (either attachments or embeds). +// - file: The File to be added. +// - opts: Optional FileOption functions to customize the file. +// +// Returns: +// - A slice of File pointers representing the updated list of files. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 +func (m *Msg) appendFile(files []*File, file *File, opts ...FileOption) []*File { + // Override defaults with optionally provided FileOption functions + for _, opt := range opts { + if opt == nil { + continue + } + opt(file) + } + + if files == nil { + return []*File{file} + } + + return append(files, file) +} + // encodeString encodes a string based on the configured message encoder and the corresponding -// charset for the Msg +// charset for the Msg. +// +// This method encodes the provided string using the message's charset and encoder settings. +// The encoding ensures that the string is properly formatted according to the message's +// character encoding (e.g., UTF-8, ISO-8859-1). +// +// Parameters: +// - str: The string to be encoded. +// +// Returns: +// - The encoded string based on the message's charset and encoder. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2047 func (m *Msg) encodeString(str string) string { return m.encoder.Encode(string(m.charset), str) } -// hasAlt returns true if the Msg has more than one part +// hasAlt returns true if the Msg has more than one part. +// +// This method checks whether the message contains more than one part, indicating that +// the message has alternative content (e.g., both plain text and HTML parts). It ignores +// any parts marked as deleted and returns true only if more than one valid part exists +// and no PGP type is set. +// +// Returns: +// - A boolean value indicating whether the message has multiple parts (true if more than one part exists). +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2046 func (m *Msg) hasAlt() bool { count := 0 for _, part := range m.parts { @@ -2090,22 +2503,66 @@ func (m *Msg) hasAlt() bool { return count > 1 && m.pgptype == 0 } -// hasMixed returns true if the Msg has mixed parts +// hasMixed returns true if the Msg has mixed parts. +// +// This method checks whether the message contains mixed content, such as attachments along with +// message parts (e.g., text or HTML). A message is considered to have mixed parts if there are both +// attachments and message parts, or if there are multiple attachments. +// +// Returns: +// - A boolean value indicating whether the message has mixed parts. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2046#section-5.1.3 func (m *Msg) hasMixed() bool { return m.pgptype == 0 && ((len(m.parts) > 0 && len(m.attachments) > 0) || len(m.attachments) > 1) } -// hasRelated returns true if the Msg has related parts +// hasRelated returns true if the Msg has related parts. +// +// This method checks whether the message contains related parts, such as inline embedded files +// (e.g., images) that are referenced within the message body. A message is considered to have +// related parts if there are both message parts and embedded files, or if there are multiple embedded files. +// +// Returns: +// - A boolean value indicating whether the message has related parts. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2387 func (m *Msg) hasRelated() bool { return m.pgptype == 0 && ((len(m.parts) > 0 && len(m.embeds) > 0) || len(m.embeds) > 1) } -// hasPGPType returns true if the Msg should be treated as PGP encoded message +// hasPGPType returns true if the Msg should be treated as a PGP-encoded message. +// +// This method checks whether the message is configured to be treated as a PGP-encoded message by examining +// the pgptype field. If the PGP type is set to a value greater than 0, the message is considered PGP-encoded. +// +// Returns: +// - A boolean value indicating whether the message is PGP-encoded. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc4880 func (m *Msg) hasPGPType() bool { return m.pgptype > 0 } -// newPart returns a new Part for the Msg +// newPart returns a new Part for the Msg. +// +// This method creates a new Part for the message with the specified content type, +// using the message's current charset and encoding settings. Optional PartOption +// functions can be applied to customize the Part further. +// +// Parameters: +// - contentType: The content type for the new Part (e.g., text/plain, text/html). +// - opts: Optional PartOption functions to customize the Part. +// +// Returns: +// - A pointer to the newly created Part structure. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2045 +// - https://datatracker.ietf.org/doc/html/rfc2046 func (m *Msg) newPart(contentType ContentType, opts ...PartOption) *Part { p := &Part{ contentType: contentType, @@ -2124,13 +2581,26 @@ func (m *Msg) newPart(contentType ContentType, opts ...PartOption) *Part { return p } -// setEncoder creates a new mime.WordEncoder based on the encoding setting of the message +// setEncoder creates a new mime.WordEncoder based on the encoding setting of the message. +// +// This method sets the message's encoder by creating a new mime.WordEncoder that matches the +// current encoding setting (e.g., quoted-printable or base64). The encoder is used to encode +// message headers and body content appropriately. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2047 func (m *Msg) setEncoder() { m.encoder = getEncoder(m.encoding) } -// checkUserAgent checks if a useragent/x-mailer is set and if not will set a default -// version string +// checkUserAgent checks if a User-Agent or X-Mailer header is set, and if not, sets a default version string. +// +// This method ensures that the message includes a User-Agent and X-Mailer header, unless the noDefaultUserAgent +// flag is set. If neither of these headers is present, a default User-Agent string with the current library +// version is added. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.7 func (m *Msg) checkUserAgent() { if m.noDefaultUserAgent { return @@ -2143,7 +2613,16 @@ func (m *Msg) checkUserAgent() { } } -// addDefaultHeader sets some default headers, if they haven't been set before +// addDefaultHeader sets default headers if they haven't been set before. +// +// This method ensures that essential headers such as "Date", "Message-ID", and "MIME-Version" are set +// in the message. If these headers are not already present, they will be set to default values. +// The "Date" and "Message-ID" headers are generated, and the "MIME-Version" is set to the message's current setting. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.1 (Date) +// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.4 (Message-ID) +// - https://datatracker.ietf.org/doc/html/rfc2045#section-4 (MIME-Version) func (m *Msg) addDefaultHeader() { if _, ok := m.genHeader[HeaderDate]; !ok { m.SetDate() @@ -2154,7 +2633,22 @@ func (m *Msg) addDefaultHeader() { m.SetGenHeader(HeaderMIMEVersion, string(m.mimever)) } -// fileFromEmbedFS returns a File pointer from a given file in the provided embed.FS +// fileFromEmbedFS returns a File pointer from a given file in the provided embed.FS. +// +// This method retrieves a file from the embedded filesystem (embed.FS) and returns a File structure +// that can be used as an attachment or embed in the email message. The file's content is read when +// writing to an io.Writer, and the file is identified by its base name. +// +// Parameters: +// - name: The name of the file to retrieve from the embedded filesystem. +// - fs: A pointer to the embed.FS from which the file will be opened. +// +// Returns: +// - A pointer to the File structure representing the embedded file. +// - An error if the file cannot be opened or read from the embedded filesystem. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func fileFromEmbedFS(name string, fs *embed.FS) (*File, error) { _, err := fs.Open(name) if err != nil { @@ -2178,7 +2672,21 @@ func fileFromEmbedFS(name string, fs *embed.FS) (*File, error) { }, nil } -// fileFromFS returns a File pointer from a given file in the system's file system +// fileFromFS returns a File pointer from a given file in the system's file system. +// +// This method retrieves a file from the system's file system and returns a File structure +// that can be used as an attachment or embed in the email message. The file is identified +// by its base name, and its content is read when writing to an io.Writer. +// +// Parameters: +// - name: The name of the file to retrieve from the system's file system. +// +// Returns: +// - A pointer to the File structure representing the file from the system's file system. +// - Nil if the file does not exist or cannot be accessed. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func fileFromFS(name string) *File { _, err := os.Stat(name) if err != nil { @@ -2203,7 +2711,22 @@ func fileFromFS(name string) *File { } } -// fileFromReader returns a File pointer from a given io.Reader +// fileFromReader returns a File pointer from a given io.Reader. +// +// This method reads all data from the provided io.Reader and creates a File structure +// that can be used as an attachment or embed in the email message. The file's content +// is stored in memory and written to an io.Writer when needed. +// +// Parameters: +// - name: The name of the file to be represented by the reader's content. +// - reader: The io.Reader from which the file content will be read. +// +// Returns: +// - A pointer to the File structure representing the content of the io.Reader. +// - An error if the content cannot be read from the io.Reader. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func fileFromReader(name string, reader io.Reader) (*File, error) { d, err := io.ReadAll(reader) if err != nil { @@ -2224,7 +2747,21 @@ func fileFromReader(name string, reader io.Reader) (*File, error) { }, nil } -// fileFromReadSeeker returns a File pointer from a given io.ReadSeeker +// fileFromReadSeeker returns a File pointer from a given io.ReadSeeker. +// +// This method creates a File structure from an io.ReadSeeker, allowing efficient handling of file content +// by seeking and reading from the source without fully loading it into memory. The content is written +// to an io.Writer when needed, and the reader's position is reset to the start after writing. +// +// Parameters: +// - name: The name of the file to be represented by the io.ReadSeeker. +// - reader: The io.ReadSeeker from which the file content will be read. +// +// Returns: +// - A pointer to the File structure representing the content of the io.ReadSeeker. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func fileFromReadSeeker(name string, reader io.ReadSeeker) *File { return &File{ Name: name, @@ -2240,7 +2777,23 @@ func fileFromReadSeeker(name string, reader io.ReadSeeker) *File { } } -// fileFromHTMLTemplate returns a File pointer form a given html/template.Template +// fileFromHTMLTemplate returns a File pointer from a given html/template.Template. +// +// This method executes the provided HTML template with the given data and creates a File structure +// representing the output. The rendered template content is stored in a buffer and then processed +// as a file attachment or embed. +// +// Parameters: +// - name: The name of the file to be created from the template output. +// - tpl: A pointer to the html/template.Template to be executed. +// - data: The data to populate the template. +// +// Returns: +// - A pointer to the File structure representing the rendered template. +// - An error if the template is nil or if it fails to execute. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func fileFromHTMLTemplate(name string, tpl *ht.Template, data interface{}) (*File, error) { if tpl == nil { return nil, errors.New(errTplPointerNil) @@ -2252,7 +2805,23 @@ func fileFromHTMLTemplate(name string, tpl *ht.Template, data interface{}) (*Fil return fileFromReader(name, &buffer) } -// fileFromTextTemplate returns a File pointer form a given text/template.Template +// fileFromTextTemplate returns a File pointer from a given text/template.Template. +// +// This method executes the provided text template with the given data and creates a File structure +// representing the output. The rendered template content is stored in a buffer and then processed +// as a file attachment or embed. +// +// Parameters: +// - name: The name of the file to be created from the template output. +// - tpl: A pointer to the text/template.Template to be executed. +// - data: The data to populate the template. +// +// Returns: +// - A pointer to the File structure representing the rendered template. +// - An error if the template is nil or if it fails to execute. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2183 func fileFromTextTemplate(name string, tpl *tt.Template, data interface{}) (*File, error) { if tpl == nil { return nil, errors.New(errTplPointerNil) @@ -2264,7 +2833,19 @@ func fileFromTextTemplate(name string, tpl *tt.Template, data interface{}) (*Fil return fileFromReader(name, &buffer) } -// getEncoder creates a new mime.WordEncoder based on the encoding setting of the message +// getEncoder creates a new mime.WordEncoder based on the encoding setting of the message. +// +// This function returns a mime.WordEncoder based on the specified encoding (e.g., quoted-printable or base64). +// The encoder is used for encoding message headers and body content according to the chosen encoding standard. +// +// Parameters: +// - enc: The Encoding type for the message (e.g., EncodingQP for quoted-printable or EncodingB64 for base64). +// +// Returns: +// - A mime.WordEncoder based on the encoding setting. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2047 func getEncoder(enc Encoding) mime.WordEncoder { switch enc { case EncodingQP: @@ -2276,8 +2857,21 @@ func getEncoder(enc Encoding) mime.WordEncoder { } } -// writeFuncFromBuffer is a common method to convert a byte buffer into a writeFunc as -// often required by this library +// writeFuncFromBuffer converts a byte buffer into a writeFunc, which is commonly required by go-mail. +// +// This function wraps a byte buffer into a write function that can be used to write the buffer's content +// to an io.Writer. It returns a function that writes the buffer's content to the given writer and returns +// the number of bytes written and any error that occurred during writing. +// +// Parameters: +// - buffer: A pointer to the bytes.Buffer containing the data to be written. +// +// Returns: +// - A function that writes the buffer's content to an io.Writer, returning the number of bytes written +// and any error encountered during the write operation. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc5322 func writeFuncFromBuffer(buffer *bytes.Buffer) func(io.Writer) (int64, error) { writeFunc := func(w io.Writer) (int64, error) { numBytes, err := w.Write(buffer.Bytes()) From d6426063ba57f3a15e58894231b60bc526e0be60 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 6 Oct 2024 12:39:02 +0200 Subject: [PATCH 47/60] Refactor comments and docstrings for clarity and detail Enhanced comments and docstrings for better readability and detail in client.go and b64linebreaker.go. Updated descriptions, added parameter and return details, and included RFC references where applicable for improved documentation quality. --- b64linebreaker.go | 29 +++- client.go | 372 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 304 insertions(+), 97 deletions(-) diff --git a/b64linebreaker.go b/b64linebreaker.go index 8f82cda..cc83973 100644 --- a/b64linebreaker.go +++ b/b64linebreaker.go @@ -16,10 +16,14 @@ var newlineBytes = []byte(SingleNewLine) // ErrNoOutWriter is the error message returned when no io.Writer is set for Base64LineBreaker. const ErrNoOutWriter = "no io.Writer set for Base64LineBreaker" -// Base64LineBreaker is used to handle base64 encoding with the insertion of new lines after a certain -// number of characters. +// Base64LineBreaker handles base64 encoding with the insertion of new lines after a certain number +// of characters. // -// It satisfies the io.WriteCloser interface. +// This struct is used to manage base64 encoding while ensuring that new lines are inserted after +// reaching a specific line length. It satisfies the io.WriteCloser interface. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2045 (Base64 and line length limitations) type Base64LineBreaker struct { line [MaxBodyLength]byte used int @@ -27,7 +31,17 @@ type Base64LineBreaker struct { } // Write writes data to the Base64LineBreaker, ensuring lines do not exceed MaxBodyLength. -// It handles continuation if data length exceeds the limit and writes new lines accordingly. +// +// This method writes the provided data to the Base64LineBreaker. It ensures that the written +// lines do not exceed the MaxBodyLength. If the data exceeds the limit, it handles the +// continuation by splitting the data and writing new lines as necessary. +// +// Parameters: +// - data: A byte slice containing the data to be written. +// +// Returns: +// - numBytes: The number of bytes written. +// - err: An error if one occurred during the write operation. func (l *Base64LineBreaker) Write(data []byte) (numBytes int, err error) { if l.out == nil { err = errors.New(ErrNoOutWriter) @@ -60,6 +74,13 @@ func (l *Base64LineBreaker) Write(data []byte) (numBytes int, err error) { } // Close finalizes the Base64LineBreaker, writing any remaining buffered data and appending a newline. +// +// This method ensures that any remaining data in the buffer is written to the output and appends +// a newline. It is used to finalize the Base64LineBreaker and should be called when no more data +// is expected to be written. +// +// Returns: +// - err: An error if one occurred during the final write operation. func (l *Base64LineBreaker) Close() (err error) { if l.used > 0 { _, err = l.out.Write(l.line[0:l.used]) diff --git a/client.go b/client.go index 26a8bb5..dc7d46c 100644 --- a/client.go +++ b/client.go @@ -41,7 +41,6 @@ const ( ) const ( - // DSNMailReturnHeadersOnly requests that only the message headers of the mail message are returned in // a DSN (Delivery Status Notification). // @@ -84,7 +83,6 @@ const ( ) type ( - // DialContextFunc defines a function type for establishing a network connection using context, network // type, and address. It is used to specify custom DialContext function. // @@ -106,7 +104,15 @@ type ( // Option is a function type that modifies the configuration or behavior of a Client instance. Option func(*Client) error - // Client is the go-mail client that is responsible for connecting and interacting with an SMTP server. + // Client is responsible for connecting and interacting with an SMTP server. + // + // This struct represents the go-mail client, which manages the connection, authentication, and communication + // with an SMTP server. It contains various configuration options, including connection timeouts, encryption + // settings, authentication methods, and Delivery Status Notification (DSN) preferences. + // + // References: + // - https://datatracker.ietf.org/doc/html/rfc3207#section-2 + // - https://datatracker.ietf.org/doc/html/rfc8314 Client struct { // connTimeout specifies timeout for the connection to the SMTP server. connTimeout time.Duration @@ -236,10 +242,18 @@ var ( ) // NewClient creates a new Client instance with the provided host and optional configuration Option functions. -// It initializes default values for connection timeout, port, TLS settings, and HELO/EHLO hostname. -// Option functions, if provided, override default values. // -// Returns an error if critical defaults are unset. +// This function initializes a Client with default values, such as connection timeout, port, TLS settings, +// and the HELO/EHLO hostname. Option functions, if provided, can override the default configuration. +// It ensures that essential values, like the host, are set. An error is returned if critical defaults are unset. +// +// Parameters: +// - host: The hostname of the SMTP server to connect to. +// - opts: Optional configuration functions to override default settings. +// +// Returns: +// - A pointer to the initialized Client. +// - An error if any critical default values are missing or options fail to apply. func NewClient(host string, opts ...Option) (*Client, error) { c := &Client{ connTimeout: DefaultTimeout, @@ -272,8 +286,17 @@ func NewClient(host string, opts ...Option) (*Client, error) { return c, nil } -// WithPort sets the port number for the Client and overrides the default port. It validates the port number to -// ensure it is between 1 and 65535. An error is returned if the provided port number is invalid. +// WithPort sets the port number for the Client and overrides the default port. +// +// This function sets the specified port number for the Client, ensuring that the port number is valid +// (between 1 and 65535). If the provided port number is invalid, an error is returned. +// +// Parameters: +// - port: The port number to be used by the Client. Must be between 1 and 65535. +// +// Returns: +// - An Option function that applies the port setting to the Client. +// - An error if the port number is outside the valid range. func WithPort(port int) Option { return func(c *Client) error { if port < 1 || port > 65535 { @@ -284,8 +307,17 @@ func WithPort(port int) Option { } } -// WithTimeout sets the connection timeout for the Client to the provided duration and overrides the default -// timeout. An error is returned if the provided timeout is invalid. +// WithTimeout sets the connection timeout for the Client and overrides the default timeout. +// +// This function configures the Client with a specified connection timeout duration. It validates that the +// provided timeout is greater than zero. If the timeout is invalid, an error is returned. +// +// Parameters: +// - timeout: The duration to be set as the connection timeout. Must be greater than zero. +// +// Returns: +// - An Option function that applies the timeout setting to the Client. +// - An error if the timeout duration is invalid. func WithTimeout(timeout time.Duration) Option { return func(c *Client) error { if timeout <= 0 { @@ -297,6 +329,11 @@ func WithTimeout(timeout time.Duration) Option { } // WithSSL enables implicit SSL/TLS for the Client. +// +// This function configures the Client to use implicit SSL/TLS for secure communication. +// +// Returns: +// - An Option function that enables SSL/TLS for the Client. func WithSSL() Option { return func(c *Client) error { c.useSSL = true @@ -307,13 +344,16 @@ func WithSSL() Option { // WithSSLPort enables implicit SSL/TLS with an optional fallback for the Client. The correct port is // automatically set. // -// If this option is used with NewClient, the default port 25 will be overriden with port 465. If fallback -// is set to true and the SSL/TLS connection fails, the Client will attempt to connect on port 25 using -// using an unencrypted connection. +// When this option is used with NewClient, the default port 25 is overridden with port 465 for SSL/TLS connections. +// If fallback is set to true and the SSL/TLS connection fails, the Client attempts to connect on port 25 using an +// unencrypted connection. If WithPort has already been used to set a different port, that port takes precedence, +// and the automatic fallback mechanism is skipped. // -// Note: If a different port has already been set otherwise using WithPort, the selected port has higher -// precedence and is used to establish the SSL/TLS connection. In this case the authmatic fallback -// mechanism is skipped at all. +// Parameters: +// - fallback: A boolean indicating whether to fall back to port 25 without SSL/TLS if the connection fails. +// +// Returns: +// - An Option function that enables SSL/TLS and configures the fallback mechanism for the Client. func WithSSLPort(fallback bool) Option { return func(c *Client) error { c.SetSSLPort(true, fallback) @@ -321,12 +361,15 @@ func WithSSLPort(fallback bool) Option { } } -// WithDebugLog enables debug logging for the Client. The debug logger will log incoming and outgoing -// communication between the Client and the server to os.StdErr. +// WithDebugLog enables debug logging for the Client. // -// Note: The SMTP communication might include unencrypted authentication data, depending if you are -// using SMTP authentication and the type of authentication mechanism. This could pose a data -// protection problem. Use debug logging with care. +// This function activates debug logging, which logs incoming and outgoing communication between the +// Client and the SMTP server to os.Stderr. Be cautious when using this option, as the logs may include +// unencrypted authentication data, depending on the SMTP authentication method in use, which could +// pose a data protection risk. +// +// Returns: +// - An Option function that enables debug logging for the Client. func WithDebugLog() Option { return func(c *Client) error { c.useDebugLog = true @@ -334,10 +377,16 @@ func WithDebugLog() Option { } } -// WithLogger defines a custom logger for the Client. The logger has to satisfy the log.Logger -// interface and is only used when debug logging is enabled on the Client. +// WithLogger defines a custom logger for the Client. // -// By default we use log.Stdlog. +// This function sets a custom logger for the Client, which must satisfy the log.Logger interface. The custom +// logger is used only when debug logging is enabled. By default, log.Stdlog is used if no custom logger is provided. +// +// Parameters: +// - logger: A logger that satisfies the log.Logger interface. +// +// Returns: +// - An Option function that sets the custom logger for the Client. func WithLogger(logger log.Logger) Option { return func(c *Client) error { c.logger = logger @@ -345,9 +394,17 @@ func WithLogger(logger log.Logger) Option { } } -// WithHELO sets the HELO/EHLO string used for the the Client. +// WithHELO sets the HELO/EHLO string used by the Client. // -// By default we use os.Hostname to identify the HELO/EHLO string. +// This function configures the HELO/EHLO string sent by the Client when initiating communication +// with the SMTP server. By default, os.Hostname is used to identify the HELO/EHLO string. +// +// Parameters: +// - helo: The string to be used for the HELO/EHLO greeting. Must not be empty. +// +// Returns: +// - An Option function that sets the HELO/EHLO string for the Client. +// - An error if the provided HELO string is empty. func WithHELO(helo string) Option { return func(c *Client) error { if helo == "" { @@ -358,9 +415,18 @@ func WithHELO(helo string) Option { } } -// WithTLSPolicy sets the TLSPolicy of the Client and overrides the DefaultTLSPolicy +// WithTLSPolicy sets the TLSPolicy of the Client and overrides the DefaultTLSPolicy. +// +// This function configures the Client's TLSPolicy, specifying how the Client handles TLS for SMTP connections. +// It overrides the default policy. For best practices regarding SMTP TLS connections, it is recommended to use +// WithTLSPortPolicy instead. +// +// Parameters: +// - policy: The TLSPolicy to be applied to the Client. +// +// Returns: +// - An Option function that sets the TLSPolicy for the Client. // -// Note: To follow best-practices for SMTP TLS connections, it is recommended to use // WithTLSPortPolicy instead. func WithTLSPolicy(policy TLSPolicy) Option { return func(c *Client) error { @@ -372,13 +438,17 @@ func WithTLSPolicy(policy TLSPolicy) Option { // WithTLSPortPolicy enables explicit TLS via STARTTLS for the Client using the provided TLSPolicy. The // correct port is automatically set. // -// If TLSMandatory or TLSOpportunistic are provided as TLSPolicy, port 587 will be used for the connection. -// If the connection fails with TLSOpportunistic, the Client will attempt to connect on port 25 using -// using an unencrypted connection as a fallback. If NoTLS is provided, the Client will always use port 25. +// When TLSMandatory or TLSOpportunistic is provided as the TLSPolicy, port 587 is used for the connection. +// If the connection fails with TLSOpportunistic, the Client attempts to connect on port 25 using an unencrypted +// connection as a fallback. If NoTLS is specified, the Client will always use port 25. +// If WithPort has already been used to set a different port, that port takes precedence, and the automatic fallback +// mechanism is skipped. // -// Note: If a different port has already been set otherwise using WithPort, the selected port has higher -// precedence and is used to establish the SSL/TLS connection. In this case the authmatic fallback -// mechanism is skipped at all. +// Parameters: +// - policy: The TLSPolicy to be used for STARTTLS communication. +// +// Returns: +// - An Option function that sets the TLSPortPolicy for the Client. func WithTLSPortPolicy(policy TLSPolicy) Option { return func(c *Client) error { c.SetTLSPortPolicy(policy) @@ -386,8 +456,17 @@ func WithTLSPortPolicy(policy TLSPolicy) Option { } } -// WithTLSConfig sets the tls.Config for the Client and overrides the default. An error is returned -// if the provided tls.Config is invalid. +// WithTLSConfig sets the tls.Config for the Client and overrides the default configuration. +// +// This function configures the Client with a custom tls.Config. It overrides the default TLS settings. +// An error is returned if the provided tls.Config is nil or invalid. +// +// Parameters: +// - tlsconfig: A pointer to a tls.Config struct to be used for the Client. Must not be nil. +// +// Returns: +// - An Option function that sets the tls.Config for the Client. +// - An error if the provided tls.Config is invalid. func WithTLSConfig(tlsconfig *tls.Config) Option { return func(c *Client) error { if tlsconfig == nil { @@ -398,7 +477,15 @@ func WithTLSConfig(tlsconfig *tls.Config) Option { } } -// WithSMTPAuth configures the Client to use the specified SMTPAuthType for the SMTP authentication. +// WithSMTPAuth configures the Client to use the specified SMTPAuthType for SMTP authentication. +// +// This function sets the Client to use the specified SMTPAuthType for authenticating with the SMTP server. +// +// Parameters: +// - authtype: The SMTPAuthType to be used for SMTP authentication. +// +// Returns: +// - An Option function that configures the Client to use the specified SMTPAuthType. func WithSMTPAuth(authtype SMTPAuthType) Option { return func(c *Client) error { c.smtpAuthType = authtype @@ -406,8 +493,16 @@ func WithSMTPAuth(authtype SMTPAuthType) Option { } } -// WithSMTPAuthCustom sets a custom SMTP authentication mechanism for the Client. The provided -// authentication mechanism has to satisfy the smtp.Auth interface. +// WithSMTPAuthCustom sets a custom SMTP authentication mechanism for the Client. +// +// This function configures the Client to use a custom SMTP authentication mechanism. The provided +// mechanism must satisfy the smtp.Auth interface. +// +// Parameters: +// - smtpAuth: The custom SMTP authentication mechanism, which must implement the smtp.Auth interface. +// +// Returns: +// - An Option function that sets the custom SMTP authentication for the Client. func WithSMTPAuthCustom(smtpAuth smtp.Auth) Option { return func(c *Client) error { c.smtpAuth = smtpAuth @@ -416,7 +511,15 @@ func WithSMTPAuthCustom(smtpAuth smtp.Auth) Option { } } -// WithUsername sets the username, the Client will use for the SMTP authentication. +// WithUsername sets the username that the Client will use for SMTP authentication. +// +// This function configures the Client with the specified username for SMTP authentication. +// +// Parameters: +// - username: The username to be used for SMTP authentication. +// +// Returns: +// - An Option function that sets the username for the Client. func WithUsername(username string) Option { return func(c *Client) error { c.user = username @@ -424,7 +527,15 @@ func WithUsername(username string) Option { } } -// WithPassword sets the password, the Client will use for the SMTP authentication. +// WithPassword sets the password that the Client will use for SMTP authentication. +// +// This function configures the Client with the specified password for SMTP authentication. +// +// Parameters: +// - password: The password to be used for SMTP authentication. +// +// Returns: +// - An Option function that sets the password for the Client. func WithPassword(password string) Option { return func(c *Client) error { c.pass = password @@ -432,13 +543,17 @@ func WithPassword(password string) Option { } } -// WithDSN enables DSN (Delivery Status Notifications) for the Client as described in the RFC 1891. DSN -// only work if the server supports them. +// WithDSN enables DSN (Delivery Status Notifications) for the Client as described in RFC 1891. // -// By default we set DSNMailReturnOption to DSNMailReturnFull and DSNRcptNotifyOption to DSNRcptNotifySuccess -// and DSNRcptNotifyFailure. +// This function configures the Client to request DSN, which provides status notifications for email delivery. +// DSN is only effective if the SMTP server supports it. By default, DSNMailReturnOption is set to DSNMailReturnFull, +// and DSNRcptNotifyOption is set to DSNRcptNotifySuccess and DSNRcptNotifyFailure. // -// https://datatracker.ietf.org/doc/html/rfc1891 +// Returns: +// - An Option function that enables DSN for the Client. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc1891 func WithDSN() Option { return func(c *Client) error { c.requestDSN = true @@ -448,12 +563,21 @@ func WithDSN() Option { } } -// WithDSNMailReturnType enables DSN (Delivery Status Notifications) for the Client as described in the -// RFC 1891. DSN only work if the server supports them. +// WithDSNMailReturnType enables DSN (Delivery Status Notifications) for the Client as described in RFC 1891. // -// It will set the DSNMailReturnOption to the provided value. +// This function configures the Client to request DSN and sets the DSNMailReturnOption to the provided value. +// DSN is only effective if the SMTP server supports it. The provided option must be either DSNMailReturnHeadersOnly +// or DSNMailReturnFull; otherwise, an error is returned. // -// https://datatracker.ietf.org/doc/html/rfc1891 +// Parameters: +// - option: The DSNMailReturnOption to be used (DSNMailReturnHeadersOnly or DSNMailReturnFull). +// +// Returns: +// - An Option function that sets the DSNMailReturnOption for the Client. +// - An error if an invalid DSNMailReturnOption is provided. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc1891 func WithDSNMailReturnType(option DSNMailReturnOption) Option { return func(c *Client) error { switch option { @@ -469,12 +593,22 @@ func WithDSNMailReturnType(option DSNMailReturnOption) Option { } } -// WithDSNRcptNotifyType enables DSN (Delivery Status Notifications) for the Client as described in the -// RFC 1891. DSN only work if the server supports them. +// WithDSNRcptNotifyType enables DSN (Delivery Status Notifications) for the Client as described in RFC 1891. // -// It will set the DSNRcptNotifyOption to the provided values. +// This function configures the Client to request DSN and sets the DSNRcptNotifyOption to the provided values. +// The provided options must be valid DSNRcptNotifyOption types. If DSNRcptNotifyNever is combined with +// any other notification type (such as DSNRcptNotifySuccess, DSNRcptNotifyFailure, or DSNRcptNotifyDelay), +// an error is returned. // -// https://datatracker.ietf.org/doc/html/rfc1891 +// Parameters: +// - opts: A variadic list of DSNRcptNotifyOption values (e.g., DSNRcptNotifySuccess, DSNRcptNotifyFailure). +// +// Returns: +// - An Option function that sets the DSNRcptNotifyOption for the Client. +// - An error if invalid DSNRcptNotifyOption values are provided or incompatible combinations are used. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc1891 func WithDSNRcptNotifyType(opts ...DSNRcptNotifyOption) Option { return func(c *Client) error { var rcptOpts []string @@ -508,8 +642,11 @@ func WithDSNRcptNotifyType(opts ...DSNRcptNotifyOption) Option { // WithoutNoop indicates that the Client should skip the "NOOP" command during the dial. // -// This is useful for servers which delay potentially unwanted clients when they perform commands -// other than AUTH. For example Microsoft Exchange's Tarpit. +// This option is useful for servers that delay potentially unwanted clients when they perform +// commands other than AUTH, such as Microsoft's Exchange Tarpit. +// +// Returns: +// - An Option function that configures the Client to skip the "NOOP" command. func WithoutNoop() Option { return func(c *Client) error { c.noNoop = true @@ -517,8 +654,16 @@ func WithoutNoop() Option { } } -// WithDialContextFunc sets the provided DialContextFunc as DialContext and overrides the default DialContext for -// connecting to the SMTP server +// WithDialContextFunc sets the provided DialContextFunc as the DialContext for connecting to the SMTP server. +// +// This function overrides the default DialContext function used by the Client when establishing a connection +// to the SMTP server with the provided DialContextFunc. +// +// Parameters: +// - dialCtxFunc: The custom DialContextFunc to be used for connecting to the SMTP server. +// +// Returns: +// - An Option function that sets the custom DialContextFunc for the Client. func WithDialContextFunc(dialCtxFunc DialContextFunc) Option { return func(c *Client) error { c.dialContextFunc = dialCtxFunc @@ -526,35 +671,50 @@ func WithDialContextFunc(dialCtxFunc DialContextFunc) Option { } } -// TLSPolicy returns the TLSPolicy that is currently set on the Client as string +// TLSPolicy returns the TLSPolicy that is currently set on the Client as a string. +// +// This method retrieves the current TLSPolicy configured for the Client and returns it as a string representation. +// +// Returns: +// - A string representing the currently set TLSPolicy for the Client. func (c *Client) TLSPolicy() string { return c.tlspolicy.String() } // ServerAddr returns the server address that is currently set on the Client in the format "host:port". +// +// This method constructs and returns the server address using the host and port currently configured +// for the Client. +// +// Returns: +// - A string representing the server address in the format "host:port". func (c *Client) ServerAddr() string { return fmt.Sprintf("%s:%d", c.host, c.port) } -// SetTLSPolicy sets or overrides the TLSPolicy that is currently set on the Client with the given -// TLSPolicy. +// SetTLSPolicy sets or overrides the TLSPolicy currently configured on the Client with the given TLSPolicy. // -// Note: To follow best-practices for SMTP TLS connections, it is recommended to use SetTLSPortPolicy -// instead. +// This method allows the user to set a new TLSPolicy for the Client. For best practices regarding +// SMTP TLS connections, it is recommended to use SetTLSPortPolicy instead. +// +// Parameters: +// - policy: The TLSPolicy to be set for the Client. func (c *Client) SetTLSPolicy(policy TLSPolicy) { c.tlspolicy = policy } -// SetTLSPortPolicy sets or overrides the TLSPolicy that is currently set on the Client with the given -// TLSPolicy. The correct port is automatically set. +// SetTLSPortPolicy sets or overrides the TLSPolicy currently configured on the Client with the given TLSPolicy. +// The correct port is automatically set based on the specified policy. // -// If TLSMandatory or TLSOpportunistic are provided as TLSPolicy, port 587 will be used for the connection. +// If TLSMandatory or TLSOpportunistic is provided as the TLSPolicy, port 587 will be used for the connection. // If the connection fails with TLSOpportunistic, the Client will attempt to connect on port 25 using -// using an unencrypted connection as a fallback. If NoTLS is provided, the Client will always use port 25. +// an unencrypted connection as a fallback. If NoTLS is provided, the Client will always use port 25. // -// Note: If a different port has already been set otherwise using WithPort, the selected port has higher -// precedence and is used to establish the SSL/TLS connection. In this case the authmatic fallback -// mechanism is skipped at all. +// Note: If a different port has already been set using WithPort, that port takes precedence and is used +// to establish the SSL/TLS connection, skipping the automatic fallback mechanism. +// +// Parameters: +// - policy: The TLSPolicy to be set for the Client. func (c *Client) SetTLSPortPolicy(policy TLSPolicy) { if c.port == DefaultPort { c.port = DefaultPortTLS @@ -570,21 +730,29 @@ func (c *Client) SetTLSPortPolicy(policy TLSPolicy) { c.tlspolicy = policy } -// SetSSL sets or overrides wether the Client should use implicit SSL/TLS. +// SetSSL sets or overrides whether the Client should use implicit SSL/TLS. +// +// This method configures the Client to either enable or disable implicit SSL/TLS for secure communication. +// +// Parameters: +// - ssl: A boolean value indicating whether to enable (true) or disable (false) implicit SSL/TLS. func (c *Client) SetSSL(ssl bool) { c.useSSL = ssl } -// SetSSLPort sets or overrides wether the Client should use implicit SSL/TLS with optional fallback. The -// correct port is automatically set. +// SetSSLPort sets or overrides whether the Client should use implicit SSL/TLS with optional fallback. +// The correct port is automatically set. // -// If ssl is set to true, the default port 25 will be overriden with port 465. If fallback is set to true -// and the SSL/TLS connection fails, the Client will attempt to connect on port 25 using using an -// unencrypted connection. +// If ssl is set to true, the default port 25 will be overridden with port 465. If fallback is set to true +// and the SSL/TLS connection fails, the Client will attempt to connect on port 25 using an unencrypted +// connection. // -// Note: If a different port has already been set otherwise using WithPort, the selected port has higher -// precedence and is used to establish the SSL/TLS connection. In this case the authmatic fallback -// mechanism is skipped at all. +// Note: If a different port has already been set using WithPort, that port takes precedence and is used +// to establish the SSL/TLS connection, skipping the automatic fallback mechanism. +// +// Parameters: +// - ssl: A boolean value indicating whether to enable implicit SSL/TLS. +// - fallback: A boolean value indicating whether to enable fallback to an unencrypted connection. func (c *Client) SetSSLPort(ssl bool, fallback bool) { if c.port == DefaultPort { if ssl { @@ -600,12 +768,15 @@ func (c *Client) SetSSLPort(ssl bool, fallback bool) { c.useSSL = ssl } -// SetDebugLog sets or overrides wether the Client is using debug logging. The debug logger will log -// incoming and outgoing communication between the Client and the server to os.StdErr. +// SetDebugLog sets or overrides whether the Client is using debug logging. The debug logger will log incoming +// and outgoing communication between the Client and the server to os.Stderr. // -// Note: The SMTP communication might include unencrypted authentication data, depending if you are -// using SMTP authentication and the type of authentication mechanism. This could pose a data -// protection problem. Use debug logging with care. +// Note: The SMTP communication might include unencrypted authentication data, depending on whether you are using +// SMTP authentication and the type of authentication mechanism. This could pose a data protection risk. Use +// debug logging with caution. +// +// Parameters: +// - val: A boolean value indicating whether to enable (true) or disable (false) debug logging. func (c *Client) SetDebugLog(val bool) { c.useDebugLog = val if c.smtpClient != nil { @@ -613,10 +784,14 @@ func (c *Client) SetDebugLog(val bool) { } } -// SetLogger sets of overrides the custom logger currently set for the Client. The logger has to satisfy -// the log.Logger interface and is only used when debug logging is enabled on the Client. +// SetLogger sets or overrides the custom logger currently used by the Client. The logger must +// satisfy the log.Logger interface and is only utilized when debug logging is enabled on the +// Client. // -// By default we use log.Stdlog. +// By default, log.Stdlog is used if no custom logger is provided. +// +// Parameters: +// - logger: A logger that satisfies the log.Logger interface to be set for the Client. func (c *Client) SetLogger(logger log.Logger) { c.logger = logger if c.smtpClient != nil { @@ -624,12 +799,18 @@ func (c *Client) SetLogger(logger log.Logger) { } } -// SetTLSConfig sets or overrides the tls.Config that is currently set for the Client with the given value. -// An error is returned if the provided tls.Config is invalid. +// SetTLSConfig sets or overrides the tls.Config currently configured for the Client with the +// given value. An error is returned if the provided tls.Config is invalid. +// +// This method ensures that the provided tls.Config is not nil before updating the Client's +// TLS configuration. +// +// Parameters: +// - tlsconfig: A pointer to the tls.Config struct to be set for the Client. Must not be nil. +// +// Returns: +// - An error if the provided tls.Config is invalid or nil. func (c *Client) SetTLSConfig(tlsconfig *tls.Config) error { - c.mutex.Lock() - defer c.mutex.Unlock() - if tlsconfig == nil { return ErrInvalidTLSConfig } @@ -637,7 +818,12 @@ func (c *Client) SetTLSConfig(tlsconfig *tls.Config) error { return nil } -// SetUsername sets or overrides the username, the Client will use for the SMTP authentication. +// SetUsername sets or overrides the username that the Client will use for SMTP authentication. +// +// This method updates the username used by the Client for authenticating with the SMTP server. +// +// Parameters: +// - username: The username to be set for SMTP authentication. func (c *Client) SetUsername(username string) { c.user = username } From 756269644ecb073c04ba2de5eef5ac66a8968139 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 6 Oct 2024 13:39:40 +0200 Subject: [PATCH 48/60] Update SMTP client documentation with detailed descriptions Enhance the SMTP client method documentation by adding detailed explanations of the methods' purposes, parameters, return values, and operational flow. This improves clarity, making it easier for developers to understand the functionality and usage of each method. --- client.go | 163 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 139 insertions(+), 24 deletions(-) diff --git a/client.go b/client.go index dc7d46c..45fd764 100644 --- a/client.go +++ b/client.go @@ -828,20 +828,38 @@ func (c *Client) SetUsername(username string) { c.user = username } -// SetPassword sets or overrides the password, the Client will use for the SMTP authentication. +// SetPassword sets or overrides the password that the Client will use for SMTP authentication. +// +// This method updates the password used by the Client for authenticating with the SMTP server. +// +// Parameters: +// - password: The password to be set for SMTP authentication. func (c *Client) SetPassword(password string) { c.pass = password } -// SetSMTPAuth sets or overrides the SMTPAuthType that is currently set on the Client for the SMTP +// SetSMTPAuth sets or overrides the SMTPAuthType currently configured on the Client for SMTP // authentication. +// +// This method updates the authentication type used by the Client for authenticating with the +// SMTP server and resets any custom SMTP authentication mechanism. +// +// Parameters: +// - authtype: The SMTPAuthType to be set for the Client. func (c *Client) SetSMTPAuth(authtype SMTPAuthType) { c.smtpAuthType = authtype c.smtpAuth = nil } -// SetSMTPAuthCustom sets or overrides the custom SMTP authentication mechanism currently set for -// the Client. The provided authentication mechanism has to satisfy the smtp.Auth interface. +// SetSMTPAuthCustom sets or overrides the custom SMTP authentication mechanism currently +// configured for the Client. The provided authentication mechanism must satisfy the +// smtp.Auth interface. +// +// This method updates the authentication mechanism used by the Client for authenticating +// with the SMTP server and sets the authentication type to SMTPAuthCustom. +// +// Parameters: +// - smtpAuth: The custom SMTP authentication mechanism to be set for the Client. func (c *Client) SetSMTPAuthCustom(smtpAuth smtp.Auth) { c.smtpAuth = smtpAuth c.smtpAuthType = SMTPAuthCustom @@ -849,15 +867,19 @@ func (c *Client) SetSMTPAuthCustom(smtpAuth smtp.Auth) { // DialWithContext establishes a connection to the server using the provided context.Context. // -// Before connecting to the server, the function will add a deadline of the Client's timeout -// to the provided context.Context. +// This function adds a deadline based on the Client's timeout to the provided context.Context +// before connecting to the server. After dialing the defined DialContextFunc and successfully +// establishing the connection, it sends the HELO/EHLO SMTP command, followed by optional +// STARTTLS and SMTP AUTH commands. If debug logging is enabled, it attaches the log.Logger. // -// After dialing the DialContextFunc defined in the Client and successfully establishing the -// connection to the SMTP server, it will send the HELO/EHLO SMTP command followed by the -// optional STARTTLS and SMTP AUTH commands. It will also attach the log.Logger in case -// debug logging is enabled on the Client. +// After this method is called, the Client will have an active (cancelable) connection to the +// SMTP server. // -// From this point in time the Client has an active (cancelable) connection to the SMTP server. +// Parameters: +// - dialCtx: The context.Context used to control the connection timeout and cancellation. +// +// Returns: +// - An error if the connection to the SMTP server fails or any subsequent command fails. func (c *Client) DialWithContext(dialCtx context.Context) error { c.mutex.Lock() defer c.mutex.Unlock() @@ -914,8 +936,15 @@ func (c *Client) DialWithContext(dialCtx context.Context) error { return nil } -// Close terminates the connection to the SMTP server, returning an error if the disconnection fails. -// If the connection is already closed, we considered this a no-op and disregard any error. +// Close terminates the connection to the SMTP server, returning an error if the disconnection +// fails. If the connection is already closed, this method is a no-op and disregards any error. +// +// This function checks if the Client's SMTP connection is active. If not, it simply returns +// without any action. If the connection is active, it attempts to gracefully close the +// connection using the Quit method. +// +// Returns: +// - An error if the disconnection fails; otherwise, returns nil. func (c *Client) Close() error { if !c.smtpClient.HasConnection() { return nil @@ -928,6 +957,13 @@ func (c *Client) Close() error { } // Reset sends an SMTP RSET command to reset the state of the current SMTP session. +// +// This method checks the connection to the SMTP server and, if the connection is valid, +// it sends an RSET command to reset the session state. If the connection is invalid or +// the command fails, an error is returned. +// +// Returns: +// - An error if the connection check fails or if sending the RSET command fails; otherwise, returns nil. func (c *Client) Reset() error { if err := c.checkConn(); err != nil { return err @@ -939,16 +975,38 @@ func (c *Client) Reset() error { return nil } -// DialAndSend establishes a connection to the server and sends out the provided Msg. It will call -// DialAndSendWithContext with an empty Context.Background +// DialAndSend establishes a connection to the server and sends out the provided Msg. +// It calls DialAndSendWithContext with an empty Context.Background. +// +// This method simplifies the process of connecting to the SMTP server and sending messages +// by using a default context. It prepares the messages for sending and ensures the connection +// is established before attempting to send them. +// +// Parameters: +// - messages: A variadic list of pointers to Msg objects to be sent. +// +// Returns: +// - An error if the connection fails or if sending the messages fails; otherwise, returns nil. func (c *Client) DialAndSend(messages ...*Msg) error { ctx := context.Background() return c.DialAndSendWithContext(ctx, messages...) } -// DialAndSendWithContext establishes a connection to the SMTP server using DialWithContext using the -// provided context.Context, then sends out the given Msg. After successful delivery the Client -// will close the connection to the server. +// DialAndSendWithContext establishes a connection to the SMTP server using DialWithContext +// with the provided context.Context, then sends out the given Msg. After successful delivery, +// the Client will close the connection to the server. +// +// This method first attempts to connect to the SMTP server using the provided context. +// Upon successful connection, it sends the specified messages and ensures that the connection +// is closed after the operation, regardless of success or failure in sending the messages. +// +// Parameters: +// - ctx: The context.Context to control the connection timeout and cancellation. +// - messages: A variadic list of pointers to Msg objects to be sent. +// +// Returns: +// - An error if the connection fails, if sending the messages fails, or if closing the +// connection fails; otherwise, returns nil. func (c *Client) DialAndSendWithContext(ctx context.Context, messages ...*Msg) error { if err := c.DialWithContext(ctx); err != nil { return fmt.Errorf("dial failed: %w", err) @@ -966,9 +1024,18 @@ func (c *Client) DialAndSendWithContext(ctx context.Context, messages ...*Msg) e return nil } -// auth attempts to authenticate the client using SMTP AUTH mechanisms. It checks the connection, determines -// the supported authentication methods, and applies the appropriate authentication type. Returns an error if -// authentication fails. +// auth attempts to authenticate the client using SMTP AUTH mechanisms. It checks the connection, +// determines the supported authentication methods, and applies the appropriate authentication +// type. An error is returned if authentication fails. +// +// This method first verifies the connection to the SMTP server. If no custom authentication +// mechanism is provided, it checks which authentication methods are supported by the server. +// Based on the configured SMTPAuthType, it sets up the appropriate authentication mechanism. +// Finally, it attempts to authenticate the client using the selected method. +// +// Returns: +// - An error if the connection check fails, if no supported authentication method is found, +// or if the authentication process fails. func (c *Client) auth() error { if err := c.checkConn(); err != nil { return fmt.Errorf("failed to authenticate: %w", err) @@ -1041,8 +1108,21 @@ func (c *Client) auth() error { return nil } -// sendSingleMsg sends out a single message and returns an error if the transmission/delivery fails. -// It is invoked by the public Send methods +// sendSingleMsg sends out a single message and returns an error if the transmission or +// delivery fails. It is invoked by the public Send methods. +// +// This method handles the process of sending a single email message through the SMTP +// client. It performs several checks and operations, including verifying the encoding, +// retrieving the sender and recipient addresses, and managing delivery status notifications +// (DSN). It attempts to send the message and handles any errors that occur during the +// transmission process, ensuring that any necessary cleanup is performed (such as resetting +// the SMTP client if an error occurs). +// +// Parameters: +// - message: A pointer to the Msg object representing the email message to be sent. +// +// Returns: +// - An error if any part of the sending process fails; otherwise, returns nil. func (c *Client) sendSingleMsg(message *Msg) error { c.mutex.Lock() defer c.mutex.Unlock() @@ -1141,7 +1221,18 @@ func (c *Client) sendSingleMsg(message *Msg) error { return nil } -// checkConn makes sure that a required server connection is available and extends the connection deadline +// checkConn ensures that a required server connection is available and extends the connection +// deadline. +// +// This method verifies whether there is an active connection to the SMTP server. If there is no +// connection, it returns an error. If the "noNoop" flag is not set, it sends a NOOP command to +// the server to confirm the connection is still valid. Finally, it updates the connection +// deadline based on the specified timeout value. If any operation fails, the appropriate error +// is returned. +// +// Returns: +// - An error if there is no active connection, if the NOOP command fails, or if extending +// the deadline fails; otherwise, returns nil. func (c *Client) checkConn() error { if !c.smtpClient.HasConnection() { return ErrNoActiveConnection @@ -1160,11 +1251,25 @@ func (c *Client) checkConn() error { } // serverFallbackAddr returns the currently set combination of hostname and fallback port. +// +// This method constructs and returns the server address using the host and fallback port +// currently configured for the Client. It is useful for establishing a connection when +// the primary port is unavailable. +// +// Returns: +// - A string representing the server address in the format "host:fallbackPort". func (c *Client) serverFallbackAddr() string { return fmt.Sprintf("%s:%d", c.host, c.fallbackPort) } // setDefaultHelo sets the HELO/EHLO hostname to the local machine's hostname. +// +// This method retrieves the local hostname using the operating system's hostname function +// and sets it as the HELO/EHLO string for the Client. If retrieving the hostname fails, +// an error is returned. +// +// Returns: +// - An error if there is a failure in reading the local hostname; otherwise, returns nil. func (c *Client) setDefaultHelo() error { hostname, err := os.Hostname() if err != nil { @@ -1176,6 +1281,16 @@ func (c *Client) setDefaultHelo() error { // tls establishes a TLS connection based on the client's TLS policy and configuration. // Returns an error if no active connection exists or if a TLS error occurs. +// +// This method first checks if there is an active connection to the SMTP server. If SSL is not +// being used and the TLS policy is not set to NoTLS, it checks for STARTTLS support. Depending +// on the TLS policy (mandatory or opportunistic), it may initiate a TLS connection using the +// StartTLS method. The method also retrieves the TLS connection state to determine if the +// connection is encrypted and returns any errors encountered during these processes. +// +// Returns: +// - An error if there is no active connection, if STARTTLS is required but not supported, +// or if there are issues during the TLS handshake; otherwise, returns nil. func (c *Client) tls() error { if !c.smtpClient.HasConnection() { return ErrNoActiveConnection From 3e8706d52eb18edbb1a13c5f5c6be5516465ac89 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 6 Oct 2024 13:41:45 +0200 Subject: [PATCH 49/60] Refactor Send method documentation in client files Updated the documentation for the Send method in client_119.go and client_120.go to provide clearer explanations, include detailed descriptions of parameters, and specify return values. Ensured consistency across files by elaborating on error handling and connection checks. --- client_119.go | 19 ++++++++++++++++--- client_120.go | 17 ++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/client_119.go b/client_119.go index 24a0118..093967e 100644 --- a/client_119.go +++ b/client_119.go @@ -10,9 +10,22 @@ package mail import "errors" // Send attempts to send one or more Msg using the Client connection to the SMTP server. -// If the Client has no active connection to the server, Send will fail with an error. For each of the -// provided Msg it will associate a SendError to the Msg in case there of a transmission or delivery -// error. +// If the Client has no active connection to the server, Send will fail with an error. For each +// of the provided Msg, it will associate a SendError with the Msg in case of a transmission +// or delivery error. +// +// This method first checks for an active connection to the SMTP server. If the connection is +// not valid, it returns a SendError. It then iterates over the provided messages, attempting +// to send each one. If an error occurs during sending, the method records the error and +// associates it with the corresponding Msg. If multiple errors are encountered, it aggregates +// them into a single SendError to be returned. +// +// Parameters: +// - messages: A variadic list of pointers to Msg objects to be sent. +// +// Returns: +// - An error that represents the sending result, which may include multiple SendErrors if +// any occurred; otherwise, returns nil. func (c *Client) Send(messages ...*Msg) error { if err := c.checkConn(); err != nil { return &SendError{Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err)} diff --git a/client_120.go b/client_120.go index c6049eb..012a4f7 100644 --- a/client_120.go +++ b/client_120.go @@ -12,9 +12,20 @@ import ( ) // Send attempts to send one or more Msg using the Client connection to the SMTP server. -// If the Client has no active connection to the server, Send will fail with an error. For each of the -// provided Msg it will associate a SendError to the Msg in case there of a transmission or delivery -// error. +// If the Client has no active connection to the server, Send will fail with an error. For each +// of the provided Msg, it will associate a SendError with the Msg in case of a transmission +// or delivery error. +// +// This method first checks for an active connection to the SMTP server. If the connection is +// not valid, it returns an error wrapped in a SendError. It then iterates over the provided +// messages, attempting to send each one. If an error occurs during sending, the method records +// the error and associates it with the corresponding Msg. +// +// Parameters: +// - messages: A variadic list of pointers to Msg objects to be sent. +// +// Returns: +// - An error that aggregates any SendErrors encountered during the sending process; otherwise, returns nil. func (c *Client) Send(messages ...*Msg) (returnErr error) { if err := c.checkConn(); err != nil { returnErr = &SendError{Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err)} From 2c1082fe42c22f1001aa47f7896fdb16df03e9d1 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 6 Oct 2024 13:51:36 +0200 Subject: [PATCH 50/60] Enhance EML parsing function documentation Added detailed doc comments to EML parsing functions, specifying parameters, return values, and providing thorough explanations of functionalities to improve code understandability and maintainability. This helps future developers and users comprehend the usage and behavior of these functions more effectively. --- eml.go | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 176 insertions(+), 9 deletions(-) diff --git a/eml.go b/eml.go index b57ad3c..35cd90d 100644 --- a/eml.go +++ b/eml.go @@ -18,13 +18,35 @@ import ( "strings" ) -// EMLToMsgFromString will parse a given EML string and returns a pre-filled Msg pointer. +// EMLToMsgFromString parses a given EML string and returns a pre-filled Msg pointer. +// +// This function takes an EML formatted string, converts it into a bytes buffer, and then +// calls EMLToMsgFromReader to parse the buffer and create a Msg object. This provides a +// convenient way to convert EML strings directly into Msg objects. +// +// Parameters: +// - emlString: A string containing the EML formatted message. +// +// Returns: +// - A pointer to the Msg object populated with the parsed data, and an error if parsing +// fails. func EMLToMsgFromString(emlString string) (*Msg, error) { eb := bytes.NewBufferString(emlString) return EMLToMsgFromReader(eb) } -// EMLToMsgFromReader will parse a reader that holds EML content and returns a pre-filled Msg pointer. +// EMLToMsgFromReader parses a reader that holds EML content and returns a pre-filled Msg pointer. +// +// This function reads EML content from the provided io.Reader and populates a Msg object +// with the parsed data. It initializes the Msg and extracts headers and body parts from +// the EML content. Any errors encountered during parsing are returned. +// +// Parameters: +// - reader: An io.Reader containing the EML formatted message. +// +// Returns: +// - A pointer to the Msg object populated with the parsed data, and an error if parsing +// fails. func EMLToMsgFromReader(reader io.Reader) (*Msg, error) { msg := &Msg{ addrHeader: make(map[AddrHeader][]*netmail.Address), @@ -45,7 +67,19 @@ func EMLToMsgFromReader(reader io.Reader) (*Msg, error) { return msg, nil } -// EMLToMsgFromFile will open and parse a .eml file at a provided file path and returns a pre-filled Msg pointer. +// EMLToMsgFromFile opens and parses a .eml file at a provided file path and returns a +// pre-filled Msg pointer. +// +// This function attempts to read and parse an EML file located at the specified file path. +// It initializes a Msg object and populates it with the parsed headers and body. Any errors +// encountered during the file operations or parsing are returned. +// +// Parameters: +// - filePath: The path to the .eml file to be parsed. +// +// Returns: +// - A pointer to the Msg object populated with the parsed data, and an error if parsing +// fails. func EMLToMsgFromFile(filePath string) (*Msg, error) { msg := &Msg{ addrHeader: make(map[AddrHeader][]*netmail.Address), @@ -67,6 +101,18 @@ func EMLToMsgFromFile(filePath string) (*Msg, error) { } // parseEML parses the EML's headers and body and inserts the parsed values into the Msg. +// +// This function extracts relevant header fields and body content from the parsed EML message +// and stores them in the provided Msg object. It handles various header types and body +// parts, ensuring that the Msg is correctly populated with all necessary information. +// +// Parameters: +// - parsedMsg: A pointer to the netmail.Message containing the parsed EML data. +// - bodybuf: A bytes.Buffer containing the body content of the EML message. +// - msg: A pointer to the Msg object to be populated with the parsed data. +// +// Returns: +// - An error if any issues occur during the parsing process; otherwise, returns nil. func parseEML(parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error { if err := parseEMLHeaders(&parsedMsg.Header, msg); err != nil { return fmt.Errorf("failed to parse EML headers: %w", err) @@ -78,6 +124,17 @@ func parseEML(parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error } // readEML opens an EML file and uses net/mail to parse the header and body. +// +// This function opens the specified EML file for reading and utilizes the net/mail package +// to parse the message's headers and body. It returns the parsed message and a buffer +// containing the body content, along with any errors encountered during the process. +// +// Parameters: +// - filePath: The path to the EML file to be opened and parsed. +// +// Returns: +// - A pointer to the parsed netmail.Message, a bytes.Buffer containing the body, and an +// error if any issues occur during file operations or parsing. func readEML(filePath string) (*netmail.Message, *bytes.Buffer, error) { fileHandle, err := os.Open(filePath) if err != nil { @@ -90,6 +147,18 @@ func readEML(filePath string) (*netmail.Message, *bytes.Buffer, error) { } // readEMLFromReader uses net/mail to parse the header and body from a given io.Reader. +// +// This function reads the EML content from the provided io.Reader and uses the net/mail +// package to parse the message's headers and body. It returns the parsed netmail.Message +// along with a bytes.Buffer containing the body content. Any errors encountered during +// the parsing process are returned. +// +// Parameters: +// - reader: An io.Reader containing the EML formatted message. +// +// Returns: +// - A pointer to the parsed netmail.Message, a bytes.Buffer containing the body, and an +// error if any issues occur during parsing. func readEMLFromReader(reader io.Reader) (*netmail.Message, *bytes.Buffer, error) { parsedMsg, err := netmail.ReadMessage(reader) if err != nil { @@ -104,8 +173,18 @@ func readEMLFromReader(reader io.Reader) (*netmail.Message, *bytes.Buffer, error return parsedMsg, &buf, nil } -// parseEMLHeaders will check the EML headers for the most common headers and set the according settings -// in the Msg. +// parseEMLHeaders parses the EML's headers and populates the Msg with relevant information. +// +// This function checks the EML headers for common headers and sets the corresponding fields +// in the Msg object. It extracts address headers, content types, and other relevant data +// for further processing. +// +// Parameters: +// - mailHeader: A pointer to the netmail.Header containing the EML headers. +// - msg: A pointer to the Msg object to be populated with parsed header information. +// +// Returns: +// - An error if parsing the headers fails; otherwise, returns nil. func parseEMLHeaders(mailHeader *netmail.Header, msg *Msg) error { commonHeaders := []Header{ HeaderContentType, HeaderImportance, HeaderInReplyTo, HeaderListUnsubscribe, @@ -173,7 +252,19 @@ func parseEMLHeaders(mailHeader *netmail.Header, msg *Msg) error { return nil } -// parseEMLBodyParts parses the body of a EML based on the different content types and encodings. +// parseEMLBodyParts parses the body of an EML based on the different content types and encodings. +// +// This function examines the content type of the parsed EML message and processes the body +// parts accordingly. It handles both plain text and multipart types, ensuring that the +// Msg object is populated with the appropriate body content. +// +// Parameters: +// - parsedMsg: A pointer to the netmail.Message containing the parsed EML data. +// - bodybuf: A bytes.Buffer containing the body content of the EML message. +// - msg: A pointer to the Msg object to be populated with the parsed body content. +// +// Returns: +// - An error if any issues occur during the body parsing process; otherwise, returns nil. func parseEMLBodyParts(parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error { // Extract the transfer encoding of the body mediatype, params, err := mime.ParseMediaType(parsedMsg.Header.Get(HeaderContentType.String())) @@ -210,7 +301,20 @@ func parseEMLBodyParts(parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *M return nil } -// parseEMLBodyPlain parses the mail body of plain type mails. +// parseEMLBodyPlain parses the mail body of plain type messages. +// +// This function handles the parsing of plain text messages based on their encoding. It +// identifies the content transfer encoding and decodes the body content accordingly, +// storing the result in the provided Msg object. +// +// Parameters: +// - mediatype: The media type of the message (e.g., text/plain). +// - parsedMsg: A pointer to the netmail.Message containing the parsed EML data. +// - bodybuf: A bytes.Buffer containing the body content of the EML message. +// - msg: A pointer to the Msg object to be populated with the parsed body content. +// +// Returns: +// - An error if any issues occur during the parsing of the plain body; otherwise, returns nil. func parseEMLBodyPlain(mediatype string, parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error { contentTransferEnc := parsedMsg.Header.Get(HeaderContentTransferEnc.String()) // If no Content-Transfer-Encoding is set, we can imply 7bit US-ASCII encoding @@ -248,7 +352,20 @@ func parseEMLBodyPlain(mediatype string, parsedMsg *netmail.Message, bodybuf *by return fmt.Errorf("unsupported Content-Transfer-Encoding") } -// parseEMLMultipart parses a multipart body part of a EML +// parseEMLMultipart parses a multipart body part of an EML message. +// +// This function handles the parsing of multipart messages, extracting the individual parts +// and determining their content types. It processes each part according to its content type +// and ensures that all relevant data is stored in the Msg object. +// +// Parameters: +// - params: A map containing the parameters from the multipart content type. +// - bodybuf: A bytes.Buffer containing the body content of the EML message. +// - msg: A pointer to the Msg object to be populated with the parsed body parts. +// +// Returns: +// - An error if any issues occur during the parsing of the multipart body; otherwise, +// returns nil. func parseEMLMultipart(params map[string]string, bodybuf *bytes.Buffer, msg *Msg) error { boundary, ok := params["boundary"] if !ok { @@ -349,6 +466,14 @@ ReadNextPart: } // parseEMLEncoding parses and determines the encoding of the message. +// +// This function extracts the content transfer encoding from the EML headers and sets the +// corresponding encoding in the Msg object. It ensures that the correct encoding is used +// for further processing of the message content. +// +// Parameters: +// - mailHeader: A pointer to the netmail.Header containing the EML headers. +// - msg: A pointer to the Msg object to be updated with the encoding information. func parseEMLEncoding(mailHeader *netmail.Header, msg *Msg) { if value := mailHeader.Get(HeaderContentTransferEnc.String()); value != "" { switch { @@ -363,6 +488,14 @@ func parseEMLEncoding(mailHeader *netmail.Header, msg *Msg) { } // parseEMLContentTypeCharset parses and determines the charset and content type of the message. +// +// This function extracts the content type and charset from the EML headers, setting them +// appropriately in the Msg object. It ensures that the Msg object is configured with the +// correct content type for further processing. +// +// Parameters: +// - mailHeader: A pointer to the netmail.Header containing the EML headers. +// - msg: A pointer to the Msg object to be updated with content type and charset information. func parseEMLContentTypeCharset(mailHeader *netmail.Header, msg *Msg) { if value := mailHeader.Get(HeaderContentType.String()); value != "" { contentType, optional := parseMultiPartHeader(value) @@ -377,6 +510,17 @@ func parseEMLContentTypeCharset(mailHeader *netmail.Header, msg *Msg) { } // handleEMLMultiPartBase64Encoding sets the content body of a base64 encoded Part. +// +// This function decodes the base64 encoded content of a multipart part and stores the +// resulting content in the provided Part object. It handles any errors that occur during +// the decoding process. +// +// Parameters: +// - multiPartData: A byte slice containing the base64 encoded data. +// - part: A pointer to the Part object where the decoded content will be stored. +// +// Returns: +// - An error if the base64 decoding fails; otherwise, returns nil. func handleEMLMultiPartBase64Encoding(multiPartData []byte, part *Part) error { part.SetEncoding(EncodingB64) content, err := base64.StdEncoding.DecodeString(string(multiPartData)) @@ -387,7 +531,17 @@ func handleEMLMultiPartBase64Encoding(multiPartData []byte, part *Part) error { return nil } -// parseMultiPartHeader parses a multipart header and returns the value and optional parts as separate map. +// parseMultiPartHeader parses a multipart header and returns the value and optional parts as a map. +// +// This function splits a multipart header into its main value and any optional parameters, +// returning them separately. It helps in processing multipart messages by extracting +// relevant information from headers. +// +// Parameters: +// - multiPartHeader: A string representing the multipart header to be parsed. +// +// Returns: +// - The main header value as a string and a map of optional parameters. func parseMultiPartHeader(multiPartHeader string) (header string, optional map[string]string) { optional = make(map[string]string) headerSplit := strings.SplitN(multiPartHeader, ";", 2) @@ -403,6 +557,19 @@ func parseMultiPartHeader(multiPartHeader string) (header string, optional map[s } // parseEMLAttachmentEmbed parses a multipart that is an attachment or embed. +// +// This function handles the parsing of multipart sections that are marked as attachments or +// embedded content. It processes the content disposition and sets the appropriate fields in +// the Msg object based on the parsed data. +// +// Parameters: +// - contentDisposition: A slice of strings containing the content disposition header. +// - multiPart: A pointer to the multipart.Part to be parsed. +// - msg: A pointer to the Msg object to be populated with the attachment or embed data. +// +// Returns: +// - An error if any issues occur during the parsing of attachments or embeds; otherwise, +// returns nil. func parseEMLAttachmentEmbed(contentDisposition []string, multiPart *multipart.Part, msg *Msg) error { cdType, optional := parseMultiPartHeader(contentDisposition[0]) filename := "generic.attachment" From dab9cc947a831181ba7e9a0f2277941ad3e7f153 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 6 Oct 2024 13:53:20 +0200 Subject: [PATCH 51/60] Improve documentation for String methods Enhanced comments for String methods on Charset, ContentType, and Encoding types. Detailed the purpose and usage of each method, emphasizing their role in formatted output and logging. --- encoding.go | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/encoding.go b/encoding.go index 2b559c4..9ed5666 100644 --- a/encoding.go +++ b/encoding.go @@ -184,18 +184,38 @@ const ( MIMERelated MIMEType = "related" ) -// String satisfies the fmt.Stringer interface for the Charset type. It converts a Charset into a printable format. +// String satisfies the fmt.Stringer interface for the Charset type. +// It converts a Charset into a printable format. +// +// This method returns the string representation of the Charset, allowing it to be easily +// printed or logged. +// +// Returns: +// - A string representation of the Charset. func (c Charset) String() string { return string(c) } -// String satisfies the fmt.Stringer interface for the ContentType type. It converts a ContentType into a printable -// format. +// String satisfies the fmt.Stringer interface for the ContentType type. +// It converts a ContentType into a printable format. +// +// This method returns the string representation of the ContentType, enabling its use +// in formatted output such as logging or displaying information to the user. +// +// Returns: +// - A string representation of the ContentType. func (c ContentType) String() string { return string(c) } -// String satisfies the fmt.Stringer interface for the Encoding type. It converts an Encoding into a printable format. +// String satisfies the fmt.Stringer interface for the Encoding type. +// It converts an Encoding into a printable format. +// +// This method returns the string representation of the Encoding, which can be used +// for displaying or logging purposes. +// +// Returns: +// - A string representation of the Encoding. func (e Encoding) String() string { return string(e) } From ac7fa5771aa141a375c763b10986662128273b31 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 6 Oct 2024 16:16:49 +0200 Subject: [PATCH 52/60] Refactor documentation and enhance comments Updated documentation for the `File` struct and its methods, providing clearer explanations and detailed parameter and return descriptions. Improved readability and consistency of comments across the codebase. --- file.go | 91 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 75 insertions(+), 16 deletions(-) diff --git a/file.go b/file.go index 77e516c..8866d97 100644 --- a/file.go +++ b/file.go @@ -12,8 +12,12 @@ import ( // FileOption is a function type used to modify properties of a File type FileOption func(*File) -// File represents a file with properties like content type, description, encoding, headers, name, and -// writer function. This can either be an attachment or an embedded file for a Msg. +// File represents a file with properties such as content type, description, encoding, headers, name, and +// a writer function. +// +// This struct can represent either an attachment or an embedded file in a Msg, and it stores relevant +// metadata such as content type and encoding, as well as a function to write the file's content to an +// io.Writer. type File struct { ContentType ContentType Desc string @@ -23,7 +27,16 @@ type File struct { Writer func(w io.Writer) (int64, error) } -// WithFileContentID sets the "Content-ID" header in the File's MIME headers to the specified id. +// WithFileContentID sets the "Content-ID" header in the File's MIME headers to the specified ID. +// +// This function updates the File's MIME headers by setting the "Content-ID" to the provided string value, +// allowing the file to be referenced by this ID within the MIME structure. +// +// Parameters: +// - id: A string representing the content ID to be set in the "Content-ID" header. +// +// Returns: +// - A FileOption function that updates the File's "Content-ID" header. func WithFileContentID(id string) FileOption { return func(f *File) { f.Header.Set(HeaderContentID.String(), id) @@ -31,27 +44,51 @@ func WithFileContentID(id string) FileOption { } // WithFileName sets the name of a File to the provided value. +// +// This function assigns the specified name to the File, updating its Name field. +// +// Parameters: +// - name: A string representing the name to be assigned to the File. +// +// Returns: +// - A FileOption function that sets the File's name. func WithFileName(name string) FileOption { return func(f *File) { f.Name = name } } -// WithFileDescription sets an optional file description for the File. The description is used in the -// Content-Description header of the MIME output. +// WithFileDescription sets an optional description for the File, which is used in the Content-Description +// header of the MIME output. +// +// This function updates the File's description, allowing an additional text description to be added to +// the MIME headers for the file. +// +// Parameters: +// - description: A string representing the description to be set in the Content-Description header. +// +// Returns: +// - A FileOption function that sets the File's description. func WithFileDescription(description string) FileOption { return func(f *File) { f.Desc = description } } -// WithFileEncoding sets the encoding type for a file. +// WithFileEncoding sets the encoding type for a File. // -// By default one should always use Base64 encoding for attachments and embeds, but there might be exceptions in -// which this might come handy. +// This function allows the specification of an encoding type for the file, typically used for attachments +// or embedded files. By default, Base64 encoding should be used, but this function can override the +// default if needed. // -// Note: that quoted-printable must never be used for attachments or embeds. If EncodingQP is provided as encoding -// to this method, it will be automatically overwritten with EncodingB64. +// Note: Quoted-printable encoding (EncodingQP) must never be used for attachments or embeds. If EncodingQP +// is passed to this function, it will be ignored and the encoding will remain unchanged. +// +// Parameters: +// - encoding: The Encoding type to be assigned to the File, unless it's EncodingQP. +// +// Returns: +// - A FileOption function that sets the File's encoding. func WithFileEncoding(encoding Encoding) FileOption { return func(f *File) { if encoding == EncodingQP { @@ -63,22 +100,44 @@ func WithFileEncoding(encoding Encoding) FileOption { // WithFileContentType sets the content type of the File. // -// By default we will try to guess the file type and its corresponding content type and fall back to -// application/octet-stream if the file type, if no matching type could be guessed. This FileOption can -// be used to override this type, in case a specific type is required. +// By default, the content type is guessed based on the file type, and if no matching type is identified, +// the default "application/octet-stream" is used. This FileOption allows overriding the guessed content +// type with a specific one if required. +// +// Parameters: +// - contentType: The ContentType to be assigned to the File. +// +// Returns: +// - A FileOption function that sets the File's content type. func WithFileContentType(contentType ContentType) FileOption { return func(f *File) { f.ContentType = contentType } } -// setHeader sets the value of a given MIME header field for the File. +// setHeader sets the value of a specified MIME header field for the File. +// +// This method updates the MIME headers of the File by assigning the provided value to the specified +// header field. +// +// Parameters: +// - header: The Header field to be updated. +// - value: A string representing the value to be set for the given header. func (f *File) setHeader(header Header, value string) { f.Header.Set(string(header), value) } -// getHeader retrieves the value of the specified MIME header field. It returns the header value and a boolean -// indicating whether the header was present or not. +// getHeader retrieves the value of the specified MIME header field. +// +// This method returns the value of the given header and a boolean indicating whether the header was found +// in the File's MIME headers. +// +// Parameters: +// - header: The Header field whose value is to be retrieved. +// +// Returns: +// - A string containing the value of the header. +// - A boolean indicating whether the header was present (true) or not (false). func (f *File) getHeader(header Header) (string, bool) { v := f.Header.Get(string(header)) return v, v != "" From 3333c784a6deb97712cc65de2118cddcf1564f41 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 6 Oct 2024 16:19:53 +0200 Subject: [PATCH 53/60] Refactor documentation for Importance methods Updated the documentation for NumString, XPrioString, and String methods in the Importance type to provide clearer descriptions of their behavior and return values. Enhanced comments for better readability and maintainability of the code. --- header.go | 43 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/header.go b/header.go index 2b25cd2..217823a 100644 --- a/header.go +++ b/header.go @@ -136,8 +136,14 @@ const ( ImportanceUrgent ) -// NumString returns a numerical string representation of the Importance, mapping ImportanceHigh and -// ImportanceUrgent to "1" and others to "0". +// NumString returns a numerical string representation of the Importance level. +// +// This method maps ImportanceHigh and ImportanceUrgent to "1", while ImportanceNonUrgent and ImportanceLow +// are mapped to "0". Other values return an empty string. +// +// Returns: +// - A string representing the numerical value of the Importance level ("1" or "0"), or an empty string +// if the Importance level is unrecognized. func (i Importance) NumString() string { switch i { case ImportanceNonUrgent: @@ -153,8 +159,14 @@ func (i Importance) NumString() string { } } -// XPrioString returns the X-Priority string representation of the Importance, mapping ImportanceHigh and -// ImportanceUrgent to "1" and others to "5". +// XPrioString returns the X-Priority string representation of the Importance level. +// +// This method maps ImportanceHigh and ImportanceUrgent to "1", while ImportanceNonUrgent and ImportanceLow +// are mapped to "5". Other values return an empty string. +// +// Returns: +// - A string representing the X-Priority value of the Importance level ("1" or "5"), or an empty string +// if the Importance level is unrecognized. func (i Importance) XPrioString() string { switch i { case ImportanceNonUrgent: @@ -170,8 +182,14 @@ func (i Importance) XPrioString() string { } } -// String satisfies the fmt.Stringer interface for the Importance type and returns the string representation of the -// Importance level. +// String satisfies the fmt.Stringer interface for the Importance type and returns the string +// representation of the Importance level. +// +// This method provides a human-readable string for each Importance level. +// +// Returns: +// - A string representing the Importance level ("non-urgent", "low", "high", or "urgent"), or an empty +// string if the Importance level is unrecognized. func (i Importance) String() string { switch i { case ImportanceNonUrgent: @@ -187,13 +205,20 @@ func (i Importance) String() string { } } -// String satisfies the fmt.Stringer interface for the Header type and returns the string representation of the Header. +// String satisfies the fmt.Stringer interface for the Header type and returns the string +// representation of the Header. +// +// Returns: +// - A string representing the Header. func (h Header) String() string { return string(h) } -// String satisfies the fmt.Stringer interface for the AddrHeader type and returns the string representation of the -// AddrHeader. +// String satisfies the fmt.Stringer interface for the AddrHeader type and returns the string +// representation of the AddrHeader. +// +// Returns: +// - A string representing the AddrHeader. func (a AddrHeader) String() string { return string(a) } From 0b105048e654fd48517958de9280b06330d402bd Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 6 Oct 2024 16:21:37 +0200 Subject: [PATCH 54/60] Refine WriteToTempFile docstring Clarify the documentation for WriteToTempFile to better explain its functionality, ensure consistency, and detail its return values. --- msg_totmpfile.go | 10 ++++++++-- msg_totmpfile_116.go | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/msg_totmpfile.go b/msg_totmpfile.go index 44c427d..b7d2ada 100644 --- a/msg_totmpfile.go +++ b/msg_totmpfile.go @@ -12,8 +12,14 @@ import ( "os" ) -// WriteToTempFile will create a temporary file and output the Msg to this file -// The method will return the filename of the temporary file +// WriteToTempFile creates a temporary file and writes the Msg content to this file. +// +// This method generates a temporary file with a ".eml" extension, writes the Msg to it, and returns the +// filename of the created temporary file. +// +// Returns: +// - A string representing the filename of the temporary file. +// - An error if the file creation or writing process fails. func (m *Msg) WriteToTempFile() (string, error) { f, err := os.CreateTemp("", "go-mail_*.eml") if err != nil { diff --git a/msg_totmpfile_116.go b/msg_totmpfile_116.go index 4611106..bb93411 100644 --- a/msg_totmpfile_116.go +++ b/msg_totmpfile_116.go @@ -12,8 +12,14 @@ import ( "io/ioutil" ) -// WriteToTempFile will create a temporary file and output the Msg to this file -// The method will return the filename of the temporary file +// WriteToTempFile creates a temporary file and writes the Msg content to this file. +// +// This method generates a temporary file with a ".eml" extension, writes the Msg to it, and returns the +// filename of the created temporary file. +// +// Returns: +// - A string representing the filename of the temporary file. +// - An error if the file creation or writing process fails. func (m *Msg) WriteToTempFile() (string, error) { f, err := ioutil.TempFile("", "go-mail_*.eml") if err != nil { From 295155ba674c91ff0356e5de4450b8a56f1e01bb Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 6 Oct 2024 16:42:50 +0200 Subject: [PATCH 55/60] Refactor and document msgWriter methods Streamline msgWriter by adding detailed documentation for each method and constant. This includes parameter descriptions, return values, and references to relevant RFCs, improving code readability and maintainability. --- msgwriter.go | 168 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 144 insertions(+), 24 deletions(-) diff --git a/msgwriter.go b/msgwriter.go index ff1b47b..ed2b41e 100644 --- a/msgwriter.go +++ b/msgwriter.go @@ -18,22 +18,39 @@ import ( "strings" ) -// MaxHeaderLength defines the maximum line length for a mail header -// RFC 2047 suggests 76 characters -const MaxHeaderLength = 76 +const ( + // MaxHeaderLength defines the maximum line length for a mail header. + // + // This constant follows the recommendation of RFC 2047, which suggests a maximum length of 76 characters. + // + // References: + // - https://datatracker.ietf.org/doc/html/rfc2047 + MaxHeaderLength = 76 -// MaxBodyLength defines the maximum line length for the mail body -// RFC 2047 suggests 76 characters -const MaxBodyLength = 76 + // MaxBodyLength defines the maximum line length for the mail body. + // + // This constant follows the recommendation of RFC 2047, which suggests a maximum length of 76 characters. + // + // References: + // - https://datatracker.ietf.org/doc/html/rfc2047 + MaxBodyLength = 76 -// SingleNewLine represents a new line that can be used by the msgWriter to issue a carriage return -const SingleNewLine = "\r\n" + // SingleNewLine represents a single newline character sequence ("\r\n"). + // + // This constant can be used by the msgWriter to issue a carriage return when writing mail content. + SingleNewLine = "\r\n" -// DoubleNewLine represents a double new line that can be used by the msgWriter to -// indicate a new segement of the mail -const DoubleNewLine = "\r\n\r\n" + // DoubleNewLine represents a double newline character sequence ("\r\n\r\n"). + // + // This constant can be used by the msgWriter to indicate a new segment of the mail when writing mail content. + DoubleNewLine = "\r\n\r\n" +) -// msgWriter handles the I/O to the io.WriteCloser of the SMTP client +// msgWriter handles the I/O operations for writing to the io.WriteCloser of the SMTP client. +// +// This struct keeps track of the number of bytes written, the character set used, and the depth of the +// current multipart section. It also handles encoding, error tracking, and managing multipart and part +// writers for constructing the email message body. type msgWriter struct { bytesWritten int64 charset Charset @@ -45,7 +62,18 @@ type msgWriter struct { writer io.Writer } -// Write implements the io.Writer interface for msgWriter +// Write implements the io.Writer interface for msgWriter. +// +// This method writes the provided payload to the underlying writer. It keeps track of the number of bytes +// written and handles any errors encountered during the writing process. If a previous error exists, it +// prevents further writing and returns the error. +// +// Parameters: +// - payload: A byte slice containing the data to be written. +// +// Returns: +// - The number of bytes successfully written. +// - An error if the writing process fails, or if a previous error was encountered. func (mw *msgWriter) Write(payload []byte) (int, error) { if mw.err != nil { return 0, fmt.Errorf("failed to write due to previous error: %w", mw.err) @@ -57,7 +85,19 @@ func (mw *msgWriter) Write(payload []byte) (int, error) { return n, mw.err } -// writeMsg formats the message and sends it to its io.Writer +// writeMsg formats the message and writes it to the msgWriter's io.Writer. +// +// This method handles the process of writing the message headers and body content, including handling +// multipart structures (e.g., mixed, related, alternative), PGP types, and attachments/embeds. It sets the +// required headers (e.g., "From", "To", "Cc") and iterates over the message parts, writing them to the +// output writer. +// +// Parameters: +// - msg: A pointer to the Msg struct containing the message data and headers to be written. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2045 (Multipurpose Internet Mail Extensions - MIME) +// - https://datatracker.ietf.org/doc/html/rfc5322 (Internet Message Format) func (mw *msgWriter) writeMsg(msg *Msg) { msg.addDefaultHeader() msg.checkUserAgent() @@ -136,7 +176,13 @@ func (mw *msgWriter) writeMsg(msg *Msg) { } } -// writeGenHeader writes out all generic headers to the msgWriter +// writeGenHeader writes out all generic headers to the msgWriter. +// +// This function extracts all generic headers from the provided Msg object, sorts them, and writes them +// to the msgWriter in alphabetical order. +// +// Parameters: +// - msg: The Msg object containing the headers to be written. func (mw *msgWriter) writeGenHeader(msg *Msg) { keys := make([]string, 0, len(msg.genHeader)) for key := range msg.genHeader { @@ -148,14 +194,32 @@ func (mw *msgWriter) writeGenHeader(msg *Msg) { } } -// writePreformatedHeader writes out all preformated generic headers to the msgWriter +// writePreformattedGenHeader writes out all preformatted generic headers to the msgWriter. +// +// This function iterates over all preformatted generic headers from the provided Msg object and writes +// them to the msgWriter in the format "key: value" followed by a newline. +// +// Parameters: +// - msg: The Msg object containing the preformatted headers to be written. func (mw *msgWriter) writePreformattedGenHeader(msg *Msg) { for key, val := range msg.preformHeader { mw.writeString(fmt.Sprintf("%s: %s%s", key, val, SingleNewLine)) } } -// startMP writes a multipart beginning +// startMP writes a multipart beginning. +// +// This function initializes a multipart writer for the msgWriter using the specified MIME type and +// boundary. It sets the Content-Type header to indicate the multipart type and writes the boundary +// information. If a boundary is provided, it is set explicitly; otherwise, a default boundary is +// generated. It also handles writing a new part when nested multipart structures are used. +// +// Parameters: +// - mimeType: The MIME type of the multipart content (e.g., "mixed", "alternative"). +// - boundary: The boundary string separating different parts of the multipart message. +// +// References: +// - https://datatracker.ietf.org/doc/html/rfc2046 func (mw *msgWriter) startMP(mimeType MIMEType, boundary string) { multiPartWriter := multipart.NewWriter(mw) if boundary != "" { @@ -175,7 +239,10 @@ func (mw *msgWriter) startMP(mimeType MIMEType, boundary string) { mw.depth++ } -// stopMP closes the multipart +// stopMP closes the multipart. +// +// This function closes the current multipart writer if there is an active multipart structure. +// It decreases the depth level of multipart nesting. func (mw *msgWriter) stopMP() { if mw.depth > 0 { mw.err = mw.multiPartWriter[mw.depth-1].Close() @@ -183,7 +250,17 @@ func (mw *msgWriter) stopMP() { } } -// addFiles adds the attachments/embeds file content to the mail body +// addFiles adds the attachments/embeds file content to the mail body. +// +// This function iterates through the list of files, setting necessary headers for each file, +// including Content-Type, Content-Transfer-Encoding, Content-Disposition, and Content-ID +// (if the file is an embed). It determines the appropriate MIME type for each file based on +// its extension or the provided ContentType. It writes file headers and file content +// to the mail body using the appropriate encoding. +// +// Parameters: +// - files: A slice of File objects to be added to the mail body. +// - isAttachment: A boolean indicating whether the files are attachments (true) or embeds (false). func (mw *msgWriter) addFiles(files []*File, isAttachment bool) { for _, file := range files { encoding := EncodingB64 @@ -242,12 +319,29 @@ func (mw *msgWriter) addFiles(files []*File, isAttachment bool) { } } -// 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. +// +// This function creates a new MIME part using the provided header information and assigns it +// to the partWriter. It interacts with the current multipart writer at the specified depth +// to create the part. +// +// Parameters: +// - header: A map containing the header fields and their corresponding values for the new part. func (mw *msgWriter) newPart(header map[string][]string) { 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. +// +// This function writes a MIME part to the message body, setting the appropriate headers such +// as Content-Type and Content-Transfer-Encoding. It determines the charset for the part, +// either using the part's own charset or a fallback charset if none is specified. If the part +// is at the top level (depth 0), headers are written directly. For nested parts, it creates +// a new MIME part with the provided headers. +// +// Parameters: +// - part: The Part object containing the data to be written. +// - charset: The Charset used as a fallback if the part does not specify one. func (mw *msgWriter) writePart(part *Part, charset Charset) { partCharset := part.charset if partCharset.String() == "" { @@ -272,7 +366,14 @@ func (mw *msgWriter) writePart(part *Part, charset Charset) { mw.writeBody(part.writeFunc, part.encoding) } -// writeString writes a string into the msgWriter's io.Writer interface +// writeString writes a string into the msgWriter's io.Writer interface. +// +// This function writes the given string to the msgWriter's underlying writer. It checks for +// existing errors before performing the write operation. It also tracks the number of bytes +// written and updates the bytesWritten field accordingly. +// +// Parameters: +// - s: The string to be written. func (mw *msgWriter) writeString(s string) { if mw.err != nil { return @@ -282,7 +383,16 @@ func (mw *msgWriter) writeString(s string) { mw.bytesWritten += int64(n) } -// writeHeader writes a header into the msgWriter's io.Writer +// writeHeader writes a header into the msgWriter's io.Writer. +// +// This function writes a header key and its associated values to the msgWriter. It ensures +// proper formatting of long headers by inserting line breaks as needed. The header values +// are joined and split into words to ensure compliance with the maximum header length +// (MaxHeaderLength). After processing the header, it is written to the underlying writer. +// +// Parameters: +// - key: The Header key to be written. +// - values: A variadic parameter representing the values associated with the header. func (mw *msgWriter) writeHeader(key Header, values ...string) { buffer := strings.Builder{} charLength := MaxHeaderLength - 2 @@ -317,7 +427,17 @@ func (mw *msgWriter) writeHeader(key Header, values ...string) { mw.writeString("\r\n") } -// writeBody writes an io.Reader into an io.Writer using provided Encoding +// writeBody writes an io.Reader into an io.Writer using the provided Encoding. +// +// This function writes data from an io.Reader to the underlying writer using a specified +// encoding (quoted-printable, base64, or no encoding). It handles encoding of the content +// and manages writing the encoded data to the appropriate writer, depending on the depth +// (whether the data is part of a multipart structure or not). It also tracks the number +// of bytes written and manages any errors encountered during the process. +// +// Parameters: +// - writeFunc: A function that writes the body content to the given io.Writer. +// - encoding: The encoding type to use when writing the content (e.g., base64, quoted-printable). func (mw *msgWriter) writeBody(writeFunc func(io.Writer) (int64, error), encoding Encoding) { var writer io.Writer var encodedWriter io.WriteCloser From e640f2df4612fe55656f8036292dc818b9ef45d0 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 6 Oct 2024 16:54:18 +0200 Subject: [PATCH 56/60] Add detailed comments and return descriptions to `Part` methods Enhanced the documentation of `Part` struct and its methods to provide clearer explanations, including parameter and return descriptions. This improves code readability and helps developers understand the functionality and usage of each method. --- part.go | 136 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 118 insertions(+), 18 deletions(-) diff --git a/part.go b/part.go index 7c76b7d..5be8059 100644 --- a/part.go +++ b/part.go @@ -12,7 +12,11 @@ import ( // PartOption returns a function that can be used for grouping Part options type PartOption func(*Part) -// Part is a part of the Msg +// Part is a part of the Msg. +// +// This struct represents a single part of a multipart message. Each part has a content type, +// charset, optional description, encoding, and a function to write its content to an io.Writer. +// It also includes a flag to mark the part as deleted. type Part struct { contentType ContentType charset Charset @@ -22,7 +26,14 @@ type Part struct { writeFunc func(io.Writer) (int64, error) } -// GetContent executes the WriteFunc of the Part and returns the content as byte slice +// GetContent executes the WriteFunc of the Part and returns the content as a byte slice. +// +// This function runs the part's writeFunc to write its content into a buffer and then returns +// the content as a byte slice. If an error occurs during the writing process, it is returned. +// +// Returns: +// - A byte slice containing the part's content. +// - An error if the writeFunc encounters an issue. func (p *Part) GetContent() ([]byte, error) { var b bytes.Buffer if _, err := p.writeFunc(&b); err != nil { @@ -31,83 +42,172 @@ func (p *Part) GetContent() ([]byte, error) { return b.Bytes(), nil } -// GetCharset returns the currently set Charset of the Part +// GetCharset returns the currently set Charset of the Part. +// +// This function returns the Charset that is currently set for the Part. +// +// Returns: +// - The Charset of the Part. func (p *Part) GetCharset() Charset { return p.charset } -// GetContentType returns the currently set ContentType of the Part +// GetContentType returns the currently set ContentType of the Part. +// +// This function returns the ContentType that is currently set for the Part. +// +// Returns: +// - The ContentType of the Part. func (p *Part) GetContentType() ContentType { return p.contentType } -// GetEncoding returns the currently set Encoding of the Part +// GetEncoding returns the currently set Encoding of the Part. +// +// This function returns the Encoding that is currently set for the Part. +// +// Returns: +// - The Encoding of the Part. func (p *Part) GetEncoding() Encoding { return p.encoding } -// GetWriteFunc returns the currently set WriterFunc of the Part +// GetWriteFunc returns the currently set WriteFunc of the Part. +// +// This function returns the WriteFunc that is currently set for the Part, which writes +// the part's content to an io.Writer. +// +// Returns: +// - The WriteFunc of the Part, which is a function that takes an io.Writer and returns +// the number of bytes written and an error (if any). func (p *Part) GetWriteFunc() func(io.Writer) (int64, error) { return p.writeFunc } -// GetDescription returns the currently set Content-Description of the Part +// GetDescription returns the currently set Content-Description of the Part. +// +// This function returns the Content-Description that is currently set for the Part. +// +// Returns: +// - The Content-Description of the Part as a string. func (p *Part) GetDescription() string { return p.description } -// SetContent overrides the content of the Part with the given string +// SetContent overrides the content of the Part with the given string. +// +// This function sets the content of the Part by creating a new writeFunc that writes the +// provided string content to an io.Writer. +// +// Parameters: +// - content: The string that will replace the current content of the Part. func (p *Part) SetContent(content string) { buffer := bytes.NewBufferString(content) p.writeFunc = writeFuncFromBuffer(buffer) } -// SetContentType overrides the ContentType of the Part +// SetContentType overrides the ContentType of the Part. +// +// This function sets a new ContentType for the Part, replacing the existing one. +// +// Parameters: +// - contentType: The new ContentType to be set for the Part. func (p *Part) SetContentType(contentType ContentType) { p.contentType = contentType } -// SetCharset overrides the Charset of the Part +// SetCharset overrides the Charset of the Part. +// +// This function sets a new Charset for the Part, replacing the existing one. +// +// Parameters: +// - charset: The new Charset to be set for the Part. func (p *Part) SetCharset(charset Charset) { p.charset = charset } -// SetEncoding creates a new mime.WordEncoder based on the encoding setting of the message +// SetEncoding creates a new mime.WordEncoder based on the encoding setting of the message. +// +// This function sets a new Encoding for the Part, replacing the existing one. +// +// Parameters: +// - encoding: The new Encoding to be set for the Part. func (p *Part) SetEncoding(encoding Encoding) { p.encoding = encoding } -// SetDescription overrides the Content-Description of the Part +// SetDescription overrides the Content-Description of the Part. +// +// This function sets a new Content-Description for the Part, replacing the existing one. +// +// Parameters: +// - description: The new Content-Description to be set for the Part. func (p *Part) SetDescription(description string) { p.description = description } -// SetWriteFunc overrides the WriteFunc of the Part +// SetWriteFunc overrides the WriteFunc of the Part. +// +// This function sets a new WriteFunc for the Part, replacing the existing one. The WriteFunc +// is responsible for writing the Part's content to an io.Writer. +// +// Parameters: +// - writeFunc: A function that writes the Part's content to an io.Writer and returns +// the number of bytes written and an error (if any). func (p *Part) SetWriteFunc(writeFunc func(io.Writer) (int64, error)) { p.writeFunc = writeFunc } -// Delete removes the current part from the parts list of the Msg by setting the -// isDeleted flag to true. The msgWriter will skip it then +// Delete removes the current part from the parts list of the Msg by setting the isDeleted flag to true. +// +// This function marks the Part as deleted by setting the isDeleted flag to true. The msgWriter +// will skip over this Part during processing. func (p *Part) Delete() { p.isDeleted = true } -// WithPartCharset overrides the default Part charset +// WithPartCharset overrides the default Part charset. +// +// This function returns a PartOption that allows the charset of a Part to be overridden +// with the specified Charset. +// +// Parameters: +// - charset: The Charset to be set for the Part. +// +// Returns: +// - A PartOption function that sets the Part's charset. func WithPartCharset(charset Charset) PartOption { return func(p *Part) { p.charset = charset } } -// WithPartEncoding overrides the default Part encoding +// WithPartEncoding overrides the default Part encoding. +// +// This function returns a PartOption that allows the encoding of a Part to be overridden +// with the specified Encoding. +// +// Parameters: +// - encoding: The Encoding to be set for the Part. +// +// Returns: +// - A PartOption function that sets the Part's encoding. func WithPartEncoding(encoding Encoding) PartOption { return func(p *Part) { p.encoding = encoding } } -// WithPartContentDescription overrides the default Part Content-Description +// WithPartContentDescription overrides the default Part Content-Description. +// +// This function returns a PartOption that allows the Content-Description of a Part +// to be overridden with the specified description. +// +// Parameters: +// - description: The Content-Description to be set for the Part. +// +// Returns: +// - A PartOption function that sets the Part's Content-Description. func WithPartContentDescription(description string) PartOption { return func(p *Part) { p.description = description From cd90c3ddf3f06320e34c75d39244588282f4f94a Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 6 Oct 2024 17:00:49 +0200 Subject: [PATCH 57/60] Update randNum function and documentation for multiple versions Expanded randNum function comments to provide detailed behavior and usage across different Go versions. Enhanced the randomStringSecure function doc to explain its cryptographic secure implementation and error handling. --- random.go | 29 ++++++++++++++++++++++++----- random_119.go | 12 +++++++++++- random_121.go | 12 +++++++++++- random_122.go | 12 ++++++++++-- 4 files changed, 56 insertions(+), 9 deletions(-) diff --git a/random.go b/random.go index 3a3f16b..2478c75 100644 --- a/random.go +++ b/random.go @@ -14,14 +14,33 @@ import ( const cr = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" // Bitmask sizes for the string generators (based on 93 chars total) +// +// These constants define bitmask-related values used for efficient random string generation. +// The bitmask operates over 93 possible characters, and the constants help determine the +// number of bits and indices used in the process. const ( - letterIdxBits = 7 // 7 bits to represent a letter index - letterIdxMask = 1< Date: Sun, 6 Oct 2024 17:05:04 +0200 Subject: [PATCH 58/60] Enhance and clarify Reader struct documentation Improved documentation for the Reader struct, its methods, and error handling. Added detailed explanations for `Read`, `Error`, `Reset`, and `empty` functions to better describe their parameters, return values, and behavior. --- reader.go | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/reader.go b/reader.go index 3c44f4c..e635e3a 100644 --- a/reader.go +++ b/reader.go @@ -8,19 +8,41 @@ import ( "io" ) -// Reader is a type that implements the io.Reader interface for a Msg +// Reader is a type that implements the io.Reader interface for a Msg. +// +// This struct represents a reader that reads from a byte slice buffer. It keeps track of the +// current read position (offset) and any initialization error. The buffer holds the data to be +// read from the message. type Reader struct { buffer []byte // contents are the bytes buffer[offset : len(buffer)] offset int // read at &buffer[offset], write at &buffer[len(buffer)] err error // initialization error } -// Error returns an error if the Reader err field is not nil +// Error returns an error if the Reader err field is not nil. +// +// This function checks the Reader's err field and returns it if it is not nil. If no error +// occurred during initialization, it returns nil. +// +// Returns: +// - The error stored in the err field, or nil if no error is present. func (r *Reader) Error() error { return r.err } -// Read reads the length of p of the Msg buffer to satisfy the io.Reader interface +// Read reads the content of the Msg buffer into the provided payload to satisfy the io.Reader interface. +// +// This function reads data from the Reader's buffer into the provided byte slice (payload). +// It checks for errors or an empty buffer and resets the Reader if necessary. If no data is available, +// it returns io.EOF. Otherwise, it copies the content from the buffer into the payload and updates +// the read offset. +// +// Parameters: +// - payload: A byte slice where the data will be copied. +// +// Returns: +// - n: The number of bytes copied into the payload. +// - err: An error if any issues occurred during the read operation or io.EOF if the buffer is empty. func (r *Reader) Read(payload []byte) (n int, err error) { if r.err != nil { return 0, r.err @@ -37,12 +59,20 @@ func (r *Reader) Read(payload []byte) (n int, err error) { return n, err } -// Reset resets the Reader buffer to be empty, but it retains the underlying storage -// for use by future writes. +// Reset resets the Reader buffer to be empty, but it retains the underlying storage for future use. +// +// This function clears the Reader's buffer by setting its length to 0 and resets the read offset +// to the beginning. The underlying storage is retained, allowing future writes to reuse the buffer. func (r *Reader) Reset() { r.buffer = r.buffer[:0] r.offset = 0 } // empty reports whether the unread portion of the Reader buffer is empty. +// +// This function checks if the unread portion of the Reader's buffer is empty by comparing +// the length of the buffer to the current read offset. +// +// Returns: +// - true if the unread portion is empty, false otherwise. func (r *Reader) empty() bool { return len(r.buffer) <= r.offset } From 6ce5c2a8606d495cf4d5c559a40d06977814df8e Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 6 Oct 2024 17:12:52 +0200 Subject: [PATCH 59/60] Enhance documentation for SendError methods and fields Improved comments for better clarity and detail in SendError-related methods and fields. Updated comments provide more in-depth explanations of functionality, parameters, return values, and usage. --- senderror.go | 75 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/senderror.go b/senderror.go index 90c8c4a..1943e28 100644 --- a/senderror.go +++ b/senderror.go @@ -54,7 +54,11 @@ const ( ErrAmbiguous ) -// SendError is an error wrapper for delivery errors of the Msg +// SendError is an error wrapper for delivery errors of the Msg. +// +// This struct represents an error that occurs during the delivery of a message. It holds +// details about the affected message, a list of errors, the recipient list, and whether +// the error is temporary or permanent. It also includes a reason code for the error. type SendError struct { affectedMsg *Msg errlist []error @@ -66,7 +70,16 @@ type SendError struct { // SendErrReason represents a comparable reason on why the delivery failed type SendErrReason int -// Error implements the error interface for the SendError type +// Error implements the error interface for the SendError type. +// +// This function returns a detailed error message string for the SendError, including the +// reason for failure, list of errors, affected recipients, and the message ID of the +// affected message (if available). If the reason is unknown (greater than 10), it returns +// "unknown reason". The error message is built dynamically based on the content of the +// error list, recipient list, and message ID. +// +// Returns: +// - A string representing the error message. func (e *SendError) Error() string { if e.Reason > 10 { return "unknown reason" @@ -101,7 +114,17 @@ func (e *SendError) Error() string { return errMessage.String() } -// Is implements the errors.Is functionality and compares the SendErrReason +// Is implements the errors.Is functionality and compares the SendErrReason. +// +// This function allows for comparison between two errors by checking if the provided +// error matches the SendError type and, if so, compares the SendErrReason and the +// temporary status (isTemp) of both errors. +// +// Parameters: +// - errType: The error to compare against the current SendError. +// +// Returns: +// - true if the errors have the same reason and temporary status, false otherwise. func (e *SendError) Is(errType error) bool { var t *SendError if errors.As(errType, &t) && t != nil { @@ -110,7 +133,13 @@ func (e *SendError) Is(errType error) bool { return false } -// IsTemp returns true if the delivery error is of temporary nature and can be retried +// IsTemp returns true if the delivery error is of a temporary nature and can be retried. +// +// This function checks whether the SendError indicates a temporary error, which suggests +// that the delivery can be retried. If the SendError is nil, it returns false. +// +// Returns: +// - true if the error is temporary, false otherwise. func (e *SendError) IsTemp() bool { if e == nil { return false @@ -118,8 +147,13 @@ func (e *SendError) IsTemp() bool { return e.isTemp } -// MessageID returns the message ID of the affected Msg that caused the error -// If no message ID was set for the Msg, an empty string will be returned +// MessageID returns the message ID of the affected Msg that caused the error. +// +// This function retrieves the message ID of the Msg associated with the SendError. +// If no message ID was set or if the SendError or Msg is nil, it returns an empty string. +// +// Returns: +// - The message ID as a string, or an empty string if no ID is available. func (e *SendError) MessageID() string { if e == nil || e.affectedMsg == nil { return "" @@ -127,7 +161,13 @@ func (e *SendError) MessageID() string { return e.affectedMsg.GetMessageID() } -// Msg returns the pointer to the affected message that caused the error +// Msg returns the pointer to the affected message that caused the error. +// +// This function retrieves the Msg associated with the SendError. If the SendError or +// the affectedMsg is nil, it returns nil. +// +// Returns: +// - A pointer to the Msg that caused the error, or nil if not available. func (e *SendError) Msg() *Msg { if e == nil || e.affectedMsg == nil { return nil @@ -135,7 +175,14 @@ func (e *SendError) Msg() *Msg { return e.affectedMsg } -// String implements the Stringer interface for the SendErrReason +// String satisfies the fmt.Stringer interface for the SendErrReason type. +// +// This function converts the SendErrReason into a human-readable string representation based +// on the error type. If the error reason does not match any predefined case, it returns +// "unknown reason". +// +// Returns: +// - A string representation of the SendErrReason. func (r SendErrReason) String() string { switch r { case ErrGetSender: @@ -164,8 +211,16 @@ func (r SendErrReason) String() string { return "unknown reason" } -// isTempError checks the given SMTP error and returns true if the given error is of temporary nature -// and should be retried +// isTempError checks if the given SMTP error is of a temporary nature and should be retried. +// +// This function inspects the error message and returns true if the first character of the +// error message is '4', indicating a temporary SMTP error that can be retried. +// +// Parameters: +// - err: The error to check. +// +// Returns: +// - true if the error is temporary, false otherwise. func isTempError(err error) bool { return err.Error()[0] == '4' } From f0388ec600f956f78c1505d5c4928e0cffa3c346 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Sun, 6 Oct 2024 17:15:39 +0200 Subject: [PATCH 60/60] Refactor TLSPolicy documentation and String method Updated the TLSPolicy type documentation to improve clarity and consistency. Enhanced the String method with detailed commentary and return value explanation, ensuring it better adheres to the fmt.Stringer interface. --- tls.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tls.go b/tls.go index bb7ad14..3adc0cc 100644 --- a/tls.go +++ b/tls.go @@ -4,25 +4,32 @@ package mail -// TLSPolicy type describes a int alias for the different TLS policies we allow +// TLSPolicy is a type wrapper for an int type and describes the different TLS policies we allow. type TLSPolicy int const ( // TLSMandatory requires that the connection to the server is // encrypting using STARTTLS. If the server does not support STARTTLS - // the connection will be terminated with an error + // the connection will be terminated with an error. TLSMandatory TLSPolicy = iota // TLSOpportunistic tries to establish an encrypted connection via the // STARTTLS protocol. If the server does not support this, it will fall - // back to non-encrypted plaintext transmission + // back to non-encrypted plaintext transmission. TLSOpportunistic - // NoTLS forces the transaction to be not encrypted + // NoTLS forces the transaction to be not encrypted. NoTLS ) -// String is a standard method to convert a TLSPolicy into a printable format +// String satisfies the fmt.Stringer interface for the TLSPolicy type. +// +// This function returns a string representation of the TLSPolicy. It matches the policy +// value to predefined constants and returns the corresponding string. If the policy does +// not match any known values, it returns "UnknownPolicy". +// +// Returns: +// - A string representing the TLSPolicy. func (p TLSPolicy) String() string { switch p { case TLSMandatory: