File size: 3,152 Bytes
6424951
 
 
 
 
 
 
 
2619049
6424951
 
 
 
 
 
 
 
 
 
 
 
2619049
 
6424951
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
"""Input validation for user-provided data."""

import re

from pydantic import BaseModel, Field, field_validator

# Patterns that indicate SQL injection attempts
SQL_INJECTION_PATTERNS: list[str] = [
    r'[";]',  # Double quotes and semicolons (apostrophes allowed for names like O'Neal)
    r"--",  # SQL comment
    r"/\*",  # Block comment start
    r"\*/",  # Block comment end
    r"\bUNION\b",  # UNION keyword
    r"\bSELECT\b",  # SELECT keyword
    r"\bINSERT\b",  # INSERT keyword
    r"\bUPDATE\b",  # UPDATE keyword
    r"\bDELETE\b",  # DELETE keyword
    r"\bDROP\b",  # DROP keyword
    r"\bEXEC\b",  # EXEC keyword
    r"\bOR\s+\d+=\d+",  # OR 1=1 pattern
    r"\bAND\s+\d+=\d+",  # AND 1=1 pattern
    r"'\s*OR\s",  # ' OR pattern (SQL injection)
    r"'\s*AND\s",  # ' AND pattern (SQL injection)
]

# Compiled regex for efficiency
SQL_INJECTION_REGEX = re.compile(
    "|".join(SQL_INJECTION_PATTERNS), re.IGNORECASE
)


class PlayerSearchInput(BaseModel):
    """Validated player search input."""

    search_term: str = Field(
        ...,
        min_length=1,
        max_length=100,
        description="Player name search term",
    )

    @field_validator("search_term")
    @classmethod
    def validate_no_sql_injection(cls, v: str) -> str:
        """Reject inputs containing SQL injection patterns.

        Args:
            v: Input search term

        Returns:
            Validated search term

        Raises:
            ValueError: If SQL injection pattern detected
        """
        if SQL_INJECTION_REGEX.search(v):
            raise ValueError(
                "Invalid characters in search term. "
                "Please use only letters, numbers, spaces, and hyphens."
            )
        return v.strip()

    @field_validator("search_term")
    @classmethod
    def validate_reasonable_characters(cls, v: str) -> str:
        """Ensure search term contains only reasonable characters.

        Args:
            v: Input search term

        Returns:
            Validated search term

        Raises:
            ValueError: If invalid characters found
        """
        # Allow letters, numbers, spaces, hyphens, periods, and apostrophes
        # (e.g., "O'Neal", "J.R. Smith")
        if not re.match(r"^[a-zA-Z0-9\s\-.']+$", v):
            raise ValueError(
                "Search term contains invalid characters. "
                "Please use only letters, numbers, spaces, hyphens, "
                "periods, and apostrophes."
            )
        return v


def validate_search_term(term: str) -> str | None:
    """Validate a player search term.

    Args:
        term: Raw search input

    Returns:
        Validated and cleaned search term, or None if invalid
    """
    try:
        validated = PlayerSearchInput(search_term=term)
        return validated.search_term
    except ValueError:
        return None


def is_valid_search_term(term: str) -> bool:
    """Check if a search term is valid without raising exceptions.

    Args:
        term: Raw search input

    Returns:
        True if valid, False otherwise
    """
    return validate_search_term(term) is not None