Spaces:
Sleeping
Sleeping
| import re | |
| from datetime import datetime | |
| from typing import Optional, Tuple | |
| import html | |
| from functools import wraps | |
| import time | |
| import gradio as gr | |
| import logging | |
| from core.logging_config import get_logger | |
| # Initialize logger | |
| logger = get_logger(__name__) | |
| def validate_datetime_format(datetime_str: str) -> Tuple[bool, Optional[datetime], Optional[str]]: | |
| """ | |
| Validates datetime string format and returns parsed datetime object | |
| Expected format: DD-MM-YYYY HH:MM:SS | |
| """ | |
| if not datetime_str or not isinstance(datetime_str, str): | |
| return False, None, "Datetime string is empty or not a string" | |
| # Check for valid format using regex | |
| pattern = r'^(\d{2})-(\d{2})-(\d{4})\s(\d{2}):(\d{2}):(\d{2})$' | |
| match = re.match(pattern, datetime_str.strip()) | |
| if not match: | |
| return False, None, f"Datetime format invalid. Expected DD-MM-YYYY HH:MM:SS, got: {datetime_str}" | |
| try: | |
| day, month, year, hour, minute, second = map(int, match.groups()) | |
| # Validate date components | |
| if not (1 <= day <= 31): | |
| return False, None, f"Invalid day: {day}. Day must be between 1 and 31" | |
| if not (1 <= month <= 12): | |
| return False, None, f"Invalid month: {month}. Month must be between 1 and 12" | |
| if year < 1900 or year > 2100: | |
| return False, None, f"Invalid year: {year}. Year must be between 1900 and 2100" | |
| if not (0 <= hour <= 23): | |
| return False, None, f"Invalid hour: {hour}. Hour must be between 0 and 23" | |
| if not (0 <= minute <= 59): | |
| return False, None, f"Invalid minute: {minute}. Minute must be between 0 and 59" | |
| if not (0 <= second <= 59): | |
| return False, None, f"Invalid second: {second}. Second must be between 0 and 59" | |
| # Try to create datetime object to catch invalid dates like 30th of February | |
| dt = datetime(year, month, day, hour, minute, second) | |
| return True, dt, None | |
| except ValueError as e: | |
| return False, None, f"Invalid date: {str(e)}" | |
| except Exception as e: | |
| return False, None, f"Error parsing datetime: {str(e)}" | |
| def validate_city_name(city: str) -> Tuple[bool, Optional[str], Optional[str]]: | |
| """ | |
| Validates city name input to prevent injection attacks | |
| """ | |
| if not city or not isinstance(city, str): | |
| return False, None, "City name is empty or not a string" | |
| # Sanitize input by removing potentially dangerous characters | |
| sanitized_city = html.escape(city.strip()) | |
| # Check for potentially dangerous patterns | |
| dangerous_patterns = [ | |
| r'[<>"\']', # HTML/SQL injection characters | |
| r'[;{}]', # Command injection characters | |
| r'\$\(', # Command substitution | |
| r'`', # Command execution | |
| ] | |
| for pattern in dangerous_patterns: | |
| if re.search(pattern, sanitized_city): | |
| return False, None, f"Potentially dangerous characters detected in city name: {sanitized_city}" | |
| # Additional check for length | |
| if len(sanitized_city) > 100: | |
| return False, None, f"City name too long: {len(sanitized_city)} characters. Maximum is 100" | |
| # Check if it contains only alphanumeric characters, spaces, hyphens, commas, slashes, periods, and cyrillic | |
| if not re.match(r'^[a-zA-Zа-яА-ЯёЁ0-9\s\-\/\.,]{2,100}$', sanitized_city): | |
| return False, None, f"City name contains invalid characters: {sanitized_city}" | |
| return True, sanitized_city, None | |
| def validate_coordinates(lat: float, lon: float) -> Tuple[bool, Optional[str]]: | |
| """ | |
| Validates geographic coordinates | |
| """ | |
| if not isinstance(lat, (int, float)) or not isinstance(lon, (int, float)): | |
| return False, "Coordinates must be numeric values" | |
| if not (-90 <= lat <= 90): | |
| return False, f"Latitude out of range: {lat}. Must be between -90 and 90" | |
| if not (-180 <= lon <= 180): | |
| return False, f"Longitude out of range: {lon}. Must be between -180 and 180" | |
| return True, None | |
| def sanitize_input(input_str: str) -> str: | |
| """ | |
| Sanitizes string input by removing potentially dangerous characters | |
| """ | |
| if not input_str or not isinstance(input_str, str): | |
| return "" | |
| # Remove potentially dangerous characters | |
| sanitized = html.escape(input_str.strip()) | |
| # Remove any control characters | |
| sanitized = ''.join(char for char in sanitized if ord(char) >= 32 or char in ['\n', '\t', '\r']) | |
| return sanitized | |
| def sanitize_inputs(datetime_str: str, city: str) -> tuple[bool, str]: | |
| """Validate and sanitize user inputs""" | |
| # Validate datetime format | |
| if not re.match(r'^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}$', datetime_str): | |
| return False, "Invalid datetime format" | |
| # Validate city name (alphanumeric, spaces, hyphens, commas, slashes, periods, and cyrillic) | |
| if not re.match(r'^[a-zA-Zа-яА-ЯёЁ0-9\s\-\/\.,]{2,100}$', city): | |
| return False, "Invalid city name" | |
| # Additional date validation | |
| try: | |
| day, month, year_time = datetime_str.split('-') | |
| year, time = year_time.split(' ') | |
| datetime(int(year), int(month), int(day)) | |
| except ValueError: | |
| return False, "Invalid date" | |
| return True, "" | |
| class RateLimiter: | |
| def __init__(self, max_calls: int, period: int): | |
| self.max_calls = max_calls | |
| self.period = period | |
| self.calls = [] | |
| def __call__(self, func): | |
| def wrapper(*args, **kwargs): | |
| now = time.time() | |
| # Remove calls outside the current period | |
| self.calls = [call for call in self.calls if now - call < self.period] | |
| if len(self.calls) >= self.max_calls: | |
| logger.warning(f"Rate limit exceeded for function {func.__name__}") | |
| raise gr.Error("Rate limit exceeded. Please try again later.") | |
| self.calls.append(now) | |
| logger.debug(f"Function {func.__name__} called, current call count: {len(self.calls)}") | |
| return func(*args, **kwargs) | |
| return wrapper |