diff --git a/bulkvalidate/bulkvalidate.go b/bulkvalidate/bulkvalidate.go index 5d9af8e..0c8940d 100644 --- a/bulkvalidate/bulkvalidate.go +++ b/bulkvalidate/bulkvalidate.go @@ -143,7 +143,7 @@ func RunBulkValidation(inputFilePath, outputFilePath string) { if err != nil { log.Printf("Error: %s %s", email, err.Error()) } - emailResults, err := mailvalidate.ValidateEmail(request) + emailResults, err := mailvalidate.ValidateEmail(request, syntaxResults) if err != nil { log.Printf("Error: %s %s", email, err.Error()) } diff --git a/go.mod b/go.mod index 61fc7ba..977bed3 100644 --- a/go.mod +++ b/go.mod @@ -16,4 +16,5 @@ require ( github.com/schollz/progressbar/v3 v3.14.6 // indirect golang.org/x/sys v0.23.0 // indirect golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect ) diff --git a/go.sum b/go.sum index f802fa5..f39ddb1 100644 --- a/go.sum +++ b/go.sum @@ -28,3 +28,5 @@ golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 8bb1ecc..df729ef 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -51,7 +51,7 @@ func VerifyEmail(email string) { request := run.BuildRequest(email) syntaxResults := VerifySyntax(email, false) domainResults := VerifyDomain(syntaxResults.Domain, false) - emailResults, err := mailvalidate.ValidateEmail(request) + emailResults, err := mailvalidate.ValidateEmail(request, syntaxResults) if err != nil { fmt.Println(err) } diff --git a/internal/dns/known_email_providers.toml b/internal/dns/known_email_providers.toml index af006ad..490ce1e 100644 --- a/internal/dns/known_email_providers.toml +++ b/internal/dns/known_email_providers.toml @@ -101,6 +101,7 @@ domains = [ ["ondmarc.com", "ondmarc"], ["powerspf.com", "power spf"], ["pphosted.com", "proofpoint"], + ["ppe-hosted.com", "proofpoint"], ["redpoints.com", "red points"], ["reflexion.net", "sophos"], ["res.cisco.com", "cisco"], diff --git a/internal/run/run.go b/internal/run/run.go index e1d47f3..17a9b0d 100644 --- a/internal/run/run.go +++ b/internal/run/run.go @@ -80,8 +80,13 @@ func BuildResponse(emailAddress string, syntax mailvalidate.SyntaxValidation, do Description: email.Description, } + cleanEmail := emailAddress + if syntax.IsValid { + cleanEmail = fmt.Sprintf("%s@%s", syntax.User, syntax.Domain) + } + response := VerifyEmailResponse{ - Email: emailAddress, + Email: cleanEmail, IsDeliverable: email.IsDeliverable, Provider: domain.Provider, Firewall: domain.Firewall, diff --git a/internal/syntax/syntax.go b/internal/syntax/syntax.go index cd3bd71..c7d355b 100644 --- a/internal/syntax/syntax.go +++ b/internal/syntax/syntax.go @@ -3,8 +3,12 @@ package syntax import ( "regexp" "strings" + "unicode" "golang.org/x/net/publicsuffix" + "golang.org/x/text/runes" + "golang.org/x/text/transform" + "golang.org/x/text/unicode/norm" ) var ( @@ -12,17 +16,31 @@ var ( domainRegex = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9](?:\.[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])*$`) ) -func IsValidEmailSyntax(email string) bool { +func IsValidEmailSyntax(email string) (bool, string) { + normalizedEmail := convertToAscii(email) + if !isValidEmailFormat(email) { - return false + return false, "" } username, domain, ok := splitEmail(email) if !ok { - return false + return false, "" } - return isValidUsername(username) && isValidDomain(domain) + return isValidUsername(username) && isValidDomain(domain), normalizedEmail +} + +func GetEmailUserAndDomain(email string) (string, string, bool) { + if strings.TrimSpace(email) != email { + return "", "", false + } + user, domain, ok := splitEmail(email) + if !isValidUsername(user) || !isValidDomain(domain) { + return "", "", false + } + + return user, domain, ok } func isValidEmailFormat(email string) bool { @@ -90,14 +108,17 @@ func isValidTLD(tld string) bool { return icann } -func GetEmailUserAndDomain(email string) (string, string, bool) { - if strings.TrimSpace(email) != email { - return "", "", false - } - user, domain, ok := splitEmail(email) - if !isValidUsername(user) || !isValidDomain(domain) { - return "", "", false +func convertToAscii(input string) string { + t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC) + result, _, _ := transform.String(t, input) + + // Step 2: Remove any remaining non-ASCII characters + ascii := make([]rune, 0, len(result)) + for _, r := range result { + if r <= unicode.MaxASCII { + ascii = append(ascii, r) + } } - return user, domain, ok + return string(ascii) } diff --git a/internal/syntax/syntax_test.go b/internal/syntax/syntax_test.go index b67ca2d..7ae4438 100644 --- a/internal/syntax/syntax_test.go +++ b/internal/syntax/syntax_test.go @@ -35,7 +35,7 @@ func TestIsValidEmailSyntax(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := IsValidEmailSyntax(tt.email); got != tt.want { + if got, _ := IsValidEmailSyntax(tt.email); got != tt.want { t.Errorf("IsValidEmailSyntax(%q) = %v, want %v", tt.email, got, tt.want) } }) diff --git a/mailvalidate/validation.go b/mailvalidate/validation.go index c929b66..b3287fe 100644 --- a/mailvalidate/validation.go +++ b/mailvalidate/validation.go @@ -52,12 +52,12 @@ type SyntaxValidation struct { func ValidateEmailSyntax(email string) SyntaxValidation { var results SyntaxValidation - ok := syntax.IsValidEmailSyntax(email) + ok, cleanEmail := syntax.IsValidEmailSyntax(email) if !ok { return results } - user, domain, ok := syntax.GetEmailUserAndDomain(email) + user, domain, ok := syntax.GetEmailUserAndDomain(cleanEmail) if !ok { return results } @@ -121,11 +121,14 @@ func ValidateDomainWithCustomKnownProviders(validationRequest EmailValidationReq return results, nil } -func ValidateEmail(validationRequest EmailValidationRequest) (EmailValidation, error) { +func ValidateEmail(validationRequest EmailValidationRequest, emailSyntaxResults SyntaxValidation) (EmailValidation, error) { var results EmailValidation if err := validateRequest(&validationRequest); err != nil { return results, errors.Wrap(err, "Invalid request") } + if !emailSyntaxResults.IsValid { + return results, fmt.Errorf("Invalid email address") + } freeEmails, err := GetFreeEmailList() if err != nil { @@ -137,20 +140,22 @@ func ValidateEmail(validationRequest EmailValidationRequest) (EmailValidation, e log.Fatal(err) } - isFreeEmail, err := IsFreeEmailCheck(validationRequest.Email, freeEmails) + email := fmt.Sprintf("%s@%s", emailSyntaxResults.User, emailSyntaxResults.Domain) + + isFreeEmail, err := IsFreeEmailCheck(email, freeEmails) if err != nil { return results, errors.Wrap(err, "Error executing free email check") } results.IsFreeAccount = isFreeEmail - isRoleAccount, err := IsRoleAccountCheck(validationRequest.Email, roleAccounts) + isRoleAccount, err := IsRoleAccountCheck(email, roleAccounts) if err != nil { return results, errors.Wrap(err, "Error executing role account check") } results.IsRoleAccount = isRoleAccount isVerified, smtpValidation, err := mailserver.VerifyEmailAddress( - validationRequest.Email, + email, validationRequest.FromDomain, validationRequest.FromEmail, validationRequest.Dns,