package security import ( "net" "regexp" "strings" ) // Validator groups lightweight validation and sanitization helpers. type Validator struct { passwordMinLength int passwordRequireSpecial bool passwordRequireNumber bool // 预编译的正则表达式,避免每次调用重复编译(P1性能优化) emailRe *regexp.Regexp phoneRe *regexp.Regexp usernameRe *regexp.Regexp urlRe *regexp.Regexp sqlPatterns []*regexp.Regexp xssPatterns []*regexp.Regexp } // NewValidator creates a validator with the configured password rules. func NewValidator(minLength int, requireSpecial, requireNumber bool) *Validator { v := &Validator{ passwordMinLength: minLength, passwordRequireSpecial: requireSpecial, passwordRequireNumber: requireNumber, } // 预编译常用验证正则(P1性能优化) v.emailRe = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`) v.phoneRe = regexp.MustCompile(`^1[3-9]\d{9}$`) v.usernameRe = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]{3,19}$`) v.urlRe = regexp.MustCompile(`^https?://[a-zA-Z0-9\-._~:/?#[\]@!$&'()*+,;=]+$`) // 预编译SQL注入检测正则(P1性能优化) sqlRawPatterns := []string{ `;[\s]*--`, `/\*.*?\*/`, `\bxp_\w+`, `\bexec[\s\(]`, `\bsp_\w+`, `\bwaitfor[\s]+delay`, `\bunion[\s]+select`, `\bdrop[\s]+table`, `\binsert[\s]+into`, `\bupdate[\s]+\w+[\s]+set`, `\bdelete[\s]+from`, } v.sqlPatterns = make([]*regexp.Regexp, len(sqlRawPatterns)) for i, p := range sqlRawPatterns { v.sqlPatterns[i] = regexp.MustCompile(`(?i)` + p) } // 预编译XSS检测正则(P1性能优化) xssRawPatterns := []string{ `(?i)]*>.*?`, `(?i)`, `(?i)]*>.*?`, `(?i)]*>.*?`, `(?i)]*>.*?`, `(?i)]*>.*?`, `(?i)javascript\s*:`, `(?i)vbscript\s*:`, `(?i)data\s*:`, `(?i)on\w+\s*=`, `(?i)]*>.*?`, } v.xssPatterns = make([]*regexp.Regexp, len(xssRawPatterns)) for i, p := range xssRawPatterns { v.xssPatterns[i] = regexp.MustCompile(p) } return v } // ValidateEmail validates email format. func (v *Validator) ValidateEmail(email string) bool { if email == "" || v.emailRe == nil { return false } return v.emailRe.MatchString(email) } // ValidatePhone validates mainland China mobile numbers. func (v *Validator) ValidatePhone(phone string) bool { if phone == "" || v.phoneRe == nil { return false } return v.phoneRe.MatchString(phone) } // ValidateUsername validates usernames. func (v *Validator) ValidateUsername(username string) bool { if username == "" || v.usernameRe == nil { return false } return v.usernameRe.MatchString(username) } // ValidatePassword validates passwords using the shared runtime policy. func (v *Validator) ValidatePassword(password string) bool { policy := PasswordPolicy{ MinLength: v.passwordMinLength, RequireSpecial: v.passwordRequireSpecial, RequireNumber: v.passwordRequireNumber, } return policy.Validate(password) == nil } // SanitizeSQL removes obviously dangerous SQL injection patterns using regex. // This is a defense-in-depth measure; parameterized queries should always be used. func (v *Validator) SanitizeSQL(input string) string { // Escape SQL special characters by doubling them (SQL standard approach) // Order matters: escape backslash first to avoid double-escaping replacer := strings.NewReplacer( `\`, `\\`, `'`, `''`, `"`, `""`, ) result := replacer.Replace(input) // 使用预编译的正则移除SQL注入模式(P1性能优化) for _, re := range v.sqlPatterns { if re != nil { result = re.ReplaceAllString(result, "") } } return result } // SanitizeXSS removes obviously dangerous XSS patterns using regex. // This is a defense-in-depth measure; output encoding should always be used. func (v *Validator) SanitizeXSS(input string) string { result := input // 使用预编译的正则移除XSS模式(P1性能优化) for _, re := range v.xssPatterns { if re != nil { result = re.ReplaceAllString(result, "") } } // Encode < and > to prevent tag construction result = strings.ReplaceAll(result, "<", "<") result = strings.ReplaceAll(result, ">", ">") // Restore entities if they were part of legitimate content result = strings.ReplaceAll(result, "<", "<") result = strings.ReplaceAll(result, ">", ">") return result } // ValidateURL validates a basic HTTP/HTTPS URL. func (v *Validator) ValidateURL(url string) bool { if url == "" || v.urlRe == nil { return false } return v.urlRe.MatchString(url) } // ValidateIP validates IPv4 or IPv6 addresses using net.ParseIP. // Supports all valid formats including compressed IPv6 (::1, fe80::1, etc.) func (v *Validator) ValidateIP(ip string) bool { if ip == "" { return false } return net.ParseIP(ip) != nil } // ValidateIPv4 validates IPv4 addresses only. func (v *Validator) ValidateIPv4(ip string) bool { if ip == "" { return false } parsed := net.ParseIP(ip) return parsed != nil && parsed.To4() != nil } // ValidateIPv6 validates IPv6 addresses only. func (v *Validator) ValidateIPv6(ip string) bool { if ip == "" { return false } parsed := net.ParseIP(ip) return parsed != nil && parsed.To4() == nil }