Pourquoi café devient café : déboguer UTF-8, les entités HTML et l'encodage des URL

Vous saisissez café dans un formulaire, l’enregistrez, puis retrouvez café. Ou le paramètre C++ d’une URL devient C . Ou encore une page affiche & au lieu d’une esperluette. Ces problèmes se ressemblent parce qu’ils concernent des caractères particuliers, mais ils proviennent de couches de représentation différentes.

Corriger la mauvaise couche peut aggraver les dégâts. Remplacer partout é par é peut améliorer temporairement une page, sans réparer la chaîne d’encodage ni traiter les autres caractères Unicode.

Ce guide distingue l’encodage des caractères, les entités HTML, l’encodage en pourcentage des URL et la représentation JSON, puis montre comment localiser le défaut avec les outils du navigateur, cURL et JavaScript.

Quatre couches faciles à confondre

Un caractère n’est ni sa suite d’octets transmise ni sa représentation dans du balisage. Prenons ce texte :

café & tea

Plusieurs représentations sont valides selon le contexte :

ContexteExempleSignification
Texte Unicodecafé & teaLes caractères que l’application veut manipuler
Octets UTF-863 61 66 C3 A9 20 26 20 74 65 61Les octets qui représentent ces caractères
Composant d’URLcaf%C3%A9%20%26%20teaLes octets UTF-8 encodés en pourcentage pour un composant
Texte source HTMLcafé & teaDu HTML affichant une esperluette littérale
Chaîne JSON"café & tea"Une chaîne dans un document JSON ; échapper l’accent est facultatif

UTF-8 est un encodage de caractères. L’encodage en pourcentage représente des octets dans une URL. Les références de caractères HTML appartiennent à la syntaxe HTML, et les échappements JSON à la syntaxe JSON. Ces conversions ne sont pas interchangeables.

L’encodeur et décodeur HTML traite les références telles que & et <. L’encodeur et décodeur d’URL traite l’encodage en pourcentage, les valeurs de formulaire et les paramètres de requête.

Problème 1 : café indique souvent un mauvais décodage

La lettre é est représentée en UTF-8 par les deux octets C3 A9. Interprétés à tort comme des caractères Windows-1252, ils s’affichent sous la forme é. Les octets ne contenaient pas intrinsèquement le mauvais mot : ils ont été lus avec le mauvais encodage.

Ce texte déformé est souvent appelé mojibake. Une connexion à une base de données, un import de fichier, un en-tête de réponse ou un ancien service peut employer un encodage différent de celui du producteur des octets.

Examinez tout le trajet des données :

  1. Comment le texte a-t-il été saisi ou importé ?
  2. Quel encodage a servi à l’écrire dans un fichier, une base ou un message ?
  3. A-t-il été converti à nouveau lors de sa lecture ou de son export ?
  4. Quels octets le serveur envoie-t-il réellement ?
  5. Comment le destinataire choisit-il l’encodage de décodage ?

Un document HTML devrait normalement déclarer UTF-8 au début :

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Text example</title>
</head>
<body>
  <p>café &amp; tea</p>
</body>
</html>

Pour une page servie par HTTP, vérifiez également l’en-tête de réponse, par exemple :

Content-Type: text/html; charset=utf-8

Un encodage explicite dans l’en-tête du serveur peut prendre le pas sur la déclaration du document. Une chaîne déjà endommagée dans la base ne sera pas réparée par une déclaration UTF-8 : celle-ci décrit les octets transmis, elle ne les corrige pas.

Problème 2 : le texte est déjà corrompu à son arrivée

Ouvrez le panneau Réseau du navigateur, sélectionnez la requête qui fournit le contenu et comparez les en-têtes, la réponse et la page affichée. Le guide d’inspection des requêtes réseau décrit cette démarche dans Chrome, Firefox, Edge et Safari.

Pour un contrôle reproductible de la réponse du serveur, enregistrez les en-têtes et les octets avec cURL :

curl --compressed --silent --show-error \
  --dump-header headers.txt \
  --output response.bin \
  'https://example.com/api/profile'

--compressed autorise cURL à décompresser les formats HTTP qu’il prend en charge. Cette opération est distincte du décodage des caractères. Pour une API JSON, vérifiez que le corps est bien du UTF-8 valide et que le serveur renvoie le type de média attendu.

Enregistrez ce code dans check-response.mjs, puis lancez node check-response.mjs pour examiner les octets avec Node.js :

import { readFileSync } from "node:fs";

const bytes = readFileSync("response.bin");
try {
  const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
  console.log(text.slice(0, 200));
} catch (error) {
  console.error("Response contains invalid UTF-8 byte sequences", error);
}

Un décodage UTF-8 réussi prouve que la séquence d’octets est bien formée. Il ne prouve pas que le texte n’a jamais été corrompu : une chaîne contenant déjà café peut être correctement encodée en UTF-8. Comparez donc une valeur connue à chaque frontière de stockage et de transport.

Le générateur de commandes cURL peut préparer une requête reproductible avec des en-têtes personnalisés. Il ne récupère ni ne valide la réponse : examinez celle-ci dans votre terminal.

Problème 3 : un signe plus ou un espace change dans une URL

L’encodage des URL possède ses propres règles. En JavaScript, encodeURIComponent convient à une valeur de paramètre, pas à une URL entière :

const value = "café & tea";
console.log(encodeURIComponent(value));
// caf%C3%A9%20%26%20tea

Construire l’URL à la main est une source d’erreurs :

// Fragile: '&' becomes a query separator.
const bad = "/search?q=" + "café & tea";

// Safer: the URL API encodes and manages the query parameter.
const url = new URL("https://example.com/search");
url.searchParams.set("q", "café & tea");
console.log(url.toString());
// https://example.com/search?q=caf%C3%A9+%26+tea

Pourquoi un signe plus ? URLSearchParams utilise la sérialisation des formulaires, où les espaces deviennent +. encodeURIComponent utilise au contraire %20. Un signe plus littéral dans une valeur de formulaire doit devenir %2B :

const query = new URLSearchParams({ language: "C++" });
console.log(query.toString()); // language=C%2B%2B

Décodez les valeurs selon le format qui les a produites. Pour une URL complète, laissez URL et URLSearchParams gérer sa structure. L’annonce de l’encodeur, décodeur et éditeur de paramètres d’URL présente les paramètres répétés et la distinction entre espaces et signes plus.

Évitez de décoder plusieurs fois une URL signée ou de la reconstruire sans précaution. Modifier l’encodage en pourcentage ou l’ordre des paramètres peut invalider une signature fondée sur la représentation exacte de l’URL.

Problème 4 : la page affiche &amp; au lieu de &

Le HTML Tom &amp; Jerry s’affiche normalement sous la forme Tom & Jerry. Si les cinq caractères &amp; apparaissent littéralement, les données ont peut-être été échappées deux fois, ou insérées avec une API textuelle après avoir été échappées.

Ce code convient lorsque value contient du texte ordinaire saisi par l’utilisateur :

const value = "Tom & Jerry";
document.querySelector("#result").textContent = value;

Ne remplacez pas d’abord l’esperluette par &amp; avant d’affecter la chaîne à textContent. Le navigateur traitera alors cette séquence comme du texte, sans l’interpréter comme du balisage.

À l’inverse, ne passez jamais à innerHTML uniquement pour décoder une entité si un utilisateur peut contrôler le contenu : cela peut introduire une faille XSS. Pour afficher du texte littéral, utilisez textContent. Pour du HTML enrichi autorisé, utilisez un outil d’assainissement maintenu et adapté au contexte. Le guide sur l’échappement et l’assainissement HTML explique cette distinction.

L’outil HTML permet d’essayer des exemples. En production, la correction consiste généralement à échapper une seule fois, au bon endroit, avec le moteur de rendu ou une bibliothèque adaptée au contexte de sortie.

Problème 5 : JSON, bases de données et formulaires divergent

JSON est un format de données, pas une alternative à UTF-8. Une réponse JSON peut contenir é directement ou l’échappement Unicode \u00e9 : après une analyse correcte, ils représentent le même caractère.

Pour la plupart des API modernes, envoyez du JSON encodé en UTF-8 avec Content-Type: application/json et validez le corps comme du JSON. N’appliquez pas un encodage d’URL à une chaîne JSON, sauf si l’API exige explicitement du JSON dans une URL ou un champ de formulaire.

Les formulaires peuvent utiliser application/x-www-form-urlencoded ou multipart/form-data, dont les règles diffèrent. Envoyer des valeurs de formulaire à un serveur qui attend du JSON crée un désaccord de format, pas nécessairement un problème d’accent. Le guide JSON, formulaires et multipart compare ces cas.

Vérifiez aussi les données des colonnes et l’encodage de la connexion à la base, sans vous fier uniquement au meta charset de la page. Si un import a définitivement remplacé des caractères par �, une sauvegarde ou une copie intacte peut être nécessaire pour les récupérer.

Tableau de diagnostic rapide

SymptômePremier point à examinerCorrection habituelle
café au lieu de caféOctets d’origine, stockage, encodage de connexionAssurer UTF-8 à chaque frontière et réparer la source si possible
� à la place d’un caractèreErreur de décodage ou import avec perteRécupérer les octets d’origine et supprimer la conversion incorrecte
&amp; littéral à l’écranÉchappement HTML et insertion dans le DOMÉviter le double échappement ; utiliser textContent pour le texte brut
+ interprété comme un espaceDécodeur de paramètres ou de formulaireEncoder le + littéral en %2B dans les valeurs de formulaire
%25C3%25A9 dans un paramètreEncodage en pourcentage répétéEncoder une fois, à la bonne frontière
JSON correct mais page HTML incorrecteTypes de contenu et encodages différentsComparer les en-têtes, les modèles et l’échappement de sortie

Une méthode fiable pour le texte international

Créez un petit jeu de régression avec des accents, du texte non latin, un emoji, un signe plus et une esperluette. Vérifiez-le à chaque étape : formulaire, serveur, base, réponse API, DOM et fichier exporté. Conservez les résultats attendus sous forme de chaînes Unicode et gardez les octets bruts pendant le diagnostic.

Si votre application envoie aussi du texte multilingue à un modèle d’IA, l’encodage et la tokenisation sont deux sujets distincts. Le compteur de tokens IA illustre le découpage en tokens, mais ne valide ni ne répare le transport UTF-8.

Pour aller plus loin

Pour un premier diagnostic, examinez une valeur défectueuse séparément dans l’encodeur et décodeur HTML et l’encodeur et décodeur d’URL. Identifier sa représentation permet souvent de trouver rapidement la bonne correction.

L'offre Premium arrive bientôt

Un abonnement Premium facultatif permettant de supprimer les publicités est prévu. Les abonnements ne sont pas encore disponibles.

Vous pouvez continuer à utiliser nos outils gratuits sans compte.