htmlspecialchars($text) convertit &, <, >, " et ' en &, <, >, " et '. Appelez-la sur chaque morceau de saisie utilisateur que vous affichez dans une page, et la saisie s'affiche en texte au lieu d'être lue comme du HTML.
Le bloc affiche deux fois le même commentaire, une fois brut et une fois échappé. Exécutez-le et comparez les deux lignes dans l'onglet Page, puis tapez votre propre HTML dans le formulaire, par exemple <h1>big</h1> ou <img src=x>, et cliquez sur Show.
Sur la ligne brute, le navigateur obéit aux balises : le mot en gras est en gras, et un visiteur qui tape <script> fait exécuter son script dans le navigateur de chaque autre lecteur. Cette attaque s'appelle le cross-site scripting (XSS). Sur la ligne échappée, les mêmes caractères arrivent sous la forme <b> et le navigateur les dessine comme du texte. Passez à l'onglet Output pour voir les entités que PHP a réellement affichées.
Ce que convertit htmlspecialchars
Cinq caractères, rien d'autre. Les lettres, les accents et les emoji passent sans changement.
& fait partie de la liste car il commence chaque entité : s'il restait tel quel, un commentaire qui mentionne < s'afficherait comme <.
Échapper les attributs, pas seulement le texte
Une saisie utilisateur dans un attribut a tout autant besoin d'être échappée. Sans cela, un guillemet dans la valeur ferme l'attribut et le reste de la saisie devient de nouveaux attributs. Ici, le « nom » glisse un attribut style ; exécutez-le et regardez les deux champs.
Dans le champ non sûr, le navigateur voit value="Ada" suivi d'un nouvel attribut style, donc le champ devient rouge et n'affiche que Ada. Un attaquant écrirait onfocus="..." à la place de style, et son code s'exécuterait. Dans le champ sûr, chaque " est devenu ", donc toute la chaîne reste dans value et s'affiche telle que tapée.
Depuis PHP 8.1, les options par défaut sont ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, donc les apostrophes sont aussi échappées et les attributs écrits avec '...' sont sûrs. Le code plus ancien passe souvent ENT_QUOTES à la main, et en PHP 7 et avant c'était obligatoire :
Une petite fonction pour les templates
Écrire htmlspecialchars($x, ENT_QUOTES, 'UTF-8') des dizaines de fois dans un template alourdit le code, c'est pourquoi la plupart des projets l'entourent d'une fonction d'une lettre. Les moteurs de templates comme Twig et Blade font la même chose automatiquement pour chaque {{ $var }}.
Le type ?string et le ?? '' comptent : passer null à htmlspecialchars() est déprécié depuis PHP 8.1, et votre PHP afficherait un avis de dépréciation pour chaque utilisateur sans biographie.
Double encodage et htmlspecialchars_decode
Si une valeur est échappée deux fois, le lecteur voit les entités : & devient & la première fois et &amp; la seconde, que le navigateur affiche comme &. Cela signifie en général que la valeur a été échappée à l'enregistrement puis à nouveau à l'affichage. Passez double_encode: false pour laisser intactes les entités existantes, et utilisez htmlspecialchars_decode() pour revenir en arrière.
La vraie solution est de stocker le texte brut et de n'échapper qu'à l'affichage. double_encode: false sert pour du texte qui contient déjà des entités que vous n'avez pas créées, comme un flux importé.
htmlspecialchars, htmlentities ou strip_tags
Ces trois fonctions sont souvent confondues. Le bloc les exécute toutes sur la même entrée et montre, pour chacune, ce qu'affiche PHP et ce qu'en fait le navigateur :
htmlspecialchars()échappe les cinq caractères HTML. Utilisez-la pour tout texte que vous affichez dans du HTML.htmlentities()transforme aussiéené. Elle était utile quand les pages n'étaient pas en UTF-8 ; aujourd'hui, elle rend seulement le code source plus difficile à lire.strip_tags()supprime les balises et garde leur texte. Elle sert à transformer du HTML en texte brut (un aperçu d'email, une meta description), pas à la sécurité : la dernière ligne montre qu'un<b>autorisé garde sononclick, et le texte placé dans un attribut n'est pas touché du tout.
Là où htmlspecialchars ne suffit pas
htmlspecialchars() est le bon échappement pour du texte HTML et des attributs entre guillemets. D'autres endroits d'une page ont d'autres règles :
- Dans une URL,
http_build_query()ouurlencode()encode la valeur ;htmlspecialchars()rend ensuite valide en HTML le&entre les paramètres. - En JavaScript,
json_encode()produit une valeur JS valide, etJSON_HEX_TAGtransforme<et>en\u003Cet\u003E, si bien qu'un</script>dans les données ne peut pas fermer la balise script. - N'affichez jamais une saisie utilisateur dans un
hrefsans vérifier le schéma :htmlspecialchars('javascript:alert(1)')est inchangé et s'exécute toujours au clic. N'acceptez que des URLhttpethttps, comme le montre la page filter_var.
Pour un traitement de formulaire qui réunit tout cela, voir les formulaires PHP.
Questions fréquentes
Que fait htmlspecialchars en PHP ?
Elle remplace &, <, >, " et ' par &, <, >, " et '. Le navigateur affiche alors ces caractères au lieu de les lire comme du HTML, donc un <script> tapé dans un formulaire s'affiche en texte et ne s'exécute jamais.
Quelle est la différence entre htmlspecialchars et htmlentities ?
htmlspecialchars() ne convertit que les cinq caractères spéciaux en HTML. htmlentities() convertit aussi chaque caractère qui a une entité nommée, donc café devient café. Avec des pages en UTF-8, les deux sont aussi sûres, et htmlspecialchars() garde la sortie lisible, c'est donc le choix habituel.
Faut-il encore ENT_QUOTES en PHP 8 ?
Pas pour la sécurité : depuis PHP 8.1, les options par défaut sont ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, donc les apostrophes sont aussi échappées. Beaucoup de projets passent encore explicitement ENT_QUOTES, 'UTF-8' pour que l'appel se comporte de la même façon sur les anciennes versions et soit évident pour les lecteurs.
Faut-il utiliser htmlspecialchars à l'entrée ou à la sortie ?
À la sortie. Stockez et validez la valeur brute, et échappez-la au moment où vous l'affichez dans du HTML. Échapper à l'entrée stocke < dans votre base de données, fausse les longueurs et les recherches, et mène à un double échappement comme &lt;.
strip_tags suffit-il pour éviter le XSS ?
Non. strip_tags() supprime les balises, mais son paramètre de balises autorisées garde leurs attributs, donc <b onclick="..."> survit, et elle ne fait rien pour du texte placé dans un attribut. Utilisez htmlspecialchars() pour afficher une saisie utilisateur.