forked from AfterShip/email-verifier
-
Notifications
You must be signed in to change notification settings - Fork 0
/
error.go
161 lines (147 loc) · 4.34 KB
/
error.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
package emailverifier
import (
"fmt"
"strconv"
"strings"
)
const (
// Standard Errors
ErrTimeout = "The connection to the mail server has timed out"
ErrNoSuchHost = "Mail server does not exist"
ErrServerUnavailable = "Mail server is unavailable"
ErrBlocked = "Blocked by mail server"
// RCPT Errors
ErrTryAgainLater = "Try again later"
ErrFullInbox = "Recipient out of disk space"
ErrTooManyRCPT = "Too many recipients"
ErrNoRelay = "Not an open relay"
ErrMailboxBusy = "Mailbox busy"
ErrExceededMessagingLimits = "Messaging limits have been exceeded"
ErrNotAllowed = "Not Allowed"
ErrNeedMAILBeforeRCPT = "Need MAIL before RCPT"
ErrRCPTHasMoved = "Recipient has moved"
)
// LookupError is an MX dns records lookup error
type LookupError struct {
Message string `json:"message" xml:"message"`
Details string `json:"details" xml:"details"`
}
// newLookupError creates a new LookupError reference and returns it
func newLookupError(message, details string) *LookupError {
return &LookupError{message, details}
}
func (e *LookupError) Error() string {
return fmt.Sprintf("%s : %s", e.Message, e.Details)
}
// ParseSMTPError receives an MX Servers response message
// and generates the corresponding MX error
func ParseSMTPError(err error) *LookupError {
errStr := err.Error()
// Verify the length of the error before reading nil indexes
if len(errStr) < 3 {
return parseBasicErr(err)
}
// Strips out the status code string and converts to an integer for parsing
status, convErr := strconv.Atoi(string([]rune(errStr)[0:3]))
if convErr != nil {
return parseBasicErr(err)
}
// If the status code is above 400 there was an error and we should return it
if status > 400 {
// Don't return an error if the error contains anything about the address
// being undeliverable
if insContains(errStr,
"undeliverable",
"does not exist",
"may not exist",
"user unknown",
"user not found",
"invalid address",
"recipient invalid",
"recipient rejected",
"address rejected",
"no mailbox") {
return newLookupError(ErrServerUnavailable, errStr)
}
switch status {
case 421:
return newLookupError(ErrTryAgainLater, errStr)
case 450:
return newLookupError(ErrMailboxBusy, errStr)
case 451:
return newLookupError(ErrExceededMessagingLimits, errStr)
case 452:
if insContains(errStr,
"full",
"space",
"over quota",
"insufficient",
) {
return newLookupError(ErrFullInbox, errStr)
}
return newLookupError(ErrTooManyRCPT, errStr)
case 503:
return newLookupError(ErrNeedMAILBeforeRCPT, errStr)
case 550: // 550 is Mailbox Unavailable - usually undeliverable, ref: https://blog.mailtrap.io/550-5-1-1-rejected-fix/
if insContains(errStr,
"spamhaus",
"proofpoint",
"cloudmark",
"banned",
"blacklisted",
"blocked",
"block list",
"denied") {
return newLookupError(ErrBlocked, errStr)
}
return newLookupError(ErrServerUnavailable, errStr)
case 551:
return newLookupError(ErrRCPTHasMoved, errStr)
case 552:
return newLookupError(ErrFullInbox, errStr)
case 553:
return newLookupError(ErrNoRelay, errStr)
case 554:
return newLookupError(ErrNotAllowed, errStr)
default:
return parseBasicErr(err)
}
}
return nil
}
// parseBasicErr parses a basic MX record response and returns
// a more understandable LookupError
func parseBasicErr(err error) *LookupError {
errStr := err.Error()
// Return a more understandable error
switch {
case insContains(errStr,
"spamhaus",
"proofpoint",
"cloudmark",
"banned",
"blocked",
"denied"):
return newLookupError(ErrBlocked, errStr)
case insContains(errStr, "timeout"):
return newLookupError(ErrTimeout, errStr)
case insContains(errStr, "no such host"):
return newLookupError(ErrNoSuchHost, errStr)
case insContains(errStr, "unavailable"):
return newLookupError(ErrServerUnavailable, errStr)
default:
return newLookupError(errStr, errStr)
}
}
// insContains returns true if any of the substrings
// are found in the passed string. This method of checking
// contains is case insensitive
func insContains(str string, subStrs ...string) bool {
for _, subStr := range subStrs {
if strings.Contains(strings.ToLower(str),
strings.ToLower(subStr)) {
return true
}
}
return false
}