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/url" "os" "reflect" "regexp" "strings" ) // GetEnv is a helper function for getting environment variables with a default func GetEnv(name string, def string) (env string) { // If variable not set, provide default if env = os.Getenv(name); env == "" { env = def } return } // CorsHeaders returns a map to allow Cors func CorsHeaders() map[string]string { return map[string]string{ "Access-Control-Allow-Origin": "*", // do not wildcard: https://stackoverflow.com/questions/13146892/cors-access-control-allow-headers-wildcard-being-ignored "Access-Control-Allow-Headers": "authorization, content-type, referer, sec-ch-ua, sec-ch-ua-mobile, sec-ch-ua-platform, user-agent, x-amz-date, x-amz-security-token", "Access-Control-Allow-Methods": "OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD", "Access-Control-Max-Age": "86400", "Access-Control-Allow-Credentials": "true", } } func DeepCopy(fromValue interface{}) (toValue interface{}) { return deepcopy.Copy(fromValue) } func Clone[T any](fromValue T) (toValue T) { return deepcopy.Copy(fromValue).(T) } func ValueToPointer[V any](value V) *V { return &value } func PointerToValue[V any](value *V) V { if value != nil { return *value } return *new(V) // zero value of V } func ValidateEmailAddress(email string) (string, error) { // 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") } // 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 } func stripInvalidCharacters(email string) string { cleanEmail := email // 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 destinationEmails } func StripEmail(email string) (strippedEmail string, strippedDomain string) { // Strip the email address from the + to the @ // Define a regular expression pattern to match the "+" to "@" part emailPattern := `(\+.*@)` // Define the regular expression pattern to match the domain part after "@" domainPattern := `@(.+)` // Compile the regular expression emailRegex := regexp.MustCompile(emailPattern) domainRegex := regexp.MustCompile(domainPattern) // Replace the matched part with an empty string strippedEmail = emailRegex.ReplaceAllString(email, "@") // Find the first match in the email address match := domainRegex.FindStringSubmatch(email) // Check if a match was found if len(match) > 1 { // The domain part (excluding "@") is in the first capture group (index 1) strippedDomain = match[1] } return strippedEmail, strippedDomain } // IsUrlStrict Returns whether a URL is valid in a strict way (Must have scheme and host) func IsUrlStrict(str string) bool { u, err := url.Parse(str) return err == nil && u.Scheme != "" && u.Host != "" } func IsValidUsername(str string) bool { regex := regexp.MustCompile("^[a-zA-Z0-9-.@+_]*$") return regex.MatchString(str) } // StandardisePhoneNumber standardises phone numbers with +27 instead of 0 prefix func StandardisePhoneNumber(number string) string { number = strings.TrimSpace(number) if number == "" { return number } // is the first rune/char of the string a 0 if []rune(number)[0] == []rune("0")[0] { // Add south african country code (hardcoded for now) number = "+27" + number[1:] } return number } func FormatPhoneNumber(phoneNumber string) string { if len(phoneNumber) > 7 { return phoneNumber } // Format as 076 453 2188 phoneNumber = insertInto(phoneNumber, 3, " ") phoneNumber = insertInto(phoneNumber, 7, " ") return phoneNumber } func insertInto(s string, index int, characters string) string { return s[:index] + characters + s[index:] } // RemoveUrlScheme Removes http:// or https:// from a URL func RemoveUrlScheme(str string) string { newStr := strings.Replace(str, "http://", "", 1) newStr = strings.Replace(str, "https://", "", 1) return newStr } // StripQueryString - Strips the query parameters from a URL func StripQueryString(inputUrl string) (string, error) { u, err := url.Parse(inputUrl) if err != nil { return inputUrl, err } u.RawQuery = "" return u.String(), nil } // EscapeOpenSearchSearchString See https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#_reserved_characters func EscapeOpenSearchSearchString(str string) string { searchString := str // Reserved characters // NOTE: first char must be "\" to prevent replacing it again after replacing other chars with "\\" reservedCharacters := []string{"\\", "+", "-", "=", "&&", "||", "!", "(", ")", "{", "}", "[", "]", "^", "\"", "~", "*", "?", ":", "/"} // Remove "<" and ">" strings.ReplaceAll(searchString, "<", "") strings.ReplaceAll(searchString, ">", "") // Escape the reserved characters with double backslashes ("\\") for _, char := range reservedCharacters { if strings.Contains(searchString, char) { re := regexp.MustCompile(char) searchString = re.ReplaceAllString(searchString, "\\"+char) } } return searchString } // IsEqual returns if the two objects are equal func IsEqual(expected interface{}, actual interface{}) bool { if expected == nil || actual == nil { return expected == actual } if exp, ok := expected.([]byte); ok { act, ok := actual.([]byte) if !ok { return false } if exp == nil || act == nil { return true } return bytes.Equal(exp, act) } return reflect.DeepEqual(expected, actual) }