diff --git a/gravatar.go b/gravatar.go index 14b373a7..e9fc87c3 100644 --- a/gravatar.go +++ b/gravatar.go @@ -18,7 +18,7 @@ type Gravatar struct { func (v *Verifier) CheckGravatar(email string) (*Gravatar, error) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - err, emailMd5 := getMD5Hash(strings.ToLower(strings.TrimSpace(email))) + emailMd5, err := getMD5Hash(strings.ToLower(strings.TrimSpace(email))) if err != nil { return nil, err } @@ -41,7 +41,7 @@ func (v *Verifier) CheckGravatar(email string) (*Gravatar, error) { return nil, err } // check body - err, md5Body := getMD5Hash(string(body)) + md5Body, err := getMD5Hash(string(body)) if err != nil { return nil, err } diff --git a/smtp.go b/smtp.go index f9f10d5c..cbc6b22a 100644 --- a/smtp.go +++ b/smtp.go @@ -29,7 +29,7 @@ type SMTP struct { // // if server is catch-all server, username will not be checked func (v *Verifier) CheckSMTP(domain, username string) (*SMTP, error) { - if !v.smtpCheckEnabled { + if !v.SmtpCheckEnabled { return nil, nil } @@ -69,7 +69,7 @@ func (v *Verifier) CheckSMTP(domain, username string) (*SMTP, error) { // Default sets catch-all to true ret.CatchAll = true - if v.catchAllCheckEnabled { + if v.CatchAllCheckEnabled { // Checks the deliver ability of a randomly generated address in // order to verify the existence of a catch-all and etc. randomEmail := GenerateRandomEmail(domain) diff --git a/util.go b/util.go index b03579cb..ff04c922 100644 --- a/util.go +++ b/util.go @@ -49,11 +49,11 @@ func callJobFuncWithParams(jobFunc interface{}, params []interface{}) []reflect. // getMD5Hash use md5 to encode string // #nosec -func getMD5Hash(str string) (error, string) { +func getMD5Hash(str string) (string, error) { h := md5.New() _, err := h.Write([]byte(str)) if err != nil { - return err, "" + return "", err } - return nil, hex.EncodeToString(h.Sum(nil)) + return hex.EncodeToString(h.Sum(nil)), nil } diff --git a/verifier.go b/verifier.go index f3cb487c..be36ed9f 100644 --- a/verifier.go +++ b/verifier.go @@ -1,6 +1,7 @@ package emailverifier import ( + "context" "fmt" "net/http" "time" @@ -8,10 +9,11 @@ import ( // Verifier is an email verifier. Create one by calling NewVerifier type Verifier struct { - smtpCheckEnabled bool // SMTP check enabled or disabled (disabled by default) - catchAllCheckEnabled bool // SMTP catchAll check enabled or disabled (enabled by default) - domainSuggestEnabled bool // whether suggest a most similar correct domain or not (disabled by default) - gravatarCheckEnabled bool // gravatar check enabled or disabled (disabled by default) + VerifyTimeout time.Duration // timeout for the verification process, defaults to 2 seconds + SmtpCheckEnabled bool // SMTP check enabled or disabled (disabled by default) + CatchAllCheckEnabled bool // SMTP catchAll check enabled or disabled (enabled by default) + DomainSuggestEnabled bool // whether suggest a most similar correct domain or not (disabled by default) + GravatarCheckEnabled bool // gravatar check enabled or disabled (disabled by default) fromEmail string // name to use in the `EHLO:` SMTP command, defaults to "user@example.org" helloName string // email to use in the `MAIL FROM:` SMTP command. defaults to `localhost` schedule *schedule // schedule represents a job schedule @@ -46,16 +48,16 @@ func init() { // NewVerifier creates a new email verifier func NewVerifier() *Verifier { return &Verifier{ + VerifyTimeout: 2 * time.Second, fromEmail: defaultFromEmail, helloName: defaultHelloName, - catchAllCheckEnabled: true, + CatchAllCheckEnabled: true, apiVerifiers: map[string]smtpAPIVerifier{}, } } // Verify performs address, misc, mx and smtp checks func (v *Verifier) Verify(email string) (*Result, error) { - ret := Result{ Email: email, Reachable: reachableUnknown, @@ -76,28 +78,70 @@ func (v *Verifier) Verify(email string) (*Result, error) { return &ret, nil } - mx, err := v.CheckMX(syntax.Domain) - if err != nil { - return &ret, err - } - ret.HasMxRecords = mx.HasMXRecord + ctx, cancel := context.WithTimeout(context.Background(), v.VerifyTimeout) + defer cancel() + + mxCh := make(chan *Mx) + smtpCh := make(chan *SMTP) + errCh := make(chan error) + + go func() { + mx, err := v.CheckMX(syntax.Domain) + if err != nil { + errCh <- err + return + } + mxCh <- mx + }() - smtp, err := v.CheckSMTP(syntax.Domain, syntax.Username) - if err != nil { + go func() { + smtp, err := v.CheckSMTP(syntax.Domain, syntax.Username) + if err != nil { + errCh <- err + return + } + smtpCh <- smtp + }() + + select { + case mx := <-mxCh: + ret.HasMxRecords = mx.HasMXRecord + case smtp := <-smtpCh: + ret.SMTP = smtp + ret.Reachable = v.calculateReachable(smtp) + case err := <-errCh: return &ret, err + case <-ctx.Done(): + return &ret, ctx.Err() } - ret.SMTP = smtp - ret.Reachable = v.calculateReachable(smtp) - if v.gravatarCheckEnabled { - gravatar, err := v.CheckGravatar(email) - if err != nil { + if v.GravatarCheckEnabled { + ctx, cancel = context.WithTimeout(context.Background(), v.VerifyTimeout) + defer cancel() + + gravatarCh := make(chan *Gravatar) + errCh = make(chan error) + + go func() { + gravatar, err := v.CheckGravatar(email) + if err != nil { + errCh <- err + return + } + gravatarCh <- gravatar + }() + + select { + case gravatar := <-gravatarCh: + ret.Gravatar = gravatar + case err := <-errCh: return &ret, err + case <-ctx.Done(): + return &ret, ctx.Err() } - ret.Gravatar = gravatar } - if v.domainSuggestEnabled { + if v.DomainSuggestEnabled { ret.Suggestion = v.SuggestDomain(syntax.Domain) } @@ -116,13 +160,13 @@ func (v *Verifier) AddDisposableDomains(domains []string) *Verifier { // EnableGravatarCheck enables check gravatar, // we don't check gravatar by default func (v *Verifier) EnableGravatarCheck() *Verifier { - v.gravatarCheckEnabled = true + v.GravatarCheckEnabled = true return v } // DisableGravatarCheck disables check gravatar, func (v *Verifier) DisableGravatarCheck() *Verifier { - v.gravatarCheckEnabled = false + v.GravatarCheckEnabled = false return v } @@ -130,7 +174,7 @@ func (v *Verifier) DisableGravatarCheck() *Verifier { // for most ISPs block outgoing SMTP requests through port 25, to prevent spam, // we don't check smtp by default func (v *Verifier) EnableSMTPCheck() *Verifier { - v.smtpCheckEnabled = true + v.SmtpCheckEnabled = true return v } @@ -155,7 +199,7 @@ func (v *Verifier) DisableAPIVerifier(name string) { // DisableSMTPCheck disables check email by smtp func (v *Verifier) DisableSMTPCheck() *Verifier { - v.smtpCheckEnabled = false + v.SmtpCheckEnabled = false return v } @@ -163,25 +207,25 @@ func (v *Verifier) DisableSMTPCheck() *Verifier { // for most ISPs block outgoing catchAll requests through port 25, to prevent spam, // we don't check catchAll by default func (v *Verifier) EnableCatchAllCheck() *Verifier { - v.catchAllCheckEnabled = true + v.CatchAllCheckEnabled = true return v } // DisableCatchAllCheck disables catchAll check by smtp func (v *Verifier) DisableCatchAllCheck() *Verifier { - v.catchAllCheckEnabled = false + v.CatchAllCheckEnabled = false return v } // EnableDomainSuggest will suggest a most similar correct domain when domain misspelled func (v *Verifier) EnableDomainSuggest() *Verifier { - v.domainSuggestEnabled = true + v.DomainSuggestEnabled = true return v } // DisableDomainSuggest will not suggest anything func (v *Verifier) DisableDomainSuggest() *Verifier { - v.domainSuggestEnabled = false + v.DomainSuggestEnabled = false return v } @@ -224,7 +268,7 @@ func (v *Verifier) Proxy(proxyURI string) *Verifier { } func (v *Verifier) calculateReachable(s *SMTP) string { - if !v.smtpCheckEnabled { + if !v.SmtpCheckEnabled { return reachableUnknown } if s.Deliverable { diff --git a/verifier_test.go b/verifier_test.go index f90d80e3..3bb63379 100644 --- a/verifier_test.go +++ b/verifier_test.go @@ -292,3 +292,29 @@ func TestCheckEmail_EnableDomainSuggest(t *testing.T) { assert.Equal(t, ret.Suggestion, "") } + +func TestVerify_ValidNonDisposableEmail(t *testing.T) { + // Arrange + var ( + username = "validuser" + domain = "non-disposable.com" + email = username + "@" + domain + ) + + verifier := NewVerifier() + result, err := verifier.Verify(email) + assert.NoError(t, err) + + expected := &Result{ + Email: email, + Reachable: reachableUnknown, + Syntax: Syntax{ + Username: username, + Domain: domain, + Valid: true, + }, + Disposable: false, + } + + assert.Equal(t, expected, result) +}