FlowingDev

Les fantômes d'Unicode : Ces caractères secrets qui sèment le chaos dans votre texte

Découvrez comment les caractères Unicode invisibles et les lettres qui se ressemblent peuvent casser votre code, créer des risques de sécurité et engendrer des bugs subtils à vous rendre fou.

Essayer l'outil: Inspecteur de Texte Unicode

En une phrase

Unicode est le standard universel pour le texte qui rend notre internet mondial possible, mais son immensité inclut des caractères invisibles, des sosies et d'autres bizarreries qui peuvent transformer une simple chaîne de caractères en un véritable champ de mines de bugs.

Le problème que ça résout

Dans la soupe primordiale de l'informatique, la vie était simple. Nous avions l'ASCII. Il nous donnait 127 caractères : des lettres majuscules, des lettres minuscules, des chiffres, de la ponctuation et quelques codes de contrôle. C'était propre, bien rangé, et ça tenait parfaitement dans un seul octet. C'était aussi désespérément anglocentrique. Si vous vouliez écrire ¿Qué pasa?, 你好 ou спасибо, pas de chance.

Cela a mené à une ère chaotique connue sous le nom de « l'enfer des pages de code » (codepage hell). Les ordinateurs de différentes régions utilisaient différents jeux de caractères sur 8 bits qui étendaient l'ASCII. Un document écrit en Windows-1252 (Europe de l'Ouest) se transformait en un méli-mélo de caractères illisibles (ce qu'on appelle affectueusement du mojibake) lorsqu'il était ouvert sur un système utilisant KOI8-R (Russe). Partager du texte à l'international, c'était comme jouer au téléphone arabe avec une ligne défectueuse.

Puis, le Consortium Unicode est arrivé sur son cheval blanc. Sa mission : un jeu de caractères unique et unifié pour tous les systèmes d'écriture, modernes et historiques. Un standard pour les gouverner tous. Chaque caractère — de 'A' à '€' en passant par l'émoji 'Pile de Caca' (💩) — aurait son propre numéro unique, un « point de code ».

Ce fut une réussite monumentale qui alimente notre monde moderne. Mais cette grande unification a créé son propre lot de problèmes merveilleusement geeks. Pour s'adapter aux complexités du langage humain, Unicode a dû inclure plus que de simples lettres visibles. Il lui fallait :

  • Des caractères combinants : Un accent (´) qui est un caractère distinct, conçu pour être placé au-dessus d'un autre (e).
  • Des caractères de largeur nulle : Des marqueurs invisibles qui peuvent suggérer un saut de ligne (U+200B Zero-Width Space) ou coller des émojis ensemble (U+200D Zero-Width Joiner).
  • Des caractères ambigus : Des dizaines de sortes d'espaces, de tirets et de guillemets différents.
  • Des sosies (homoglyphes) : La lettre latine a et la lettre cyrillique а semblent identiques dans de nombreuses polices, mais pour un ordinateur, elles sont aussi différentes que a et b.

Soudain, ce que vous voyez n'est pas ce que vous obtenez. Une chaîne qui ressemble à "chat" pourrait contenir un caractère invisible, ce qui porterait sa longueur à 4, et non 3. Un nom de variable safeString pourrait cacher un 'α' grec au lieu d'un 'a' latin. C'est le problème qu'un inspecteur de texte résout : il enfile des lunettes à rayons X pour vous montrer la vérité brute et non filtrée de ce dont votre chaîne est réellement composée, révélant les fantômes cachés dans la machine.

Comment ça marche sous le capot

Pour disséquer une chaîne de caractères, nous devons comprendre ses trois couches fondamentales : le caractère abstrait, sa représentation en octets, et les trucs bizarres qui se cachent entre les deux.

Points de code : L'adresse d'un caractère

À la base, Unicode n'est qu'une liste géante. Chaque caractère se voit attribuer un numéro unique appelé point de code. C'est l'adresse permanente du caractère dans l'univers Unicode. On les écrit en utilisant la notation U+XXXX, où XXXX est un nombre hexadécimal.

  • U+0041 est A (Lettre majuscule latine A)
  • U+00E9 est é (Lettre minuscule latine E avec accent aigu)
  • U+20AC est € (Symbole Euro)
  • U+1F4A9 est 💩 (Pile de Caca)

Un point de code est une idée abstraite. Ce n'est pas un octet ou une police de caractères. C'est juste un nombre mappé à un caractère. La façon dont nous stockons ce nombre est une autre histoire.

Encodages : Stocker les points de code en octets

On ne peut pas enregistrer un « point de code » dans un fichier. Il faut enregistrer des octets. Un encodage est un ensemble de règles pour convertir une séquence de points de code en une séquence d'octets.

Le roi des encodages aujourd'hui, c'est l'UTF-8. Son génie réside dans sa conception à longueur variable.

  • Pour tout caractère qui se trouve également dans le jeu ASCII original (comme A, U+0041), l'UTF-8 utilise un seul octet — exactement le même octet que l'ASCII. Cela l'a rendu rétrocompatible et facile à adopter.
  • Pour les autres caractères, il utilise une séquence de 2, 3 ou 4 octets. Les premiers bits de chaque octet agissent comme des signaux, indiquant à l'ordinateur combien d'octets font partie du caractère actuel.

Jetons un œil à ¡Hola! :

Caractère Point de code Octets UTF-8 (Hex)
¡ U+00A1 C2 A1
H U+0048 48
o U+006F 6F
l U+006C 6C
a U+0061 61
! U+0021 21

Un inspecteur de texte effectue ce processus inverse. Il lit les octets bruts de votre chaîne, les interprète selon un encodage (généralement UTF-8) et vous montre la séquence de points de code qui la compose.

Les trublions invisibles

C'est là que ça devient amusant. Le travail principal d'un inspecteur de texte est de mettre en lumière les caractères qui ne ressemblent à rien.

Catégorie Exemple de caractère & Point de code Le but sournois
Espace de largeur nulle U+200B Ne ressemble à rien. Un caractère invisible qui suggère un bon endroit pour un saut de ligne dans un mot long ou une URL.
Liant de largeur nulle U+200D La super-glu pour les émojis. 👨 + ZWJ + 👩 + ZWJ + 👧 = 👨‍👩‍👧. Il joint des caractères qui normalement ne se connecteraient pas.
Espace insécable U+00A0 Ressemble à un espace normal, mais interdit un saut de ligne. Utile pour des choses comme 100 km ou Dr. Strange.
Marque combinante U+0301 (Accent aigu combinant) Un accent (´) qui est un caractère à part entière. Il est dessiné par-dessus le caractère précédent.
Sosie (Homoglyphe) U+0430 (Lettre minuscule cyrillique A) Semble identique au a latin (U+0061) dans la plupart des polices, mais c'est un point de code complètement différent.

Cela nous amène au concept de normalisation. Le caractère é peut être représenté de deux manières :

  1. Forme composée (NFC) : Un seul point de code, U+00E9.
  2. Forme décomposée (NFD) : Deux points de code, e (U+0065) suivi de l'accent combinant ´ (U+0301).

Visuellement, ils sont identiques. Mais pour un ordinateur effectuant une simple comparaison octet par octet, "\u00E9" n'est pas égal à "e\u0301". Un inspecteur de texte peut révéler quelle forme vous avez et vous aider à convertir entre elles.

Histoires vécues

La catastrophe du copier-coller

Un développeur junior travaille tard, essayant de corriger un bug. Il trouve une solution sur un blog, une seule ligne de JavaScript : const timeout = 100;. Il la copie, la colle dans son éditeur de code et sauvegarde. Tout le build de l'application échoue avec une SyntaxError: Invalid or unexpected token cryptique.

Il regarde la ligne fixement. Elle est parfaite. Il la réécrit manuellement. Ça marche. Il colle à nouveau la ligne copiée. Ça casse. Est-il en train de devenir fou ? Après une heure à s'arracher les cheveux, un développeur senior plisse les yeux sur la ligne et dit : « Colle ça dans un inspecteur de texte. »

Le résultat : const[U+00A0]timeout[U+00A0]=[U+00A0]100;. Le CSS du blog avait enjolivé le code, remplaçant les espaces standard (U+0020) par des espaces insécables (U+00A0). Ils semblent identiques, mais le moteur JavaScript n'a aucune idée de ce qu'est un « espace insécable » dans ce contexte.

Leçon : Le texte copié depuis le web (ou des PDF, ou des documents Word) est coupable jusqu'à preuve du contraire. Il est souvent contaminé par des guillemets « intelligents » (smart quotes), des espaces non standard et d'autres gremlins invisibles.

L'utilisateur fantôme qui ne pouvait pas se connecter

Un nouvel utilisateur s'inscrit à un service avec le nom François. Le système crée joyeusement le compte. Le lendemain, François essaie de se connecter. Il tape son nom, appuie sur Entrée... « Nom d'utilisateur ou mot de passe invalide. » Il réessaie, soigneusement. Même résultat. Il est bloqué.

Dans la base de données, son nom était stocké en utilisant des caractères décomposés : F, r, a, n, c, o, i, s et une U+0327 (Cédille combinante). Le formulaire de connexion, cependant, envoyait le caractère précomposé ç (U+00E7). Visuellement, c + ¸ est identique à ç. Mais le serveur effectuait une simple comparaison de chaînes : François (décomposé) n'est pas égal à François (composé). La requête WHERE username = '...' a échoué.

Leçon : Normalisez toujours les entrées utilisateur dans une forme cohérente (la NFC est le choix le plus courant) avant de les stocker dans une base de données ou d'effectuer des comparaisons.

Le domaine trompeur

Un employé reçoit un e-mail qui semble provenir du service informatique de son entreprise. « Mise à jour de sécurité requise : Veuillez vous connecter à microsоft.com/update pour sécuriser votre compte. » Le lien semble légitime. Le nom de domaine est juste là. Il clique, saisit ses identifiants sur une page qui ressemble exactement à la vraie, et continue sa journée.

Il vient de se faire hameçonner (phishing). Le domaine n'était pas microsoft.com. C'était microsоft.com. Le deuxième 'o' n'était pas le 'o' latin (U+006F) mais le 'о' cyrillique (U+043E). C'est une attaque d'homographes IDN. Pour l'œil humain, c'est une contrefaçon parfaite. Pour le système DNS, c'est une adresse complètement différente, menant au serveur de l'escroc.

Leçon : Soyez extrêmement méfiant envers les identifiants qui mélangent les jeux de caractères. Bien que les navigateurs modernes aient quelques protections, le principe des attaques par homographes est une menace constante dans les noms d'utilisateur, les règles de validation et partout où les chaînes sont utilisées pour la sécurité.

Erreurs et pièges courants

  • Supposer que string.length compte les caractères. Dans de nombreux langages (comme JavaScript), cela compte les unités de code, pas les caractères perçus. Par exemple, "👍🏽".length vaut 4 en JS, car il est composé de l'émoji « pouce levé » (👍, 2 unités) et du « modificateur de teint de peau moyen » (🏽, 2 unités).
  • Traiter tous les espaces de la même manière. Exécuter trim() sur une chaîne ne supprimera pas un U+200B Espace de largeur nulle caché au milieu. Une regex pour \s+ pourrait ne pas attraper l' U+00A0 Espace insécable. Vous devez savoir ce que vous chassez.
  • Ignorer la normalisation. Comme vu avec François, comparer des chaînes qui semblent identiques mais ont des représentations en octets sous-jacentes différentes est un bug classique et frustrant. string1.normalize() === string2.normalize() est votre ami.
  • Faire confiance à vos yeux. Vous ne pouvez pas déboguer ces problèmes en regardant le texte rendu. Un inspecteur de texte qui montre les points de code individuels et leurs noms est le seul moyen d'être sûr de ce qui s'y trouve vraiment.
  • Créer son propre nettoyeur de « mauvais caractères ». Essayer d'écrire une regex pour supprimer tous les caractères « bizarres » est une entreprise vaine. Soit vous en manquerez, soit, pire, vous supprimerez des caractères légitimes nécessaires pour d'autres langues, massacrant les noms et les textes de vos utilisateurs.

Pourquoi vous devriez garder ça sur votre radar

Vous devriez dégainer un inspecteur de texte Unicode chaque fois qu'un texte se comporte de manière inattendue. C'est un outil de débogage indispensable. Pensez-y quand :

  • Une comparaison de chaînes échoue alors qu'elle devrait « évidemment » réussir.
  • Vous obtenez une erreur de syntaxe sur une ligne de code qui semble parfaitement valide.
  • Vous validez des entrées fournies par l'utilisateur comme des noms d'utilisateur, des e-mails ou des URL.
  • Vous travaillez avec des données provenant de plusieurs systèmes, surtout s'ils impliquent différentes langues.
  • Vous avez besoin de comprendre pourquoi string.length vous donne un « mauvais » chiffre.
  • Vous construisez un système qui doit être robuste, sécurisé et fonctionner pour un public mondial.

En bref, chaque fois qu'un ordinateur et un humain ne sont pas d'accord sur ce que dit un morceau de texte, l'ordinateur a probablement raison sur les octets, et un inspecteur de texte est votre traducteur.

Pour aller plus loin

Théorie bouclée. Place à la pratique — 100 % dans ton navigateur.

Essayer l'outil: Inspecteur de Texte Unicode