From b61c918e05124ddb0bb3102a626ca913a0ab4f3a Mon Sep 17 00:00:00 2001 From: polo Date: Thu, 19 Jun 2025 02:53:01 +0200 Subject: =?UTF-8?q?upload=20image=20=C3=A9diteur:=20nom=20d'origine=20+=20?= =?UTF-8?q?uniqid=20+=20extension=20d'origine=20dans=20une=20liste=20autor?= =?UTF-8?q?is=C3=A9e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/js/tinymce.js | 16 ++++++++-------- src/controller/Security.php | 10 +++++++--- src/controller/ajax.php | 42 +++++++++++++++++++++++++++++++----------- 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/public/js/tinymce.js b/public/js/tinymce.js index 55a37cc..f8d78c8 100644 --- a/public/js/tinymce.js +++ b/public/js/tinymce.js @@ -49,9 +49,9 @@ function openEditor(id, page = '') { } }); editor.on('PastePreProcess', function (e){ // déclenchement au collage AVANT insertion dans l'éditeur - let parser = new DOMParser(); - let doc = parser.parseFromString(e.content, 'text/html'); - let images = doc.querySelectorAll('img'); + const parser = new DOMParser(); + const doc = parser.parseFromString(e.content, 'text/html'); + const images = doc.querySelectorAll('img'); let downloads_in_progress = []; @@ -59,11 +59,11 @@ function openEditor(id, page = '') { if(img.src.startsWith('file://')){ // détection d'images non insérables console.warn('Image locale non insérable dans tinymce :', img.src); img.outerHTML = '
' + -"Image locale non insérée. Pour insérer une image depuis LibreOffice, copiez l'image seule et recoller." + +"Image locale non insérée (vient-elle de LibreOffice ?). Effacez cet encadré et copiez-collez l'image seule. Ensuite cliquez sur le bouton Insérer une image puis dans la nouvelle fenêtre sur Enregistrer." + '
'; } else if(img.src.startsWith('http')){ // détection d'images web - let promise = fetch('index.php?action=upload_image_url', { // promesse d'un fichier téléchargeable sur le serveur + const promise = fetch('index.php?action=upload_image_url', { // promesse d'un fichier téléchargeable sur le serveur method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ image_url: img.src }) @@ -75,7 +75,7 @@ function openEditor(id, page = '') { } }) .catch(error => { - console.error('Erreur lors de l’upload de l’image :', error); + console.error('Erreur lors de l’upload de l’image distante:', error); }); downloads_in_progress.push(promise); @@ -96,7 +96,7 @@ function openEditor(id, page = '') { } }); // fin editor.on('PastePreProcess'... }, - // upload d'image + // upload d'image natif de tinymce avec le bouton "Insérer une image" images_upload_handler: (blobInfo, progress) => new Promise((resolve, reject) => { const formData = new FormData(); formData.append("file", blobInfo.blob()); @@ -107,7 +107,7 @@ function openEditor(id, page = '') { }) .then(response => response.json()) .then(data => { - if (data.location) { + if(data.location) { resolve(data.location); } else { diff --git a/src/controller/Security.php b/src/controller/Security.php index 7d592e9..cd31cb8 100644 --- a/src/controller/Security.php +++ b/src/controller/Security.php @@ -59,11 +59,15 @@ class Security // => on remplace tout par des _ // filtrer / et \ semble inutile - $cibles = [' ', '/', '\\', ':', '*', '?', '<', '>', '|', '=', "'", '`', '"', '%22', '#']; + /*$cibles = [' ', '/', '\\', ':', '*', '?', '<', '>', '|', '=', "'", '`', '"', '%22', '#']; $chaine = str_replace($cibles, '_', $chaine); // nécéssite l'extension mbstring $chaine = mb_strtolower($chaine); - return($chaine); - + return($chaine);*/ + + $chaine = preg_replace('/[^a-zA-Z0-9_-]/', '_', $chaine); // ne garder que les lettres, chiffres, tirets et underscores + $chaine = preg_replace('/_+/', '_', $chaine); // doublons d'underscores + return trim($chaine, '_'); + // les problèmes avec \ persistent !! // => javascript // malheureusement document.getElementById('upload').files[0].name = chaine; ne marche pas! interdit! diff --git a/src/controller/ajax.php b/src/controller/ajax.php index 943c027..6813d45 100644 --- a/src/controller/ajax.php +++ b/src/controller/ajax.php @@ -9,15 +9,17 @@ use App\Entity\Article; // mettre ça ailleurs -function imagickCleanImage(string $image_data, string $local_path): bool // "string" parce que file_get_contents... +function imagickCleanImage(string $image_data, string $local_path, string $format = 'jpeg'): bool // "string" parce que file_get_contents... { try{ $imagick = new Imagick(); $imagick->readImageBlob($image_data); $imagick->stripImage(); // nettoyage métadonnées - $imagick->setImageFormat('jpeg'); - $imagick->setImageCompression(Imagick::COMPRESSION_JPEG); - $imagick->setImageCompressionQuality(85); // optionnel + $imagick->setImageFormat($format); + if($format === 'jpeg'){ + $imagick->setImageCompression(Imagick::COMPRESSION_JPEG); + $imagick->setImageCompressionQuality(85); // optionnel + } $imagick->writeImage($local_path); // enregistrement $imagick->clear(); $imagick->destroy(); @@ -75,18 +77,23 @@ if(strpos($_SERVER['CONTENT_TYPE'], 'multipart/form-data') !== false && isset($_ mkdir($dest_mini, 0700, true); } - $filePath = $dest . basename($file['name']); + $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'tif']; + $name = Security::secureFileName(pathinfo($file['name'], PATHINFO_FILENAME)); + $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); + if(!in_array($extension, $allowed_extensions) || $extension === 'jpg'){ + $extension = 'jpeg'; + } + $file_path = $dest . $name . '_' . uniqid() . '.' . $extension; // créer une miniature de l'image // - if(move_uploaded_file($file['tmp_name'], $filePath)) { - $image_url = str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']); - echo json_encode(['location' => $image_url . $filePath]); // renvoyer l'URL de l'image téléchargée + if(imagickCleanImage(file_get_contents($file['tmp_name']), $file_path, $extension)){ // recréer l’image pour la nettoyer + echo json_encode(['location' => $file_path]); // renvoyer l'URL de l'image téléchargée } else{ http_response_code(500); - echo json_encode(['message' => 'Erreur 500: Internal Server Error']); + echo json_encode(['message' => 'Erreur image non valide']); } } else{ @@ -101,15 +108,28 @@ elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image_url'){ if(isset($json['image_url'])){ $image_data = curlDownloadImage($json['image_url']); // téléchargement de l’image par le serveur avec cURL au lieu de file_get_contents + $dest = 'images/'; + if(!is_dir($dest)) { // Vérifier si le répertoire existe, sinon le créer + mkdir($dest, 0777, true); + } + if($image_data === false){ http_response_code(400); echo json_encode(['message' => "Erreur, le serveur n'a pas réussi à télécharger l'image."]); die; } - $local_path = 'images/' . uniqid() . '.jpg'; - if(imagickCleanImage($image_data, $local_path)){ // recréer l’image pour la nettoyer + $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'tif']; + $url_path = parse_url($json['image_url'], PHP_URL_PATH); + $name = Security::secureFileName(pathinfo($url_path, PATHINFO_FILENAME)); + $extension = strtolower(pathinfo($url_path, PATHINFO_EXTENSION)); + if(!in_array($extension, $allowed_extensions) || $extension === 'jpg'){ + $extension = 'jpeg'; + } + $local_path = $dest . $name . '_' . uniqid() . '.' . $extension; + + if(imagickCleanImage($image_data, $local_path, $extension)){ // recréer l’image pour la nettoyer echo json_encode(['location' => $local_path]); // nouvelle adresse } else{ -- cgit v1.2.3