Encodeur et décodeur d’entités HTML

Échappez du texte pour qu’il soit sûr dans du HTML, ou décodez les entités nommées et numériques vers les caractères qu’elles représentent.

Texte
Encodé

La sortie apparaîtra ici

Ce que fait cet outil

HTML n’a aucun moyen de distinguer un signe inférieur que vous vouliez comme texte d’un signe qui ouvre une balise. Le langage règle cela avec les entités : de courtes séquences d’échappement qui représentent un caractère sans être lues comme du balisage. Cet outil convertit dans les deux sens — du texte brut vers sa forme échappée, et du HTML échappé vers les caractères qu’il représente.

Les deux sens s’exécutent entièrement dans votre navigateur. Cela compte ici plus que pour la plupart des outils, car le texte que l’on colle dans un décodeur d’entités est en général une page moissonnée, un courriel de client ou une colonne de base de données que quelqu’un essaie de comprendre.

Les cinq caractères qui comptent

Il existe des milliers d’entités, mais quelques-unes suffisent à rendre un texte sûr. L’encodage remplace ici exactement cinq caractères et laisse tout le reste lisible :

  • L’esperluette devient & — celle-ci d’abord, sinon chaque autre remplacement serait à son tour relu comme une entité.
  • Le signe inférieur devient < — le caractère capable d’ouvrir une balise.
  • Le signe supérieur devient > — moins dangereux à lui seul, échappé par symétrie et pour survivre aux analyseurs peu rigoureux.
  • Le guillemet droit double devient " — nécessaire dans les valeurs d’attribut écrites avec des guillemets doubles.
  • L’apostrophe devient ' — nécessaire dans les valeurs d’attribut écrites avec des apostrophes.

Cette dernière est écrite sous forme numérique à dessein. Il existe un nom, ', mais il vient de XML et HTML 4 ne l’a jamais défini : les vieux analyseurs le laissent passer comme texte littéral et l’échappement échoue en silence. La forme numérique a toujours fonctionné partout, et c’est pour cela que les bibliothèques d’échappement sérieuses l’émettent.

Nommées, décimales et hexadécimales

Tout caractère peut s’écrire de trois façons, et le décodage les accepte toutes. Le tiret cadratin en est un bon exemple :

named     —      la forme lisible, mais réservée aux caractères qui ont un nom
decimal   —      le point de code en base 10
hex       —     le même point de code en base 16, la notation des tables Unicode

Au décodage, cet outil reconnaît les 255 noms du jeu HTML 4 — les lettres latines, l’alphabet grec, les flèches, les symboles mathématiques et la ponctuation typographique, qui couvrent à eux tous à peu près tout ce que l’on rencontre dans du texte réel. HTML 5 en définit environ 2200 de plus, presque tous des symboles mathématiques rares ; embarquer cette table ajouterait des dizaines de kilo-octets à chaque chargement de page en échange de noms que la plupart des documents n’utilisent jamais. Un nom hors du jeu est signalé plutôt qu’effacé : le coût est un avertissement, jamais une réponse fausse.

L’encodage va dans l’autre sens et reste volontairement sobre : des noms pour les cinq caractères ci-dessus, des nombres décimaux pour tout le reste. Rien ne devient … ni ’ alors que le caractère lui-même est parfaitement valide dans un document UTF-8.

Échapper n’est pas une règle mais quatre

Il est tentant de voir l’échappement d’entités comme la réponse à l’injection, et dans le contenu d’un élément c’en est vraiment une. Mais HTML est en fait quatre langages imbriqués les uns dans les autres, chacun avec ses propres règles. L’échappement d’entités est correct dans les deux premiers de ces endroits et inutile dans les autres :

  • Le contenu d’un élément — le texte entre les balises. L’échappement d’entités est exactement ce qu’il faut.
  • Les valeurs d’attribut entre guillemets. L’échappement d’entités convient, à condition que l’attribut soit réellement entre guillemets ; sans eux, une espace dans la valeur termine l’attribut et l’échappement ne vous apporte rien.
  • À l’intérieur d’un bloc script. Ce sont les règles d’échappement des chaînes JavaScript qui s’appliquent, pas les entités — le navigateur n’y décode pas les entités, si bien qu’un guillemet échappé arrive sous la forme des caractères littéraux ".
  • Les attributs d’URL comme href et src. L’échappement d’entités n’arrête pas une URL javascript:, car le danger tient au schéma, pas à un caractère qu’une entité remplacerait.

La règle pratique : échappez à l’endroit où le texte est inséré dans un document, selon les règles du contexte exact où il atterrit — et non une seule fois, tôt, en espérant que cela tiendra partout ensuite.

Pourquoi l’on voit parfois &

C’est de loin la raison la plus fréquente de recourir à un décodeur d’entités. Le texte a été échappé, puis échappé de nouveau — en général une fois par le code applicatif et une fois par un moteur de gabarits ou un cadriciel qui échappe déjà sa sortie :

original     Fish & Chips
escaped      Fish & Chips
escaped x2   Fish & Chips

La deuxième ligne s’affiche dans la page comme l’original. La troisième s’affiche comme la deuxième : le visiteur voit l’entité au lieu du caractère. Le décodage retire exactement une couche, donc une seule passe vous dit dans quel cas vous êtes. Si la sortie contient encore des entités, il y avait plus d’une couche. Le correctif n’est presque jamais de retirer les entités à l’affichage : il faut trouver et supprimer l’étape d’échappement en double, car le même défaut corrompt silencieusement les données stockées.

Le non-ASCII, et l’option correspondante

Une page servie en UTF-8 n’a besoin d’aucune entité pour les lettres accentuées, l’hébreu, l’arabe, le chinois ou les emoji. Ces caractères sont valides tels quels, et les laisser lisibles garde la source lisible et le fichier plus léger. C’est le comportement par défaut ici.

L’option existe parce que certaines destinations exigent encore de l’ASCII : vieux gabarits de courriel, systèmes qui abîment les octets au-delà de 127, ou colonne de base de données dont personne ne veut changer l’encodage. Une fois activée, chaque caractère de ce type s’écrit en référence décimale — é devient é, א devient א.

Les emoji méritent une remarque. Un emoji s’écrit en une seule référence pour son point de code entier, par exemple 🙂, jamais en deux moitiés. Si vous avez utilisé l’outil d’échappement JSON, c’est l’inverse de ce qui s’y passe, où le format impose justement de couper un emoji en deux échappements \u. Même caractère, deux réponses correctes, parce que les deux formats définissent l’échappement sur des unités différentes.

Quand une référence ne peut pas être décodée

Le décodage n’échoue jamais ici et ne supprime jamais rien. Une référence impossible à résoudre est laissée exactement telle quelle et listée au-dessus de la sortie avec sa ligne et sa colonne. Trois cas sont signalés :

  • Un nom inconnu, comme &nbps; — presque toujours une faute de frappe pour  . C’est l’avertissement sur lequel il faut agir : il signifie qu’une espace manque dans votre contenu et que personne ne l’a vu.
  • Un nombre qui n’est pas un caractère, comme � ou une valeur au-delà de la plage Unicode. Il n’y a rien vers quoi le décoder, il reste donc du texte.
  • Un nombre entre 128 et 159. Voir ci-dessous.

Une esperluette isolée n’est pas signalée du tout. En HTML, R&D et Q&A sont du texte ordinaire, et traiter chaque esperluette égarée comme une erreur noierait les avertissements utiles sous le bruit de la prose.

La plage 128 à 159 est une véritable curiosité. Ces nombres désignent des caractères de contrôle invisibles que personne ne veut jamais écrire ; ils apparaissent parce que le document a été rédigé en Windows-1252, où ces mêmes nombres sont des guillemets courbes, des tirets et le signe euro. Les navigateurs les lisent discrètement comme du Windows-1252 pour cette raison même, et cet outil fait de même afin que le résultat corresponde à ce qu’une page afficherait réellement — mais il le dit dans les avertissements, car une substitution silencieuse que vous n’avez pas demandée est pire qu’une substitution bruyante.

Questions fréquentes

Échapper ces cinq caractères suffit-il à arrêter le XSS ?
Dans le contenu d’un élément et dans les valeurs d’attribut entre guillemets, oui : c’est précisément à cela que cela sert. Ce n’est pas suffisant dans un bloc script ou style, dans un attribut sans guillemets, ni dans un attribut d’URL comme href, qui exigent chacun leurs propres règles. L’échappement est une propriété de l’endroit où le texte atterrit, pas du texte.
Pourquoi l’apostrophe est-elle encodée en ' et non en ' ?
Parce que ' est une entité XML que HTML 4 n’a jamais définie. Les analyseurs antérieurs à HTML 5 la laissent en texte littéral : une apostrophe que vous croyiez échappée ne l’est pas. La forme numérique fonctionne dans tous les analyseurs, d’où ce choix par défaut.
Ma page affiche & au lieu d’une esperluette. Que s’est-il passé ?
Le texte a été échappé deux fois. Décodez-le ici : une passe transforme & en &, ce qui confirme la couche superflue. Corrigez la chaîne de traitement plutôt que le texte — en général votre code échappe une sortie que le moteur de gabarits échappait déjà.
Dois-je encoder l’hébreu, l’arabe ou les emoji ?
Pas pour une page moderne servie en UTF-8 : ils sont valides tels quels. N’activez l’option non-ASCII que si quelque chose en aval exige de l’ASCII, comme un vieux gabarit de courriel. Chaque emoji s’écrit alors en une seule référence numérique.
Pourquoi l’outil avertit-il sur “ alors qu’il l’a bien décodé ?
Parce que 147 désigne un caractère de contrôle invisible, pas le guillemet que vous voyez. Les documents qui l’emploient ont été écrits en Windows-1252, où 147 est un guillemet courbe : les navigateurs le lisent ainsi, et cet outil aussi. L’avertissement vous signale qu’une substitution a eu lieu, afin que vous corrigiez l’encodage source s’il vous appartient.
Mon texte est-il envoyé quelque part ?
Non. L’encodage et le décodage s’exécutent entièrement dans votre navigateur ; rien de ce que vous collez ne quitte votre appareil.