|
|
|
@ -8,6 +8,42 @@ import (
|
|
|
|
"unicode"
|
|
|
|
"unicode"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
|
|
// PlayerNameMaxLength defines the maximum length for player names.
|
|
|
|
|
|
|
|
PlayerNameMaxLength = 50
|
|
|
|
|
|
|
|
// AnswerMaxLength defines the maximum length for answer submissions.
|
|
|
|
|
|
|
|
AnswerMaxLength = 500
|
|
|
|
|
|
|
|
// QuestionTextMaxLength defines the maximum length for admin-authored question text.
|
|
|
|
|
|
|
|
QuestionTextMaxLength = 1000
|
|
|
|
|
|
|
|
// ThemeMaxLength defines the maximum length for theme names.
|
|
|
|
|
|
|
|
ThemeMaxLength = 100
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
|
|
|
// Sanitization order is trim -> collapse spaces -> HTML escape -> clamp -> allowed pattern.
|
|
|
|
|
|
|
|
spaceRegex = regexp.MustCompile(`\s+`)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Player names allow letters, numbers, spaces, '-', '_' and '.'.
|
|
|
|
|
|
|
|
playerNameAllowedPattern = regexp.MustCompile(`^[a-zA-Z0-9\s\-_.]+$`)
|
|
|
|
|
|
|
|
// Themes allow letters, numbers, spaces, '-' and '_'.
|
|
|
|
|
|
|
|
themeAllowedPattern = regexp.MustCompile(`^[a-zA-Z0-9\s\-_]+$`)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tagRegex = regexp.MustCompile(`<[^>]*>`)
|
|
|
|
|
|
|
|
scriptRegex = regexp.MustCompile(`(?i)<script[^>]*>.*?</script>`)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dangerousPatterns = []string{
|
|
|
|
|
|
|
|
"javascript:",
|
|
|
|
|
|
|
|
"data:",
|
|
|
|
|
|
|
|
"vbscript:",
|
|
|
|
|
|
|
|
"<script",
|
|
|
|
|
|
|
|
"</script",
|
|
|
|
|
|
|
|
"onerror",
|
|
|
|
|
|
|
|
"onload",
|
|
|
|
|
|
|
|
"onclick",
|
|
|
|
|
|
|
|
"onmouseover",
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// SanitizeOptions configures sanitization behavior.
|
|
|
|
// SanitizeOptions configures sanitization behavior.
|
|
|
|
type SanitizeOptions struct {
|
|
|
|
type SanitizeOptions struct {
|
|
|
|
TrimWhitespace bool
|
|
|
|
TrimWhitespace bool
|
|
|
|
@ -38,7 +74,6 @@ func Sanitize(input string, opts SanitizeOptions) string {
|
|
|
|
|
|
|
|
|
|
|
|
// Remove multiple consecutive spaces
|
|
|
|
// Remove multiple consecutive spaces
|
|
|
|
if opts.RemoveMultipleSpaces {
|
|
|
|
if opts.RemoveMultipleSpaces {
|
|
|
|
spaceRegex := regexp.MustCompile(`\s+`)
|
|
|
|
|
|
|
|
result = spaceRegex.ReplaceAllString(result, " ")
|
|
|
|
result = spaceRegex.ReplaceAllString(result, " ")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -48,8 +83,8 @@ func Sanitize(input string, opts SanitizeOptions) string {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Apply maximum length
|
|
|
|
// Apply maximum length
|
|
|
|
if opts.MaxLength > 0 && len(result) > opts.MaxLength {
|
|
|
|
if opts.MaxLength > 0 {
|
|
|
|
result = result[:opts.MaxLength]
|
|
|
|
result = clampByRunes(result, opts.MaxLength)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Validate against allowed pattern
|
|
|
|
// Validate against allowed pattern
|
|
|
|
@ -66,8 +101,8 @@ func SanitizePlayerName(input string) string {
|
|
|
|
TrimWhitespace: true,
|
|
|
|
TrimWhitespace: true,
|
|
|
|
RemoveMultipleSpaces: true,
|
|
|
|
RemoveMultipleSpaces: true,
|
|
|
|
HTMLEscape: true,
|
|
|
|
HTMLEscape: true,
|
|
|
|
MaxLength: 50,
|
|
|
|
MaxLength: PlayerNameMaxLength,
|
|
|
|
AllowedPattern: regexp.MustCompile(`^[a-zA-Z0-9\s\-_.]+$`),
|
|
|
|
AllowedPattern: playerNameAllowedPattern,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Sanitize(input, opts)
|
|
|
|
return Sanitize(input, opts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -78,7 +113,7 @@ func SanitizeAnswer(input string) string {
|
|
|
|
TrimWhitespace: true,
|
|
|
|
TrimWhitespace: true,
|
|
|
|
RemoveMultipleSpaces: true,
|
|
|
|
RemoveMultipleSpaces: true,
|
|
|
|
HTMLEscape: true,
|
|
|
|
HTMLEscape: true,
|
|
|
|
MaxLength: 500,
|
|
|
|
MaxLength: AnswerMaxLength,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
result := Sanitize(input, opts)
|
|
|
|
result := Sanitize(input, opts)
|
|
|
|
@ -95,13 +130,12 @@ func SanitizeQuestionText(input string) string {
|
|
|
|
TrimWhitespace: true,
|
|
|
|
TrimWhitespace: true,
|
|
|
|
RemoveMultipleSpaces: true,
|
|
|
|
RemoveMultipleSpaces: true,
|
|
|
|
HTMLEscape: true,
|
|
|
|
HTMLEscape: true,
|
|
|
|
MaxLength: 1000,
|
|
|
|
MaxLength: QuestionTextMaxLength,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
result := Sanitize(input, opts)
|
|
|
|
result := Sanitize(input, opts)
|
|
|
|
|
|
|
|
|
|
|
|
// Remove potential script content
|
|
|
|
// Remove potential script content
|
|
|
|
scriptRegex := regexp.MustCompile(`(?i)<script[^>]*>.*?</script>`)
|
|
|
|
|
|
|
|
result = scriptRegex.ReplaceAllString(result, "")
|
|
|
|
result = scriptRegex.ReplaceAllString(result, "")
|
|
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
return result
|
|
|
|
@ -113,8 +147,8 @@ func SanitizeTheme(input string) string {
|
|
|
|
TrimWhitespace: true,
|
|
|
|
TrimWhitespace: true,
|
|
|
|
RemoveMultipleSpaces: true,
|
|
|
|
RemoveMultipleSpaces: true,
|
|
|
|
HTMLEscape: true,
|
|
|
|
HTMLEscape: true,
|
|
|
|
MaxLength: 100,
|
|
|
|
MaxLength: ThemeMaxLength,
|
|
|
|
AllowedPattern: regexp.MustCompile(`^[a-zA-Z0-9\s\-_]+$`),
|
|
|
|
AllowedPattern: themeAllowedPattern,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
result := Sanitize(input, opts)
|
|
|
|
result := Sanitize(input, opts)
|
|
|
|
@ -137,24 +171,11 @@ func SanitizeTheme(input string) string {
|
|
|
|
|
|
|
|
|
|
|
|
// RemoveHTMLTags removes all HTML tags from a string.
|
|
|
|
// RemoveHTMLTags removes all HTML tags from a string.
|
|
|
|
func RemoveHTMLTags(input string) string {
|
|
|
|
func RemoveHTMLTags(input string) string {
|
|
|
|
tagRegex := regexp.MustCompile(`<[^>]*>`)
|
|
|
|
|
|
|
|
return tagRegex.ReplaceAllString(input, "")
|
|
|
|
return tagRegex.ReplaceAllString(input, "")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ContainsDangerousPatterns checks if input contains potentially dangerous patterns.
|
|
|
|
// ContainsDangerousPatterns checks if input contains potentially dangerous patterns.
|
|
|
|
func ContainsDangerousPatterns(input string) bool {
|
|
|
|
func ContainsDangerousPatterns(input string) bool {
|
|
|
|
dangerousPatterns := []string{
|
|
|
|
|
|
|
|
"javascript:",
|
|
|
|
|
|
|
|
"data:",
|
|
|
|
|
|
|
|
"vbscript:",
|
|
|
|
|
|
|
|
"<script",
|
|
|
|
|
|
|
|
"</script",
|
|
|
|
|
|
|
|
"onerror",
|
|
|
|
|
|
|
|
"onload",
|
|
|
|
|
|
|
|
"onclick",
|
|
|
|
|
|
|
|
"onmouseover",
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lowerInput := strings.ToLower(input)
|
|
|
|
lowerInput := strings.ToLower(input)
|
|
|
|
for _, pattern := range dangerousPatterns {
|
|
|
|
for _, pattern := range dangerousPatterns {
|
|
|
|
if strings.Contains(lowerInput, pattern) {
|
|
|
|
if strings.Contains(lowerInput, pattern) {
|
|
|
|
@ -170,3 +191,16 @@ func IsValidEmail(email string) bool {
|
|
|
|
emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
|
|
|
|
emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
|
|
|
|
return emailRegex.MatchString(email)
|
|
|
|
return emailRegex.MatchString(email)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func clampByRunes(input string, max int) string {
|
|
|
|
|
|
|
|
if max <= 0 {
|
|
|
|
|
|
|
|
return input
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
runes := []rune(input)
|
|
|
|
|
|
|
|
if len(runes) <= max {
|
|
|
|
|
|
|
|
return input
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return string(runes[:max])
|
|
|
|
|
|
|
|
}
|
|
|
|
|