Files
kjol/go/validation/validation.go
2026-07-15 11:28:24 -04:00

262 lines
6.6 KiB
Go
Raw Blame History

package validation
import (
"errors"
"regexp"
"slices"
"strconv"
"strings"
"unicode/utf8"
)
func SanitizeEmail(input string) string {
input = strings.ToLower(strings.TrimSpace(input))
re := regexp.MustCompile(`[a-z0-9!#$%&'*+/=?^_` + "`" + `{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_` + "`" + `{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?`)
match := re.FindString(input)
return match
}
// Checks an email address against a first and last name and returns true if it matches one of the following patterns:
// firstlast@domain.tld, flast@domain.tld, firstl@domain.tld, first.last@domain.tld, f.last@domain.tld,
// first.l@domain.tld, first-last@domain.tld, f-last@domain.tld, first-l@domain.tld. Otherwise, false.
// Also handles hyphenated and space-separated last names (e.g., Meyer-Ogren or Van Der Berg)
func DoesNameMatchEmail(email string, firstName string, lastName string) bool {
// Validate inputs
if email == "" || firstName == "" || lastName == "" {
return true
}
email = strings.ToLower(email)
firstName = strings.ToLower(firstName)
lastName = strings.ToLower(lastName)
emailIdentifier := strings.Split(email, "@")[0]
firstInitial := string(firstName[0])
// Split hyphenated and space-separated last names
lastNameParts := []string{lastName}
// Also add concatenated version (removing hyphens/spaces)
lastNameNormalized := strings.ReplaceAll(lastName, "-", "")
lastNameNormalized = strings.ReplaceAll(lastNameNormalized, " ", "")
if lastNameNormalized != lastName {
lastNameParts = append(lastNameParts, lastNameNormalized)
}
// Add individual parts
if strings.Contains(lastName, "-") {
lastNameParts = append(lastNameParts, strings.Split(lastName, "-")...)
}
if strings.Contains(lastName, " ") {
lastNameParts = append(lastNameParts, strings.Split(lastName, " ")...)
}
separators := []string{"", ".", "-"}
// Check standalone first or last names
if emailIdentifier == firstName {
return true
}
// Check each part of the last name
for _, lastPart := range lastNameParts {
if lastPart == "" {
continue
}
if emailIdentifier == lastPart {
return true
}
lastPartInitial := string(lastPart[0])
// Check all separator combinations with each last name part
for _, separator := range separators {
switch emailIdentifier {
case strings.Join([]string{firstName, lastPart}, separator),
strings.Join([]string{firstInitial, lastPart}, separator),
strings.Join([]string{firstName, lastPartInitial}, separator):
return true
}
}
}
return false
}
func SanitizePhone(input string) string {
// Regular expression to match non-digit characters
re := regexp.MustCompile(`[^0-9]`)
// Replace non-digit characters with empty string
sanitized := re.ReplaceAllString(input, "")
return sanitized
}
func ValidatePhoneNumber(phoneNumber string) error {
isNumber, _ := regexp.MatchString(`^\d+$`, phoneNumber)
if !isNumber {
return errors.New("Phone number must only contain numbers.")
}
if len(phoneNumber) != 10 {
return errors.New("Invalid phone number length.")
}
areaCode, _ := strconv.Atoi(phoneNumber[:3])
// Lowest area code is 200, so less than (200) 000-0000
if areaCode < 200 {
return errors.New("Phone number has invalid area code.")
}
return nil
}
// Removes invalid UTF-8 sequences and replaces special characters
func SanitizeString(s string) string {
// Replace non-breaking spaces (0xa0) with regular spaces
s = strings.ReplaceAll(s, "\u00a0", " ")
// Remove other common problematic characters
s = strings.ReplaceAll(s, "\r", "")
// Remove replacement character (<28>) that appears when invalid UTF-8 is encountered
s = strings.ReplaceAll(s, "\ufffd", "")
// Validate and fix UTF-8 encoding
if !utf8.ValidString(s) {
// Convert to valid UTF-8 by removing invalid bytes
v := make([]rune, 0, len(s))
for _, r := range s {
if r != utf8.RuneError {
v = append(v, r)
}
}
s = string(v)
}
// Trim whitespace
return strings.TrimSpace(s)
}
var stateCodes []string = []string{
"AL", "AK", "AZ", "AR", "AS", "CA", "CO", "CT", "DE", "DC", "FL", "GA",
"GU", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD", "MA",
"MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NM", "NY", "NC",
"ND", "MP", "OH", "OK", "OR", "PA", "PR", "RI", "SC", "SD", "TN", "TX",
"TT", "UT", "VT", "VA", "VI", "WA", "WV", "WI", "WY",
}
// Checks if the input string is a proper two-letter state code
func ValidateStateCode(s string) error {
if slices.Contains(stateCodes, s) {
return nil
}
return errors.New("Invalid state code.")
}
func ValidateTaxId(id string) error {
isNumber, _ := regexp.MatchString(`^\d+$`, id)
if !isNumber {
return errors.New("Tax ID must only contain numbers.")
}
if len(id) != 9 {
return errors.New("Tax ID must be 9 digits long.")
}
idNum, _ := strconv.Atoi(id)
if idNum == 0 {
return errors.New("Tax ID cannot be 00-0000000.")
}
return nil
}
func ValidateZipCode(zip string) error {
isNumber, _ := regexp.MatchString(`^\d+$`, zip)
if !isNumber {
return errors.New("ZIP code must only contain numbers.")
}
if (len(zip) != 5) && (len(zip) != 9) {
return errors.New("Invalid length for ZIP code.")
}
zipCode, _ := strconv.Atoi(zip[:5])
// Lowest 5 digit ZIP code is 00501
if zipCode < 501 {
return errors.New("Invalid ZIP code.")
}
return nil
}
func ValidateEmail(email string) error {
err := errors.New("Invalid email address.")
if email == "" {
return err
}
emailParts := strings.Split(email, "@")
if len(emailParts) != 2 {
return err
}
account := emailParts[0]
address := emailParts[1]
lenErr := errors.New("Email address is too long.")
if len(account) > 64 {
return lenErr
} else if len(address) > 255 {
return lenErr
}
domainParts := strings.Split(address, ".")
for _, part := range domainParts {
if len(part) > 63 {
return lenErr
}
}
re := regexp.MustCompile(`^[-!#$%&'*+/0-9=?A-Z^_a-z` + "`" + `{|}~](\.?[-!#$%&'*+/0-9=?A-Z^_a-z` + "`" + `{|}~])*@[a-zA-Z0-9](-*\.?[a-zA-Z0-9])*\.[a-zA-Z](-?[a-zA-Z0-9])+$`)
if !re.MatchString(email) {
return err
}
return nil
}
func ValidateUsername(username string) error {
if len(username) < 5 || len(username) > 50 {
return errors.New("Username must have 5-50 characters.")
}
isAlphanumeric, _ := regexp.MatchString(`^[A-Za-z0-9]*$`, username)
if !isAlphanumeric {
return errors.New("Username must only contain alphanumeric characters.")
}
return nil
}
func ValidateUrl(url string) error {
err := errors.New("Invalid URL.")
valid, _ := regexp.MatchString(`[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)`, url)
if !valid {
return err
}
return nil
}