""" Input Validation Utilities 입력값 검증 및 sanitization SQL Injection 방지를 위한 추가 검증 레이어 """ import re from typing import Any, List, Optional from fastapi import HTTPException # UUID v4 정규식 UUID_PATTERN = re.compile( r'^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$', re.IGNORECASE ) def validate_uuid(value: str, field_name: str = "id") -> str: """ UUID 형식 검증 Args: value: 검증할 값 field_name: 필드 이름 (에러 메시지용) Returns: 검증된 UUID 문자열 Raises: HTTPException: UUID 형식이 아닌 경우 """ if not value or not isinstance(value, str): raise HTTPException( status_code=400, detail=f"{field_name} must be a valid UUID" ) value = value.strip().lower() if not UUID_PATTERN.match(value): raise HTTPException( status_code=400, detail=f"{field_name} must be a valid UUID format" ) return value def validate_user_id(user_id: str) -> str: """ 사용자 ID 검증 (UUID 형식) Args: user_id: 사용자 ID Returns: 검증된 user_id """ return validate_uuid(user_id, "user_id") def validate_ids(ids: List[str], field_name: str = "ids") -> List[str]: """ 여러 UUID 검증 Args: ids: UUID 리스트 field_name: 필드 이름 Returns: 검증된 UUID 리스트 """ if not ids or not isinstance(ids, list): raise HTTPException( status_code=400, detail=f"{field_name} must be a non-empty list" ) if len(ids) > 100: raise HTTPException( status_code=400, detail=f"{field_name} list too large (max 100)" ) return [validate_uuid(id_val, field_name) for id_val in ids] def sanitize_string(value: str, max_length: int = 1000) -> str: """ 문자열 sanitization Args: value: 입력 문자열 max_length: 최대 길이 Returns: 정제된 문자열 """ if not value or not isinstance(value, str): return "" # 앞뒤 공백 제거 value = value.strip() # 길이 제한 if len(value) > max_length: value = value[:max_length] # 제어 문자 제거 (줄바꿈, 탭 제외) value = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '', value) return value def validate_pagination( limit: int, offset: int, max_limit: int = 100 ) -> tuple[int, int]: """ 페이지네이션 파라미터 검증 Args: limit: 조회 개수 offset: 시작 위치 max_limit: 최대 limit 값 Returns: (limit, offset) 튜플 """ if limit < 1: limit = 10 if limit > max_limit: limit = max_limit if offset < 0: offset = 0 return limit, offset def validate_enum( value: str, allowed_values: List[str], field_name: str = "value" ) -> str: """ Enum 값 검증 Args: value: 검증할 값 allowed_values: 허용된 값 목록 field_name: 필드 이름 Returns: 검증된 값 """ if not value or value not in allowed_values: raise HTTPException( status_code=400, detail=f"{field_name} must be one of: {', '.join(allowed_values)}" ) return value def validate_coordinates(lat: float, lng: float) -> tuple[float, float]: """ 좌표 유효성 검증 Args: lat: 위도 lng: 경도 Returns: (lat, lng) 튜플 """ if not isinstance(lat, (int, float)) or not isinstance(lng, (int, float)): raise HTTPException( status_code=400, detail="Coordinates must be numeric" ) if not -90 <= lat <= 90: raise HTTPException( status_code=400, detail="Latitude must be between -90 and 90" ) if not -180 <= lng <= 180: raise HTTPException( status_code=400, detail="Longitude must be between -180 and 180" ) return lat, lng