Gzip

Collez du gzip, zlib ou deflate brut en Base64 pour lire son contenu — les octets disent lequel — ou compressez un texte en l’un des trois, dans le navigateur.

Entrée
ou déposez-en un ici : il est lu en local et jamais téléversé
Sortie
[
  {
    "name": "Léa",
    "city": "Paris"
  },
  {
    "name": "Hugo",
    "city": "Lyon"
  },
  {
    "name": "Chloé",
    "city": "Marseille"
  },
  {
    "name": "Louis",
    "city": "Bordeaux"
  },
  {
    "name": "Manon",
    "city": "Lille"
  },
  {
    "name": "Gabriel",
    "city": "Montréal"
  },
  {
    "name": "Inès",
    "city": "Bruxelles"
  },
  {
    "name": "Jules",
    "city": "Genève"
  }
]

Lu comme du gzip.

Octets non compressés
419
Octets compressés
171
Variation
-59,2 %
Caractères en Base64
228

Une compression dans trois enveloppes, et le mot deflate

gzip, zlib et deflate brut sont une seule et même compression, DEFLATE, enveloppée de trois façons. La RFC 1951 définit DEFLATE proprement dit : les octets découpés en blocs, chacun écrit sous forme de codes qui représentent un octet ou une suite recopiée depuis ce qui précède. L’enveloppe de gzip, la RFC 1952, place devant un en-tête d’au moins dix octets, et derrière huit octets : un CRC-32 des octets d’origine et leur longueur ; celle de zlib, la RFC 1950, place deux octets devant et un Adler-32 derrière ; le deflate brut est la compression sans aucune enveloppe. Les données compressées qu’elles contiennent peuvent être les mêmes dans les trois, si bien que l’enveloppe fait à elle seule toute la différence.

C’est sur le mot deflate que les noms s’emmêlent. Pour HTTP, Content-Encoding: deflate désigne l’enveloppe de zlib, et la RFC 9110 note que certains serveurs envoient malgré tout du deflate brut sous ce nom. Le compresseur du navigateur lui-même suit HTTP : il donne le nom deflate à l’enveloppe de zlib, et le nom deflate-raw au deflate brut. Le DeflateStream de .NET et le gzdeflate de PHP écrivent du deflate brut, tandis que le gzcompress de PHP et le zlib.compress de Python écrivent l’enveloppe de zlib. Des octets étiquetés deflate peuvent donc être l’un ou l’autre, et seuls les octets peuvent dire lequel.

C’est pourquoi la page, quand elle décompresse, lit l’enveloppe dans les octets et dit laquelle elle a lue, sous ce qui en est sorti : « Lu comme du gzip », « Lu comme du zlib » ou « Lu comme du deflate brut ». Ce sont les octets qui tranchent : le gzip commence toujours par 1f 8b, les deux premiers octets de zlib, lus ensemble comme un seul nombre, forment un multiple de 31, et aucun encodeur ne fait commencer un deflate brut par l’une ou l’autre de ces deux ouvertures. Un fichier nommé .gz qui contient du zlib est lu comme du zlib, avec un avis disant que le nom et les octets ne concordent pas, et des octets qui ne sont dans aucune des trois sont refusés : il n’y a rien à décompresser. Pour compresser, c’est vous qui choisissez l’enveloppe, et c’est gzip tant que vous n’en choisissez pas une autre.

D’où viennent les charges utiles, et ce que sont H4sI et eJ

Des octets compressés parviennent à un développeur sous quelques formes : comme corps d’une réponse HTTP dont le Content-Encoding nomme gzip ou deflate, comme fichier, ou comme texte, écrit en Base64 ou en hexadécimal. Tout flux gzip commence par les trois mêmes octets, 1f 8b 08 — ses deux octets de signature, puis le numéro de sa méthode de compression, qui est DEFLATE —, et trois octets font exactement quatre caractères de Base64, si bien que du gzip écrit en Base64 commence toujours par H4sI. C’est ce qu’un abonnement CloudWatch Logs remet à une fonction Lambda ou à un flux Kinesis dans les données de chaque enregistrement, et c’est ainsi que Helm stocke une release : son JSON compressé avec gzip, puis écrit en Base64. Quand Helm garde une release dans un Secret Kubernetes, la lire directement dans le Secret donne du Base64 dans du Base64, puisqu’un Secret conserve lui-même ses données en Base64 ; l’outil Base64 décode cette couche extérieure et rend le H4sI… que lit cette page.

Les deux octets d’en-tête de zlib indiquent sa méthode, sa fenêtre et le niveau auquel il a été écrit, si bien qu’avec la fenêtre par défaut de zlib, son Base64 commence de l’une de quatre façons : eJ au niveau par défaut, ce qu’écrit le zlib.compress de Python si on ne lui demande pas autre chose, eA ou eF aux niveaux en dessous de celui-ci, et eN à ceux au-dessus. Le deflate brut n’a aucune signature, et son Base64 commence au gré de son premier bloc.

« Compressé en » dit comment la page lit et écrit ces octets sous forme de texte : « Base64 », sur lequel la page s’ouvre, ou « Hexadécimal ». Ce réglage vaut dans les deux sens et la page ne le change jamais à votre place, parce que chaque chiffre hexadécimal est aussi un caractère du Base64 : 1f8b0800 est un texte valable dans l’un comme dans l’autre, et des octets différents dans chacun. Le Base64 est lu dans l’alphabet standard ou dans l’alphabet compatible URL, avec ou sans son remplissage, en ignorant les espaces et les sauts de ligne, et il est écrit avec remplissage, ou compatible URL et sans remplissage quand l’interrupteur « Compatible URL » est activé. L’hexadécimal est écrit en paires séparées par des espaces, et lu espacé ou accolé, avec 0x devant, ou sous forme de vidage hexadécimal — et 0x est la façon dont T-SQL écrit une valeur binaire, comme le gzip que renvoie le COMPRESS() de SQL Server. Quand de l’hexadécimal lu comme du Base64 échoue, ou que du Base64 lu comme de l’hexadécimal est refusé à un caractère que l’hexadécimal n’a pas, et que dans l’un ou l’autre cas le texte entier se lit dans l’autre forme, un avis le signale à côté d’un bouton qui fait passer à l’autre ; rien ne change tant que vous n’appuyez pas dessus.

Lire un flux : les membres, les octets qui le suivent, une fin prématurée

La RFC 1952 fait d’un fichier gzip une suite de membres, des flux gzip complets placés les uns après les autres, chacun avec son propre en-tête et son propre en-queue. cat a.gz b.gz produit un tel fichier, tout comme le fait d’en compléter un avec gzip -c file >> archive.gz, l’exemple même du manuel de GNU gzip ; gunzip lit chaque membre et joint ce qu’ils contiennent. Cette page les lit de la même façon : chaque membre est lu et vérifié, ce qu’ils contiennent s’affiche joint, et un avis dit combien ont été lus. Tous les décompresseurs n’en font pas autant. La norme que suit le décompresseur du navigateur lui-même n’autorise qu’un seul membre dans un flux gzip et tient un second pour une erreur, et le zlib.decompress de Python s’arrête après le premier sans rien signaler.

Les octets placés après la fin qui n’ouvrent aucun membre sont sautés plutôt que refusés, puisque tout ce qui les précède est complet et vérifié, et un avis dit combien il y en a, le décalage auquel ils commencent et s’ils sont tous nuls. Des zéros à cet endroit sont du remplissage — le manuel de GNU gzip en rencontre sur bande magnétique, écrits jusqu’à la fin d’un bloc — et gunzip les passe en silence ; il passe les autres octets avec un avertissement disant que des données superflues en fin de fichier ont été ignorées, là où le gzip.decompress de Python refuse le fichier. Il en va de même après l’Adler-32 d’un flux zlib, et après le dernier bloc d’un flux brut, même si le deflate brut ne porte aucune somme de contrôle à vérifier.

Un flux qui s’arrête avant sa fin, comme un collage tronqué ou un téléchargement interrompu en cours de route, s’affiche aussi loin qu’il va, avec un avis disant que la somme de contrôle n’a pas été vérifiée. Un flux endommagé est refusé, et rien n’en est affiché. C’est plus strict que gunzip, qui écrit ce qu’il a décompressé avant d’atteindre la somme de contrôle qui le révèle faux ; mais un bit modifié peut se décoder en silence sur une certaine longueur avant que quoi que ce soit ne le trahisse, si bien que ce qui est sorti avant que le dommage n’apparaisse peut déjà être faux. Aucune position n’est donnée non plus, puisque l’endroit où un décodeur remarque un dommage n’est pas celui où il se trouve. Et un flux zlib qui demande un dictionnaire prédéfini est refusé par une phrase qui lui est propre : le flux identifie les octets avec lesquels son compresseur a été amorcé, sans les contenir, si bien qu’il n’y a rien avec quoi le lire.

Ce qui en sort est écrit comme le dit « Octets en » : en « Texte », en UTF-8, ou en « Hexadécimal ». Il s’agit souvent de JSON, que le Formateur JSON met en forme et vérifie, en indiquant la ligne et la colonne où il casse. Une image ou une archive compressée avec gzip n’est pas du texte, si bien qu’avec « Texte », chaque séquence de ses octets qui n’est pas de l’UTF-8 s’affiche sous la forme U+FFFD, avec un avis qui compte ces séquences à côté d’un bouton qui affiche les octets en hexadécimal ; « Télécharger » enregistre les octets eux-mêmes dans les deux cas. Une ligne sous le résultat montre ce qu’enregistre l’en-tête du premier membre — un nom, une heure en UTC et un commentaire —, et le nom enregistré ne sert jamais à nommer le fichier qu’enregistre « Télécharger ». Une sortie qui dépasse le maximum que la page décompresse d’un coup s’arrête là, avec un avis qui nomme cette taille, et quand l’en-queue d’un gzip indique une longueur qui le dépasse, la page le dit pendant que le travail s’exécute.

Pourquoi une petite entrée grossit, et ce qu’ajoute le Base64

La compression gagne de la place en trouvant des répétitions, et un texte court en contient peu, alors que chaque enveloppe coûte des octets qui lui sont propres : l’en-tête et l’en-queue de gzip font 18 octets quand l’en-tête n’enregistre rien de plus, ceux de zlib 6, et le deflate brut n’en a aucun, même si DEFLATE dépense quelques bits à délimiter ses blocs. Ainsi Hello, world!, 13 octets, devient 33 octets en gzip, 21 en zlib et 15 en deflate brut. La ligne des tailles, sous un résultat, compte les octets de chaque côté et la variation entre les deux, +153,8 % pour ce gzip, et un avis dit pourquoi quand un résultat grossit. Un texte riche en répétitions, comme du JSON ou un journal, regagne ce coût bien des fois : l’exemple sur lequel la page s’ouvre, un petit JSON, sort à moins de la moitié de sa taille.

Écrits sous forme de texte, les octets coûtent encore davantage. Le Base64 prend quatre caractères pour trois octets, soit un tiers de plus, comme l’explique le guide de l’outil Base64, si bien que ces 33 octets de gzip font 44 caractères ; l’hexadécimal prend deux chiffres par octet, et la page les écrit en paires séparées par des espaces, soit 98 caractères pour les mêmes 33 octets. La ligne des tailles compte aussi ces caractères, et le Convertisseur de tailles de données exprime n’importe lequel de ses nombres en KB ou en KiB.

Compresser : pas de niveau, pas les octets de gzip -c, et pas de Brotli

Compresser, c’est le travail du navigateur lui-même, au moyen du CompressionStream que définit la norme Compression, et cette norme n’offre aucun niveau de compression, si bien que cette page n’en offre pas non plus : ce que vous obtenez, c’est le réglage par défaut du navigateur. gzip -9 peut produire un résultat un peu plus petit, et rarement de beaucoup.

Les octets ne sont pas non plus ceux qu’écrit gzip -c, même si les deux, une fois décompressés, donnent le même texte. Les en-têtes diffèrent d’abord. GNU gzip enregistre le nom et l’heure d’un fichier sauf si on lui passe -n, et, depuis un tube, une heure à zéro ; il note dans un octet de l’en-tête quel niveau lui a été demandé, -9 ou -1 ; et dans le dixième octet, que la RFC 1952 attribue au système d’exploitation, il écrit 03 sous Linux, le numéro d’Unix. Le navigateur ne reçoit que les octets, si bien que ni le nom de votre fichier ni son heure ne peuvent partir avec le résultat : Chromium n’enregistre aucun nom et met l’heure à zéro, ce que choisit gzip -n, et dans le dixième octet il écrit 03 sous Linux, là où Chrome sous Windows écrit 0a. Ensuite, les données compressées diffèrent, puisque le compresseur de GNU gzip n’est pas celui du navigateur : sur un texte long, les deux choisissent des octets différents au même niveau. Une différence avec gzip -c ne signifie donc rien en soi ; ce qui compte, c’est que les deux donnent les mêmes octets une fois décompressés.

Brotli n’est pas là, pour l’instant. La norme Compression le nomme, mais tous les navigateurs ne l’écrivent pas encore, et un choix que la page ne pourrait proposer que là où le navigateur le permet ferait d’elle une page différente d’un navigateur à l’autre. zstd ne figure pas du tout dans cette norme. Ce que cette page lit et écrit, c’est DEFLATE dans ses trois enveloppes, et rien d’autre.

Un fichier en entrée, un fichier en sortie, et rien de téléversé

Un fichier peut prendre la place de la zone de texte dans les deux sens : choisissez-en un, ou déposez-le sur la zone. Ses octets sont lus tels quels, sans passer par « Compressé en » ni par « Octets en », et un fichier n’a ici aucune limite, là où la zone de texte en a une, même si un très gros fichier fait apparaître un avertissement disant que le navigateur risque de ne pas pouvoir le tenir en mémoire. Pour décompresser, « Télécharger » nomme le résultat comme le fait gunzip : le nom du fichier sans .gz, .tgz devenant .tar, et aussi sans .zz ni .deflate, les extensions que cette page écrit pour les deux autres enveloppes ; tout autre nom, et tout ce qui a été collé, sort sous le nom bytes.bin. Pour compresser, il ajoute .gz, .zz ou .deflate au nom du fichier, .zz étant la façon dont pigz nomme le zlib, ou au mot text pour ce que vous avez tapé.

« Utiliser comme entrée » place un résultat dans la zone et inverse le sens, si bien qu’un aller-retour tient en un clic, et un résultat qui venait d’un fichier, ou qui est trop gros pour la zone, passe sous forme de fichier. Dans un sens comme dans l’autre, le fichier est lu dans cet onglet et le travail tourne dans un Web Worker que la page démarre sur votre propre appareil : ni charge utile ni fichier n’est téléversé pour cela.

La même chose avec gunzip et Python

En ligne de commande, gunzip et zcat lisent le gzip comme le fait cette page, tous les membres compris, une fois que base64 -d a reconverti une charge utile en octets, mais ni l’un ni l’autre ne lit le zlib ou le deflate brut, que tous deux rejettent comme n’étant pas du gzip ; gzip -k compresse un fichier et garde l’original à côté. En Python, gzip.decompress lit l’enveloppe de gzip et chacun des membres qu’elle contient, et zlib.decompress lit n’importe laquelle des trois enveloppes, celle que lui indique son wbits :

# Une charge utile : la décoder, puis la décompresser
printf 'H4sIAAAAAAAAA/NIzcnJ11Eozy/KSVEEAObG5usNAAAA' | base64 -d | gunzip   # Hello, world!

# Un fichier : le compresser en gardant l’original, puis l’afficher à nouveau
printf 'Hello, world!' > hello.txt
gzip -k hello.txt
zcat hello.txt.gz                                  # Hello, world!

# Deux membres, le second ajouté au premier, relus comme un seul
printf 'Hello, ' | gzip > two.gz
printf 'world!' | gzip >> two.gz
zcat two.gz                                        # Hello, world!

# La même chose dans un script : tous les membres, puis une enveloppe à la fois
import base64, gzip, zlib
two = open('two.gz', 'rb').read()
zl = base64.b64decode('eJzzSM3JyddRKM8vyklRBAAgXgSK')
raw = base64.b64decode('80jNycnXUSjPL8pJUQQA')
gzip.decompress(two)                               # b'Hello, world!'
zlib.decompress(two, wbits=31)                     # b'Hello, '
zlib.decompress(zl, wbits=15)                      # b'Hello, world!'
zlib.decompress(raw, wbits=-15)                    # b'Hello, world!'
zlib.decompress(zl, wbits=47)                      # b'Hello, world!'

Avec wbits, 31 demande l’enveloppe de gzip, tandis que 15, la valeur par défaut, demande celle de zlib, et que -15 demande le deflate brut, le 15 de chacun désignant la plus grande fenêtre, alors que 47 accepte celle de gzip comme celle de zlib, selon celle par laquelle commencent les octets. zlib.decompress ne lit cependant qu’un seul membre et laisse tomber le reste sans rien dire, ce qui explique qu’il ne rende que b'Hello, ' à partir des deux membres ci-dessus ; gzip.decompress les lit tous. Python est en outre plus strict que cette page sur deux points : gzip.decompress refuse les octets placés après la fin qui ne sont pas des zéros, et les deux fonctions refusent un flux qui s’arrête avant sa fin, là où la page montre ce qui en est sorti.

Questions fréquentes

Pourquoi mon résultat compressé est-il plus gros que ce que j’ai tapé ?
Parce que chaque enveloppe coûte des octets qui lui sont propres et qu’un texte court offre peu à retirer à la compression : gzip ajoute 18 octets, zlib 6, et DEFLATE délimite en plus ses blocs, si bien que quelques mots grossissent là où un long JSON rétrécit, et la page le dit dans un avis. Le deflate brut est le plus petit des trois. Écrit en Base64, n’importe quel résultat est encore plus long d’un tiers.
Pourquoi la sortie ne correspond-elle pas à gzip -c ?
Rien ne promet qu’elle y corresponde. Un en-tête gzip peut contenir un nom, une heure, une marque du niveau et un octet pour le système d’exploitation, que GNU gzip et le navigateur remplissent différemment, et les deux sont des compresseurs différents, si bien que sur un texte plus long, même les données situées entre l’en-tête et l’en-queue peuvent différer. Deux flux gzip d’un même texte sont tous deux justes dès lors que chacun redonne ce texte une fois décompressé, si bien que c’est le résultat décompressé qu’il faut comparer : « Utiliser comme entrée » renvoie aussitôt le résultat de cette page dans l’autre sens.
Que signifie H4sI au début d’une charge utile ?
Que la charge utile est du gzip écrit en Base64 : tout flux gzip commence par les octets 1f 8b 08, et ces trois octets s’écrivent H4sI en Base64. Collez-la ici telle quelle. Une charge utile qui commence par eJ est très probablement du zlib à son niveau par défaut, et une charge utile qui ne commence par aucun des deux peut être du deflate brut, ou n’être pas compressée du tout, ce que la page vous indique.
Le gzip est-il un chiffrement ?
Non. La compression ne prend aucune clé, si bien que quiconque détient les octets peut les décompresser, ici ou avec gunzip, et chaque octet revient. Un mot de passe à l’intérieur d’une charge utile compressée avec gzip est aussi exposé qu’un mot de passe écrit en clair.
Ma charge utile ou mon fichier sont-ils téléversés quelque part ?
Non. La charge utile que vous collez et le fichier que vous choisissez ou déposez sont tous deux lus dans cet onglet, où un Web Worker que la page démarre se charge de la compression et de la décompression, et « Télécharger » construit son fichier à partir du résultat, sur place. Ni l’un ni l’autre n’est envoyé à ce site ni à qui que ce soit.

Outils connexes

  • Formateur JSON

    Valide et embellit le JSON, avec des emplacements d’erreur clairs.

  • Convertisseur de tailles de données

    Convertissez entre Ko, Mo, Go et Kio, Mio, Gio.

  • Base64

    Encode et décode le Base64 — prise en charge complète de l’UTF-8.

  • Base32

    Encode et décode le Base32 et le base32hex — du texte en UTF-8 ou des octets en hexadécimal.