import os import hashlib import re from flask import current_app, request, abort from functools import wraps from flask_login import current_user from werkzeug.utils import secure_filename # Try to import magic with a fallback for Windows try: import magic HAS_MAGIC = True except ImportError: HAS_MAGIC = False current_app.logger.warning("libmagic not found, using basic file type detection") from utils.file_utils import detect_file_type def check_file_type(file_stream, allowed_mime_types): """ Verificar el tipo MIME real de un archivo. Args: file_stream: Stream del archivo a verificar allowed_mime_types (list): Lista de tipos MIME permitidos Returns: bool: True si el tipo es permitido, False en caso contrario """ # Guardar posición actual en el stream current_position = file_stream.tell() # Leer los primeros bytes para detectar el tipo file_head = file_stream.read(2048) file_stream.seek(current_position) # Restaurar posición # Detectar tipo MIME mime = magic.Magic(mime=True) file_type = mime.from_buffer(file_head) return file_type in allowed_mime_types def calculate_checksum(file_path): """ Calcular el hash SHA-256 de un archivo. Args: file_path (str): Ruta al archivo Returns: str: Hash SHA-256 en formato hexadecimal """ sha256_hash = hashlib.sha256() with open(file_path, "rb") as f: # Leer por bloques para archivos grandes for byte_block in iter(lambda: f.read(4096), b""): sha256_hash.update(byte_block) return sha256_hash.hexdigest() def sanitize_filename(filename): """ Sanitizar nombre de archivo. Args: filename (str): Nombre de archivo original Returns: str: Nombre de archivo sanitizado """ # Primero usar secure_filename de Werkzeug safe_name = secure_filename(filename) # Eliminar caracteres problemáticos adicionales safe_name = re.sub(r"[^\w\s.-]", "", safe_name) # Reemplazar espacios por guiones bajos safe_name = safe_name.replace(" ", "_") return safe_name def generate_unique_filename(base_dir, filename_pattern): """ Generar nombre de archivo único basado en un patrón. Args: base_dir (str): Directorio base filename_pattern (str): Patrón de nombre (puede contener {counter}) Returns: str: Nombre de archivo único """ counter = 1 filename = filename_pattern.format(counter=counter) while os.path.exists(os.path.join(base_dir, filename)): counter += 1 filename = filename_pattern.format(counter=counter) return filename def permission_required(min_level): """ Decorador para verificar nivel de permisos. Args: min_level (int): Nivel mínimo requerido Returns: function: Decorador configurado """ def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): if not current_user.is_authenticated: return current_app.login_manager.unauthorized() if not current_user.has_permission(min_level): return forbidden_error() return f(*args, **kwargs) return decorated_function return decorator def forbidden_error(): """Respuesta para error 403 (acceso denegado).""" from flask import render_template return ( render_template("error.html", error_code=403, error_message="Acceso denegado"), 403, )