Skip to content
Snippets Groups Projects
Commit dcfa4ffe authored by Francé Wilke's avatar Francé Wilke
Browse files

Improve email address validation

parent 0519da16
No related branches found
No related tags found
No related merge requests found
......@@ -26,6 +26,45 @@ const (
regexIndexSubmatchEnd = 3
)
var WhitespaceChars = []string{
// Standard whitespace characters
"\u0009", // Character tabulation
"\u000A", // Line feed
"\u000B", // Line tabulation
"\u000C", // Form feed
"\u000D", // Carriage return
"\u0020", // Space
"\u0085", // Next line
"\u00A0", // No-break space
"\u1680", // Ogham space mark
"\u2000", // En quad
"\u2001", // Em quad
"\u2002", // En space
"\u2003", // Em space
"\u2004", // Three-per-em space
"\u2005", // Four-per-em space
"\u2006", // Six-per-em space
"\u2007", // Figure space
"\u2008", // Punctuation space
"\u2009", // Thin space
"\u200A", // Hair space
"\u2028", // Line separator
"\u2029", // Paragraph separator
"\u202F", // Narrow no-break space
"\u205F", // Medium mathematical space
"\u3000", // Ideographic space
}
var NonOfficialWhitespaceChars = []string{
// Characters with property White_Space=no
"\u180E", // Mongolian vowel separator
"\u200B", // Zero width space
"\u200C", // Zero width non-joiner
"\u200D", // Zero width joiner
"\u2060", // Word joiner
"\uFEFF", // Zero width no-break space
}
var snakeCaseRegex = regexp.MustCompile("^" + snakeCasePattern + "$")
func IsSnakeCase(name string) bool {
......@@ -49,7 +88,11 @@ func ReplaceNonSpacingMarks(str string) string {
}
func RemoveAllWhiteSpaces(s string) string {
return strings.ReplaceAll(strings.ReplaceAll(s, " ", ""), "\t", "")
cleanedString := strings.ReplaceAll(strings.ReplaceAll(s, " ", ""), "\t", "")
for _, whitespaceChar := range WhitespaceChars {
cleanedString = strings.ReplaceAll(cleanedString, whitespaceChar, "")
}
return cleanedString
}
func ReplaceCaseInsensitive(string, toReplace, replaceWith string) string {
......
......@@ -2,10 +2,10 @@ package utils
import (
"bytes"
emailverifier "github.com/AfterShip/email-verifier"
"github.com/mohae/deepcopy"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/string_utils"
"net/mail"
"net/url"
"os"
"reflect"
......@@ -55,23 +55,68 @@ func PointerToValue[V any](value *V) V {
}
func ValidateEmailAddress(email string) (string, error) {
if email == "" {
// To lower
cleanedEmail := strings.ToLower(strings.TrimSpace(email))
// Remove all whitespaces
cleanedEmail = string_utils.RemoveAllWhiteSpaces(cleanedEmail)
// Also remove unofficial whitespaces
for _, char := range string_utils.NonOfficialWhitespaceChars {
cleanedEmail = strings.ReplaceAll(cleanedEmail, char, "")
}
// Strip invalid characters
cleanedEmail = stripInvalidCharacters(cleanedEmail)
// Make sure the email is not empty
if cleanedEmail == "" {
return "", errors.Error("email address is empty")
}
cleanEmail := strings.ToLower(strings.TrimSpace(email))
cleanEmail = string_utils.RemoveAllWhiteSpaces(cleanEmail)
// Parse and verify the email
verifier := emailverifier.NewVerifier()
result, err := verifier.Verify(cleanedEmail)
if err != nil || !result.Syntax.Valid {
return cleanedEmail, errors.Wrap(err, "could not parse email address")
}
return cleanedEmail, nil
}
// Remove ZWSP ("\u200B") characters with an empty string to remove it
cleanEmail = strings.ReplaceAll(cleanEmail, "\u200B", "")
func stripInvalidCharacters(email string) string {
cleanEmail := email
// We validate it but still return it since in some cases we don't want to break everything if the email is bad
_, err := mail.ParseAddress(cleanEmail)
if err != nil {
return cleanEmail, errors.Wrap(err, "could not parse email address")
// Replace quotes, asterisks, etc.
cleanEmail = strings.ReplaceAll(cleanEmail, "'", "")
cleanEmail = strings.ReplaceAll(cleanEmail, "*", "")
cleanEmail = strings.ReplaceAll(cleanEmail, "!", "")
cleanEmail = strings.ReplaceAll(cleanEmail, "+", "")
// Trim invalid characters, like underscore, so that it still fails if it's inside the email
cleanEmail = strings.Trim(cleanEmail, "_")
return cleanEmail
}
func SplitAndCleanEmailAddresses(emails string) []string {
var destinationEmails []string
splitEmails := string_utils.SplitString(emails, []rune{',', ';'})
if len(splitEmails) >= 1 {
// Success - return these emails
for _, email := range splitEmails {
cleanedEmail, err := ValidateEmailAddress(email)
if err == nil && cleanedEmail != "" {
destinationEmails = append(destinationEmails, cleanedEmail)
}
}
if len(destinationEmails) > 0 {
return destinationEmails
}
}
return cleanEmail, nil
return destinationEmails
}
func StripEmail(email string) (strippedEmail string, strippedDomain string) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment