166 lines
3.5 KiB
Go
166 lines
3.5 KiB
Go
package repositories
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
type SearchSanitizer struct {
|
|
MaxQueryLength int
|
|
MaxSpecialChars int
|
|
}
|
|
|
|
func NewSearchSanitizer() *SearchSanitizer {
|
|
return &SearchSanitizer{
|
|
MaxQueryLength: 100,
|
|
MaxSpecialChars: 10,
|
|
}
|
|
}
|
|
|
|
func (s *SearchSanitizer) SanitizeSearchQuery(query string) (string, error) {
|
|
query = strings.TrimSpace(query)
|
|
|
|
if query == "" {
|
|
return "", nil
|
|
}
|
|
|
|
if len(query) > s.MaxQueryLength {
|
|
return "", &SearchError{
|
|
Type: "QueryTooLong",
|
|
Message: "Search query exceeds maximum length",
|
|
}
|
|
}
|
|
|
|
specialCharCount := 0
|
|
for _, char := range query {
|
|
if !unicode.IsLetter(char) && !unicode.IsDigit(char) && !unicode.IsSpace(char) {
|
|
specialCharCount++
|
|
}
|
|
}
|
|
|
|
if specialCharCount > s.MaxSpecialChars {
|
|
return "", &SearchError{
|
|
Type: "TooManySpecialChars",
|
|
Message: "Search query contains too many special characters",
|
|
}
|
|
}
|
|
|
|
query = s.removeDangerousPatterns(query)
|
|
|
|
query = s.normalizeWhitespace(query)
|
|
|
|
if len(query) > s.MaxQueryLength {
|
|
query = query[:s.MaxQueryLength]
|
|
}
|
|
|
|
return query, nil
|
|
}
|
|
|
|
func (s *SearchSanitizer) removeDangerousPatterns(query string) string {
|
|
query = regexp.MustCompile(`\*{3,}`).ReplaceAllString(query, "**")
|
|
query = regexp.MustCompile(`%{3,}`).ReplaceAllString(query, "%%")
|
|
|
|
query = regexp.MustCompile(`\.{3,}`).ReplaceAllString(query, "..")
|
|
|
|
query = regexp.MustCompile(`\?{3,}`).ReplaceAllString(query, "??")
|
|
|
|
query = regexp.MustCompile(`\+{3,}`).ReplaceAllString(query, "++")
|
|
|
|
query = regexp.MustCompile(`\{[^}]*\{[^}]*\}`).ReplaceAllString(query, "")
|
|
|
|
query = regexp.MustCompile(`\[[^\]]*\[[^\]]*\]`).ReplaceAllString(query, "")
|
|
|
|
query = regexp.MustCompile(`\([^)]*\([^)]*\)`).ReplaceAllString(query, "")
|
|
|
|
return query
|
|
}
|
|
|
|
func (s *SearchSanitizer) normalizeWhitespace(query string) string {
|
|
query = regexp.MustCompile(`\s+`).ReplaceAllString(query, " ")
|
|
|
|
query = strings.TrimSpace(query)
|
|
|
|
return query
|
|
}
|
|
|
|
func (s *SearchSanitizer) ValidateSearchQuery(query string) error {
|
|
if len(query) == 0 {
|
|
return nil
|
|
}
|
|
|
|
sqlInjectionPatterns := []string{
|
|
"';", "--", "/*", "*/", "xp_", "sp_", "exec", "execute",
|
|
"union", "select", "insert", "update", "delete", "drop",
|
|
"create", "alter", "grant", "revoke", "truncate",
|
|
}
|
|
|
|
lowerQuery := strings.ToLower(query)
|
|
for _, pattern := range sqlInjectionPatterns {
|
|
if strings.Contains(lowerQuery, pattern) {
|
|
return &SearchError{
|
|
Type: "InvalidQuery",
|
|
Message: "Search query contains potentially dangerous patterns",
|
|
}
|
|
}
|
|
}
|
|
|
|
if s.hasExcessiveRepetition(query) {
|
|
return &SearchError{
|
|
Type: "DoSPattern",
|
|
Message: "Search query contains patterns that could cause denial of service",
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *SearchSanitizer) hasExcessiveRepetition(query string) bool {
|
|
if s.hasRepeatedCharacters(query, 5) {
|
|
return true
|
|
}
|
|
|
|
words := strings.Fields(query)
|
|
wordCount := make(map[string]int)
|
|
for _, word := range words {
|
|
wordCount[strings.ToLower(word)]++
|
|
if wordCount[strings.ToLower(word)] > 3 {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (s *SearchSanitizer) hasRepeatedCharacters(str string, maxRepeats int) bool {
|
|
if len(str) <= maxRepeats {
|
|
return false
|
|
}
|
|
|
|
currentChar := rune(0)
|
|
count := 0
|
|
|
|
for _, char := range str {
|
|
if char == currentChar {
|
|
count++
|
|
if count > maxRepeats {
|
|
return true
|
|
}
|
|
} else {
|
|
currentChar = char
|
|
count = 1
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
type SearchError struct {
|
|
Type string
|
|
Message string
|
|
}
|
|
|
|
func (e *SearchError) Error() string {
|
|
return e.Message
|
|
}
|