import os import argparse import uuid import shutil from datetime import datetime, timedelta from flask import Flask, render_template, request, jsonify, send_file, redirect, url_for from werkzeug.utils import secure_filename from dotenv import load_dotenv import json from database import init_database, save_file, get_file_by_id, get_all_files, delete_file, hard_delete_file, increment_download_count, cleanup_expired_files, get_file_stats # Cargar variables de entorno desde .env load_dotenv() app = Flask(__name__) app.config['MAX_CONTENT_LENGTH'] = int(os.getenv('MAX_FILE_SIZE', 16 * 1024 * 1024)) # 16MB max file size app.config['UPLOAD_FOLDER'] = os.getenv('UPLOAD_FOLDER', 'uploads') app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'tu_clave_secreta_aqui') # Manejador de errores para archivos demasiado grandes @app.errorhandler(413) def too_large(e): return jsonify({ 'error': f'El archivo es demasiado grande. Tamaño máximo: {app.config["MAX_CONTENT_LENGTH"] // (1024*1024)}MB' }), 413 # Manejador de errores general @app.errorhandler(500) def internal_error(e): return jsonify({'error': 'Error interno del servidor'}), 500 @app.errorhandler(404) def not_found(e): return jsonify({'error': 'Página no encontrada'}), 404 # Verificar y crear carpeta de uploads si no existe def ensure_upload_folder(): if not os.path.exists(app.config['UPLOAD_FOLDER']): os.makedirs(app.config['UPLOAD_FOLDER']) print(f"Carpeta '{app.config['UPLOAD_FOLDER']}' creada automáticamente.") # Inicializar base de datos def init_app(): ensure_upload_folder() init_database() print("Base de datos inicializada correctamente.") # Configurar argumentos de línea de comandos def parse_arguments(): parser = argparse.ArgumentParser(description='Servidor de archivos temporales') parser.add_argument('--host', default=os.getenv('HOST', '127.0.0.1'), help='Host del servidor (default: 127.0.0.1)') parser.add_argument('--port', type=int, default=int(os.getenv('PORT', 5000)), help='Puerto del servidor (default: 5000)') parser.add_argument('--https', action='store_true', help='Usar HTTPS') parser.add_argument('--debug', action='store_true', default=os.getenv('DEBUG', 'False').lower() == 'true', help='Modo debug') return parser.parse_args() @app.route('/') def index(): """Página principal para subir archivos""" # Pasar configuración al template max_file_size = app.config['MAX_CONTENT_LENGTH'] max_file_size_mb = max_file_size // (1024 * 1024) return render_template('index.html', max_file_size=max_file_size, max_file_size_mb=max_file_size_mb) @app.route('/myfiles') def myfiles(): """Página para ver enlaces guardados en localStorage""" return render_template('myfiles.html') @app.route('/upload', methods=['POST']) def upload_file(): """Manejar la subida de archivos""" try: # Verificar si hay archivo en la request if 'file' not in request.files: return jsonify({'error': 'No se seleccionó ningún archivo'}), 400 file = request.files['file'] if file.filename == '': return jsonify({'error': 'No se seleccionó ningún archivo'}), 400 # Obtener tiempo de vida del archivo (por defecto 24 horas) expires_hours = request.form.get('expires_hours', 24, type=int) # Validar tiempo de vida (entre 1 hora y 7 días) if expires_hours < 1 or expires_hours > 168: # 168 horas = 7 días expires_hours = 24 # Validar tamaño del archivo antes de procesar max_size = app.config['MAX_CONTENT_LENGTH'] max_size_mb = max_size // (1024 * 1024) # Verificar content_length si está disponible if request.content_length and request.content_length > max_size: return jsonify({ 'error': f'El archivo es demasiado grande. Tamaño máximo: {max_size_mb}MB' }), 413 # Verificar el tamaño del archivo después de recibirlo if file: # Leer el archivo en chunks para verificar el tamaño file.seek(0, 2) # Ir al final del archivo file_size = file.tell() file.seek(0) # Volver al inicio if file_size > max_size: return jsonify({ 'error': f'El archivo es demasiado grande. Tamaño máximo: {max_size_mb}MB' }), 413 # Generar nombre único para el archivo original_filename = file.filename if not original_filename: return jsonify({'error': 'Nombre de archivo no válido'}), 400 filename = secure_filename(original_filename) if not filename: return jsonify({'error': 'Nombre de archivo no válido'}), 400 file_id = str(uuid.uuid4()) file_extension = os.path.splitext(filename)[1] unique_filename = f"{file_id}{file_extension}" # Guardar archivo file_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename) file.save(file_path) # Guardar en base de datos con el tiempo de vida especificado save_file(file_id, original_filename, unique_filename, file_size, expires_hours) # Crear enlace temporal con el tiempo de vida especificado # Usar el dominio configurado o el host actual domain = os.getenv('DOMAIN', request.host) protocol = 'https' if args.https else 'http' download_url = f"{protocol}://{domain}/download/{file_id}" return jsonify({ 'success': True, 'download_url': download_url, 'file_id': file_id, 'original_filename': original_filename, 'expires_at': (datetime.now() + timedelta(hours=expires_hours)).isoformat() }) else: return jsonify({'error': 'No se pudo procesar el archivo'}), 400 except Exception as e: print(f"Error en upload: {str(e)}") return jsonify({'error': 'Error interno del servidor'}), 500 @app.route('/download/') def download_file(file_id): """Descargar archivo por ID""" # Buscar archivo en la base de datos file_info = get_file_by_id(file_id) if not file_info: return "Archivo no encontrado", 404 # Verificar si el archivo ha expirado expires_at = datetime.fromisoformat(file_info['expires_at']) if expires_at <= datetime.now(): return "Archivo expirado", 410 # Verificar si el archivo existe físicamente file_path = os.path.join(app.config['UPLOAD_FOLDER'], file_info['stored_filename']) if not os.path.exists(file_path): return "Archivo no encontrado", 404 # Incrementar contador de descargas increment_download_count(file_id) # Enviar archivo return send_file(file_path, as_attachment=True, download_name=file_info['original_filename']) @app.route('/api/links') def get_links(): """API para obtener enlaces guardados desde la base de datos""" try: files = get_all_files() links = [] for file_info in files: # Crear URL de descarga domain = os.getenv('DOMAIN', request.host) protocol = 'https' if args.https else 'http' download_url = f"{protocol}://{domain}/download/{file_info['id']}" links.append({ 'id': file_info['id'], 'filename': file_info['original_filename'], 'url': download_url, 'created_at': file_info['created_at'], 'expires_at': file_info['expires_at'], 'file_size': file_info['file_size'], 'download_count': file_info['download_count'] }) return jsonify(links) except Exception as e: print(f"Error obteniendo enlaces: {str(e)}") return jsonify({'error': 'Error interno del servidor'}), 500 @app.route('/api/delete/', methods=['DELETE']) def delete_link(file_id): """API para eliminar un enlace permanentemente""" try: # Obtener información del archivo file_info = get_file_by_id(file_id) if not file_info: return jsonify({'error': 'Archivo no encontrado'}), 404 # Eliminar archivo físico file_path = os.path.join(app.config['UPLOAD_FOLDER'], file_info['stored_filename']) if os.path.exists(file_path): os.remove(file_path) # Eliminar permanentemente de la base de datos hard_delete_file(file_id) return jsonify({'success': True, 'message': 'Archivo eliminado permanentemente'}) except Exception as e: print(f"Error eliminando archivo: {str(e)}") return jsonify({'error': 'Error interno del servidor'}), 500 @app.route('/api/stats') def get_stats(): """API para obtener estadísticas""" try: stats = get_file_stats() return jsonify(stats) except Exception as e: print(f"Error obteniendo estadísticas: {str(e)}") return jsonify({'error': 'Error interno del servidor'}), 500 if __name__ == '__main__': args = parse_arguments() # Inicializar aplicación (carpeta de uploads y base de datos) init_app() # Configurar protocolo protocol = 'https' if args.https else 'http' print(f"Servidor iniciando en {protocol}://{args.host}:{args.port}") print(f"Modo debug: {'Activado' if args.debug else 'Desactivado'}") app.run( host=args.host, port=args.port, debug=args.debug )