diff --git a/lib/is/is.go b/lib/is/is.go index a697523..296e3c6 100644 --- a/lib/is/is.go +++ b/lib/is/is.go @@ -3,6 +3,7 @@ package is import ( "encoding/base64" "github.com/getevo/json" + "html" "math" "net" "net/url" @@ -162,8 +163,8 @@ func Hexadecimal(str string) bool { return err == nil } -// Hexcolor check if the string is a hexadecimal color. -func Hexcolor(str string) bool { +// HexColor check if the string is a hexadecimal color. +func HexColor(str string) bool { if str == "" { return false } @@ -185,8 +186,8 @@ func Hexcolor(str string) bool { return true } -// RGBcolor check if the string is a valid RGB color in form rgb(RRR, GGG, BBB). -func RGBcolor(str string) bool { +// RGBColor check if the string is a valid RGB color in form rgb(RRR, GGG, BBB). +func RGBColor(str string) bool { if str == "" || len(str) < 10 { return false } @@ -212,6 +213,33 @@ func RGBcolor(str string) bool { return true } +// RGBAColor check if the string is a valid RGBA color in form rgb(RRR, GGG, BBB,ALPHA). +func RGBAColor(str string) bool { + if str == "" || len(str) < 10 { + return false + } + + if str[0:5] != "rgba(" || str[len(str)-1] != ')' { + return false + } + + str = str[5 : len(str)-1] + str = strings.TrimSpace(str) + + for _, p := range strings.Split(str, ",") { + if len(p) > 1 && p[0] == '0' { + return false + } + + p = strings.TrimSpace(p) + if i, e := strconv.Atoi(p); (255 < i || i < 0) || e != nil { + return false + } + } + + return true +} + // LowerCase check if the string is lowercase. Empty string is valid. func LowerCase(str string) bool { if len(str) == 0 { @@ -643,3 +671,90 @@ func toInt(str string) (int64, error) { } return res, err } + +func Cron(str string) bool { + parts := strings.Fields(str) + if len(parts) != 5 { + return false + } + + // Define validation rules for each field: + // position: range or '*' allowed + ranges := []struct { + min, max int + }{ + {0, 59}, // minute + {0, 23}, // hour + {1, 31}, // day-of-month + {1, 12}, // month + {0, 6}, // day-of-week + } + + for i, field := range parts { + if field == "*" { + continue // wildcard is allowed in any field + } + + val, err := strconv.Atoi(field) + if err != nil { + // Not an integer + return false + } + + // Check range + if val < ranges[i].min || val > ranges[i].max { + return false + } + } + + return true +} + +// SafeHTML checks if the given HTML is "safe" by ensuring that it doesn't contain +// known dangerous tags or attributes commonly used for XSS or malicious content. +// It first decodes any HTML entities, then checks for dangerous patterns. +func SafeHTML(input string) bool { + // Decode HTML entities first + decoded := html.UnescapeString(input) + + // Convert to lowercase for case-insensitive matching + content := strings.ToLower(decoded) + + // A list of patterns that, if found, indicate potentially unsafe HTML + dangerousPatterns := []string{ + "|<|<=|>=|==|!=|<>|=)(\d+)$`): lenValidator, - regexp.MustCompile(`^(>|<|<=|>=|==|!=|<>|=)(\d+)$`): numericalValidator, - regexp.MustCompile(`^([+\-]?)int$`): intValidator, - regexp.MustCompile(`^([+\-]?)float$`): floatValidator, - regexp.MustCompile(`^password\((.*)\)$`): passwordValidator, - regexp.MustCompile(`^domain$`): domainValidator, - regexp.MustCompile(`^url$`): urlValidator, - regexp.MustCompile(`^ip$`): ipValidator, - regexp.MustCompile(`^date$`): dateValidator, + regexp.MustCompile("(?i)^text$"): textValidator, + regexp.MustCompile("(?i)^name$"): nameValidator, + regexp.MustCompile("(?i)^alpha$"): alphaValidator, + regexp.MustCompile("(?i)^latin$"): latinValidator, + regexp.MustCompile("(?i)^name$"): nameValidator, + regexp.MustCompile("(?i)^digit$"): digitValidator, + regexp.MustCompile("(?i)^alphanumeric$"): alphaNumericValidator, + regexp.MustCompile("(?i)^required$"): requiredValidator, + regexp.MustCompile("(?i)^email$"): emailValidator, + regexp.MustCompile(`(?i)^regex\((.*)\)$`): regexValidator, + regexp.MustCompile(`(?i)^len(>|<|<=|>=|==|!=|<>|=)(\d+)$`): lenValidator, + regexp.MustCompile(`(?i)^(>|<|<=|>=|==|!=|<>|=)([+\-]?\d+)$`): numericalValidator, + regexp.MustCompile(`(?i)^([+\-]?)int$`): intValidator, + regexp.MustCompile(`(?i)^([+\-]?)float$`): floatValidator, + regexp.MustCompile(`(?i)^password\((.*)\)$`): passwordValidator, + regexp.MustCompile(`(?i)^domain$`): domainValidator, + regexp.MustCompile(`(?i)^url$`): urlValidator, + regexp.MustCompile(`(?i)^ip$`): ipValidator, + regexp.MustCompile(`(?i)^date$`): dateValidator, + regexp.MustCompile(`(?i)^longitude`): longitudeValidator, + regexp.MustCompile(`(?i)^latitude`): latitudeValidator, + regexp.MustCompile(`(?i)^port$`): portValidator, + regexp.MustCompile(`(?i)^json$`): jsonValidator, + regexp.MustCompile(`(?i)^ISBN$`): isbnValidator, + regexp.MustCompile(`(?i)^ISBN10$`): isbn10Validator, + regexp.MustCompile(`(?i)^ISBN13$`): isbn13Validator, + regexp.MustCompile(`(?i)^[credit[-]?card$`): creditCardValidator, + regexp.MustCompile(`(?i)^uuid$`): uuidValidator, + regexp.MustCompile(`(?i)^upperCase$`): upperCaseValidator, + regexp.MustCompile(`(?i)^lowerCase$`): lowerCaseValidator, + regexp.MustCompile(`(?i)^rgb-color$`): _RGBColorValidator, + regexp.MustCompile(`(?i)^rgba-color$`): _RGBAColorValidator, + regexp.MustCompile(`(?i)^hex-color$`): _HEXColorValidator, + regexp.MustCompile(`(?i)^hex$`): _HEXValidator, + regexp.MustCompile(`(?i)^country-alpha-2$`): _CountryAlpha2Validator, + regexp.MustCompile(`(?i)^country-alpha-3`): _CountryAlpha3Validator, + regexp.MustCompile(`(?i)^btc_address`): _BTCAddressValidator, + regexp.MustCompile(`(?i)^eth_address`): _ETHAddressValidator, + regexp.MustCompile(`(?i)^cron`): cronValidator, + regexp.MustCompile(`(?i)^duration`): durationValidator, + regexp.MustCompile(`(?i)^time$`): timestampValidator, + regexp.MustCompile(`(?i)^(unix-timestamp|unix-ts)$`): unixTimestampValidator, + regexp.MustCompile(`(?i)^timezone$`): timezoneValidator, + regexp.MustCompile(`(?i)^e164$`): e164Validator, + regexp.MustCompile(`(?i)^safe-html`): safeHTMLValidator, + regexp.MustCompile(`(?i)^no-html$`): noHTMLValidator, +} + +func noHTMLValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if is.NoHTMLTags(v) { + return fmt.Errorf("value must not contain any html tags") + } + return nil +} + +func safeHTMLValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if is.SafeHTML(v) { + return fmt.Errorf("value must not contain any possible XSS tokens") + } + return nil +} + +func e164Validator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + var e164Regex = regexp.MustCompile(`^\+[1-9]\d{1,14}$`) + if !e164Regex.MatchString(v) { + return fmt.Errorf("value must be a valid E164 phone number") + } + return nil +} + +func timezoneValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + _, err := time.LoadLocation(v) + if err != nil { + return fmt.Errorf("value must be a valid timezone") + } + return nil +} + +func unixTimestampValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + _, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return fmt.Errorf("value must be a valid unix timestamp") + } + + return nil +} + +func timestampValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + _, err := time.Parse(time.RFC3339, v) + if err != nil { + return fmt.Errorf("value must be a valid RFC3339 timestamp") + } + return nil +} + +func durationValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + _, err := time.ParseDuration(v) + if err != nil { + return fmt.Errorf("value must be a valid duration format") + } + return nil +} + +func cronValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.Cron(v) { + return fmt.Errorf("value must be a valid CRON format") + } + return nil +} + +func _BTCAddressValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + var re = regexp.MustCompile(`^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$`) + if !re.MatchString(v) { + return fmt.Errorf("value must be a valid Bitcoin address") + } + return nil +} + +func _ETHAddressValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + var re = regexp.MustCompile(`^(0x)[a-zA-Z0-9]{40}$`) + if !re.MatchString(v) { + return fmt.Errorf("value must be a valid ETH address") + } + return nil +} + +func _CountryAlpha3Validator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.ISO3166Alpha3(v) { + return fmt.Errorf("value must be a valid ISO3166 Alpha 3 Format") + } + return nil +} + +func _CountryAlpha2Validator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.ISO3166Alpha3(v) { + return fmt.Errorf("value must be a valid ISO3166 Alpha 2 Format") + } + return nil +} + +func _HEXValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.Hexadecimal(v) { + return fmt.Errorf("value must be a valid HEX string") + } + return nil +} + +func _HEXColorValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.HexColor(v) { + return fmt.Errorf("value must be HEX color") + } + return nil +} + +func _RGBColorValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.RGBColor(v) { + return fmt.Errorf("value must be RGB color") + } + return nil +} + +func _RGBAColorValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.RGBAColor(v) { + return fmt.Errorf("value must be RGBA color") + } + return nil +} + +func lowerCaseValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.LowerCase(v) { + return fmt.Errorf("value must be in lower case") + } + return nil +} + +func upperCaseValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.UpperCase(v) { + return fmt.Errorf("value must be in upper case") + } + return nil +} + +func uuidValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.UUID(v) { + return fmt.Errorf("value must be valid uuid") + } + return nil +} + +func creditCardValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.CreditCard(v) { + return fmt.Errorf("value must be credit card number") + } + return nil +} + +func isbn13Validator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.ISBN13(v) { + return fmt.Errorf("value must be ISBN-13 format") + } + return nil +} + +func isbn10Validator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.ISBN10(v) { + return fmt.Errorf("value must be ISBN-10 format") + } + return nil +} + +func isbnValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.ISBN10(v) { + return fmt.Errorf("value must be ISBN-10 format") + } + return nil +} + +func jsonValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.JSON(v) { + return fmt.Errorf("value must be valid JSON format") + } + return nil +} + +func portValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.ISBN13(v) { + return fmt.Errorf("value must be valid port number") + } + return nil +} + +func latitudeValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.ISBN13(v) { + return fmt.Errorf("value must be valid latitude") + } + return nil +} + +func longitudeValidator(match []string, value *generic.Value) error { + var v = value.String() + if v == "" || v == "" { + return nil + } + if !is.ISBN13(v) { + return fmt.Errorf("value must be valid longitude") + } + return nil } func latinValidator(match []string, value *generic.Value) error { @@ -206,7 +540,7 @@ func foreignKeyValidator(match []string, value *generic.Value, stmt *gorm.Statem func textValidator(match []string, value *generic.Value) error { var v = value.String() - if v == "" { + if v == "" || v == "" { return nil } var re = regexp.MustCompile(`(?m)`) @@ -218,7 +552,7 @@ func textValidator(match []string, value *generic.Value) error { func digitValidator(match []string, value *generic.Value) error { var v = value.String() - if v == "" { + if v == "" || v == "" { return nil } @@ -411,7 +745,7 @@ func lenValidator(match []string, value *generic.Value) error { if v == "" || v == "" { return nil } - var size = len(v) + var size = utf8.RuneCountInString(v) t, _ := strconv.ParseInt(match[2], 10, 6) length := int(t) switch match[1] {