144 lines
3.5 KiB
Python
144 lines
3.5 KiB
Python
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,
|
|
)
|