diff --git a/app.py b/app.py index 3bfa753..a4dba1e 100644 --- a/app.py +++ b/app.py @@ -8,14 +8,14 @@ import hashlib import secrets import subprocess import platform -from datetime import datetime, timedelta +from datetime import datetime import sys import psutil # ===== Third-Party Imports ===== from flask import ( Flask, render_template, request, jsonify, session, - redirect, url_for, flash, send_file + redirect, url_for, flash, send_file, make_response ) from werkzeug.utils import secure_filename from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC @@ -83,20 +83,21 @@ def simple_decode(text: str) -> str: return ''.join(ALPHABET[(ALPHABET.index(c) - 3) % 26] if c in ALPHABET else c for c in text.lower()) def advanced_encrypt(plaintext: str, password: str) -> str: - """Encrypt text using AES-GCM with password-derived key.""" + """Encrypt plaintext with AES-GCM and return base64-encoded result.""" salt = os.urandom(16) - key = derive_key(password, salt) nonce = os.urandom(12) - ct = AESGCM(key).encrypt(nonce, plaintext.encode(), None) - return base64.urlsafe_b64encode(salt + nonce + ct).decode() + key = derive_key(password, salt) + ciphertext = AESGCM(key).encrypt(nonce, plaintext.encode(), None) + return base64.b64encode(salt + nonce + ciphertext).decode() -def advanced_decrypt(token_b64: str, password: str) -> str: - """Decrypt text using AES-GCM with password-derived key.""" +def advanced_decrypt(data_b64: str, password: str) -> str: + """Decrypt base64-encoded AES-GCM encrypted data.""" try: - data = base64.urlsafe_b64decode(token_b64.encode()) - salt, nonce, ct = data[:16], data[16:28], data[28:] + data = base64.b64decode(data_b64) + salt, nonce, ciphertext = data[:16], data[16:28], data[28:] key = derive_key(password, salt) - return AESGCM(key).decrypt(nonce, ct, None).decode() + plaintext = AESGCM(key).decrypt(nonce, ciphertext, None) + return plaintext.decode() except Exception: return "[Error] Invalid password or corrupted data!" @@ -207,7 +208,7 @@ def handle_file_upload(request): meta = { 'pickup_password': base64.urlsafe_b64encode(hashlib.sha256(pickup_password.encode()).digest()).decode(), - 'original_name': filename, + 'original_name': encrypt_filename(filename, enc_password), 'timestamp': datetime.now().isoformat() } with open(os.path.join(UPLOAD_FOLDER, f"{random_id}.json"), 'w') as f: @@ -217,7 +218,6 @@ def handle_file_upload(request): return jsonify({"success": True, "pickup_url": pickup_url}) def handle_text_operation(request): - """Process text encryption/decryption operations.""" data = request.get_json() encryption_type = data.get("encryption-type", "basic") operation = data.get("operation", "") @@ -226,10 +226,27 @@ def handle_text_operation(request): if encryption_type == "basic": result = simple_encode(message) if operation == "encrypt" else simple_decode(message) - else: - result = advanced_encrypt(message, password) if operation == "encrypt" else advanced_decrypt(message, password) + return jsonify(result=html.escape(result)) - return jsonify(result=html.escape(result)) + if operation == "encrypt": + encrypted = advanced_encrypt(message, password) + return jsonify(result=encrypted) + else: + decrypted = advanced_decrypt(message, password) + return jsonify(result=html.escape(decrypted)) + +def encrypt_filename(filename: str, password: str) -> str: + salt = os.urandom(16) + key = derive_key(password, salt) + nonce = os.urandom(12) + ct = AESGCM(key).encrypt(nonce, filename.encode(), None) + return base64.urlsafe_b64encode(salt + nonce + ct).decode() + +def decrypt_filename(enc_filename_b64: str, password: str) -> str: + raw = base64.urlsafe_b64decode(enc_filename_b64) + salt, nonce, ct = raw[:16], raw[16:28], raw[28:] + key = derive_key(password, salt) + return AESGCM(key).decrypt(nonce, ct, None).decode() # ===== File Pickup Route ===== @app.route("/pickup/", methods=["GET", "POST"]) @@ -278,15 +295,22 @@ def handle_file_pickup(request, meta_path, enc_path, file_id): os.remove(enc_path) log_admin_event(f"File {file_id} downloaded and deleted.") + try: + original_name = decrypt_filename(meta['original_name'], enc_password) + except Exception: + original_name = "retrieved_file" + response = send_file( io.BytesIO(decrypted), as_attachment=True, - download_name=meta['original_name'], + + download_name=original_name, mimetype='application/octet-stream' ) # Add headers for better mobile compatibility - response.headers['Content-Disposition'] = f'attachment; filename="{meta["original_name"]}"' + + response.headers['Content-Disposition'] = f'attachment; filename="{original_name}"' response.headers['Content-Type'] = 'application/octet-stream' response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' response.headers['Pragma'] = 'no-cache' @@ -661,6 +685,96 @@ def robots_txt(): ] return "\n".join(lines), 200, {"Content-Type": "text/plain"} +# ===== API Endpoints ===== +@app.route("/api/encrypt", methods=["POST"]) +def api_encrypt(): + try: + req = request.get_json() + password = req.get("password") + data_b64 = req.get("data") + + if not password or not data_b64: + return jsonify({"error": "Missing data or password"}), 400 + + file_data = base64.b64decode(data_b64) + salt = os.urandom(16) + key = derive_key(password, salt) + nonce = os.urandom(12) + ct = AESGCM(key).encrypt(nonce, file_data, None) + encrypted_binary = salt + nonce + ct + + # Return base64 string of the binary + encrypted_b64 = base64.b64encode(encrypted_binary).decode() + return jsonify({"result": encrypted_b64}), 200 + + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route("/api/decrypt", methods=["POST"]) +def api_decrypt(): + try: + req = request.get_json() + password = req.get("password") + encrypted_b64 = req.get("data") + + if not password or not encrypted_b64: + return jsonify({"error": "Missing data or password"}), 400 + + encrypted_binary = base64.b64decode(encrypted_b64) + salt, nonce, ct = encrypted_binary[:16], encrypted_binary[16:28], encrypted_binary[28:] + key = derive_key(password, salt) + decrypted = AESGCM(key).decrypt(nonce, ct, None) + + return jsonify({"result": base64.b64encode(decrypted).decode()}), 200 + + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route("/api/pacshare", methods=["POST"]) +def api_pacshare(): + try: + enc_password = request.form.get("enc_password") + pickup_password = request.form.get("pickup_password") + file = request.files.get("file") + + if not file or not enc_password or not pickup_password: + return jsonify({"error": "Missing file or fields"}), 400 + + file_data = file.read() + filename = secure_filename(file.filename) + + salt = os.urandom(16) + key = derive_key(enc_password, salt) + nonce = os.urandom(12) + ct = AESGCM(key).encrypt(nonce, file_data, None) + encrypted = salt + nonce + ct + + file_id = secrets.token_urlsafe(24) + enc_path = os.path.join(UPLOAD_FOLDER, f"{file_id}.enc") + meta_path = os.path.join(UPLOAD_FOLDER, f"{file_id}.json") + + with open(enc_path, "wb") as f: + f.write(encrypted) + + encrypted_filename = encrypt_filename(filename, enc_password) + + meta = { + 'pickup_password': base64.urlsafe_b64encode( + hashlib.sha256(pickup_password.encode()).digest() + ).decode(), + 'original_name': encrypted_filename, + 'timestamp': datetime.now().isoformat() + } + + with open(meta_path, "w") as f: + json.dump(meta, f) + + pickup_url = request.host_url.rstrip('/') + url_for('pickup_file', file_id=file_id) + return jsonify({"pickup_url": pickup_url}) + + except Exception as e: + return jsonify({"error": str(e)}), 500 + # ===== Error Handlers ===== @app.errorhandler(404) def page_not_found(e): @@ -695,3 +809,4 @@ if __name__ == "__main__": else: print("[INFO] Running in DEVELOPMENT mode with Flask server.") app.run(debug=True, host="0.0.0.0", port=5000) + diff --git a/static/fonts/PressStart2P-Regular.ttf b/static/fonts/PressStart2P-Regular.ttf new file mode 100644 index 0000000..2442aff Binary files /dev/null and b/static/fonts/PressStart2P-Regular.ttf differ diff --git a/static/js/ui.js b/static/js/ui.js index b1be2c6..e721547 100644 --- a/static/js/ui.js +++ b/static/js/ui.js @@ -7,16 +7,13 @@ import { encryptFile, decryptFile } from './fileops.js'; // ===== UI Initialization ===== export function setupUI() { - // Set initial state of remove button to hidden const removeBtn = document.getElementById("remove-file-btn"); if (removeBtn) { removeBtn.style.display = "none"; } - initializeEventListeners(); } -// ===== Event Listeners ===== function initializeEventListeners() { const elements = { encryptionType: document.getElementById("encryption-type"), @@ -55,10 +52,7 @@ function setupElementListeners(elements) { elements.toggleSwitch.addEventListener("change", () => { console.log("Mode:", elements.toggleSwitch.checked ? "Decrypt" : "Encrypt"); }); - - - // Add file input change listener const fileInput = document.getElementById("file-input"); if (fileInput) { fileInput.addEventListener("change", () => { @@ -93,7 +87,6 @@ function setupShareLinkListeners(elements) { } } -// ===== UI State Management ===== function toggleEncryptionOptions() { const type = document.getElementById("encryption-type").value.trim().toLowerCase(); const passwordInputWrapper = document.getElementById("password-input"); @@ -101,19 +94,11 @@ function toggleEncryptionOptions() { const isAdvanced = type.includes("advanced"); if (passwordInputWrapper) { - if (isAdvanced) { - passwordInputWrapper.classList.remove("hidden"); - } else { - passwordInputWrapper.classList.add("hidden"); - } + passwordInputWrapper.classList.toggle("hidden", !isAdvanced); } if (fileSection) { - if (isAdvanced) { - fileSection.classList.remove("hidden"); - } else { - fileSection.classList.add("hidden"); - } + fileSection.classList.toggle("hidden", !isAdvanced); } updateToggleLabels(); @@ -150,7 +135,6 @@ function toggleInputMode() { removeBtn.style.display = fileSelected ? "inline-block" : "none"; } -// ===== Form Handling ===== async function handleSubmit(event) { event.preventDefault(); @@ -189,14 +173,18 @@ async function handleTextOperation(encryptionType, operation, password) { headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); + const data = await response.json(); - document.getElementById("output-text").value = data.result; + + const outputField = document.getElementById("output-text"); + if (outputField) { + outputField.value = data.result || "[Error] No response received."; + } } catch (err) { alert("Error processing request: " + err.message); } } -// ===== Utility Functions ===== function removeFile() { const fileInput = document.getElementById("file-input"); if (fileInput) fileInput.value = ""; @@ -214,7 +202,6 @@ function generateRandomPassword() { const passwordField = document.getElementById("generated-password"); if (passwordField) { passwordField.value = password; - // Check if we should start Pacman checkForPacman(); } } @@ -225,33 +212,27 @@ function copyToClipboard(elementId, feedbackId) { if (!el || !el.value) return; - // Create a temporary textarea element const textarea = document.createElement('textarea'); textarea.value = el.value; textarea.style.position = 'fixed'; textarea.style.opacity = '0'; document.body.appendChild(textarea); - - // Select and copy the text + textarea.select(); - textarea.setSelectionRange(0, 99999); // For mobile devices - + textarea.setSelectionRange(0, 99999); + try { - // Try using the modern clipboard API first navigator.clipboard.writeText(el.value).then(() => { showFeedback(feedback); }).catch(() => { - // Fallback to execCommand for older browsers document.execCommand('copy'); showFeedback(feedback); }); } catch (err) { - // Final fallback document.execCommand('copy'); showFeedback(feedback); } - - // Clean up + document.body.removeChild(textarea); } @@ -298,6 +279,7 @@ function checkForPacman() { window.exitGame(); } } + function copyShareLink() { const linkEl = document.getElementById("share-link"); const feedback = document.getElementById("shared-link-feedback"); @@ -346,8 +328,5 @@ function showCopyFeedback(feedbackEl) { }, 3000); } - function startPacman() { } -function exitGame() { } - - +function exitGame() { } \ No newline at end of file