La maison des horreurs de l’encoding


Ah, l’encoding, le truc que tout le monde veut mettre sous le tapis. Il faut dire que c’est dur à gérer. En fait tellement dur que:

  • Les logiciels et pages web continuent parfois d’afficher des ?? en 2017. Tous les langages laxistes laissent des données corrompues plutôt que d’avertir le codeur qui du coup n’apprend jamais à faire les choses correctement.
  • PHP a littéralement abandonné la version 6 car impossible de trouver une solution propre avec leur design.
  • Perl a mis 10 ans à sortir la version 6 qui gère proprement l’unicode.
  • NodeJS ignore juste la question, fout tout en utf8 et vous dit de télécharger une lib externe si vous voulez gérer autre chose.

Tout ce bordel amène les devs à essayer d’ignorer le problème le plus longtemps possible. Ca marche assez bien pour les anglophones car leur environnement est assez homogène, orienté ASCII, et certains peuvent faire une très belle carrière en restant joyeusement ignorant.

Ca marche beaucoup moins bien pour les européens, et pas du tout pour le monde arabe et asiatique. Néanmoins, pas besoin de chercher bien loin pour trouver des échecs critiques.

Naviguez tranquillement sur un site espagnol a priori joli, moderne, utilisant des tildes et tout ce qu’il faut. Maintenant regardez la requête HTTP, vous noterez que le serveur n’indique pas le charset du contenu. Fort heureusement dans le HTML vous trouvez:

<meta http-equiv="Content-Type" content="ISO-8859-1">

Nickel, récupérons le texte du bouton “Ver más ideas”:

>>> import requests
>>> res = requests.get('http://www.airedefiesta.com/76-pinatas-y-chuches.html') 
>>> data = res.content.split(b'http://www.airedefiesta.com/ideas.html?c=76">')[1].split(b'</a>')[0]
>>> data
b'Ver m\xc3\xa1s ideas'

Une suite de bits comme maman les aime. On décode:

>>> data.decode('ISO-8859-1')
'Ver más ideas'
Chenille dans labyrinthe

Et puis on ne voudrait pas que vous arriviez au château trop vite

Enfer et sodomie ! Le charset déclaré n’est pas celui utilisé. Tentons un truc au hasard:

>>> data.decode('utf8')
'Ver más ideas'

Bref, en 2017, on se touche la nouille pour savoir qui a son architecture multi-services load balancée web scale à base de NoSQL, de containers orchestrés et de serveurs asynchrones. Mais pour afficher du texte y a plus personne hein…

Vous croyez que ce ne sont que les amateurs qui font ces erreurs. Naaaaaaaaa. Par exemple le standard pour les fichiers zip a une vision très… hum… personnelle du traitement de l’encoding des noms de fichier.

L’encoding, c’est la raison majeur de l’incompatibilité de Python 2 et 3, mais aussi un signe de la bonne santé de la techno puisque c’est un des rares vieux langages (je rappelle que Python est plus vieux que Java) à gérer la chose correctement. A savoir:

  • Avoir un type haut niveau qui fait abstraction de l’encoding pour le texte.
  • Forcer le développeur à spécifier l’encoding pour les entrées et les sorties.
  • Eviter toute conversion automatique.
  • Avoir de l’utf8 par défaut là où ça a du sens.
  • Lever des erreurs plutôt que de corrompre les données.
  • Avoir une API unifiée autour de la notion de “codec” qui marche pour le FS, le réseau, les chaînes internes, etc.

Python n’est pas parfait pour autant. Par exemple il garantit un accès 0(1) indexing sur les strings, ce qui à mon sens est inutile. Swift a un meilleur design pour ce genre de choses. Mais globalement c’est quand même super bon.

Si ne savez toujours pas comment ça marche, on a évidement un tuto pour ça.

Alors pourquoi l’encoding c’est un truc compliqué ?

Et bien parce que comme pour le temps ou l’i18n, ça touche à la culture, au langage, à la politique, et on a accumulé les problèmes au fil des années.

A solid dick from an iron man

Mais je vous jure ça avait du sens y a 40 ans !

Par exemple, parlons un peu d’UTF.

Vous savez, on vous dit toujours d’utiliser utf8 partout pour être tranquille…

Mais déjà se pose la question : avec ou sans BOM ?

Le BOM, c’est une suite d’octets qui indique en début de fichier qu’il contient de l’UTF. Si ça à l’air pratique, c’est parce que ça l’est. Malheureusement, celui-ci n’est pas obligatoire, certaines applications le requièrent, d’autres l’ignorent, et d’autres plantent face au BOM. D’ailleurs, le standard unicode lui-même ne le recommande pas:

Use of a BOM is neither required nor recommended for UTF-8

Ca aide vachement à faire son choix.

Perso je ne le mets jamais, sauf si je dois mélanger des fichiers de différents encodings et les différencier plus tard.

Mais Powershell et Excel par exemple, fonctionnent parfois mieux si vous leur passez des données avec le BOM :)

Si vous avez un peu creusé la question, vous savez qu’il existe aussi UTF16 (par défaut dans l’API de Windows 7 et 8 et les chaînes de .NET), UTF32 et UTF64. Ils ont des variantes Big et Little Endians, qui ne supportent pas le BOM, et une version neutre qui le supporte, pour faciliter la chose.

Bien, bien, bien.

Mais saviez-vous qu’il existe aussi UTF-1, 5 et 6 ? Si, si. Et UTF9 et UTF18 aussi, mais sauf que eux ce sont des poissons d’avril, parce que les gens qui écrivent les RFC sont des mecs trop funs en soirées.

Que sont devenus ces derniers ? Et bien ils ont été proposés comme encoding pour l’internationalisation des noms de domaine. UTF5 est un encoding en base 32, comme son nom l’indique. Si, 2 puissance 5 ça fait 32. Funs en soirée, tout ça.

Néanmoins quelqu’un est arrivé avec une plus grosse bit(e), punycode, en base 36, et a gagné la partie. J’imagine que les gens se sont dit qu’utiliser base64 était déjà trop fait par tout le monde et qu’on allait pas se priver de cette occasion fabuleuse de rajouter un standard.

Standard qui ne vous dispense pas, dans les URLs, d’encoder DIFFÉREMMENT ce qui n’est pas le nom de domaine avec les bons escaping. Et son lot de trucs fantastiques. Encoding qui est différent pour les valeurs de formulaire.

Python supporte par ailleurs très bien tout ça:

>>> 'Père noël'.encode('punycode')
b'Pre nol-2xa6a'
>>> import urllib
>>> urllib.parse.quote('Père Noël')
'P%C3%A8re%20No%C3%ABl'
>>> urllib.parse.quote_plus('Père Noël')
'P%C3%A8re+No%C3%ABl'

En plus, si Punycode est l’encoding par défaut utilisé dans les noms de domaine, c’est donc aussi celui des adresses email. Ce qui vous permettra de profiter des interprétations diverses de la spec, comme par exemple le retour de la valeur d’un HTML input marqué “email”, qui diffère selon les navigateurs.

Président faisant un fuck dans idiocracy

If you don’t encode in Tarrlytons…fuck you!

Pourquoi je vous parle des adresses emails tout à coup ? Ah ah ah ah ah ah ah !

Mes pauvres amis.

Je ne vous avais jamais parlé d’utf7 ?

Non, je ne me fous pas de votre gueule. Je suis très sérieux.

Figurez-vous que le format email MIME accepte l’utilisation d’utf7 en lieu et place de base64.

Mais ce n’est pas ça le plus drôle.

Y a mieux, je vous jure.

UTF7 est en effet l’encoding par défaut pour IMAP, particulièrement les noms des boîtes aux lettres. Vous savez, “INBOX”, “Spams” et “Messages envoy&AOk-s” ;)

Or comme l’enculerie ne serait pas aussi délicieuse sans un peu de sable…

La version utilisée maintenant (et pour toujourssssssss) par IMAP est une version d’UTF7 non standard et modifiée.

Pourquoi ? Ben parce qu’allez-vous faire foutre.

The choosen one, crying

The choosen one would soon realize that some things survived outside of the vault. Like bad UI and terrible IT standards. And his ‘science’ skills is at 42% and life sucks.

Au final je n’ai fait que parloter d’UTF, mais souvenez-vous que:

>>> import encodings
>>> len(encodings.aliases.aliases)
322

Donc on n’a fait qu’effleurer la surface de l’anus boursouflé de la mouche.

J’espère que la nuit, à 3h du mat, lorsque votre prochaine mise en prod agonisera sur un UnicodeDecodeError, vous penserez à moi et pendant un instant, un sourire se dessinera sous vos larmes.

Ecriture extra-terrestre de the arrival

Militaire : Votre avis ? – Unicode Consortium : Tuez les tous.

13 thoughts on “La maison des horreurs de l’encoding

  • Toniob

    “Pyhton n’est pas parfait pour autant” à remplacer par “Python n’est pas parfait pour autant”.

  • Sam Post author

    Oh, les petits elfs correcteurs sont revenus :) Merci petit elfs.

  • Flux de bite

    je rappelle que Python et plus vieux que Java => est plus vieux

    Pyhton n’est pas parfait pour autant. => Python

  • Cym13

    Et l’on a pas évoqué les difficultés de concept pour manipuler l’encoding lui-même en matière de bytes, de code points et de graphèmes…

    Par contre j’ai été désolé d’apprendre que la “solid dick” d’iron man est fausse en fait

  • dineptus

    Pendant ce temps en 2016 Google App Engine ne supporte toujours pas Python 3 sauf en “beta” aux US.

  • Dinosaure

    L’affaire pour l’UTF-7 est un peu plus complexe en vérité car en effet ce dernier a été pensé pour optimiser le poids d’un email mais le standard précise que ce dernier doit être explicitement spécifié (notamment avec le Content-Type). Le logiciel traitant les emails devrait, à priori s’en sortir assez simplement – car c’est un comportement spécifié.

    Après, dans la réalité, c’est autre chose … Le problème peut concerner l’UTF-7 qui reste difficile à gérer mais le côté européen n’est pas en reste en considérant de facto que l’ISO-8859 est l’encodage par défaut alors que la spécification ne le dit pas – pas mal d’emails français font cette erreur (et des emails venant de grosses entreprises).

    Heureusement, les mecs de la spécification ont décidé une bonne fois pour toute de considérer l’UTF-8 par défaut désormais avec la RFC 6532 (cette dernière ayant été en partie produite, bizarrement, par des chinois). Pour le coup, il est vrai que l’email est un véritable bordel où l’on peut facilement compter une dizaine de RFC à implémenter pour gérer le gros des emails (et on peut les blâmer de n’avoir jamais imposer une homogénéisation – mais intervient la notion de legacy que Python a d’ailleurs omis lors de son passage à sa version 3) mais le problème concerne surtout les générateurs des emails (automatique ou humain ou les deux – dans le sens d’une automatisation produite par un humain) et le gagnant dans l’histoire est bien entendu Outlook (Microsoft n’a pas qu’exceller dans le domaine des standards avec IE, hein …).

    Bref, c’est un gros bordel et ça le restera un bon bout de temps malheureusement car les logiciels essayant de traiter les emails sont toujours face à l’email fatidique (issu d’un mélange de RFC et de règles tacites que les reptiliens-développeurs des MUAs s’échangent dans le plus grand des secrets) qui ne respecte pas les standars mais qu’il faut traiter – intervient alors le patch-monkey, le cas particulier, le if de trop …

    Voilà un petit complément d’information pour vous convaincre de ne pas traiter des emails.

  • ultra

    Pour répondre à cette histoire de ‘Ver más ideas’.

    En fait, le serveur http a un encoding par défaut, par exemple pour apache 2.4 dans httpd.conf c’est la directive AddDefaultCharset utf-8

    Il y a également l’encoding de l’application qui est fixé par

    Par conséquent, si l’application ne fixe aucun encoding, c’est l’encoding du serveur http qui est utilisé, donc forcément, si les encodings diffèrent, on se retrouve avec des contradictions.

    C’est pour cela que l’encoding par défaut du serveur http doit toujours être le même que celui de l’application.

    Dans le cas particulier du site espagnol, l’administrateur système a bien fait son boulot en fixant le utf-8, en revanche, les dèvs ont fait un boulot de sagouin en travaillent avec du ISO-8859-1. D’ailleurs, les dèvs doivent avoir des problèmes avec mysql qui doit être également en utf-8.

  • Francis G

    Pour passer une BdD MySQL encodée en uft8mb4 en uft8, quelqu’un a une piste ? En Python ou pas.

  • k

    Sympa l’article :) Un beau bordel tout ça :D

    Encore une petite coquille :

    les gens se sont dit qu’utiliser base64 était déjà trop fait par tout le monde est qu’on allait pas se priver de cette occasion

  • saiaman

    “****** Le charset déclaré n’est pas celui utilisé. *******”

    Enfin d’une façon générale on se base surtout sur la réponse serveur pas le méta html ….

    dans le cas présent : Content-Type: text/html; charset=utf-8

    Dans le cas présent l’utf-8 n’est pas un hazard et il est bien précisé par le serveur en réponse… D’ou d’ailleurs le fait que le navigateur sache l’afficher…

    Apres que “requests” decode pas directement tout seul …. ça je conseillerais une pull request dessus :)

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.