diff --git a/api.php b/api.php
index fd92937..ed54a64 100644
--- a/api.php
+++ b/api.php
@@ -2,8 +2,12 @@
define('CYLA_CORE', true);
require_once 'core.php';
+// Augmenter la limite de temps d'exécution pour les uploads multiples
+set_time_limit(300); // 5 minutes
+
// Vérifier si l'utilisateur est connecté
if (!Cyla::isLoggedIn()) {
+ header('Content-Type: application/json');
http_response_code(401);
echo json_encode(['error' => 'Non autorisé']);
exit;
@@ -11,6 +15,7 @@ if (!Cyla::isLoggedIn()) {
// Vérifier le token CSRF
if (!isset($_POST['csrf_token']) || !Cyla::verifyCSRFToken($_POST['csrf_token'])) {
+ header('Content-Type: application/json');
http_response_code(403);
echo json_encode(['error' => 'Token CSRF invalide']);
exit;
@@ -18,18 +23,35 @@ if (!isset($_POST['csrf_token']) || !Cyla::verifyCSRFToken($_POST['csrf_token'])
// Gérer l'upload de fichier
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
- $validation = Cyla::validateUpload($_FILES['file']);
+ header('Content-Type: application/json');
- if (!$validation['valid']) {
- http_response_code(400);
- echo json_encode(['error' => $validation['error']]);
- exit;
- }
+ try {
+ // Vérification de l'espace disque disponible
+ $uploadDir = UPLOAD_DIR;
+ $freeSpace = disk_free_space($uploadDir);
+ if ($freeSpace < $_FILES['file']['size']) {
+ throw new Exception('Espace disque insuffisant');
+ }
+
+ $validation = Cyla::validateUpload($_FILES['file']);
+ if (!$validation['valid']) {
+ throw new Exception($validation['error']);
+ }
+
+ $filename = Cyla::generateUniqueFilename($_FILES['file']['name']);
+ $destination = $uploadDir . $filename;
+
+ // Upload avec gestion de la mémoire
+ if (!move_uploaded_file($_FILES['file']['tmp_name'], $destination)) {
+ throw new Exception('Erreur lors du déplacement du fichier');
+ }
+
+ // Vérifier l'intégrité du fichier
+ if (!file_exists($destination) || filesize($destination) !== $_FILES['file']['size']) {
+ unlink($destination); // Nettoyer en cas d'erreur
+ throw new Exception('Erreur d\'intégrité du fichier');
+ }
- $filename = Cyla::generateUniqueFilename($_FILES['file']['name']);
- $destination = UPLOAD_DIR . $filename;
-
- if (move_uploaded_file($_FILES['file']['tmp_name'], $destination)) {
echo json_encode([
'success' => true,
'file' => [
@@ -38,9 +60,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
'url' => 'share.php?file=' . urlencode($filename)
]
]);
- } else {
- http_response_code(500);
- echo json_encode(['error' => 'Erreur lors de l\'upload du fichier']);
+ } catch (Exception $e) {
+ http_response_code(400);
+ echo json_encode([
+ 'error' => $e->getMessage(),
+ 'details' => error_get_last()
+ ]);
}
exit;
}
diff --git a/js/UploadZone.jsx b/js/UploadZone.jsx
index 4da6030..6e800d5 100644
--- a/js/UploadZone.jsx
+++ b/js/UploadZone.jsx
@@ -1,25 +1,16 @@
-import React, { useState, useRef } from 'react';
-import { X, Upload, CheckCircle, AlertCircle } from 'lucide-react';
+import React, { useState, useRef, useCallback } from 'react';
+import { X, Upload, CheckCircle, AlertCircle, Loader } from 'lucide-react';
const UploadZone = () => {
const [isDragging, setIsDragging] = useState(false);
const [files, setFiles] = useState([]);
const fileInputRef = useRef(null);
- const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 Mo
- const ALLOWED_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'webm', 'mp4', 'wmv', 'mp3', 'flac', 'ogg', 'zip', 'css', 'pdf', 'rar', 'm3u', 'm3u8', 'txt'];
+ const MAX_FILES = 10; // Limite stricte à 10 fichiers
+ const MAX_FILE_SIZE = 100 * 1024 * 1024;
+ const ALLOWED_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'webm', 'mp4', 'wmv', 'mp3', 'flac', 'ogg', 'zip', 'css', 'pdf', 'rar', 'm3u', 'm3u8', 'txt'];
- const handleDragOver = (e) => {
- e.preventDefault();
- setIsDragging(true);
- };
-
- const handleDragLeave = (e) => {
- e.preventDefault();
- setIsDragging(false);
- };
-
- const validateFile = (file) => {
+ const validateFile = useCallback((file) => {
const extension = file.name.split('.').pop().toLowerCase();
if (!ALLOWED_EXTENSIONS.includes(extension)) {
return { valid: false, error: `Extension .${extension} non autorisée` };
@@ -28,95 +19,86 @@ const UploadZone = () => {
return { valid: false, error: 'Fichier trop volumineux (max 100 Mo)' };
}
return { valid: true, error: null };
- };
-
- const handleDrop = (e) => {
- e.preventDefault();
- setIsDragging(false);
-
- const droppedFiles = Array.from(e.dataTransfer.files);
- addFiles(droppedFiles);
- };
-
- const handleFileSelect = (e) => {
- const selectedFiles = Array.from(e.target.files);
- addFiles(selectedFiles);
- };
-
- const addFiles = (newFiles) => {
- const processedFiles = newFiles.map(file => {
- const validation = validateFile(file);
- return {
- file,
- id: Math.random().toString(36).substring(7),
- status: validation.valid ? 'pending' : 'error',
- error: validation.error,
- progress: 0
- };
- });
-
- setFiles(currentFiles => [...currentFiles, ...processedFiles]);
- };
-
- const removeFile = (fileId) => {
- setFiles(files => files.filter(f => f.id !== fileId));
- };
+ }, []);
const uploadFile = async (fileInfo) => {
- const formData = new FormData();
- formData.append('file', fileInfo.file);
- formData.append('action', 'upload');
-
try {
- // Simuler un upload progressif pour la démo
- await new Promise(resolve => {
- let progress = 0;
- const interval = setInterval(() => {
- progress += 10;
- setFiles(files =>
- files.map(f =>
- f.id === fileInfo.id
- ? { ...f, progress, status: progress === 100 ? 'complete' : 'uploading' }
- : f
- )
- );
- if (progress >= 100) {
- clearInterval(interval);
- resolve();
- }
- }, 500);
+ const formData = new FormData();
+ formData.append('file', fileInfo.file);
+ formData.append('csrf_token', document.querySelector('input[name="csrf_token"]')?.value || '');
+
+ // Mettre le statut en uploading
+ setFiles(files =>
+ files.map(f =>
+ f.id === fileInfo.id ? { ...f, status: 'uploading' } : f
+ )
+ );
+
+ const response = await fetch('api.php', {
+ method: 'POST',
+ body: formData
});
+
+ const result = await response.json();
+
+ if (!response.ok || !result.success) {
+ throw new Error(result.error || 'Erreur lors du téléversement');
+ }
+
+ setFiles(files =>
+ files.map(f =>
+ f.id === fileInfo.id ? { ...f, status: 'complete' } : f
+ )
+ );
} catch (error) {
setFiles(files =>
files.map(f =>
f.id === fileInfo.id
- ? { ...f, status: 'error', error: 'Erreur lors du téléversement' }
+ ? { ...f, status: 'error', error: error.message }
: f
)
);
}
};
- const uploadAllFiles = () => {
- const pendingFiles = files.filter(f => f.status === 'pending');
- pendingFiles.forEach(uploadFile);
+ const handleFiles = (newFiles) => {
+ // Vérifier si on n'a pas déjà atteint la limite
+ if (files.length >= MAX_FILES) {
+ alert(`Vous pouvez téléverser uniquement ${MAX_FILES} fichiers à la fois`);
+ return;
+ }
+
+ // Calculer combien de fichiers on peut encore ajouter
+ const remainingSlots = MAX_FILES - files.length;
+ const filesToAdd = Array.from(newFiles).slice(0, remainingSlots);
+
+ if (filesToAdd.length < newFiles.length) {
+ alert(`Seuls les ${remainingSlots} premiers fichiers seront traités. Maximum ${MAX_FILES} fichiers à la fois.`);
+ }
+
+ const processedFiles = filesToAdd.map(file => ({
+ file,
+ id: Math.random().toString(36).substring(7),
+ status: validateFile(file).valid ? 'pending' : 'error',
+ error: validateFile(file).error
+ }));
+
+ setFiles(current => [...current, ...processedFiles]);
+
+ // Lancer l'upload pour chaque fichier valide
+ processedFiles
+ .filter(f => f.status === 'pending')
+ .forEach(uploadFile);
};
- const getStatusColor = (status) => {
- switch (status) {
- case 'complete': return 'text-green-500';
- case 'error': return 'text-red-500';
- case 'uploading': return 'text-blue-500';
- default: return 'text-gray-500';
- }
- };
+ const handleDrop = useCallback((e) => {
+ e.preventDefault();
+ setIsDragging(false);
+ handleFiles(e.dataTransfer.files);
+ }, []);
- const getStatusIcon = (status) => {
- switch (status) {
- case 'complete': return
- Max 100 Mo par fichier · {ALLOWED_EXTENSIONS.join(', ')}
+ Maximum {MAX_FILES} fichiers à la fois · 100 Mo par fichier
+
+ Extensions : {ALLOWED_EXTENSIONS.join(', ')}