// 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:",
"