mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
dev day #80
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
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)<script[^>]*>.*?</script>`)
|
||||
content = re.ReplaceAllString(content, "")
|
||||
|
||||
// Remove iframe tags
|
||||
re = regexp.MustCompile(`(?i)<iframe[^>]*>.*?</iframe>`)
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ValidateUsername checks if a username meets requirements
|
||||
func ValidateUsername(username string) error {
|
||||
u := strings.TrimSpace(username)
|
||||
if u == "" {
|
||||
return errors.New("username cannot be empty")
|
||||
}
|
||||
if len(u) < 3 {
|
||||
return errors.New("username must be at least 3 characters")
|
||||
}
|
||||
if len(u) > 32 {
|
||||
return errors.New("username cannot exceed 32 characters")
|
||||
}
|
||||
// Only allow lowercase letters, numbers, hyphen, underscore, dot
|
||||
validUsername := regexp.MustCompile(`^[a-z0-9\-_.]+$`)
|
||||
if !validUsername.MatchString(u) {
|
||||
return errors.New("username can only contain lowercase letters, numbers, and characters: - _ .")
|
||||
}
|
||||
// Prevent consecutive special chars
|
||||
if strings.Contains(u, "--") || strings.Contains(u, "__") || strings.Contains(u, "..") {
|
||||
return errors.New("username cannot contain consecutive special characters")
|
||||
}
|
||||
// Cannot start or end with special chars
|
||||
if strings.HasPrefix(u, "-") || strings.HasPrefix(u, "_") || strings.HasPrefix(u, ".") ||
|
||||
strings.HasSuffix(u, "-") || strings.HasSuffix(u, "_") || strings.HasSuffix(u, ".") {
|
||||
return errors.New("username cannot start or end with special characters")
|
||||
}
|
||||
// Prevent reserved words
|
||||
reserved := []string{"admin", "administrator", "mod", "moderator", "root", "system", "support", "official", "bot"}
|
||||
for _, r := range reserved {
|
||||
if strings.EqualFold(u, r) {
|
||||
return errors.New("username is reserved")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SanitizeRewardName cleans and validates reward names
|
||||
func SanitizeRewardName(name string) (string, error) {
|
||||
n := strings.TrimSpace(name)
|
||||
if n == "" {
|
||||
return "", errors.New("reward name cannot be empty")
|
||||
}
|
||||
if len(n) > 255 {
|
||||
return "", errors.New("reward name too long (max 255 characters)")
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// ValidateRewardType ensures reward type is allowed
|
||||
func ValidateRewardType(rewardType string) error {
|
||||
allowed := map[string]bool{
|
||||
"avatar_static": true,
|
||||
"avatar_animated": true,
|
||||
"avatar_upload_unlock": true,
|
||||
"merch_coupon": true,
|
||||
"merch_physical": true,
|
||||
"merch_digital": true,
|
||||
"custom": true,
|
||||
}
|
||||
if !allowed[rewardType] {
|
||||
return errors.New("invalid reward type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatePoints checks if points value is reasonable
|
||||
func ValidatePoints(points int64) error {
|
||||
if points < 0 {
|
||||
return errors.New("points cannot be negative")
|
||||
}
|
||||
if points > 1000000 {
|
||||
return errors.New("points value too large (max 1,000,000)")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateXP checks if XP value is reasonable
|
||||
func ValidateXP(xp int64) error {
|
||||
if xp < 0 {
|
||||
return errors.New("XP cannot be negative")
|
||||
}
|
||||
if xp > 10000000 {
|
||||
return errors.New("XP value too large (max 10,000,000)")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateRewardStock checks stock value
|
||||
func ValidateRewardStock(stock int) error {
|
||||
if stock < -1 {
|
||||
return errors.New("stock cannot be less than -1 (use -1 for unlimited)")
|
||||
}
|
||||
if stock > 100000 {
|
||||
return errors.New("stock value too large (max 100,000)")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateAchievementCode checks achievement code format
|
||||
func ValidateAchievementCode(code string) error {
|
||||
c := strings.TrimSpace(code)
|
||||
if c == "" {
|
||||
return errors.New("achievement code cannot be empty")
|
||||
}
|
||||
if len(c) > 64 {
|
||||
return errors.New("achievement code too long (max 64 characters)")
|
||||
}
|
||||
// Only alphanumeric and underscore
|
||||
validCode := regexp.MustCompile(`^[a-z0-9_]+$`)
|
||||
if !validCode.MatchString(c) {
|
||||
return errors.New("achievement code can only contain lowercase letters, numbers, and underscores")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateRedemptionStatus checks if redemption status is valid
|
||||
func ValidateRedemptionStatus(status string) error {
|
||||
allowed := map[string]bool{
|
||||
"pending": true,
|
||||
"approved": true,
|
||||
"rejected": true,
|
||||
"fulfilled": true,
|
||||
}
|
||||
if !allowed[status] {
|
||||
return errors.New("invalid redemption status")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user