From 07baed87131b0bb48dddbd0d053df763f5647243 Mon Sep 17 00:00:00 2001 From: Fred Tempez Date: Tue, 9 Apr 2024 17:23:26 +0200 Subject: [PATCH] 13200 secure_file_put_contents and new jsondb json db save --- core/class/helper.class.php | 2 +- core/class/jsondb/JsonDb.class.php | 71 +++++++++------------ core/class/router.class.php | 10 +-- core/core.php | 99 +++++++++++------------------- core/include/update.inc.php | 6 +- core/module/config/config.php | 14 ++--- core/module/install/install.php | 4 +- core/module/language/language.php | 8 +-- core/module/page/page.php | 2 +- core/module/plugin/plugin.php | 4 +- core/module/theme/theme.php | 8 +-- 11 files changed, 95 insertions(+), 133 deletions(-) diff --git a/core/class/helper.class.php b/core/class/helper.class.php index 50bc756e..548c0430 100644 --- a/core/class/helper.class.php +++ b/core/class/helper.class.php @@ -77,7 +77,7 @@ class helper // Créer la variable $data = array_merge($data, [$text => '']); } - file_put_contents('site/i18n/' . $to . '.json', json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT), LOCK_EX); + file_put_contents('site/i18n/' . $to . '.json', json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); } } diff --git a/core/class/jsondb/JsonDb.class.php b/core/class/jsondb/JsonDb.class.php index ddda845b..760e4445 100644 --- a/core/class/jsondb/JsonDb.class.php +++ b/core/class/jsondb/JsonDb.class.php @@ -148,53 +148,40 @@ class JsonDb extends \Prowebcraft\Dot */ public function save() { - $jsonOptions = JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_FORCE_OBJECT; - $jsonData = json_encode($this->data, $jsonOptions); + // Encode les données au format JSON avec les options spécifiées + $encoded_data = json_encode($this->data, JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT | JSON_PRETTY_PRINT); - $attempts = 0; - while ($attempts < self::MAX_JSON_ENCODE_ATTEMPTS) { - if ($jsonData !== false) { - break; // Sortir de la boucle si l'encodage réussit + // Vérifie la longueur de la chaîne JSON encodée + $encoded_length = strlen($encoded_data); + + // Initialise le compteur de tentatives + $attempt = 0; + + // Tente d'encoder les données en JSON et de les sauvegarder jusqu'à 5 fois en cas d'échec + while ($attempt < 5) { + // Essaye d'écrire les données encodées dans le fichier de base de données + $write_result = file_put_contents($this->db, $encoded_data, LOCK_EX); // Les utilisateurs multiples obtiennent un verrou + + // Vérifie si l'écriture a réussi + if ($write_result === $encoded_length) { + // Sort de la boucle si l'écriture a réussi + break; } - $attempts++; - error_log('Erreur d\'encodage JSON (tentative ' . $attempts . ') : ' . json_last_error_msg()); - $jsonData = json_encode($this->data, $jsonOptions); // Réessayer l'encodage - sleep(self::RETRY_DELAY_SECONDS); // Attendre avant de réessayer + + // Incrémente le compteur de tentatives + $attempt++; + + // Attente + sleep(1); } - if ($jsonData === false) { - error_log('Impossible d\'encoder les données en format JSON.'); - return false; - } - $lockHandle = fopen($this->db, 'r+'); + // Vérifie si l'écriture a échoué même après plusieurs tentatives + if ($write_result !== $encoded_length) { + // Enregistre un message d'erreur dans le journal des erreurs + error_log('Erreur d\'écriture, les données n\'ont pas été sauvegardées.'); - if (flock($lockHandle, LOCK_EX)) { - $attempts = 0; - $bytesWritten = false; - while ($attempts < self::MAX_FILE_WRITE_ATTEMPTS && $bytesWritten === false) { - ftruncate($lockHandle, 0); // Vide le fichier - rewind($lockHandle); // Remet le pointeur au début du fichier - $bytesWritten = fwrite($lockHandle, $jsonData); - if ($bytesWritten === false) { - $attempts++; - error_log('Erreur d\'écriture (tentative ' . $attempts . ') : impossible de sauvegarder les données.'); - sleep(self::RETRY_DELAY_SECONDS); // Attendre avant de réessayer - } - } - flock($lockHandle, LOCK_UN); // Libérer le verrouillage - fclose($lockHandle); // Fermer le fichier - - if ($bytesWritten === false || $bytesWritten != strlen($jsonData)) { - error_log('Erreur d\'écriture, les données n\'ont pas été sauvegardées.'); - return false; - } - } else { - error_log('Impossible d\'obtenir un verrouillage sur le fichier de base de données.'); - fclose($lockHandle); // Fermer le fichier - return false; + // Affiche un message d'erreur et termine le script + exit('Erreur d\'écriture, les données n\'ont pas été sauvegardées.'); } - return true; - - } } \ No newline at end of file diff --git a/core/class/router.class.php b/core/class/router.class.php index 8256581e..0fa2c8ec 100644 --- a/core/class/router.class.php +++ b/core/class/router.class.php @@ -61,17 +61,17 @@ class core extends common // Crée le fichier de personnalisation avancée if (file_exists(self::DATA_DIR . 'custom.css') === false) { - $this->secureFilePutContents(self::DATA_DIR . 'custom.css', file_get_contents('core/module/theme/resource/custom.css')); + $this->secure_file_put_contents(self::DATA_DIR . 'custom.css', file_get_contents('core/module/theme/resource/custom.css')); chmod(self::DATA_DIR . 'custom.css', 0755); } // Crée le fichier de personnalisation if (file_exists(self::DATA_DIR . 'theme.css') === false) { - $this->secureFilePutContents(self::DATA_DIR . 'theme.css', ''); + $this->secure_file_put_contents(self::DATA_DIR . 'theme.css', ''); chmod(self::DATA_DIR . 'theme.css', 0755); } // Crée le fichier de personnalisation de l'administration if (file_exists(self::DATA_DIR . 'admin.css') === false) { - $this->secureFilePutContents(self::DATA_DIR . 'admin.css', ''); + $this->secure_file_put_contents(self::DATA_DIR . 'admin.css', ''); chmod(self::DATA_DIR . 'admin.css', 0755); } @@ -273,7 +273,7 @@ class core extends common $css .= '#footerCopyright{text-align:' . $this->getData(['theme', 'footer', 'copyrightAlign']) . '}'; // Enregistre la personnalisation - $this->secureFilePutContents(self::DATA_DIR . 'theme.css', $css); + $this->secure_file_put_contents(self::DATA_DIR . 'theme.css', $css); // Effacer le cache pour tenir compte de la couleur de fond TinyMCE header("Expires: Tue, 01 Jan 2000 00:00:00 GMT"); @@ -367,7 +367,7 @@ class core extends common // Bordure du contour TinyMCE $css .= '.mce-tinymce{border: 1px solid ' . $this->getData(['admin', 'borderBlockColor']) . '!important;}'; // Enregistre la personnalisation - $this->secureFilePutContents(self::DATA_DIR . 'admin.css', $css); + $this->secure_file_put_contents(self::DATA_DIR . 'admin.css', $css); } } /** diff --git a/core/core.php b/core/core.php index d53f3dd2..2a7cc276 100644 --- a/core/core.php +++ b/core/core.php @@ -602,73 +602,48 @@ class common public function setPage($page, $value, $lang) { - return $this->secureFilePutContents(self::DATA_DIR . $lang . '/content/' . $page . '.html', $value); + return $this->secure_file_put_contents(self::DATA_DIR . $lang . '/content/' . $page . '.html', $value); } - - /** - * Écriture sécurisée dans un fichier en utilisant un verrouillage de fichier pour éviter les accès concurrents. - * Les données sont encodées au format JSON si l'extension du fichier est JSON. - * - * @param string $filename Le chemin du fichier dans lequel écrire les données. - * @param mixed $data Les données à écrire dans le fichier. - * @param int $options Les options pour la fonction file_put_contents, par défaut 0. - * - * @return bool Retourne true si l'écriture dans le fichier est réussie, false sinon. - */ - public static function secureFilePutContents($filename, $data, $options = 0) + /** + * Écrit les données dans un fichier avec plusieurs tentatives d'écriture et verrouillage + * + * @param string $filename Le nom du fichier + * @param string $data Les données à écrire dans le fichier + * @param int $flags Les drapeaux optionnels à passer à la fonction $this->secure_file_put_contents + * @return bool True si l'écriture a réussi, sinon false + */ + function secure_file_put_contents($filename, $data, $flags = 0) { - // Vérifier si l'extension du fichier est JSON - $extension = pathinfo($filename, PATHINFO_EXTENSION); - $encodeJson = strtolower($extension) === 'json'; - - // Tentatives d'encodage JSON si nécessaire - if ($encodeJson) { - $jsonData = null; - $attempts = 0; - while ($attempts < self::MAX_JSON_ENCODE_ATTEMPTS) { - $jsonData = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); - if ($jsonData !== false) { - break; // Sortir de la boucle si l'encodage réussit - } - $attempts++; - error_log('Erreur d\'encodage JSON (tentative ' . $attempts . ') : ' . json_last_error_msg()); - sleep(self::RETRY_DELAY_SECONDS); // Attendre avant de réessayer - } - - if ($jsonData === false) { - error_log('Impossible d\'encoder les données en format JSON.'); - return false; - } - } else { - // Pas d'encodage JSON nécessaire - $jsonData = $data; + // Vérifie si le fichier existe + if (!file_exists($filename)) { + // Crée le fichier s'il n'existe pas + $handle = fopen($filename, 'w'); + fclose($handle); } - - // Écriture sécurisée dans le fichier avec un verrouillage + + // Initialise le compteur de tentatives $attempts = 0; - while ($attempts < self::MAX_FILE_WRITE_ATTEMPTS) { - $lockHandle = fopen($filename, 'c+'); - if ($lockHandle !== false && flock($lockHandle, LOCK_EX)) { - $bytesWritten = fwrite($lockHandle, $jsonData); - if ($bytesWritten !== false && $bytesWritten === strlen($jsonData)) { - fflush($lockHandle); // Vider le tampon - ftruncate($lockHandle, ftell($lockHandle)); // Tronquer le fichier à la position actuelle du pointeur - flock($lockHandle, LOCK_UN); // Libérer le verrouillage - fclose($lockHandle); // Fermer le fichier - return file_put_contents($filename, $jsonData, $options) !== false; // Écriture réussie - } - flock($lockHandle, LOCK_UN); // Libérer le verrouillage en cas d'échec d'écriture - } - if ($lockHandle !== false) { - fclose($lockHandle); // Fermer le fichier en cas d'échec d'acquisition du verrouillage + + // Vérifie la longueur des données + $data_length = strlen($data); + + // Effectue jusqu'à 5 tentatives d'écriture + while ($attempts < 5) { + // Essaye d'écrire les données dans le fichier avec verrouillage exclusif + $write_result = file_put_contents($filename, $data, LOCK_EX | $flags); + + // Vérifie si l'écriture a réussi + if ($write_result !== false && $write_result === $data_length) { + // Sort de la boucle si l'écriture a réussi + return true; } + + // Incrémente le compteur de tentatives $attempts++; - error_log('Erreur d\'écriture (tentative ' . $attempts . ') : impossible de sauvegarder les données dans ' . $filename); - sleep(self::RETRY_DELAY_SECONDS); // Attendre avant de réessayer } - - error_log('Impossible d\'écrire dans le fichier ' . $filename . ' après ' . self::MAX_FILE_WRITE_ATTEMPTS . ' tentatives.'); + + // Échec de l'écriture après plusieurs tentatives return false; } @@ -906,7 +881,7 @@ class common // Enregistrement : 3 tentatives for ($i = 0; $i < 3; $i++) { - if ($this->secureFilePutContents('core/vendor/tinymce/link_list.json',$parents) !== false) { + if ($this->secure_file_put_contents('core/vendor/tinymce/link_list.json',$parents) !== false) { break; } // Pause de 10 millisecondes @@ -1200,7 +1175,7 @@ class common } $sitemap->updateRobots(); } else { - $this->secureFilePutContents('robots.txt', 'User-agent: *' . PHP_EOL . 'Disallow: /'); + $this->secure_file_put_contents('robots.txt', 'User-agent: *' . PHP_EOL . 'Disallow: /'); } // Submit your sitemaps to Google, Yahoo, Bing and Ask.com @@ -1477,7 +1452,7 @@ class common $dataLog .= $message ? $this->getUrl() . ';' . $message : $this->getUrl(); $dataLog .= PHP_EOL; if ($this->getData(['config', 'connect', 'log'])) { - $this->secureFilePutContents(self::DATA_DIR . 'journal.log', $dataLog, FILE_APPEND); + $this->secure_file_put_contents(self::DATA_DIR . 'journal.log', $dataLog, FILE_APPEND); } } diff --git a/core/include/update.inc.php b/core/include/update.inc.php index c10255e2..004d81aa 100644 --- a/core/include/update.inc.php +++ b/core/include/update.inc.php @@ -135,7 +135,7 @@ if ($this->getData(['core', 'dataVersion']) < 10200) { } // Créer les en-têtes du journal $d = 'Date;Heure;IP;Id;Action' . PHP_EOL; - $this->secureFilePutContents(self::DATA_DIR . 'journal.log', $d); + $this->secure_file_put_contents(self::DATA_DIR . 'journal.log', $d); // Init préservation htaccess $this->setData(['config', 'autoUpdateHtaccess', false]); // Options de barre de membre simple @@ -459,7 +459,7 @@ if ($this->getData(['core', 'dataVersion']) < 11000) { } foreach ($hierarchy as $parentKey => $parent) { $content = $this->getData(['page', $parent, 'content']); - //$this->secureFilePutContents(self::DATA_DIR . self::$siteContent . '/content/' . $parent . '.html', $content); + //$this->secure_file_put_contents(self::DATA_DIR . self::$siteContent . '/content/' . $parent . '.html', $content); $this->setPage($parent, $content, 'fr'); $this->setData(['page', $parent, 'content', $parent . '.html']); } @@ -982,7 +982,7 @@ if ($this->getData(['core', 'dataVersion']) < 12309) { $d = json_decode(file_get_contents(self::DATA_DIR . $key . '/locale.json'), true); $d = array_merge($d['locale'], ['poweredPageLabel' => 'Motorisé par']); $t['locale'] = $d; - $this->secureFilePutContents(self::DATA_DIR . $key . '/locale.json', $t); + $this->secure_file_put_contents(self::DATA_DIR . $key . '/locale.json', $t); } } diff --git a/core/module/config/config.php b/core/module/config/config.php index e1371b04..a55a2b0b 100644 --- a/core/module/config/config.php +++ b/core/module/config/config.php @@ -534,7 +534,7 @@ class config extends common '' . PHP_EOL . '# URL rewriting' . PHP_EOL; $fileContent = str_replace('# URL rewriting', $rewriteData, $fileContent); - $this->secureFilePutContents( + $this->secure_file_put_contents( '.htaccess', $fileContent ); @@ -550,7 +550,7 @@ class config extends common $fileContent = file_get_contents('.htaccess'); $fileContent = explode('# URL rewriting', $fileContent); $fileContent = $fileContent[0] . '# URL rewriting' . $fileContent[2]; - $this->secureFilePutContents( + $this->secure_file_put_contents( '.htaccess', $fileContent ); @@ -654,10 +654,10 @@ class config extends common ) { // Ecrire les fichiers de script if ($this->geturl(2) === 'head') { - $this->secureFilePutContents(self::DATA_DIR . 'head.inc.html', $this->getInput('configScriptHead', null)); + $this->secure_file_put_contents(self::DATA_DIR . 'head.inc.html', $this->getInput('configScriptHead', null)); } if ($this->geturl(2) === 'body') { - $this->secureFilePutContents(self::DATA_DIR . 'body.inc.html', $this->getInput('configScriptBody', null)); + $this->secure_file_put_contents(self::DATA_DIR . 'body.inc.html', $this->getInput('configScriptBody', null)); } // Valeurs en sortie $this->addOutput([ @@ -699,7 +699,7 @@ class config extends common unlink(self::DATA_DIR . 'journal.log'); // Créer les en-têtes des journaux $d = 'Date;Heure;IP;Id;Action' . PHP_EOL; - $this->secureFilePutContents(self::DATA_DIR . 'journal.log', $d); + $this->secure_file_put_contents(self::DATA_DIR . 'journal.log', $d); // Valeurs en sortie $this->addOutput([ 'title' => helper::translate('Configuration'), @@ -775,7 +775,7 @@ class config extends common ob_start(); $fileName = self::TEMP_DIR . 'blacklist.log'; $d = 'Date dernière tentative;Heure dernière tentative;Id;Adresse IP;Nombre d\'échecs' . PHP_EOL; - $this->secureFilePutContents($fileName, $d); + $this->secure_file_put_contents($fileName, $d); if (file_exists($fileName)) { $d = $this->getData(['blacklist']); $data = ''; @@ -783,7 +783,7 @@ class config extends common $data .= helper::dateUTF8('%Y %m %d', $item['lastFail'], self::$i18nUI) . ' - ' . helper::dateUTF8('%H:%M', time(), self::$i18nUI); $data .= $key . ';' . $item['ip'] . ';' . $item['connectFail'] . PHP_EOL; } - $this->secureFilePutContents($fileName, $data, FILE_APPEND); + $this->secure_file_put_contents($fileName, $data, FILE_APPEND); header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Transfer-Encoding: binary'); diff --git a/core/module/install/install.php b/core/module/install/install.php index 00f370fc..e64bd43e 100644 --- a/core/module/install/install.php +++ b/core/module/install/install.php @@ -292,7 +292,7 @@ class install extends common case 2: $success = true; $message = ''; - $this->secureFilePutContents(self::TEMP_DIR . 'update.tar.gz', helper::getUrlContents(common::ZWII_UPDATE_URL . common::ZWII_UPDATE_CHANNEL . '/update.tar.gz')); + $this->secure_file_put_contents(self::TEMP_DIR . 'update.tar.gz', helper::getUrlContents(common::ZWII_UPDATE_URL . common::ZWII_UPDATE_CHANNEL . '/update.tar.gz')); $md5origin = helper::getUrlContents(common::ZWII_UPDATE_URL . common::ZWII_UPDATE_CHANNEL . '/update.md5'); $md5origin = explode(' ', $md5origin); $md5target = md5_file(self::TEMP_DIR . 'update.tar.gz'); @@ -401,7 +401,7 @@ class install extends common '' . PHP_EOL . '# URL rewriting' . PHP_EOL; $fileContent = str_replace('# URL rewriting', $rewriteData, $fileContent); - $success = $this->secureFilePutContents( + $success = $this->secure_file_put_contents( '.htaccess', $fileContent ); diff --git a/core/module/language/language.php b/core/module/language/language.php index 20977bcf..6e5e1e04 100644 --- a/core/module/language/language.php +++ b/core/module/language/language.php @@ -99,7 +99,7 @@ class language extends common is_array($descripteur['language'][$lang]) ) { if ($this->setData(['language', $lang, $descripteur['language'][$lang]])) { - $success = $this->secureFilePutContents(self::I18N_DIR . $lang . '.json', $languageData); + $success = $this->secure_file_put_contents(self::I18N_DIR . $lang . '.json', $languageData); $success = is_int($success) ? true : false; } } @@ -430,7 +430,7 @@ class language extends common $this->setData(['locale', $data['locale']]); } else { // Sauver sur le disque - $this->secureFilePutContents(self::DATA_DIR . $lang . '/locale.json', $data); + $this->secure_file_put_contents(self::DATA_DIR . $lang . '/locale.json', $data); } // Valeurs en sortie @@ -512,7 +512,7 @@ class language extends common $data[$key] = $target; } } - $this->secureFilePutContents(self::I18N_DIR . $lang . '.json', $data); + $this->secure_file_put_contents(self::I18N_DIR . $lang . '.json', $data); // Mettre à jour le descripteur $this->setData([ @@ -546,7 +546,7 @@ class language extends common $data[$key] = ''; } } - $this->secureFilePutContents(self::I18N_DIR . $lang . '.json', $data); + $this->secure_file_put_contents(self::I18N_DIR . $lang . '.json', $data); // Tableau des chaines à traduire dans la langue sélectionnée foreach ($data as $key => $value) { diff --git a/core/module/page/page.php b/core/module/page/page.php index b161fa94..46c868dd 100644 --- a/core/module/page/page.php +++ b/core/module/page/page.php @@ -177,7 +177,7 @@ class page extends common if (!is_dir(self::DATA_DIR . self::$siteContent . '/content')) { mkdir(self::DATA_DIR . self::$siteContent . '/content', 0755); } - //$this->secureFilePutContents(self::DATA_DIR . self::$siteContent . '/content/' . $pageId . '.html', '

Contenu de votre nouvelle page.

'); + //$this->secure_file_put_contents(self::DATA_DIR . self::$siteContent . '/content/' . $pageId . '.html', '

Contenu de votre nouvelle page.

'); $this->setPage($pageId, '

Contenu de votre nouvelle page.

', self::$siteContent); // Met à jour le sitemap diff --git a/core/module/plugin/plugin.php b/core/module/plugin/plugin.php index 4a56d021..ae3aa4b2 100644 --- a/core/module/plugin/plugin.php +++ b/core/module/plugin/plugin.php @@ -314,7 +314,7 @@ class plugin extends common mkdir(self::FILE_DIR . 'source/modules', 0755); } // Sauver les données du fichiers - $this->secureFilePutContents(self::FILE_DIR . 'source/modules/' . $moduleFile, $moduleData); + $this->secure_file_put_contents(self::FILE_DIR . 'source/modules/' . $moduleFile, $moduleData); // Installation directe if (file_exists(self::FILE_DIR . 'source/modules/' . $moduleFile)) { @@ -592,7 +592,7 @@ class plugin extends common $fileName = $moduleId . str_replace('.', '-', $infoModule[$moduleId]['version']) . '.zip'; // Régénération du descripteur du module - $this->secureFilePutContents(self::MODULE_DIR . $moduleId . '/enum.json', $infoModule[$moduleId]); + $this->secure_file_put_contents(self::MODULE_DIR . $moduleId . '/enum.json', $infoModule[$moduleId]); // Construire l'archive $this->makeZip($tmpFolder . $fileName, self::MODULE_DIR . $moduleId); diff --git a/core/module/theme/theme.php b/core/module/theme/theme.php index 6c8f0824..795f811e 100644 --- a/core/module/theme/theme.php +++ b/core/module/theme/theme.php @@ -304,7 +304,7 @@ class theme extends common $this->isPost() ) { // Enregistre le CSS - $this->secureFilePutContents(self::DATA_DIR . 'custom.css', $this->getInput('themeAdvancedCss', null)); + $this->secure_file_put_contents(self::DATA_DIR . 'custom.css', $this->getInput('themeAdvancedCss', null)); // Valeurs en sortie $this->addOutput([ 'notification' => helper::translate('Modifications enregistrées'), @@ -1290,7 +1290,7 @@ class theme extends common } // Sauvegarder la chaîne modifiée if ($count > 0) { - $this->secureFilePutContents($file, $data); + $this->secure_file_put_contents($file, $data); } // Retourner le nombre d'occurrences return ($count); @@ -1396,8 +1396,8 @@ class theme extends common } // Enregistre la personnalisation - $this->secureFilePutContents(self::DATA_DIR . 'font/font.html', $fileContent); + $this->secure_file_put_contents(self::DATA_DIR . 'font/font.html', $fileContent); // Enregistre la personnalisation - $this->secureFilePutContents(self::DATA_DIR . 'font/font.css', $fileContentCss); + $this->secure_file_put_contents(self::DATA_DIR . 'font/font.css', $fileContentCss); } } \ No newline at end of file