Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions gravatar.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions smtp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
102 changes: 73 additions & 29 deletions verifier.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package emailverifier

import (
"context"
"fmt"
"net/http"
"time"
)

// 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
Expand Down Expand Up @@ -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,
Expand All @@ -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)
}

Expand All @@ -116,21 +160,21 @@ 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
}

// EnableSMTPCheck enables check email by smtp,
// 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
}

Expand All @@ -155,33 +199,33 @@ 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
}

// EnableCatchAllCheck enables catchAll check by smtp
// 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
}

Expand Down Expand Up @@ -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 {
Expand Down
26 changes: 26 additions & 0 deletions verifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}