// Package security provides security utilities for the KnowFoolery application. package security import ( "html" "regexp" "strings" "unicode" ) // SanitizeOptions configures sanitization behavior. type SanitizeOptions struct { TrimWhitespace bool RemoveMultipleSpaces bool HTMLEscape bool MaxLength int AllowedPattern *regexp.Regexp } // DefaultSanitizeOptions returns default sanitization options. func DefaultSanitizeOptions() SanitizeOptions { return SanitizeOptions{ TrimWhitespace: true, RemoveMultipleSpaces: true, HTMLEscape: true, MaxLength: 0, // No limit } } // Sanitize sanitizes a string according to the given options. func Sanitize(input string, opts SanitizeOptions) string { result := input // Trim whitespace if opts.TrimWhitespace { result = strings.TrimSpace(result) } // Remove multiple consecutive spaces if opts.RemoveMultipleSpaces { spaceRegex := regexp.MustCompile(`\s+`) result = spaceRegex.ReplaceAllString(result, " ") } // HTML escape if opts.HTMLEscape { result = html.EscapeString(result) } // Apply maximum length if opts.MaxLength > 0 && len(result) > opts.MaxLength { result = result[:opts.MaxLength] } // Validate against allowed pattern if opts.AllowedPattern != nil && !opts.AllowedPattern.MatchString(result) { return "" } return result } // SanitizePlayerName sanitizes a player name. func SanitizePlayerName(input string) string { opts := SanitizeOptions{ TrimWhitespace: true, RemoveMultipleSpaces: true, HTMLEscape: true, MaxLength: 50, AllowedPattern: regexp.MustCompile(`^[a-zA-Z0-9\s\-_.]+$`), } return Sanitize(input, opts) } // SanitizeAnswer sanitizes an answer submission. func SanitizeAnswer(input string) string { opts := SanitizeOptions{ TrimWhitespace: true, RemoveMultipleSpaces: true, HTMLEscape: true, MaxLength: 500, } result := Sanitize(input, opts) // Normalize to lowercase for comparison result = strings.ToLower(result) return result } // SanitizeQuestionText sanitizes question text (admin input). func SanitizeQuestionText(input string) string { opts := SanitizeOptions{ TrimWhitespace: true, RemoveMultipleSpaces: true, HTMLEscape: true, MaxLength: 1000, } result := Sanitize(input, opts) // Remove potential script content scriptRegex := regexp.MustCompile(`(?i)]*>.*?`) result = scriptRegex.ReplaceAllString(result, "") return result } // SanitizeTheme sanitizes a theme name. func SanitizeTheme(input string) string { opts := SanitizeOptions{ TrimWhitespace: true, RemoveMultipleSpaces: true, HTMLEscape: true, MaxLength: 100, AllowedPattern: regexp.MustCompile(`^[a-zA-Z0-9\s\-_]+$`), } result := Sanitize(input, opts) // Title case words := strings.Fields(result) for i, word := range words { if len(word) > 0 { runes := []rune(word) runes[0] = unicode.ToUpper(runes[0]) for j := 1; j < len(runes); j++ { runes[j] = unicode.ToLower(runes[j]) } words[i] = string(runes) } } return strings.Join(words, " ") } // RemoveHTMLTags removes all HTML tags from a string. func RemoveHTMLTags(input string) string { tagRegex := regexp.MustCompile(`<[^>]*>`) return tagRegex.ReplaceAllString(input, "") } // ContainsDangerousPatterns checks if input contains potentially dangerous patterns. func ContainsDangerousPatterns(input string) bool { dangerousPatterns := []string{ "javascript:", "data:", "vbscript:", "