package validation import ( "errors" "regexp" "strings" "unicode/utf8" ) // ValidateCommentContent checks if comment content meets requirements func ValidateCommentContent(content string) error { c := strings.TrimSpace(content) if c == "" { return errors.New("comment cannot be empty") } length := utf8.RuneCountInString(c) if length < 6 { return errors.New("comment too short (minimum 6 characters)") } if length > 2000 { return errors.New("comment too long (maximum 2000 characters)") } // Check for excessive caps (anti-spam) if isExcessiveCaps(c) { return errors.New("too many capital letters detected") } // Check for excessive repetition if hasExcessiveRepetition(c) { return errors.New("excessive character repetition detected") } return nil } // ValidateCommentTargetType checks if target type is allowed func ValidateCommentTargetType(targetType string) error { allowed := map[string]bool{ "article": true, "event": true, "gallery_album": true, "youtube_video": true, } if !allowed[targetType] { return errors.New("invalid target type") } return nil } // ValidateCommentStatus checks if status is valid func ValidateCommentStatus(status string) error { if status != "visible" && status != "hidden" { return errors.New("status must be 'visible' or 'hidden'") } return nil } // ValidateBanReason checks ban reason func ValidateBanReason(reason string) error { r := strings.TrimSpace(reason) if r == "" { return errors.New("ban reason cannot be empty") } if len(r) > 500 { return errors.New("ban reason too long (max 500 characters)") } return nil } // ValidateBanDuration checks ban duration in hours func ValidateBanDuration(hours int) error { if hours < 0 { return errors.New("ban duration cannot be negative (use 0 for permanent)") } if hours > 8760 { // More than 1 year return errors.New("ban duration too long (max 1 year = 8760 hours)") } return nil } // Helper: check if more than 50% of letters are uppercase func isExcessiveCaps(text string) bool { if len(text) < 20 { return false // Allow caps in short messages } var upper, lower int for _, r := range text { if r >= 'A' && r <= 'Z' { upper++ } else if r >= 'a' && r <= 'z' { lower++ } } total := upper + lower if total == 0 { return false } return float64(upper)/float64(total) > 0.5 } // Helper: detect excessive character repetition (e.g., "aaaaaa", "!!!!!!") func hasExcessiveRepetition(text string) bool { // Check for 5+ consecutive identical characters re := regexp.MustCompile(`(.)\1{4,}`) return re.MatchString(text) } // SanitizeCommentContent removes dangerous HTML but preserves basic formatting func SanitizeCommentContent(content string) string { // Remove script tags and their content re := regexp.MustCompile(`(?i)]*>.*?`) content = re.ReplaceAllString(content, "") // Remove iframe tags re = regexp.MustCompile(`(?i)]*>.*?`) content = re.ReplaceAllString(content, "") // Remove on* event handlers re = regexp.MustCompile(`(?i)\s*on\w+\s*=\s*["'][^"']*["']`) content = re.ReplaceAllString(content, "") return strings.TrimSpace(content) } // ValidateReportReason checks report reason func ValidateReportReason(reason string) error { r := strings.TrimSpace(reason) if len(r) > 255 { return errors.New("report reason too long (max 255 characters)") } return nil } // ValidateReactionType checks if reaction type is valid func ValidateReactionType(reactionType string) error { allowed := map[string]bool{ "like": true, "heart": true, "smile": true, "laugh": true, "thumbs_up": true, "thumbs_down": true, "sad": true, "angry": true, } if !allowed[reactionType] { return errors.New("invalid reaction type") } return nil }