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 (�) 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 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 }