speed up email checks execution
This commit is contained in:
172
vendor/gitlab.com/etke.cc/go/validator/emails.go
generated
vendored
172
vendor/gitlab.com/etke.cc/go/validator/emails.go
generated
vendored
@@ -1,98 +1,142 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/mail"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"blitiri.com.ar/go/spf"
|
||||
"gitlab.com/etke.cc/go/trysmtp"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Email checks if email is valid
|
||||
func (v *V) Email(email string, optionalSenderIP ...net.IP) bool {
|
||||
var senderIP net.IP
|
||||
if len(optionalSenderIP) > 0 {
|
||||
senderIP = optionalSenderIP[0]
|
||||
}
|
||||
|
||||
// edge case: email may be optional
|
||||
if email == "" {
|
||||
return !v.enforce.Email
|
||||
}
|
||||
|
||||
length := len(email)
|
||||
// email cannot too short and too big
|
||||
if length < 3 || length > 254 {
|
||||
v.log.Info("email %s invalid, reason: length", email)
|
||||
return false
|
||||
}
|
||||
|
||||
_, err := mail.ParseAddress(email)
|
||||
address, err := mail.ParseAddress(email)
|
||||
if err != nil {
|
||||
v.log.Info("email %s invalid, reason: %v", email, err)
|
||||
return false
|
||||
}
|
||||
|
||||
emailb := []byte(email)
|
||||
for _, spamregex := range v.spamlist {
|
||||
if spamregex.Match(emailb) {
|
||||
v.log.Info("email %s invalid, reason: spamlist", email)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if v.enforce.MX {
|
||||
if v.emailNoMX(email) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if v.enforce.SPF {
|
||||
if v.emailNoSPF(email, senderIP) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if v.enforce.SMTP {
|
||||
if v.emailNoSMTP(email) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
email = address.Address
|
||||
return v.emailChecks(email, optionalSenderIP...)
|
||||
}
|
||||
|
||||
func (v *V) emailNoMX(email string) bool {
|
||||
at := strings.LastIndex(email, "@")
|
||||
domain := email[at+1:]
|
||||
|
||||
nomx := !v.MX(domain)
|
||||
if nomx {
|
||||
v.log.Info("email %s domain %s invalid, reason: no MX", email, domain)
|
||||
return true
|
||||
func (v *V) emailChecks(email string, optionalSenderIP ...net.IP) bool {
|
||||
maxChecks := 4
|
||||
var senderIP net.IP
|
||||
if len(optionalSenderIP) > 0 {
|
||||
senderIP = optionalSenderIP[0]
|
||||
}
|
||||
errchan := make(chan error, maxChecks)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
return false
|
||||
}
|
||||
go v.emailSpamlist(ctx, email, errchan)
|
||||
go v.emailNoMX(ctx, email, errchan)
|
||||
go v.emailNoSPF(ctx, email, senderIP, errchan)
|
||||
go v.emailNoSMTP(ctx, email, errchan)
|
||||
|
||||
func (v *V) emailNoSMTP(email string) bool {
|
||||
client, err := trysmtp.Connect(v.from, email)
|
||||
if err != nil {
|
||||
if strings.HasPrefix(err.Error(), "45") {
|
||||
v.log.Info("email %s may be invalid, reason: SMTP check (%v)", email, err)
|
||||
var checks int
|
||||
for {
|
||||
checks++
|
||||
err := <-errchan
|
||||
if err != nil {
|
||||
v.log.Info("email %q is invalid, reason: %v", email, err)
|
||||
return false
|
||||
}
|
||||
|
||||
v.log.Info("email %s invalid, reason: SMTP check (%v)", email, err)
|
||||
return true
|
||||
if checks >= maxChecks {
|
||||
return true
|
||||
}
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (v *V) emailNoSPF(email string, senderIP net.IP) bool {
|
||||
result, _ := spf.CheckHostWithSender(senderIP, "", email, spf.WithTraceFunc(v.log.Info)) //nolint:errcheck // not a error
|
||||
return result == spf.Fail
|
||||
func (v *V) emailSpamlist(ctx context.Context, email string, errchan chan error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
emailb := []byte(email)
|
||||
for _, spamregex := range v.spamlist {
|
||||
if spamregex.Match(emailb) {
|
||||
errchan <- fmt.Errorf("spamlist")
|
||||
return
|
||||
}
|
||||
}
|
||||
errchan <- nil
|
||||
}
|
||||
}
|
||||
|
||||
func (v *V) emailNoMX(ctx context.Context, email string, errchan chan error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
if !v.enforce.MX {
|
||||
errchan <- nil
|
||||
return
|
||||
}
|
||||
|
||||
at := strings.LastIndex(email, "@")
|
||||
domain := email[at+1:]
|
||||
if !v.MX(domain) {
|
||||
v.log.Info("email %s domain %s invalid, reason: no MX", email, domain)
|
||||
errchan <- fmt.Errorf("no MX")
|
||||
return
|
||||
}
|
||||
errchan <- nil
|
||||
}
|
||||
}
|
||||
|
||||
func (v *V) emailNoSMTP(ctx context.Context, email string, errchan chan error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
if !v.enforce.SMTP {
|
||||
errchan <- nil
|
||||
return
|
||||
}
|
||||
|
||||
client, err := trysmtp.Connect(v.from, email)
|
||||
if err != nil {
|
||||
if strings.HasPrefix(err.Error(), "45") {
|
||||
v.log.Info("email %s may be invalid, reason: SMTP check (%v)", email, err)
|
||||
errchan <- nil
|
||||
return
|
||||
}
|
||||
|
||||
v.log.Info("email %s invalid, reason: SMTP check (%v)", email, err)
|
||||
errchan <- fmt.Errorf("SMTP")
|
||||
return
|
||||
}
|
||||
client.Close()
|
||||
errchan <- nil
|
||||
}
|
||||
}
|
||||
|
||||
func (v *V) emailNoSPF(ctx context.Context, email string, senderIP net.IP, errchan chan error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
if !v.enforce.SPF {
|
||||
errchan <- nil
|
||||
return
|
||||
}
|
||||
|
||||
result, _ := spf.CheckHostWithSender(senderIP, "", email, spf.WithTraceFunc(v.log.Info)) //nolint:errcheck // not a error
|
||||
if result == spf.Fail {
|
||||
errchan <- fmt.Errorf("SPF")
|
||||
return
|
||||
}
|
||||
errchan <- nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user