import os import shutil from flask import Blueprint, request, render_template_string, send_from_directory, redirect, url_for, flash, session from werkzeug.utils import secure_filename def create_filemanager_blueprint(base_dir, url_prefix='/files', auth_password=None): """ Creates a Flask blueprint providing a simple file manager with a clipboard-style move operation (cut/paste) for the given base directory. """ base_dir = os.path.abspath(base_dir) os.makedirs(base_dir, exist_ok=True) filemanager = Blueprint('filemanager', __name__, url_prefix=url_prefix) def secure_path(path): """Ensure that the provided relative path stays within the base_dir.""" safe_path = os.path.abspath(os.path.join(base_dir, path)) if not safe_path.startswith(base_dir): raise Exception("Invalid path") return safe_path @filemanager.before_request def require_login(): if auth_password is not None: # Allow access to login and logout pages without being authenticated. if request.endpoint in ['filemanager.login', 'filemanager.logout']: return None if not session.get('filemanager_authenticated'): return redirect(url_for('filemanager.login', next=request.url)) return None @filemanager.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': password = request.form.get('password', '') if password == auth_password: session['filemanager_authenticated'] = True flash("Logged in successfully") next_url = request.args.get('next') or url_for('filemanager.index') return redirect(next_url) else: flash("Incorrect password") return render_template_string(''' Login

Login

{% with messages = get_flashed_messages() %} {% if messages %} {% endif %} {% endwith %}
''') @filemanager.route('/logout', methods=['POST']) def logout(): session.pop('filemanager_authenticated', None) flash("Logged out") return redirect(url_for('filemanager.login')) @filemanager.route('/') def index(): # Determine current directory from query parameter; defaults to the base. rel_path = request.args.get('path', '') try: abs_path = secure_path(rel_path) except Exception: return "Invalid path", 400 if not os.path.isdir(abs_path): return "Not a directory", 400 # Build a list of items (files and folders) in the current directory. items = [] for entry in os.listdir(abs_path): entry_path = os.path.join(abs_path, entry) rel_entry_path = os.path.join(rel_path, entry) if rel_path else entry items.append({ 'name': entry, 'is_dir': os.path.isdir(entry_path), 'path': rel_entry_path }) items.sort(key=lambda x: (not x['is_dir'], x['name'].lower())) parent = os.path.dirname(rel_path) if rel_path else '' # Minimal HTML template that displays the file tree along with a clipboard if active. template = """ File Manager

File Manager

Current Directory: /{{ rel_path }}

{% if rel_path %}

Go up

{% endif %} {% if session.clipboard %}

Clipboard

{% endif %}

Upload File(s)


Create New Directory

""" return render_template_string(template, items=items, rel_path=rel_path, parent=parent) @filemanager.route('/download') def download(): rel_path = request.args.get('path', '') try: abs_path = secure_path(rel_path) except Exception: return "Invalid path", 400 if not os.path.isfile(abs_path): return "File not found", 404 directory = os.path.dirname(abs_path) filename = os.path.basename(abs_path) return send_from_directory(directory, filename, as_attachment=True) @filemanager.route('/delete', methods=['POST']) def delete(): rel_path = request.form.get('path', '') try: abs_path = secure_path(rel_path) except Exception: return "Invalid path", 400 if os.path.isfile(abs_path): os.remove(abs_path) elif os.path.isdir(abs_path): shutil.rmtree(abs_path) else: return "Not found", 404 flash("Deleted successfully") parent = os.path.dirname(rel_path) return redirect(url_for('filemanager.index', path=parent)) @filemanager.route('/upload', methods=['POST']) def upload(): rel_path = request.form.get('path', '') try: abs_path = secure_path(rel_path) except Exception: return "Invalid path", 400 if not os.path.isdir(abs_path): return "Not a directory", 400 files = request.files.getlist('file') if files: for file in files: if file and file.filename: filename = secure_filename(file.filename) file.save(os.path.join(abs_path, filename)) flash("Uploaded files successfully") return redirect(url_for('filemanager.index', path=rel_path)) @filemanager.route('/rename', methods=['POST']) def rename(): rel_path = request.form.get('path', '') new_name = request.form.get('new_name', '') try: abs_path = secure_path(rel_path) new_rel_path = os.path.join(os.path.dirname(rel_path), new_name) new_abs_path = secure_path(new_rel_path) except Exception: return "Invalid path", 400 os.rename(abs_path, new_abs_path) flash("Renamed successfully") parent = os.path.dirname(rel_path) return redirect(url_for('filemanager.index', path=parent)) @filemanager.route('/cut', methods=['POST']) def cut(): # Add one or more items to the clipboard. paths = request.form.getlist('paths') session['clipboard'] = paths flash("Item(s) added to clipboard") return redirect(request.referrer or url_for('filemanager.index')) @filemanager.route('/paste', methods=['POST']) def paste(): # Move clipboard items to the destination (current folder). dest = request.form.get('dest', '') try: dest_abs = secure_path(dest) except Exception: return "Invalid destination", 400 if not os.path.isdir(dest_abs): return "Destination not a directory", 400 clipboard = session.get('clipboard', []) for rel_path in clipboard: try: abs_path = secure_path(rel_path) filename = os.path.basename(abs_path) shutil.move(abs_path, os.path.join(dest_abs, filename)) except Exception as e: flash(f"Error moving {rel_path}: {str(e)}") session.pop('clipboard', None) flash("Moved items successfully") return redirect(url_for('filemanager.index', path=dest)) @filemanager.route('/mkdir', methods=['POST']) def mkdir(): # The current directory is passed as a hidden form field. rel_path = request.form.get('path', '') # The new directory name is passed from the form. dirname = request.form.get('dirname', '') if not dirname: flash("Directory name cannot be empty") return redirect(url_for('filemanager.index', path=rel_path)) try: # Use secure_filename to sanitize the new directory name. safe_dirname = secure_filename(dirname) # Build the target path relative to the current directory. new_dir_path = secure_path(os.path.join(rel_path, safe_dirname)) os.makedirs(new_dir_path, exist_ok=False) flash(f"Directory '{dirname}' created successfully") except Exception as e: flash(f"Error creating directory: {str(e)}") return redirect(url_for('filemanager.index', path=rel_path)) @filemanager.route('/cancel_move', methods=['POST']) def cancel_move(): session.pop('clipboard', None) flash("Move cancelled") return redirect(request.referrer or url_for('filemanager.index')) return filemanager