Un problème récurent qui peut affecter divers fichiers d’un site web (page xHTML, CSS, script PHP…) concerne la présence de caractères blancs en amont et aval de tels fichiers. Diverses raisons peuvent mener au besoin de supprimer ces caractères, d’où la réalisation du script qui suit qui s’occupe automatiquement d’identifier récursivement les fichiers concernés et de les traiter.
Ces espaces blancs peuvent être :
- \n
- \r
- \t
- » «
- \x0B
- \0
Attention toutefois au \0 qui est marqueur de fin de fichier pour certains.
Mais pourquoi et comment j’en suis venu à pondre ce petit script ?
La première raison concerne la compression des fichiers. Sur un serveur web, il y a très souvent des fichiers qui ont un nombre incalculable d’espace ou de retour à la ligne en amont ou en aval. Pourquoi les conserver? Sachant qu’à chaque appel de la page il sont transmis au navigateur de l’utilisateur à raison d’un octet par caractère. Ça fait du débit inutile et supplémentaire!
La seconde raison est lors de la conversion d’encodage de fichier. C’est précisément le problème que j’ai rencontré. J’ai converti toute une base de données, tous les fichiers et toute la configuration d’un serveur de l’ISO-8859-1 vers UTF-8. Que vous utilisiez « iconv » ou une autre solution de conversion, il se peut qu’un désavantage survienne à cette conversion : certains caractères blancs en ISO sont doublés ou convertis en d’autres whitespace lors de cette conversion. Ce qui a engendré dans mon cas sur plusieurs milliers de fichiers l’ajout de plusieurs retour à la ligne en amont ou en aval…
La troisième raison, qui découle de la seconde, est que lorsqu’un fichier PHP a des whitespaces en amont ou en aval, ces caractères sont directement transmis lors de leur interprétation au navigateur de l’utilisateur. Ainsi, dans le cas d’utilisation de session, donc avec un « session_start() » à un certain endroit, il ne faut ABSOLUMENT PAS que des données aient été transférées au préalable, sinon une erreur survient. Donc, tous les fichiers interprétés ou inclus ne doivent pas effectuer d’echo ou afficher des caractères html ou whitespace avant ce « session_start() ». D’où ce robot de nettoyage.
Le problème des « sessions_start() » n’en est qu’un parmi plusieurs. Toutes générations en PHP de document/fichier dynamique d’un autre type nécessite qu’aucune donnée ne soit transmise avant. C’est le cas de la génération d’image dynamique via GD2 ou même de la génération de PDF dynamique avec FPDF. Ces générations sont faites en PHP et avant de les transmettre au client un en-tête avec un content-type MIME correcte doit être défini, ce qui implique qu’aucune données ne doit être transmises au préalable.
Bien sûr, il y a la solution de capturer le tampon avec des « ob_start() », « ob_end() », « ob_end_flush() », « ob_get_contents() » et appliquer des « trim() » avant d’envoyer le tampon, mais c’est de la « triche » et des traitements supplémentaires côté serveur…
J’ai donc développé le script suivant qui analyse récursivement un path absolu, vérifie tous les fichiers, affiche en rouge ceux ayant des whistespace en amont ou aval, et supprime les whitespaces des fichiers sélectionnés après confirmation sans toucher au reste du contenu du fichier.
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www.w3.org/1999/xhtml'> <head> <style> *{ font-family:verdana; font-size:10px; } .error{ font-weight:bold; color:#AA0000; } .success{ font-weight:bold; color:#00AA00; } </style> </head> <body> <form action='' method='post'> <table> <tr> <td>Absolute path to analyze :</td> <td><input type='text' name='path' size='100' value='<?php echo $_SERVER['DOCUMENT_ROOT'];?>' /></td> </tr> </table> <input type='submit' value='Run' /> </form> <?php function printFile($file, $blankExists){ echo "<li class='" . (($blankExists) ? 'error' : 'success') . "'> <input type='checkbox' value='$file' name='files[]' " . (($blankExists) ? 'checked' : '') . "/> $file </li>"; } function analyzeFile($file){ $contents = @file_get_contents($file); return (preg_match('/^(\n|\t|\r| |\x0B|\\0)/i', $contents) || preg_match('/(\n|\t|\r| |\x0B|\\0)$/i', $contents)); } function cleanFile($file){ $flag = false; $contents = @file_get_contents($file); $fp = @fopen($file, 'w'); if($fp){ @fputs($fp, trim($contents)); @fclose($fp); $flag = true; } return $flag; } function fileWalker($luke){ $dp = @opendir($luke) or die("Directory $luke doesn't exist..."); $indexExists = false; $folders = array(); while($file = @readdir($dp)){ if(in_array($file, array('.', '..', '.svn'))) continue; if(is_dir($luke.'/'.$file)) $folders[] = $luke.'/'.$file; else { printFile($luke.'/'.$file, analyzeFile($luke.'/'.$file)); } } @closedir($dp); foreach($folders as $folder){ echo '<ul>'; fileWalker($folder); echo '</ul>'; } } if(isset($_POST['path'])){ echo "<form action='' method='post'> <input type='submit' value='Trim files' /> <ul>"; fileWalker(trim(strval($_POST['path']))); echo " </ul> <input type='submit' value='Trim files' /> </form>"; } elseif(isset($_POST['files']) && is_array($_POST['files']) && count($_POST['files']) > 0){ foreach($_POST['files'] as $file){ $file = trim(strval($file)); if(file_exists($file) && !is_dir($file) && is_writable($file)){ if(cleanFile($file)){ echo "<div class='success'>$file</div>"; } else echo "<div class='error'>$file</div>"; } } } ?> </body> </html>
En espérant que ça puisse dépanner certains! 🙂