app.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. import os
  2. import sqlite3
  3. import uuid
  4. import hashlib
  5. import threading
  6. import time
  7. import secrets
  8. import string
  9. import argparse
  10. from datetime import datetime, timedelta
  11. from werkzeug.utils import secure_filename
  12. from flask import Flask, request, jsonify, render_template, send_from_directory, abort
  13. from werkzeug.security import generate_password_hash
  14. import re
  15. def generate_secret_key(length=32):
  16. """Generate a random secret key with specified length"""
  17. alphabet = string.ascii_letters + string.digits + string.punctuation
  18. return ''.join(secrets.choice(alphabet) for _ in range(length))
  19. def parse_arguments():
  20. """Parse command line arguments"""
  21. parser = argparse.ArgumentParser(description='Flask Temporary File Upload Server')
  22. parser.add_argument('--host', default='127.0.0.1', help='Host to bind to (default: 127.0.0.1)')
  23. parser.add_argument('--port', type=int, default=5000, help='Port to bind to (default: 5000)')
  24. parser.add_argument('--debug', action='store_true', help='Enable debug mode')
  25. parser.add_argument('--base-url', default=None, help='Base URL for download links (e.g., https://mysite.com)')
  26. parser.add_argument('--network', action='store_true', help='Bind to all network interfaces (0.0.0.0)')
  27. parser.add_argument('--max-file-size', type=int, default=16, help='Maximum file size in MB (default: 16)')
  28. return parser.parse_args()
  29. # Parse command line arguments
  30. args = parse_arguments()
  31. # Set host based on arguments
  32. if args.network:
  33. HOST = '0.0.0.0'
  34. else:
  35. HOST = args.host
  36. # Set base URL for download links
  37. BASE_URL = args.base_url or os.environ.get('BASE_URL', f'http://{HOST}:{args.port}')
  38. # Validate max file size
  39. if args.max_file_size <= 0:
  40. print("❌ Error: Maximum file size must be a positive integer")
  41. exit(1)
  42. app = Flask(__name__)
  43. app.config['SECRET_KEY'] = generate_secret_key(32)
  44. app.config['UPLOAD_FOLDER'] = 'uploads'
  45. app.config['MAX_CONTENT_LENGTH'] = args.max_file_size * 1024 * 1024 # Convert MB to bytes
  46. app.config['BASE_URL'] = BASE_URL
  47. # Ensure upload directory exists
  48. os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
  49. # Database initialization
  50. def init_db():
  51. conn = sqlite3.connect('files.db')
  52. cursor = conn.cursor()
  53. cursor.execute('''
  54. CREATE TABLE IF NOT EXISTS files (
  55. id TEXT PRIMARY KEY,
  56. original_filename TEXT NOT NULL,
  57. sanitized_filename TEXT NOT NULL,
  58. file_path TEXT NOT NULL,
  59. user_session TEXT NOT NULL,
  60. upload_date TEXT NOT NULL,
  61. expiration_date TEXT NOT NULL,
  62. duration_hours INTEGER NOT NULL,
  63. file_size INTEGER NOT NULL,
  64. mime_type TEXT
  65. )
  66. ''')
  67. conn.commit()
  68. conn.close()
  69. def sanitize_filename(filename):
  70. """Sanitize filename for safe storage"""
  71. # Remove special characters and spaces
  72. name, ext = os.path.splitext(filename)
  73. sanitized = re.sub(r'[^a-zA-Z0-9_-]', '_', name)
  74. # Generate unique identifier
  75. unique_id = str(uuid.uuid4())[:8]
  76. return f"{sanitized}_{unique_id}{ext}"
  77. def cleanup_expired_files():
  78. """Remove expired files from database and filesystem"""
  79. conn = sqlite3.connect('files.db')
  80. cursor = conn.cursor()
  81. # Get expired files
  82. cursor.execute('''
  83. SELECT file_path FROM files
  84. WHERE expiration_date < ?
  85. ''', (datetime.now().isoformat(),))
  86. expired_files = cursor.fetchall()
  87. # Delete files from filesystem
  88. for (file_path,) in expired_files:
  89. full_path = os.path.join(app.config['UPLOAD_FOLDER'], file_path)
  90. try:
  91. if os.path.exists(full_path):
  92. os.remove(full_path)
  93. print(f"Deleted expired file: {file_path}")
  94. except Exception as e:
  95. print(f"Error deleting file {file_path}: {e}")
  96. # Remove from database
  97. cursor.execute('DELETE FROM files WHERE expiration_date < ?',
  98. (datetime.now().isoformat(),))
  99. conn.commit()
  100. conn.close()
  101. print(f"Cleaned up {len(expired_files)} expired files")
  102. def cleanup_worker():
  103. """Background worker to cleanup expired files every 10 minutes"""
  104. while True:
  105. time.sleep(600) # 10 minutes
  106. cleanup_expired_files()
  107. @app.route('/')
  108. def index():
  109. max_file_size = app.config['MAX_CONTENT_LENGTH'] // (1024 * 1024) # Convert bytes to MB
  110. return render_template('index.html', max_file_size=max_file_size)
  111. @app.route('/upload', methods=['POST'])
  112. def upload_file():
  113. if 'file' not in request.files:
  114. return jsonify({'error': 'No file provided'}), 400
  115. file = request.files['file']
  116. user_session = request.form.get('user_session')
  117. duration_hours = int(request.form.get('duration_hours', 24))
  118. if file.filename == '':
  119. return jsonify({'error': 'No file selected'}), 400
  120. if not user_session:
  121. return jsonify({'error': 'No user session provided'}), 400
  122. if file:
  123. # Generate file info
  124. file_id = str(uuid.uuid4())
  125. original_filename = file.filename
  126. sanitized_filename = sanitize_filename(original_filename)
  127. upload_date = datetime.now()
  128. expiration_date = upload_date + timedelta(hours=duration_hours)
  129. # Save file
  130. file_path = sanitized_filename
  131. full_path = os.path.join(app.config['UPLOAD_FOLDER'], file_path)
  132. file.save(full_path)
  133. # Get file info
  134. file_size = os.path.getsize(full_path)
  135. mime_type = file.content_type
  136. # Save to database
  137. conn = sqlite3.connect('files.db')
  138. cursor = conn.cursor()
  139. cursor.execute('''
  140. INSERT INTO files (id, original_filename, sanitized_filename, file_path,
  141. user_session, upload_date, expiration_date, duration_hours,
  142. file_size, mime_type)
  143. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  144. ''', (file_id, original_filename, sanitized_filename, file_path, user_session,
  145. upload_date.isoformat(), expiration_date.isoformat(), duration_hours,
  146. file_size, mime_type))
  147. conn.commit()
  148. conn.close()
  149. return jsonify({
  150. 'success': True,
  151. 'file_id': file_id,
  152. 'original_filename': original_filename,
  153. 'download_url': f'{app.config["BASE_URL"]}/download/{file_id}',
  154. 'expiration_date': expiration_date.isoformat(),
  155. 'file_size': file_size
  156. })
  157. @app.route('/files/<user_session>')
  158. def get_user_files(user_session):
  159. conn = sqlite3.connect('files.db')
  160. cursor = conn.cursor()
  161. cursor.execute('''
  162. SELECT id, original_filename, upload_date, expiration_date,
  163. duration_hours, file_size, mime_type
  164. FROM files
  165. WHERE user_session = ? AND expiration_date > ?
  166. ORDER BY upload_date DESC
  167. ''', (user_session, datetime.now().isoformat()))
  168. files = cursor.fetchall()
  169. conn.close()
  170. file_list = []
  171. for file_data in files:
  172. file_list.append({
  173. 'id': file_data[0],
  174. 'original_filename': file_data[1],
  175. 'upload_date': file_data[2],
  176. 'expiration_date': file_data[3],
  177. 'duration_hours': file_data[4],
  178. 'file_size': file_data[5],
  179. 'mime_type': file_data[6],
  180. 'download_url': f'{app.config["BASE_URL"]}/download/{file_data[0]}'
  181. })
  182. return jsonify(file_list)
  183. @app.route('/download/<file_id>')
  184. def download_file(file_id):
  185. conn = sqlite3.connect('files.db')
  186. cursor = conn.cursor()
  187. cursor.execute('''
  188. SELECT original_filename, file_path, expiration_date
  189. FROM files
  190. WHERE id = ?
  191. ''', (file_id,))
  192. file_data = cursor.fetchone()
  193. conn.close()
  194. if not file_data:
  195. abort(404)
  196. original_filename, file_path, expiration_date = file_data
  197. # Check if file has expired
  198. if datetime.fromisoformat(expiration_date) < datetime.now():
  199. abort(410) # Gone
  200. # Check if file exists
  201. full_path = os.path.join(app.config['UPLOAD_FOLDER'], file_path)
  202. if not os.path.exists(full_path):
  203. abort(404)
  204. return send_from_directory(app.config['UPLOAD_FOLDER'], file_path,
  205. as_attachment=True, download_name=original_filename)
  206. @app.route('/delete/<file_id>', methods=['DELETE'])
  207. def delete_file(file_id):
  208. user_session = request.json.get('user_session')
  209. if not user_session:
  210. return jsonify({'error': 'No user session provided'}), 400
  211. conn = sqlite3.connect('files.db')
  212. cursor = conn.cursor()
  213. # Get file info and verify ownership
  214. cursor.execute('''
  215. SELECT file_path FROM files
  216. WHERE id = ? AND user_session = ?
  217. ''', (file_id, user_session))
  218. file_data = cursor.fetchone()
  219. if not file_data:
  220. conn.close()
  221. return jsonify({'error': 'File not found or unauthorized'}), 404
  222. file_path = file_data[0]
  223. # Delete from filesystem
  224. full_path = os.path.join(app.config['UPLOAD_FOLDER'], file_path)
  225. try:
  226. if os.path.exists(full_path):
  227. os.remove(full_path)
  228. except Exception as e:
  229. print(f"Error deleting file {file_path}: {e}")
  230. # Delete from database
  231. cursor.execute('DELETE FROM files WHERE id = ? AND user_session = ?',
  232. (file_id, user_session))
  233. conn.commit()
  234. conn.close()
  235. return jsonify({'success': True})
  236. if __name__ == '__main__':
  237. # Print configuration info
  238. print(f"🚀 Starting Flask Temporary File Upload Server")
  239. print(f"📁 Upload folder: {app.config['UPLOAD_FOLDER']}")
  240. print(f"🔑 Secret key: {app.config['SECRET_KEY'][:8]}... (32 chars)")
  241. print(f"🌐 Base URL: {app.config['BASE_URL']}")
  242. print(f"🖥️ Server: http://{HOST}:{args.port}")
  243. print(f"🔒 Max file size: {args.max_file_size}MB")
  244. print(f"⏰ Cleanup interval: 10 minutes")
  245. print(f"🐛 Debug mode: {'ON' if args.debug else 'OFF'}")
  246. print("-" * 50)
  247. init_db()
  248. # Start cleanup worker in background
  249. cleanup_thread = threading.Thread(target=cleanup_worker, daemon=True)
  250. cleanup_thread.start()
  251. # Initial cleanup
  252. cleanup_expired_files()
  253. app.run(debug=args.debug, host=HOST, port=args.port)