Placer iconographie du dossier ici Ce billet fait partie de la série en <img /> :

  1. Histoires en <img />
  2. Manuel en <img />
  3. Ressources en <img />
  4. Bricolages en <img />
  5. Progressivement en <img />
  6. Finesse en <img />
  7. Propositions en <img /> / English version

Maintenant que nous avons défini la méthode d'appel dans la syntaxe html et que nous connaissons les formats d'images actuellement supportés, intéressons-nous sur la manière dont les navigateurs web travaillent, avec en prime un petit hack amusant.

Reverse engineering : comment les navigateurs gèrent <img />

Le plus important à savoir est que pour les clients web l'extension du nom de fichier n'a aucune influence.
Croire qu'il faut un .gif en fin de nom de fichier pour mettre votre truc animé, c'est un peu la même croyance que croire mordicus que .html en fin d'url aide précieusement votre référencement. Le W3C, l'IETF et toutes les cyberpolices qui définissent internet se foutent de chaque charlatan qui professe le contraire. Ces derniers sont des SEO-LOL qui travaillent exclusivement sous Windows et qui ne connaissent rien au web, ni à internet, ni à l'informatique.

Un spécialiste SEO diplômé Sup-De-LOL avec son appeau à crawlers.
Photo CC de Rodrigo Álvarez avec Oscar Zimmerman

Parce que cela n'a jamais été le cas : Il n'est pas rare dans la vie d'un crawler de moteur de recherche de tomber sur du texte ou des images générés dynamiquement et servis par des URL se terminant en .pl, .php ou .aspx. Une extension de nom de fichier n'est jamais une information fiable pour identifier un type de document.
Dans le protocole HTTP, si un logiciel client, qu'il soit navigateur web ou crawler, doit identifier le type de document (si c'est une image ou une page de texte) autrement que par son contenu, il doit se référer au type-mime indiqué dans l'entête Content-type: par le serveur. Si cette information ne semble pas fiable, on passe alors à une analyse divinatoire du type de document, familièrement appelée magic.

Le format réel de la ressource image est devinée par la bibliothèque de décodage graphique embarquée par le navigateur. GIF ? JFIF ? PNG ? Pas de souci : ces bibliothèques procèdent à une divination magic optimisée, testant les formats de documents qu'elle supportent. Elles sauront très bien se dépatouiller avec l'image.

Mais je vais être honnête : Dans les faits, quand il faut construire l'header d'une ressource à servir, les serveurs web en production correctement paramétrés ne se basent pas sur les définitions magic des fichiers, mais… sur l'extension de leur nom. Nettement plus rapide qu'analyser le contenu du document et le confronter à plusieurs milliers de règles regex.
Arrêtez de pouffer dans le fond, nous verrons plus bas que son utilité est toute relative pour <img />.

C'est l'histoire d'un trou de fort beau gabarit

Et puisqu'on parle de cela, il que je vous conte l'histoire d'un trou de sécurité qui resta très longtemps béant.
Il était une fois un navigateur qui avait gagné une guerre, et qui fut le plus populaire des navigateurs de son temps : MSIE (MSIE 6 inclus jusqu'à la XP SP1).

En ce temps-là, le navigateur fourni d'office par Microsoft était tellement imbriqué dans Ms-Windows qu'il demandait aux procédures standards de l'OS de deviner à sa place les fichiers images. Jusqu'ici rien d'anormal : les équipes de développement Microsoft utilisaient pragmatiquement sans le savoir l'approche DRY. Do not Repeat Yourself (il est inutile de répéter les mêmes fonctions) est une très bonne pratique de développement de projet.

MSIE n'étant disponible que dans l'OS de Microsoft (l'équipe de la version Mac mourrait d'inanition), la IETeam n'allait pas s'embêter à ré-inventer la roue et se baser sur la gestion des types de documents propre à Ms-Windows.
Du coup, si le système hôte supporte un nouveau type de document qui est un format d'image, le gestionnaire de document tout comme le navigateur imbriqué vont aussi le gérer, comme tout logiciel de cet éditeur. Ce mode de conception se retrouve dans d'autres OS et navigateurs : Rekonq se base sur les bibliothèques Qt de l'environnement KDE, et Safari se reposait en défaut sur Quicktime pour les ressources qu'il ne comprenait pas.
On y retrouve d'ailleurs le même manque fonctionnel par construction : l'affichage durant le chargement du gif 89a entrelacé et du jpeg progressif n'est pas possible, toute image n'est affichée qu'une fois complètement décodée.

Sauf que dans l'écosystème de Microsoft, cette procédure était trop permissive : Gestion par extension de fichier plus oubli de limite de périmètre fonctionnel. Les documents “images” appelés via <img /> se finissant en .exe ou .dll étaient… exécutés… avec le plus haut niveau de privilège possible…

Double facepalm, quand un seul ne suffit pas.

Si je vous dis qu'il était à l'époque “courant” de piéger des pages web qui changaient par ce biais le numéro d'appel du modem pour accéder à Internet en un 08 99… soigneusement surfacturé. Oui, Monsieur le Président, je plaide la complicité.

Ce fut la dernière fois qu'un navigateur se basait sur une extension de nom de fichier pour gérer les éléments d'une page web au lieu de vérifier son contenu effectif. Et depuis, MSIE fait comme tout le monde et utilise une bibliothèque compartimentée pour le décodage et l'affichage des images.

D'ailleurs, c'est suite au comblement de cette faille qu'on a commencé à voir arriver des favicons animées. La recette est simple : renommer une animation gif de taille 16×16 en favicon.ico et le mettre à la racine du site. Encore une fois terriblement efficace.

C'est cette astuce qui allait me donner l'idée d'un hack…

(teasing)

Classique des CMS : Image source et sous-formats

Quand vous construisez un CMS ou n'importe quel service qui doit servir des images fournies par des utilisateurs dans un contexte formaté, l'exercice de style archi-classique est la gestion des sous-formats d'images.

Tout outil de blogging ou d'e-commerce doit forcément gérer le fait qu'un contenu texte ou qu'un item soit enrichi d'une image. Il est dans les faits extrêmement rare que l'utilisateur qui crée le contenu envoie au serveur une image aux dimensions exactes préconisées par le designer.

mockup
Mock-up d'un design d'article magazine. Flottant à gauche, un espace contraint est prévu pour des images.
Source : Étude de redesign du site news.uci.edu

Et il est probable que cette image d'illustration soit reprise dans d'autres contextes, par exemple une page de une, de sommaire ou de recherche mais dans d'autres dimensions contraintes. Cette image doit donc être réduite ou/et coupée par l'interface d'édition afin de tenir dans un ou plusieurs certain canevas. Une opération plus cohérente et largement meilleure que de faire de la sur/sous-résolution.

Le CMS crée à partir de cette image source (ou master) des sous-images qu'on a conformé aux dimensions du design graphique du site. Ces sous-formats sont parfois optimisés pour le transfert en jouant sur les paramètres de compression, en suppression des métadonnées embarquées, etc…

L'écueil du nommage des sous-formats

« Qui amat bene, castigat bene. » (maxime latine qui explique qu'au rugby, on aime beaucoup ses adversaires).
Pour l'avoir beaucoup trituré dans tous les sens, je vais parler du moteur de blog qui motorise mon site depuis 10 ans. Dotclear, dans sa version 1 n'avait qu'un seul format de sous-images. Si j'envoyai capture.jpg, le script créait capture.TN__.jpg. Ce qui posait un souci de lisibilité dans les répertoires, puisque les entrées étaient doublées dans les gestionnaires de transfert.

Dotclear 2 propose une gestion paramétrables de multiples sous-formats. Cette fois-ci, Dieu décida de cacher le sous-formats d'images en préfixant par un point le nom de fichier (le standard Unix pour cacher un document, dans l'univers MS-DOS, c'est un bit d'état dans les métadonnées). Ainsi, on passait d'une structure :

  • capture.png
  • capture.__TN.jpg
à
  • capture.png
  • .capture_m.jpg
  • .capture_s.jpg
  • .capture_sq.jpg
  • .capture_t.jpg
La génération de certains sous-formats ne se fait pas si l'image master est en-dessous des tailles maximales du canevas de ce format.

Extrait d'une vue de l'arborescence des illustrations de ce dossier. À droite, les documents originaux que j'ai envoyé, à gauche, les sous-formats générés par Dotclear. Ces derniers sont en sous-opacité car ils sont “cachés”.

Mais il reste un souci : jusqu'à la version 2.5, les sous-formats générés par le moteur de blog sont forcément des jpeg. Imaginez que vous envoyiez exemple.jpg et exemple.jpeg, si les masters sont bien différenciés, vous aurez forcément une collision parce que le nom du document hors extension est le même.

Pour moi, cette imprécision n'était pas acceptable car j'ai régulièrement eu des utilisateurs qui se plantaient à l'extension. Et je ne vais pas les en blâmer : mes clients humains étaient des chefs d'entreprises dont le métier n'est pas l'informatique, je devais donc m'adapter, ou en suer à gérer le “bug” par téléphone.

Forcément, quand j'ai créé le mien…

Pour mon service dAgence, je souhaitais avoir la plus grande plasticité possible pour générer les sous-formats à partir d'une image originale. Comme nous (Nicolas “Xylpho” Guilhou au design et moi au code) construisions une solution B2B destinée à des freelances et des agences web, il fallait avoir un service générique, exhaustif capable de répondre à leurs demandes en étant le plus souple possible.

Le problème de collision de nom de document hors extension était, croyez-moi, le moins pire de nos soucis : Vous ne pouvez pas imaginer de quoi est capable un client, surtout s'il est conseillé par un spécialiste design qui n'a jamais fait de css de sa vie.

Pour illustrer, voici les paramétrages de sous-formats d'images utilisés pour mon site corporate dascritch.com, actuellement toujours hébergé sur dAgence.

Capture du backoffice béta, sur la liste des sous-formats d'images générées

En clair, si j'envoie un capture.png, j'aurais forcément pour les besoins du backoffice, un sous-format favicon (qui tronque en 16×16 et exporte en png) et icone (qui réduit en 128×128, en jpeg). Sur cette portion capturée, on ne le voit pas très bien mais ces formats ne sont pas modifiables.

En frontal (le site accessible à tous, quoi), je crée et paramètre autant de sous-formats que je le souhaite. Ainsi, sur mon site, j'ai le dsn (qui ne modifie pas la taille, mais transcode jpeg à 80%, sans métas) et realisat, générée à partir des paramétrages suivants :

capture du backoffice sur le sous-format d'image “realisat”

Comme le laisse deviner le titre de la dernière section que je n'ai pas inclus, on peut aussi y surimposer une image en filigrane ou en tampon.

Ce qui me rappelle un projet magnifique : Un client avait prévu que son site web soit disponible en au moins 7 langues, et que les images aient des versions avec un tampon nouveau !, un avec promo ! dans chacune des versions linguistiques. 14 filigranes, plus l'original multiplié par les 4 formats minimum designés, ça faisait un joli paquet.
Oui, le client voulait absolument que toutes ses images soient filigranées, au cas où un concurrent parse son catalogue pour remplir le sien, comme lui-même a dû le faire pendant des années.
Petit coquinou.

Alors je me suis gratté la tête

Donc, quand j'en étais à créer un gestionnaire de média, j'ai dû faire un choix sur la stratégie de nommage des sous-formats d'images. J'avais écarté l'idée de créer dans chaque répertoire un sous-répertoire par sous-formats, car c'était reculer pour mieux se planter. Je voulais rester dans l'idée de sous-formats dans le même répertoire que le document original. Il fallait trouver une solution en jouant sur les standards et pratiques courantes.
Et je me suis dit que malgré toutes les bêtises que mes clients humains aient pu faire, je n'avais jamais vu un élément sans extension se terminer par une virgule puis un mot.

Bingo.

Pour m'assurer que les navigateurs un peu problématiques de l'époque ne soient pas perdus (de mémoire, des navigateurs pour feature-phones comme Open-Wave), je leur envoie un type-mime d'image universellement supporté, mais sans support de chargement progressif. Donc forcément un png. Je demande donc au serveur http du service de fournir les documents avec le type-mime image/png sans se baser sur le contenu interne du document servi mais sur le nom de fichier (ce qu'il fait quand même… eh oui).

Paramétrages pour un serveur Apache :
<FilesMatch ,[A-Za-z0-9_\-]+$>
    ForceType image/png
</FilesMatch>

Pour un serveur nginx :
location ,[A-Za-z0-9_\-]+$ {
    default_type image/png;
}

Et voilà *.
Peu importe le type d'image généré, que cela soit un gif, png ou jpeg, votre serveur web déclarera dans les headers http un Content-Type: image/png, les navigateurs clients qui le récupèrent comme ressource <img /> ou composante css laissent à leur bibliothèque de gestion des images le soin de se dépatouiller avec. Et tous les navigateurs l'ont très bien supporté, même MSIE6.
(* En Français dans le texte)

Avant de tester ce dernier fait, petits rappels :

  • les directives apache ou nginx, moins il y en a, mieux c'est ;
  • les directives avec des expressions régulières dedans, c'est une chute de performance annoncée. Mais comme les méthodes magic en sont farcies, ici, au contraire, on peut faire gagner du temps au serveur ;
  • sur serveur apache, évitez autant que possible de mettre des directives en .htaccess : si elles ne sont pas destinées à bouger sans un redémarrage serveur, préférez les mettre dans votre arborescence /etc/apache2/

Ainsi, si j'envoie dans le backoffice de mon site pro, une image appelée capture.png, mon serveur va construire dans un répertoire les documents :

  • capture.png
  • capture.png,demo
  • capture.png,dsn
  • capture.png,favicon
  • capture.png,icone
  • capture.png,realisat

Let's test !

Il s'agit du même test dynamique que pour le chapitre précédent : Si l'image “oui” s'affiche, c'est que le navigateur le supporte.

FormatType/mime standardsupportforcé en
image/png
text/xmlapplication/binary
xbmimage/x-xbitmap nonnonnonnon
xpmimage/x-pixmapnonnonnonnon
pbmimage/x-portable-bitmapnonnonnonnon
pgmimage/x-portable-graymapnonnonnonnon
ppmimage/x-portable-pixmapnonnonnonnon
bmpimage/x-ms-bmpnonnonnonnon
gifimage/gifnonnonnonnon
icoimage/vnd.microsoft.iconnonnonnonnon
jpeg/jfifimage/jpegnonnonnonnon
jpeg2000image/jp2nonnonnonnon
jpeg XRimage/vnd.ms-photononnonnonnon
tgaimage/x-targanonnonnonnon
pngimage/pngnonnonnonnon
mng/jngvideo/x-mngnonnonnonnon
apngimage/pngnonnonnonnon
svgimage/svg+xmlnonnonnonnon
webpimage/x-webpnonnonnonnon

Quels que soient les navigateurs web, pour que la ressource d'une <img /> soit parfaitement interprétée, le seul besoin impérieux est que le fichier doit être correct. La seule exception où nous avons besoin d'un type-mime exact concerne le format svg.
La raison est évidente : la nature vectorielle du svg le fait passer par le moteur de rendu “normal” du navigateur, donc un arbre dom. À l'usage, une image svg n'a pas une consommation mémoire aussi indécente qu'une <iframe></>, puisque les navigateurs très récents ne chargent pas un interpréteur complet.
Pas fous.

Ceci dit, même si le format svg est celui dont la gestion des éléments est le plus aléatoire dans les navigateurs, les corrections avancent. Je ne serais pas surpris que le tableau fasse mentir le paragraphe qui le suit dans les mois qui viennent.

Vos retours sur le hack

Est-ce que mon hack est propre ou dégueulasse ? Survivra-t-il à l'épreuve du temps et de l'élégance ? Est-ce que l'Inspecteur Dirty Hacky doit dégainer et flinguer ?

Je serais curieux d'avoir votre retour. Positif, négatif, analyses… On pourrait peut-être en parler IRL à la prochaine soirée HTML Sur <Table>

Dans le prochain épisode, l'épineux problème du chargement progressif pour qu'il soit ni trop lent, ni trop rapide. Si. Si.