On va démonter une archive .zip à la main, octet par octet, et découvrir qu’elle ne se lit pas dans le sens où on croit. Puis on va fabriquer un fichier qui est deux choses à la fois. Et on finira sur une question qui n’a pas de réponse propre.
Tout ce qui suit a été mesuré sur ma machine le 4 septembre 2026. Rien n’est repris d’ailleurs.
L’archive la plus simple possible
Un fichier texte de 36 octets, compressé. L’archive fait 202 octets.
Ses quatre premiers octets sont 50 4b 03 04, soit PK suivi de deux octets. PK pour Phil Katz, co-auteur du format. Voilà pour la partie que tout le monde connaît.
Maintenant les 22 derniers :
504b 0506 0000 0000 0100 0100 4e00 0000
6600 0000 0000
50 4b 05 06, encore PK. C’est le marqueur de fin de catalogue central, et il commence à l’octet 180 sur 202. En le décodant, il annonce : une entrée, un catalogue de 78 octets, qui commence à l’octet 102.
Arrêtons-nous là, parce que c’est tout le sujet. Le fichier porte, dans ses vingt-deux derniers octets, l’adresse de son propre sommaire. Pour savoir ce que contient une archive, un programme ne la lit pas du début : il saute à la fin, cherche PK 05 06 à reculons, y trouve une adresse, et remonte.
La spécification de PKWARE, l’APPNOTE, le dit sans détour : une archive « MUST contain an “end of central directory record” ». Sans ce marqueur, il n’y a pas de catalogue, donc pas d’archive.
Pourquoi à la fin
Parce qu’à la fin des années 1980, on écrivait sur des disquettes.
La même spécification prévoit qu’une archive « MAY span multiple volumes », et impose que le marqueur de fin réside sur le même disque que le catalogue. Quand vous compressez trente fichiers sur des disquettes de 1,44 Mo, vous ne savez pas à l’avance combien vous en remplirez. Vous écrivez au fil de l’eau, et quand vous avez fini, vous savez enfin quoi écrire dans le sommaire. Alors vous l’écrivez là où vous êtes : à la fin.
C’est une décision d’ingénierie tout à fait raisonnable, prise pour un support qui n’existe plus, et dont tout le monde dépend encore. Un fichier .docx, un .jar, un .apk, un .epub, une extension de navigateur : ce sont des zips. Ils se lisent tous par la fin.
Ce que ça permet, et qui est troublant
Si un lecteur cherche le sommaire à partir de la fin, alors ce qu’il y a avant ne le regarde pas.
J’ai collé 5 000 octets tirés au hasard devant mon archive de 202 octets. Le fichier résultant fait 5 202 octets, ses premiers octets ne sont plus PK, et file ne le reconnaît plus comme une archive.
unzip l’ouvre quand même. Il émet un avertissement poli, 5000 extra bytes at beginning or within zipfile, extrait le fichier, et la somme de contrôle du contenu est identique à l’original.
Alors j’ai remplacé les octets au hasard par quelque chose de moins abstrait : une vraie image, celle qui illustre notre article du jour sur un tableur. 75 154 octets de WebP, puis l’archive collée derrière. Un seul fichier de 75 356 octets.
Ce fichier est une image valide. file répond « Web/P image, VP8 encoding, 1600x893 ». Le lecteur d’images du système l’ouvre et le mesure : 1600 par 893.
Ce fichier est aussi une archive valide. unzip y trouve note.txt, 36 octets, et l’extrait.
Même fichier, même somme de contrôle, deux lectures qui ne se rencontrent jamais. Ce n’est pas une astuce ni une faille : c’est ce que produisent mécaniquement deux formats dont l’un se lit par le début et l’autre par la fin. C’est aussi, exactement, la géométrie d’une archive auto-extractible : un programme, puis une archive collée derrière.
Là où ça devient vraiment intéressant
Jusqu’ici, tout est déterminé. On peut maintenant poser la question qui ne l’est pas.
Un lecteur cherche PK 05 06 à reculons. Jusqu’où remonte-t-il ?
La spécification ne le dit pas. Elle dit seulement que le champ « .ZIP file comment length » tient sur deux octets, ce qui plafonne un commentaire à 65 535 octets. On pourrait en déduire qu’un lecteur doit balayer 65 535 octets et s’arrêter. C’est une déduction, pas une règle, et j’ai voulu voir.
J’ai donc ajouté n octets de rebut derrière l’archive, en déclarant un commentaire de longueur nulle, et cherché la bascule par dichotomie.
unzip version 6.00 du 20 avril 2009, celle livrée avec macOS :
- 73 989 octets de rebut : lisible.
- 73 990 octets : illisible.
Le module zipfile de Python 3.10.9, sur la même machine, le même fichier, la même seconde :
- 65 536 octets : lisible.
- 65 537 octets : illisible.
Les deux se trompent, si l’on veut. Aucun ne suit la borne des 65 535 : l’un s’arrête un octet plus loin, l’autre continue pendant 8 454 octets de plus. Ce sont deux tailles de tampon, décidées séparément, par des gens qui n’en ont jamais parlé ensemble.
Et voici la conséquence, que j’ai fabriquée exprès. Un fichier avec 70 000 octets de rebut à la fin :
unzip: archive valide, contientnote.txt.- Python : ce n’est pas une archive.
Ce qu’on croyait savoir
On dit « ce fichier est un zip » comme on dit « ce nombre est premier ». Comme si c’était une propriété de la chose, vérifiable, sur laquelle deux personnes de bonne foi finissent toujours par tomber d’accord.
Ce n’en est pas une. Mon fichier de 70 000 octets de rebut n’est ni un zip ni pas un zip. Il est un zip pour unzip, et il n’en est pas un pour Python. Ces deux phrases sont vraies en même temps, sur la même machine, et il n’existe aucune autorité pour les départager. La spécification, qui devrait trancher, ne dit rien sur ce point précis : elle décrit ce qu’une archive doit contenir, pas jusqu’où un lecteur doit chercher.
C’est plus dérangeant qu’une bizarrerie de format. Nous passons nos journées à faire examiner des fichiers par une chaîne de programmes : un antivirus, un serveur, une bibliothèque, une application. Nous supposons tous qu’ils regardent la même chose. Ils regardent le même flux d’octets, ce qui n’est pas pareil, et chacun décide seul de ce qu’il y voit.
Je n’ai pas mesuré ce que font les autres lecteurs, et je ne l’affirmerai donc pas : je ne sais pas où s’arrêtent 7-Zip, l’explorateur de Windows, Java, Go ou le décompresseur de votre téléphone. Chacun a sa borne, et j’ignore laquelle.
Ce que je sais, c’est que la question « qu’est-ce que ce fichier ? » n’a pas de réponse. Elle a des réponses, au pluriel, et il faut demander à chaque programme la sienne.
Reste une question que je vous laisse, parce que je n’ai pas de bonne réponse. Si deux programmes de votre chaîne ne voient pas le même fichier, lequel a raison : celui qui accepte, ou celui qui refuse ?