diff --git a/index-update.php b/index-update.php
new file mode 100644
index 0000000..dc3cc82
--- /dev/null
+++ b/index-update.php
@@ -0,0 +1,804 @@
+', '"', "'"], ['<', '>', '"', '''], $trimmed);
+}
+
+/**
+ * Generate CSRF token
+ */
+function generate_csrf_token() {
+ if (session_status() === PHP_SESSION_NONE) {
+ // Configure secure session settings
+ ini_set('session.cookie_httponly', 1);
+ ini_set('session.cookie_secure', 1);
+ ini_set('session.cookie_samesite', 'Strict');
+ session_start();
+ }
+ if (!isset($_SESSION['csrf_token'])) {
+ $_SESSION['csrf_token'] = bin2hex(random_bytes(CSRF_TOKEN_LENGTH));
+ }
+ return $_SESSION['csrf_token'];
+}
+
+/**
+ * Validate CSRF token
+ */
+function validate_csrf_token($token) {
+ if (session_status() === PHP_SESSION_NONE) {
+ // Configure secure session settings
+ ini_set('session.cookie_httponly', 1);
+ ini_set('session.cookie_secure', 1);
+ ini_set('session.cookie_samesite', 'Strict');
+ session_start();
+ }
+ return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
+}
+
+/**
+ * Check if user is authenticated
+ */
+function is_authenticated() {
+ if (session_status() === PHP_SESSION_NONE) {
+ session_start();
+ }
+ return isset($_SESSION['authenticated']) && $_SESSION['authenticated'] === true;
+}
+
+/**
+ * Authenticate user
+ */
+function authenticate($password) {
+ if (session_status() === PHP_SESSION_NONE) {
+ session_start();
+ }
+ if ($password === APP_PASSWORD) {
+ $_SESSION['authenticated'] = true;
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Logout user
+ */
+function logout() {
+ if (session_status() === PHP_SESSION_NONE) {
+ session_start();
+ }
+ $_SESSION['authenticated'] = false;
+ session_destroy();
+}
+
+/**
+ * Encrypt file content
+ */
+function encrypt_file($source_path, $destination_path, $password) {
+ $key = hash('sha256', $password, true);
+ $iv = openssl_random_pseudo_bytes(16);
+
+ $content = file_get_contents($source_path);
+ $encrypted = openssl_encrypt($content, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
+
+ // Store IV + encrypted data
+ file_put_contents($destination_path, $iv . $encrypted);
+
+ return true;
+}
+
+/**
+ * Decrypt file content
+ */
+function decrypt_file($source_path, $password) {
+ $key = hash('sha256', $password, true);
+
+ $content = file_get_contents($source_path);
+ $iv = substr($content, 0, 16);
+ $encrypted = substr($content, 16);
+
+ $decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
+
+ return $decrypted;
+}
+
+/**
+ * Sanitize filename for headers to prevent HTTP Response Splitting
+ */
+function sanitize_filename_for_header($filename) {
+ // Remove any control characters and limit to ASCII printable chars
+ $filename = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '', $filename);
+ // Remove quotes and backslashes to prevent header injection
+ $filename = str_replace(['"', '\\', "\r", "\n"], '', $filename);
+ // Limit length to prevent excessively long headers
+ return mb_substr($filename, 0, 255);
+}
+
+/**
+ * Generate a UUID v4
+ */
+function generate_uuid() {
+ return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
+ mt_rand(0, 0xffff), mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff),
+ mt_rand(0, 0x0fff) | 0x4000,
+ mt_rand(0, 0x3fff) | 0x8000,
+ mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
+ );
+}
+
+/**
+ * Get client IP address
+ */
+function get_client_ip() {
+ $ip_keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'];
+
+ foreach ($ip_keys as $key) {
+ if (!empty($_SERVER[$key])) {
+ $ip = $_SERVER[$key];
+ // Handle comma-separated list of IPs
+ if (strpos($ip, ',') !== false) {
+ $ip = trim(explode(',', $ip)[0]);
+ }
+ // Validate IP
+ if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
+ return $ip;
+ }
+ }
+ }
+
+ return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
+}
+
+/**
+ * Hash IP for GDPR compliance
+ */
+function hash_ip($ip) {
+ // normalize IPv4/IPv6
+ $normalized = inet_ntop(inet_pton($ip));
+ return hash('sha256', HASH_SALT . $normalized);
+}
+
+/**
+ * Check if IP is rate limited for downloads
+ */
+function is_rate_limited($ip) {
+ if (!file_exists(DOWNLOAD_LOG)) {
+ return false;
+ }
+
+ $hashed_ip = hash_ip($ip);
+ $current_time = time();
+
+ $fp = fopen(DOWNLOAD_LOG, 'r');
+ if (!$fp) return false;
+
+ while (($line = fgets($fp)) !== false) {
+ $parts = explode('|', trim($line));
+ if (count($parts) >= 2) {
+ [$log_ip, $timestamp] = $parts;
+ $timestamp = intval($timestamp);
+
+ if ($log_ip === $hashed_ip && ($current_time - $timestamp) < DOWNLOAD_RATE_LIMIT_SECONDS) {
+ fclose($fp);
+ return true;
+ }
+ }
+ }
+
+ fclose($fp);
+ return false;
+}
+
+/**
+ * Log download attempt
+ */
+function log_download($ip) {
+ $hashed_ip = hash_ip($ip);
+ $log_entry = $hashed_ip . '|' . time() . "\n";
+
+ // Append safely
+ file_put_contents(DOWNLOAD_LOG, $log_entry, FILE_APPEND | LOCK_EX);
+
+ // Keep log size under control
+ $lines = file(DOWNLOAD_LOG, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+ if (count($lines) > MAX_LOG_LINES) {
+ $lines = array_slice($lines, -MAX_LOG_LINES); // keep recent only
+ file_put_contents(DOWNLOAD_LOG, implode("\n", $lines) . "\n", LOCK_EX);
+ }
+}
+
+/**
+ * Load files data from JSON
+ */
+function load_files_data() {
+ $json_content = file_get_contents(FILES_JSON);
+ return json_decode($json_content, true) ?: [];
+}
+
+/**
+ * Save files data to JSON
+ */
+function save_files_data($data) {
+ return file_put_contents(FILES_JSON, json_encode($data, JSON_PRETTY_PRINT), LOCK_EX);
+}
+
+/**
+ * Validate file extension
+ */
+function is_allowed_file_type($filename) {
+ $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
+ return in_array($extension, ALLOWED_EXTENSIONS);
+}
+
+/**
+ * Clean up old files
+ */
+function cleanup_old_files() {
+ $files_data = load_files_data();
+ $current_time = time();
+ $deleted = false;
+
+ foreach ($files_data as $uuid => $file_info) {
+ $age_days = ($current_time - $file_info['upload_timestamp']) / (60 * 60 * 24);
+
+ if ($age_days > AUTO_DELETE_DAYS) {
+ $file_path = DOWNLOAD_DIR . '/' . $uuid;
+ if (file_exists($file_path)) {
+ unlink($file_path);
+ }
+ unset($files_data[$uuid]);
+ $deleted = true;
+ }
+ }
+
+ if ($deleted) {
+ save_files_data($files_data);
+ }
+}
+
+/**
+ * Display error message and exit
+ */
+function display_error($message) {
+ error_log("User error: " . $message);
+ echo render_page("Error", "
$message
");
+ exit;
+}
+
+/**
+ * Display success message and exit
+ */
+function display_success($message) {
+ echo render_page("Success", "$message
");
+ exit;
+}
+
+/**
+ * Render HTML page
+ */
+function render_page($title, $content) {
+ // Set security headers
+ header('Content-Type: text/html; charset=UTF-8');
+ header('X-Content-Type-Options: nosniff');
+ header('X-Frame-Options: DENY');
+ header('X-XSS-Protection: 1; mode=block');
+ header('Content-Security-Policy: default-src \'self\' cdn.tailwindcss.com; script-src \'self\' \'unsafe-inline\' cdn.tailwindcss.com; style-src \'self\' \'unsafe-inline\' cdn.tailwindcss.com; img-src \'self\' data:; connect-src \'self\'');
+
+ $base_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . dirname($_SERVER['SCRIPT_NAME']);
+
+ $logout_button = '';
+ if (is_authenticated() && !isset($_GET['download'])) {
+ $logout_button = 'Se déconnecter';
+ }
+
+ return <<
+
+
+
+
+ $title - HopTransfert
+
+
+
+
+
+
HopTransfert
+ $logout_button
+
+ $content
+
+
+
+HTML;
+}
+
+// =============================================================================
+// MAIN APPLICATION LOGIC
+// =============================================================================
+
+// Force UTF-8 encoding
+mb_internal_encoding('UTF-8');
+mb_http_output('UTF-8');
+
+// Cleanup old files
+cleanup_old_files();
+
+// Sanitize all input
+$_GET = sanitize_input($_GET);
+$_POST = sanitize_input($_POST);
+
+// Handle logout
+if (isset($_GET['logout'])) {
+ logout();
+ header('Location: ' . $_SERVER['SCRIPT_NAME']);
+ exit;
+}
+
+// Check authentication for upload page
+if (!isset($_GET['download'])) {
+ if (!is_authenticated()) {
+ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['app_password'])) {
+ if (authenticate($_POST['app_password'])) {
+ header('Location: ' . $_SERVER['SCRIPT_NAME']);
+ exit;
+ } else {
+ show_login_form('Mot de passe incorrect');
+ exit;
+ }
+ } else {
+ show_login_form();
+ exit;
+ }
+ }
+}
+
+// Route handling
+if (isset($_GET['download'])) {
+ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['password'])) {
+ handle_download($_GET['download'], $_POST['password'], $_POST['csrf_token'] ?? '');
+ } else {
+ show_download_form($_GET['download']);
+ }
+} elseif ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
+ handle_upload();
+} else {
+ show_upload_form();
+}
+
+// =============================================================================
+// ROUTE HANDLERS
+// =============================================================================
+
+/**
+ * Show login form
+ */
+function show_login_form($error = '') {
+ $error_html = $error ? "$error
" : '';
+
+ $form = "
+ $error_html
+
+ ";
+
+ echo render_page("Connexion", $form);
+}
+
+/**
+ * Handle file upload
+ */
+function handle_upload() {
+ try {
+ // Validate request method
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
+ throw new Exception('Invalid request method');
+ }
+
+ // Validate CSRF token
+ $csrf_token = $_POST['csrf_token'] ?? '';
+ if (!validate_csrf_token($csrf_token)) {
+ throw new Exception('Invalid CSRF token. Please refresh the page and try again.');
+ }
+
+ // Check if file was uploaded
+ if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
+ throw new Exception('No file uploaded or upload error occurred');
+ }
+
+ $file = $_FILES['file'];
+ $password = $_POST['password'] ?? '';
+
+ // Validate password
+ if (strlen($password) < PASSWORD_MIN_LENGTH) {
+ throw new Exception('Password must be at least ' . PASSWORD_MIN_LENGTH . ' characters long');
+ }
+
+ // Validate file size
+ if ($file['size'] > MAX_FILE_SIZE) {
+ throw new Exception('File size exceeds maximum allowed size of ' . (MAX_FILE_SIZE / 1024 / 1024) . 'MB');
+ }
+
+ // Validate file type
+ if (!is_allowed_file_type($file['name'])) {
+ throw new Exception('File type not allowed. Allowed types: ' . implode(', ', ALLOWED_EXTENSIONS));
+ }
+
+ // Generate UUID and hash password
+ $uuid = generate_uuid();
+ $password_hash = password_hash($password, PASSWORD_DEFAULT);
+
+ // Encrypt and save file
+ $file_path = DOWNLOAD_DIR . '/' . $uuid;
+ if (!encrypt_file($file['tmp_name'], $file_path, $password)) {
+ throw new Exception('Failed to encrypt and save file');
+ }
+
+ // Add to files database
+ $files_data = load_files_data();
+ $files_data[$uuid] = [
+ 'uuid' => $uuid,
+ 'original_filename' => $file['name'],
+ 'download_password_hash' => $password_hash,
+ 'upload_timestamp' => time()
+ ];
+
+ if (!save_files_data($files_data)) {
+ // Clean up uploaded file if database save fails
+ unlink($file_path);
+ throw new Exception('Failed to save file metadata');
+ }
+
+ // Generate links
+ $base_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'];
+ $download_link = $base_url . '?download=' . urlencode($uuid);
+
+ $success_message = "
+ Fichier uploadé avec succès !
+
+
+
+
+
+
+
+
+
+
Important : Partagez ce lien avec le destinataire. Il aura besoin du mot de passe que vous avez défini.
+
Le fichier sera automatiquement supprimé après téléchargement ou après " . AUTO_DELETE_DAYS . " jours.
+
Les téléchargements sont limités à 1 par minute par adresse IP.
+
+
Uploader un autre fichier
+
+
+
+ ";
+
+ display_success($success_message);
+
+ } catch (Exception $e) {
+ display_error($e->getMessage());
+ }
+}
+
+/**
+ * Show download form
+ */
+function show_download_form($uuid) {
+ // Validate UUID format
+ if (!preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/', $uuid)) {
+ display_error('Invalid file ID');
+ return;
+ }
+
+ // Load files data to check if file exists
+ $files_data = load_files_data();
+ if (!isset($files_data[$uuid])) {
+ display_error('File not found or has been deleted');
+ return;
+ }
+
+ $file_info = $files_data[$uuid];
+ $original_filename = htmlspecialchars($file_info['original_filename']);
+ $csrf_token = generate_csrf_token();
+
+ $form = "
+
+
Télécharger le fichier
+
Fichier : $original_filename
+
+
+
+
+
+
Note : Le fichier sera automatiquement supprimé après téléchargement.
+
Les téléchargements sont limités à 1 par minute par adresse IP.
+
+ ";
+
+ echo render_page("Télécharger", $form);
+}
+
+/**
+ * Handle file download
+ */
+function handle_download($uuid, $token, $csrf_token) {
+ try {
+ // Validate CSRF token
+ if (!validate_csrf_token($csrf_token)) {
+ throw new Exception('Invalid CSRF token. Please refresh the page and try again.');
+ }
+
+ // Validate UUID format
+ if (!preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/', $uuid)) {
+ throw new Exception('Invalid file ID');
+ }
+
+ // Check rate limiting
+ $client_ip = get_client_ip();
+ if (is_rate_limited($client_ip)) {
+ throw new Exception('Rate limit exceeded. Please wait before downloading another file.');
+ }
+
+ // Load files data
+ $files_data = load_files_data();
+
+ // Check if file exists in database
+ if (!isset($files_data[$uuid])) {
+ throw new Exception('File not found or has been deleted');
+ }
+
+ $file_info = $files_data[$uuid];
+
+ // Verify password
+ if (!password_verify($token, $file_info['download_password_hash'])) {
+ throw new Exception('Invalid download password');
+ }
+
+ $file_path = DOWNLOAD_DIR . '/' . $uuid;
+
+ // Check if physical file exists
+ if (!file_exists($file_path)) {
+ // Remove orphaned database entry
+ unset($files_data[$uuid]);
+ save_files_data($files_data);
+ throw new Exception('File not found or has been deleted');
+ }
+
+ // Decrypt file
+ $decrypted_content = decrypt_file($file_path, $token);
+
+ if ($decrypted_content === false) {
+ throw new Exception('Failed to decrypt file. Invalid password.');
+ }
+
+ // Log the download
+ log_download($client_ip);
+
+ // Serve the file
+ $original_filename = sanitize_filename_for_header($file_info['original_filename']);
+
+ // Set headers for file download
+ header('Content-Type: application/octet-stream');
+ header('Content-Disposition: attachment; filename="' . $original_filename . '"');
+ header('Content-Length: ' . strlen($decrypted_content));
+ header('Cache-Control: no-cache, must-revalidate');
+ header('Pragma: no-cache');
+
+ // Security headers
+ header('X-Content-Type-Options: nosniff');
+ header('X-Frame-Options: DENY');
+ header('X-XSS-Protection: 1; mode=block');
+
+ // Output file contents
+ echo $decrypted_content;
+
+ // Delete file after successful download
+ unlink($file_path);
+ unset($files_data[$uuid]);
+ save_files_data($files_data);
+
+ exit;
+
+ } catch (Exception $e) {
+ display_error($e->getMessage());
+ }
+}
+
+/**
+ * Show upload form
+ */
+function show_upload_form() {
+ $max_size_mb = MAX_FILE_SIZE / 1024 / 1024;
+ $allowed_types = implode(', ', ALLOWED_EXTENSIONS);
+
+ $csrf_token = generate_csrf_token();
+
+ $form = "
+
+
+
+
Comment ça marche :
+
+ - Uploadez un fichier et définissez un mot de passe
+ - Partagez le lien de téléchargement avec le destinataire
+ - Le destinataire entre le mot de passe pour télécharger
+ - Le fichier est crypté sur le serveur avec votre mot de passe
+ - Suppression automatique après téléchargement ou après " . AUTO_DELETE_DAYS . " jours
+ - Téléchargements limités à 1 par minute par IP
+
+
+
+
+ ";
+
+ echo render_page("Upload", $form);
+}
+
+?>