- RFC 9839 définit clairement les caractères Unicode problématiques pouvant apparaître dans des champs texte lors du développement logiciel
- Cette RFC traite des problèmes liés au manque de cohérence dans le traitement de ces caractères selon les langages et bibliothèques
- La 9839 propose trois sous-ensembles moins problématiques, utilisables de manière optionnelle
- Elle est plus simple et plus facile à appliquer que le framework PRECIS existant
- Une bibliothèque Go pour RFC 9839 a également été publiée afin d’en faciliter l’usage concret
Contexte et aperçu de la RFC 9839
- Unicode est utilisé comme standard dans presque tous les traitements de données textuelles
- Mais dans la conception réelle de structures de données ou de protocoles, autoriser tous les caractères Unicode peut poser problème
- Paul Hoffman et l’auteur ont soumis à l’IETF un draft individuel afin de proposer des critères clairs face à des problèmes Unicode récurrents
- Après deux ans de discussions, le texte a été adopté comme standard officiel et publié sous la forme de la RFC 9839
- Ce document décrit en détail les types de caractères problématiques, les raisons pour lesquelles ils posent problème (techniques et normatives), ainsi que trois sous-ensembles parmi lesquels les utilisateurs peuvent choisir
Principaux points de la RFC 9839
- Il s’agit d’un document de référence essentiel pour la conception de champs texte dans les environnements logiciels et réseau
- La RFC 9839 fait 10 pages, ce qui en fait un document plutôt concis parmi les standards de l’IETF
- Elle est principalement expliquée de manière accessible pour les développeurs logiciels et les ingénieurs réseau
Exemples de caractères Unicode problématiques
- Par exemple, le champ
usernamed’un JSON peut contenir une chaîne comme celle-ci{ "username": "\u0000\u0089\uDEAD\uD9BF\uDFFF" } - Problèmes associés à chaque point de code
U+0000: caractère NULL sans signification, qui perturbe le fonctionnement de certains langages de programmationU+0089: code de contrôle C1 (CHARACTER TABULATION WITH JUSTIFICATION), dont le comportement est complexe et peu cohérentU+DEAD: surrogate non apparié, problème issu des limites de l’UTF-16, produisant des données indésirables\uD9BF\uDFFF(en réalitéU+7FFFF) : noncharacter, dont l’échange est interdit par la norme
- De tels points de code rendent un traitement cohérent impossible dans les structures de données et protocoles, et provoquent des erreurs inattendues
- La RFC 9839 définit officiellement ces caractères problématiques et indique clairement les catégories à exclure
Conception et limites de JSON
- Ce n’est pas la responsabilité de Doug Crockford, le créateur de JSON
- JSON a été conçu à une époque où Unicode n’était pas encore suffisamment mature, ce qui a empêché de restreindre strictement le jeu de caractères
- Comme il n’est plus possible de modifier le standard aujourd’hui, il faut exclure empiriquement les caractères problématiques
Différences avec le framework PRECIS de l’IETF
- Avant la RFC 9839 de 2025, l’IETF proposait déjà plusieurs standards, dont la RFC 8264 (PRECIS Framework)
- Ce framework traite en détail des méthodes de préparation, d’application et de comparaison des chaînes internationalisées
- Avec ses 43 pages, il est très complet à la fois dans ses explications de fond et dans ses solutions
- PRECIS dépend fortement de la version d’Unicode, et présente l’inconvénient d’être complexe et difficile à mettre en œuvre
- RFC 9839 est concise, centrée sur l’aspect pratique, et facile à adopter rapidement lors de la définition de nouveaux protocoles
Sous-ensembles de la RFC 9839 et exemples d’usage
- La 9839 propose trois sous-ensembles réalistes (
scalars,XML,assignables) - Chaque sous-ensemble diffère légèrement dans l’étendue des caractères problématiques à exclure
- Voici un résumé du tableau expliquant comment les principaux formats de données et les sous-ensembles de la RFC 9839 traitent les caractères problématiques
- Certains formats comme CBOR, TOML, XML, YAML excluent partiellement les surrogates ou les caractères de contrôle
- I-JSON exclut les surrogates et les noncharacters
- JSON standard et Protobufs ne les excluent pas
- XML et YAML, en raison des caractéristiques de leur charset, n’excluent que partiellement les noncharacters et codes de contrôle
- Remarque : XML et YAML n’excluent pas les noncharacters en dehors du Basic Multilingual Pane
Bibliothèque RFC 9839 pour Go
- Une petite bibliothèque Go prenant en charge la validation des caractères pour les trois sous-ensembles de la RFC 9839 a été publiée
- Elle a été suffisamment testée, même si les optimisations sont encore en cours
- Les tests en conditions réelles et les retours d’expérience sont bienvenus
Importance de la RFC 9839 et processus de travail
- La RFC 9839 a été publiée officiellement après plusieurs cycles de retours avec les co-auteurs et plus de 15 révisions de drafts
- Grâce aux discussions et contributions de nombreux experts de la communauté, elle est devenue un document bien plus abouti que sa première version
- Les contributeurs sont mentionnés dans la section “Acknowledgements”
Expérience d’une soumission RFC individuelle
- La RFC 9839 a été menée sous forme de soumission individuelle (individual submission)
- Par rapport à la méthode traditionnelle via un Working Group, la charge en efforts et en procédures est plus lourde
- À la lumière de l’expérience acquise dans les Working Groups, la méthode traditionnelle est plus efficace et davantage recommandée
1 commentaires
Avis Hacker News
Je pense clairement que certains caractères posent problème, mais j’ai l’impression que le pire scénario, c’est quand les concepteurs de structures de données ou de protocoles prennent l’habitude de ne pas vouloir autoriser arbitrairement toutes sortes de caractères, même correctement échappés. Par exemple, à mon avis, la validation d’un nom d’utilisateur doit être gérée à une autre couche. Il s’agit de vérifier qu’un nom d’utilisateur fait moins de 60 caractères, d’interdire les emoji ou les caractères zalgo, d’interdire les octets nuls, etc., puis de renvoyer une erreur appropriée via l’API. Je ne veux pas que ce genre de problème fasse échouer l’étape de parsing JSON au lieu d’une validation préalable. Bien sûr, certaines classes de caractères sont manifestement inadaptées dans un nom d’utilisateur. Mais si j’envoie un fichier texte réellement utilisé contenant des tabulations, etc., j’attends que tout ce qui peut être traité par le type
stringutf8 de mon langage puisse être encodé. Les octets nuls, en particulier, ont de nombreux usages et apparaissent réellement assez souvent en JSON. Cela dit, s’il faut n’utiliser qu’un ensemble restreint de caractères Unicode « normaux », il vaut mieux qu’il existe un standard plutôt que chacun fabrique son mini-standard. En conclusion, l’idée elle-même me paraît bonne, mais les arguments donnés dans le billet de blog ne me convainquent pas vraimentEn 2025, je pense que les seules représentations de chaînes réellement défendables dans un protocole bas niveau sur le fil sont les suivantes
Sérieusement, j’aimerais qu’on n’utilise pas les caractères C0 (à l’exception du saut de ligne et, à contrecœur, HT) ni les caractères C1 dans les fichiers texte brut. Je comprends qu’on veuille stocker des choses comme du balisage de couleur ANSI, mais dans ce cas ce n’est pas vraiment du texte brut, c’est une sorte de format de balisage textuel. Semblable à Markdown, sauf qu’il utilise un encodage de la plage C0. On ne peut pas dire que c’est du texte brut juste parce que les données s’affichent joliment avec une commande comme
cat. Je reconnais qu’il existe beaucoup de formats de balisage encodés en texte brut pour des raisons d’interopérabilitéJe pense que l’idée selon laquelle commencer à interdire des groupes arbitraires de caractères dans des structures de données et des protocoles serait le pire scénario est elle-même une vision déconnectée de la réalité. Le vrai pire scénario, c’est une faille logicielle dans un parseur ou ailleurs qui entraîne une compromission de sécurité
Je me demande s’il existe vraiment des systèmes qui autorisent UTF-8 dans les noms d’utilisateur. Il me semble évident que tous les identifiants manipulés ou évalués programmatiquement (identifiants de connexion, mots de passe, etc.) doivent impérativement être en ASCII. Pas en ISO-8859-1, uniquement en ASCII. Unicode n’est pas adapté à ce type d’usage. Pour afficher un nom d’utilisateur, peu importe, mais comme identifiant pour une connexion système, les encodages non ASCII devraient être interdits sans exception. Même les logiciels de clavier ne peuvent pas garantir à eux seuls une cohérence de représentation visuelle de l’UTF-8 dès qu’on sort de l’ASCII, et cela devient encore plus confus selon l’OS et la configuration. Rien ne garantit non plus que les binaires et les IA futures interprétant Unicode seront d’accord. Et sur la question de la cohérence, ni la RFC 9839 ni l’article ne disent clairement si des cas comme IVS ou la normalisation (NFC/NFD/NFKC/NFKD) sont explicitement dans le périmètre ou hors périmètre. On dirait même que la section sur les objectifs manque complètement. Il n’y a guère que des allusions vagues du genre « il existe des points de code non-caractères »
Je me demande pourquoi il faudrait interdire les emoji dans les noms d’utilisateur
Je tiens à préciser que l’IETF n’a pas attendu 2025 pour traiter le Bad Unicode. Cela fait déjà longtemps que RFC 8264: PRECIS Framework traite largement de divers problèmes liés au Bad Unicode. Il est aussi utile de consulter les RFC associées, comme RFC 8265 (lien), 8266 (lien), etc. En général, les éléments comme les mots de passe, qui peuvent changer la direction du texte ou être encodés différemment selon le périphérique de saisie, ne devraient pas être utilisés dans les noms d’utilisateur/mots de passe. On peut gérer cela de manière sûre via ces profils RFC. Pour ce genre d’objectif, le principe de « failing closed » (bloquer plus strictement) est plus sûr. Même si de nouveaux emoji apparaissent, je préfère les interdire et rester conservateur plutôt que de les autoriser dans les noms d’utilisateur et impacter toutes les pages
Unicode a clairement de « bons » côtés, mais c’est décevant de devoir savoir qu’il existe des caractères à exclure exceptionnellement. C’est le résultat d’une tentative de prise en charge trop large des systèmes d’écriture, devenue excessivement complexe. C’est fatigant de devoir toujours garder en tête quels caractères exigent un traitement spécial. Du coup, je traite les chaînes Unicode comme une unité de données à part entière. Je les reçois, je les stocke, je les rends, je compare leur égalité, mais je n’essaie pas d’interpréter leur contenu. Même concaténer ou manipuler des chaînes me paraît peu rassurant
Unicode ressemble à un abîme sans fin de trivia et de mauvaises décisions. Par exemple, les RFC associées mettent en garde contre les anciens caractères de contrôle ASCII (risque de confusion à l’affichage), mais ne disent rien des caractères de changement de direction qui posent de graves problèmes de sécurité, comme les Explicit Directional Overrides
Exemple simple : si la première chaîne se termine par un modificateur d’emoji orphelin et que la seconde commence par un emoji modifiable, on a déjà un problème. Et plus on ajoute de cas complexes, plus le problème grossit
La complexité est réelle, mais parmi ces éléments, les surrogates et les codes de contrôle ne viennent pas de besoins liés aux systèmes d’écriture ; ce sont des bizarreries de conception héritées du passé
Unicode est gênant, mais je pense qu’il l’est moins que les autres anciens standards d’encodage
Je pense que la plupart des problèmes peuvent déjà être traités en rejetant les séquences d’octets UTF-8 invalides ou en renvoyant globalement une erreur. Par exemple, les surrogates sont déjà illégaux en UTF-8, donc un langage qui utilise utf-8 devrait renvoyer une erreur pour ce type de séquence. Ce qui pose réellement problème, ce sont plutôt certains « points de code » problématiques (non imprimables, etc.). Il est nettement plus utile de traiter cela comme un concept distinct des séquences d’octets illégales
Unicode définit déjà la catégorie de chaque point de code (General Category) pour classer les types de caractères inhabituels. On peut consulter cet article Wikipédia. Par exemple, en Python,
unicodedata.category(chr(0))renvoie"Cc"(control), etunicodedata.category(chr(0xdead))renvoie"Cs"(surrogate)Exclure tous les caractères de « legacy control », non seulement en littéral mais aussi dans les chaînes échappées (par ex.
"\\u0027"), me paraît excessif. C1 est peu utilisé, donc cela passe, mais certains caractères C0 ont de vrais cas d’usage. escape, EOF, NUL, etc. ont encore des usages évidentsJe pense que certains caractères C0 un peu particuliers (comme U+001E Record Separator) sont très utiles dans les flux de données. On peut les interdire dans les documents, mais ils restent utiles dans les données en flux
J’ai déjà vu le caractère form feed (U+000C) dans du code source. Emacs le prend en charge depuis longtemps pour la navigation par page, donc ce genre de caractère peut s’y retrouver
Je ne pense pas qu’Unicode soit « bien ». Quel que soit le jeu de caractères, les types de caractères réellement utilisables (caractères de contrôle, caractères graphiques, longueur maximale, etc.) doivent de toute façon être décidés selon chaque application. Tenter d’inclure ou d’exclure cela au niveau de JSON ou d’autres formats n’a pas beaucoup d’effet. Il peut parfois être utile de donner un nom à un certain sous-ensemble (ou sur-ensemble) d’Unicode, d’ASCII ou d’un autre charset, mais il ne faut pas croire que ce soit un bon choix pour tout le monde. La RFC 9839 donne des noms à certains sous-ensembles Unicode, mais cela ne garantit pas qu’ils soient automatiquement les bons pour le service que je vais construire. Ma conclusion, c’est qu’il faut aussi envisager de ne pas utiliser Unicode du tout, ou de ne pas l’imposer
Je me demande s’il faut contrôler l’entrée, ou bien encapsuler les données dans un type capable d’afficher en sécurité des entrées non fiables (pour le web + logs + debug)
J’aimerais qu’il existe dans le standard une limite sur le nombre de valeurs scalaires Unicode pouvant entrer dans une unité graphique. La dernière fois que j’ai vérifié (il y a quelques années), le standard n’imposait pas une telle limite, et se contentait de recommander, pour les applications de streaming, de limiter une unité graphique à 128 octets. Si cette limite était clairement définie dans le standard, l’implémentation serait bien plus simple et il n’y aurait pas de restriction inutile
J’ai déjà rencontré des cas où un programme cassait parce qu’il partait du principe qu’« il n’y a pas de caractères de contrôle » (alors que form feed sert notamment à séparer des pages, escape à des usages terminaux, etc.). L’hypothèse « tout est en UTF-8 » casse aussi parfois (anciens fichiers de données, logs, etc.). Si on ne fait pas de traitement textuel significatif, le mieux est de transmettre les données comme une simple séquence d’octets, sans en modifier le contenu. Mais à cause de Microsoft Windows, il faut parfois faire transiter des séquences de
char16_t. UTF-16 diffère fondamentalement de UTF-8 en entrée/sortie. Lors de la conversion, il faut utiliser respectivement WTF-8 (UTF-16) et la surrogate escape (UTF-8) au moment du passage des données externes vers la représentation interne. On ne peut pas mélanger les deux approches