summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpolo <ordipolo@gmx.fr>2025-12-30 20:16:44 +0100
committerpolo <ordipolo@gmx.fr>2025-12-30 20:16:44 +0100
commit4fa8395994b478a867afbc9efc0143cb64c7c70b (patch)
tree8ba79073d20e7ad15d654fda02884af6d7e57eca
parent30c133b81d55250bb5fe363f1196a1bc32bd5fe0 (diff)
downloadtinymce-4fa8395994b478a867afbc9efc0143cb64c7c70b.tar.gz
tinymce-4fa8395994b478a867afbc9efc0143cb64c7c70b.tar.bz2
tinymce-4fa8395994b478a867afbc9efc0143cb64c7c70b.zip
upload de documents + sécurités attaque par téléchargement de fichier
-rw-r--r--public/index.php131
1 files changed, 121 insertions, 10 deletions
diff --git a/public/index.php b/public/index.php
index d32dfff..d8d26e2 100644
--- a/public/index.php
+++ b/public/index.php
@@ -57,6 +57,36 @@ function sanitizeFileName(string $filename): string {
57 return trim($filename, '_'); 57 return trim($filename, '_');
58} 58}
59 59
60function checkFileDownload(array $file): bool
61{
62 $extensions_white_list = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'odt', 'ods', 'odp'];
63 $mime_type_white_list = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.presentation'];
64
65 // 1/ extension
66 $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
67 if(!in_array($extension, $extensions_white_list, true)){
68 return false;
69 }
70
71 // 2/ fichier obtenu par HTTP POST (théoriquement inutile si le routeur est solide, mais ça ne mange pas de pain)
72 if(!is_uploaded_file($file['tmp_name'])){
73 return false;
74 }
75
76 $finfo = finfo_open(FILEINFO_MIME_TYPE);
77
78 // 3/ objet $finfo valide (dépend du paramètre FILEINFO_MIME_TYPE)
79 if($finfo === false){
80 return false;
81 }
82
83 $real_type = finfo_file($finfo, $file['tmp_name']);
84 finfo_close($finfo);
85
86 // 4/ contrôle du "vrai" type mime (finfo_file lit les 1ers octets des fichiers pour y trouver des "signatures", très fiable sauf avec les conteneurs: doc, zip...)
87 return in_array($real_type, $mime_type_white_list, true);
88}
89
60if(isset($_GET['action']) && $_GET['action'] == 'editor_submit'){ 90if(isset($_GET['action']) && $_GET['action'] == 'editor_submit'){
61 // récupération des données 91 // récupération des données
62 $data = file_get_contents('php://input'); 92 $data = file_get_contents('php://input');
@@ -95,12 +125,12 @@ elseif(isset($_GET['action']) && $_GET['action'] == 'delete_article'){
95 die; 125 die;
96} 126}
97elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image'){ 127elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image'){
98 if (isset($_FILES['file'])) { 128 if(isset($_FILES['file'])){
99 $file = $_FILES['file']; 129 $file = $_FILES['file'];
100 $dest = 'images/'; 130 $dest = 'images/';
101 131
102 if(!is_dir($dest)) { // Vérifier si le répertoire existe, sinon le créer 132 if(!is_dir($dest)){
103 mkdir($dest, 0777, true); 133 mkdir($dest, 0755, true);
104 } 134 }
105 135
106 $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'tif']; 136 $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'tif'];
@@ -111,8 +141,13 @@ elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image'){
111 } 141 }
112 $file_path = $dest . $name . '_' . uniqid() . '.' . $extension; 142 $file_path = $dest . $name . '_' . uniqid() . '.' . $extension;
113 143
144 if(!is_uploaded_file($file['tmp_name'])) {
145 http_response_code(500);
146 echo json_encode(['message' => "Le fichier n'a pas été téléchargé correctement."]);
147 }
148
114 if(imagickCleanImage(file_get_contents($file['tmp_name']), $file_path, $extension)){ // recréer l’image pour la nettoyer 149 if(imagickCleanImage(file_get_contents($file['tmp_name']), $file_path, $extension)){ // recréer l’image pour la nettoyer
115 echo json_encode(['location' => $file_path]); // renvoyer l'URL de l'image téléchargée 150 echo json_encode(['location' => $file_path]);
116 } 151 }
117 else{ 152 else{
118 http_response_code(500); 153 http_response_code(500);
@@ -132,8 +167,8 @@ elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image_url'){
132 $image_data = curlDownloadImage($json['image_url']); // téléchargement de l’image par le serveur avec cURL au lieu de file_get_contents 167 $image_data = curlDownloadImage($json['image_url']); // téléchargement de l’image par le serveur avec cURL au lieu de file_get_contents
133 $dest = 'images/'; 168 $dest = 'images/';
134 169
135 if(!is_dir($dest)) { // Vérifier si le répertoire existe, sinon le créer 170 if(!is_dir($dest)){
136 mkdir($dest, 0777, true); 171 mkdir($dest, 0755, true);
137 } 172 }
138 173
139 if($image_data === false){ 174 if($image_data === false){
@@ -152,7 +187,7 @@ elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image_url'){
152 $local_path = $dest . $name . '_' . uniqid() . '.' . $extension; 187 $local_path = $dest . $name . '_' . uniqid() . '.' . $extension;
153 188
154 if(imagickCleanImage($image_data, $local_path, $extension)){ // recréer l’image pour la nettoyer 189 if(imagickCleanImage($image_data, $local_path, $extension)){ // recréer l’image pour la nettoyer
155 echo json_encode(['location' => $local_path]); // nouvelle adresse 190 echo json_encode(['location' => $local_path]);
156 } 191 }
157 else{ 192 else{
158 http_response_code(500); 193 http_response_code(500);
@@ -168,8 +203,8 @@ elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image_base64'){
168 $json = json_decode(file_get_contents('php://input'), true); 203 $json = json_decode(file_get_contents('php://input'), true);
169 $dest = 'images/'; 204 $dest = 'images/';
170 205
171 if(!is_dir('images')){ 206 if(!is_dir($dest)){
172 mkdir('images', 0777, true); 207 mkdir($dest, 0755, true);
173 } 208 }
174 209
175 // détection de data:image/ et de ;base64, et capture du format dans $type 210 // détection de data:image/ et de ;base64, et capture du format dans $type
@@ -203,6 +238,39 @@ elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image_base64'){
203 } 238 }
204 die; 239 die;
205} 240}
241elseif(isset($_GET['action']) && $_GET['action'] == 'upload_file'){
242 if(isset($_FILES['file'])){
243 $file = $_FILES['file'];
244 $dest = 'media/';
245
246 if(!is_dir($dest)){ // Vérifier si le répertoire existe, sinon le créer
247 mkdir($dest, 0755, true);
248 }
249
250 $name = sanitizeFileName(pathinfo($file['name'], PATHINFO_FILENAME)); // retirer caractères spéciaux et changer espaces en underscores
251 $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
252 $file_path = $dest . $name . '_' . uniqid() . '.' . $extension; // nom unique
253
254 if(checkFileDownload($file)){
255 if(move_uploaded_file($file['tmp_name'], $file_path)){
256 echo json_encode(['location' => $file_path]);
257 }
258 else{
259 http_response_code(500);
260 echo json_encode(['message' => 'Erreur enregistrement du fichier.']);
261 }
262 }
263 else{
264 http_response_code(400);
265 echo json_encode(['message' => 'Erreur 400: fichier non valide.']);
266 }
267 }
268 else{
269 http_response_code(400);
270 echo json_encode(['message' => 'Erreur 400: Bad Request']);
271 }
272 die;
273}
206?> 274?>
207<!DOCTYPE html> 275<!DOCTYPE html>
208<html lang="fr"> 276<html lang="fr">
@@ -356,6 +424,20 @@ elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image_base64'){
356 }, 424 },
357 // upload d'image natif de tinymce avec le bouton "Insérer une image" 425 // upload d'image natif de tinymce avec le bouton "Insérer une image"
358 images_upload_handler: images_upload_handler, // = fonction fléchée 426 images_upload_handler: images_upload_handler, // = fonction fléchée
427 // upload de documents (bouton "insérer un lien")
428 files_upload_handler: files_upload_handler, // = fonction fléchée
429 documents_file_types: [ // files_upload_handler a besoin qu'on lui donne tous les types mime
430 { mimeType: 'application/pdf', extensions: [ 'pdf' ] },
431 { mimeType: 'application/msword', extensions: [ 'doc' ] },
432 { mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', extensions: [ 'docx' ] },
433 { mimeType: 'application/vnd.ms-excel', extensions: [ 'xls' ] },
434 { mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', extensions: [ 'xlsx' ] },
435 { mimeType: 'application/vnd.ms-powerpoint', extensions: [ 'ppt' ] },
436 { mimeType: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', extensions: [ 'pptx' ] },
437 { mimeType: 'application/vnd.oasis.opendocument.text', extensions: [ 'odt' ] },
438 { mimeType: 'application/vnd.oasis.opendocument.spreadsheet', extensions: [ 'ods' ] },
439 { mimeType: 'application/vnd.oasis.opendocument.presentation', extensions: [ 'odp' ] }
440 ],
359 image_caption: true 441 image_caption: true
360 }); 442 });
361 443
@@ -374,7 +456,7 @@ elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image_base64'){
374 .then(response => response.json()) 456 .then(response => response.json())
375 .then(data => { 457 .then(data => {
376 if(data.location) { 458 if(data.location) {
377 resolve(data.location); 459 resolve({url: data.location});
378 } 460 }
379 else { 461 else {
380 reject("Erreur: Chemin d'image invalide"); 462 reject("Erreur: Chemin d'image invalide");
@@ -385,6 +467,35 @@ elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image_base64'){
385 }); 467 });
386 }); 468 });
387 469
470 const files_upload_handler = (blobInfo, progress) => new Promise((resolve, reject) => {
471 const formData = new FormData();
472 formData.append("file", blobInfo.blob());
473
474 fetch('index.php?action=upload_file', {
475 method: 'POST',
476 body: formData
477 })
478 .then(response => response.json())
479 .then(data => {
480 if(data.location) {
481 console.log(blobInfo.filename());
482 console.log(data.location);
483
484 // resolve et reject fonctionne avec Promise => type de retour standardisé et évite l'utilistion de callbacks
485 resolve({
486 url: data.location,
487 fileName: blobInfo.filename(),
488 });
489 }
490 else {
491 reject("Erreur: Chemin du fichier invalide");
492 }
493 })
494 .catch(error => {
495 reject("Erreur lors de l'upload");
496 });
497 });
498
388 function deleteArticle(articleId) { 499 function deleteArticle(articleId) {
389 if (confirm('Voulez-vous vraiment supprimer cet article ?')) { 500 if (confirm('Voulez-vous vraiment supprimer cet article ?')) {
390 // Envoyer une requête au serveur pour supprimer l'article 501 // Envoyer une requête au serveur pour supprimer l'article