-
-
Notifications
You must be signed in to change notification settings - Fork 51
/
senderror.go
302 lines (274 loc) · 9.12 KB
/
senderror.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
// SPDX-FileCopyrightText: 2022-2023 The go-mail Authors
//
// SPDX-License-Identifier: MIT
package mail
import (
"errors"
"regexp"
"strconv"
"strings"
)
// List of SendError reasons
const (
// ErrGetSender is returned if the Msg.GetSender method fails during a Client.Send
ErrGetSender SendErrReason = iota
// ErrGetRcpts is returned if the Msg.GetRecipients method fails during a Client.Send
ErrGetRcpts
// ErrSMTPMailFrom is returned if the Msg delivery failed when sending the MAIL FROM command
// to the sending SMTP server
ErrSMTPMailFrom
// ErrSMTPRcptTo is returned if the Msg delivery failed when sending the RCPT TO command
// to the sending SMTP server
ErrSMTPRcptTo
// ErrSMTPData is returned if the Msg delivery failed when sending the DATA command
// to the sending SMTP server
ErrSMTPData
// ErrSMTPDataClose is returned if the Msg delivery failed when trying to close the
// Client data writer
ErrSMTPDataClose
// ErrSMTPReset is returned if the Msg delivery failed when sending the RSET command
// to the sending SMTP server
ErrSMTPReset
// ErrWriteContent is returned if the Msg delivery failed when sending Msg content
// to the Client writer
ErrWriteContent
// ErrConnCheck is returned if the Msg delivery failed when checking if the SMTP
// server connection is still working
ErrConnCheck
// ErrNoUnencoded is returned if the Msg delivery failed when the Msg is configured for
// unencoded delivery but the server does not support this
ErrNoUnencoded
// ErrAmbiguous is a generalized delivery error for the SendError type that is
// returned if the exact reason for the delivery failure is ambiguous
ErrAmbiguous
)
// 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
errcode int
enhancedStatusCode string
errlist []error
isTemp bool
rcpt []string
Reason SendErrReason
}
// SendErrReason represents a comparable reason on why the delivery failed
type SendErrReason int
// 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 > ErrAmbiguous {
return "unknown reason"
}
var errMessage strings.Builder
errMessage.WriteString(e.Reason.String())
if len(e.errlist) > 0 {
errMessage.WriteRune(':')
for i := range e.errlist {
errMessage.WriteRune(' ')
errMessage.WriteString(e.errlist[i].Error())
if i != len(e.errlist)-1 {
errMessage.WriteString(",")
}
}
}
if len(e.rcpt) > 0 {
errMessage.WriteString(", affected recipient(s): ")
for i := range e.rcpt {
errMessage.WriteString(e.rcpt[i])
if i != len(e.rcpt)-1 {
errMessage.WriteString(", ")
}
}
}
if e.affectedMsg != nil && e.affectedMsg.GetMessageID() != "" {
errMessage.WriteString(", affected message ID: ")
errMessage.WriteString(e.affectedMsg.GetMessageID())
}
return errMessage.String()
}
// 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 {
return e.Reason == t.Reason && e.isTemp == t.isTemp
}
return false
}
// 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
}
return e.isTemp
}
// 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 ""
}
return e.affectedMsg.GetMessageID()
}
// 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
}
return e.affectedMsg
}
// EnhancedStatusCode returns the enhanced status code of the server response if the
// server supports it, as described in RFC 2034.
//
// This function retrieves the enhanced status code of an error returned by the server. This
// requires that the receiving server supports this SMTP extension as described in RFC 2034.
// Since this is the SendError interface, we only collect status codes for error responses,
// meaning 4xx or 5xx. If the server does not support the ENHANCEDSTATUSCODES extension or
// the error did not include an enhanced status code, it will return an empty string.
//
// Returns:
// - The enhanced status code as returned by the server, or an empty string is not supported.
//
// References:
// - https://datatracker.ietf.org/doc/html/rfc2034
func (e *SendError) EnhancedStatusCode() string {
if e == nil {
return ""
}
return e.enhancedStatusCode
}
// ErrorCode returns the error code of the server response.
//
// This function retrieves the error code the error returned by the server. The error code will
// start with 5 on permanent errors and with 4 on a temporary error. If the error is not returned
// by the server, but is generated by go-mail, the code will be 0.
//
// Returns:
// - The error code as returned by the server, or 0 if not a server error.
func (e *SendError) ErrorCode() int {
if e == nil {
return 0
}
return e.errcode
}
// 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:
return "getting sender address"
case ErrGetRcpts:
return "getting recipient addresses"
case ErrSMTPMailFrom:
return "sending SMTP MAIL FROM command"
case ErrSMTPRcptTo:
return "sending SMTP RCPT TO command"
case ErrSMTPData:
return "sending SMTP DATA command"
case ErrSMTPDataClose:
return "closing SMTP DATA writer"
case ErrSMTPReset:
return "sending SMTP RESET command"
case ErrWriteContent:
return "sending message content"
case ErrConnCheck:
return "checking SMTP connection"
case ErrNoUnencoded:
return ErrServerNoUnencoded.Error()
case ErrAmbiguous:
return "ambiguous reason, check Msg.SendError for message specific reasons"
}
return "unknown reason"
}
// 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'
}
func errorCode(err error) int {
rootErr := errors.Unwrap(err)
if rootErr != nil {
err = rootErr
}
firstrune := err.Error()[0]
if firstrune < 52 || firstrune > 53 {
return 0
}
code := err.Error()[0:3]
errcode, cerr := strconv.Atoi(code)
if cerr != nil {
return 0
}
return errcode
}
func enhancedStatusCode(err error, supported bool) string {
if err == nil || !supported {
return ""
}
rootErr := errors.Unwrap(err)
if rootErr != nil {
err = rootErr
}
firstrune := err.Error()[0]
if firstrune != 50 && firstrune != 52 && firstrune != 53 {
return ""
}
re, rerr := regexp.Compile(`\b([245])\.\d{1,3}\.\d{1,3}\b`)
if rerr != nil {
return ""
}
return re.FindString(err.Error())
}