CodeGym /Cours /JAVA 25 SELF /Archives/compression : java.util.zip

Archives/compression : java.util.zip

JAVA 25 SELF
Niveau 41 , Leçon 4
Disponible

1. Introduction : pourquoi a-t-on besoin des archives et de la compression en Java

Dans le monde actuel, travailler avec des archives et des fichiers compressés est une tâche courante : sauvegardes, partage de fichiers, journalisation, stockage de grandes quantités de données. Java propose des moyens standard pour manipuler les archives ZIP et les fichiers compressés GZIP via le paquet java.util.zip.

Ce que Java sait faire :

  • Lire et créer des archives ZIP (conteneurs multi-fichiers).
  • Lire et créer des fichiers GZIP (compression d’un seul fichier).
  • Gérer le contenu des archives, filtrer des fichiers par masque.
  • Contrôler le niveau de compression.
  • Vérifier la sécurité lors de la décompression (lutte contre le zip slip et la zip bomb).

2. Classes principales

ZipInputStream et ZipOutputStream

Ce sont des classes orientées flux pour la lecture/écriture séquentielle des archives ZIP. Quand les utiliser : si vous devez lire ou créer une archive « à la volée », sans accès aléatoire aux fichiers individuels.

Exemple : lecture d’une archive

import java.io.*;
import java.util.zip.*;

try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
        System.out.println("Fichier: " + entry.getName());
        // Vous pouvez lire le contenu de entry via zis.read(...)
        zis.closeEntry();
    }
}

Exemple : création d’une archive

import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("archive.zip"))) {
    ZipEntry entry = new ZipEntry("hello.txt");
    zos.putNextEntry(entry);
    zos.write("Bonjour, archive!".getBytes());
    zos.closeEntry();
}

ZipFile

Classe pour l’accès aléatoire au contenu d’une archive ZIP : vous pouvez obtenir rapidement la liste des fichiers, ouvrir n’importe quel fichier par son nom et lire son contenu.

Exemple :

import java.util.zip.*;
import java.io.*;

ZipFile zipFile = new ZipFile("archive.zip");
zipFile.stream().forEach(entry -> System.out.println(entry.getName()));

ZipEntry entry = zipFile.getEntry("hello.txt");
try (InputStream is = zipFile.getInputStream(entry)) {
    // Lire le contenu du fichier
}
zipFile.close();

Quand utiliser ZipFile ?

  • Si vous devez rapidement obtenir la liste des fichiers, leurs métadonnées, taille, date.
  • Si vous devez lire des fichiers individuels sans parcourir séquentiellement toute l’archive.

ZipEntry

Objet représentant un fichier ou un dossier à l’intérieur de l’archive. Contient le nom, la taille, la date, des indicateurs, le niveau de compression, etc.

import java.util.zip.ZipEntry;

ZipEntry entry = new ZipEntry("docs/readme.txt");
entry.setComment("Description du fichier");
entry.setTime(System.currentTimeMillis());

Niveaux de compression (Deflater)

Lors de la création d’une archive, vous pouvez régler le degré de compression (de 0 — sans compression, à 9 — compression maximale) :

import java.io.FileOutputStream;
import java.util.zip.Deflater;
import java.util.zip.ZipOutputStream;

try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("archive.zip"))) {
    zos.setLevel(Deflater.BEST_COMPRESSION); // ou 0..9
    // ...
}
  • Deflater.NO_COMPRESSION (0)
  • Deflater.BEST_SPEED (1)
  • Deflater.BEST_COMPRESSION (9)
  • Deflater.DEFAULT_COMPRESSION (-1)

Règle : plus le niveau est élevé — plus c’est lent, mais plus la compression est forte.

3. Compression d’un seul fichier

GZIP est un format pour compresser un seul fichier (ce n’est pas une archive !). Utilisé pour les logs, les fichiers temporaires et la transmission réseau.

Exemple : compresser un fichier

import java.util.zip.*;
import java.io.*;

try (GZIPOutputStream gos = new GZIPOutputStream(new FileOutputStream("file.txt.gz"));
     FileInputStream fis = new FileInputStream("file.txt")) {
    fis.transferTo(gos);
}

Exemple : décompresser un fichier

import java.util.zip.GZIPInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

try (GZIPInputStream gis = new GZIPInputStream(new FileInputStream("file.txt.gz"));
     FileOutputStream fos = new FileOutputStream("file.txt")) {
    gis.transferTo(fos);
}

Rappelez-vous : GZIP ne fonctionne qu’avec un seul fichier — il ne conserve pas la structure des dossiers ni des métadonnées supplémentaires. À l’inverse, ZIP permet d’empaqueter plusieurs fichiers et des dossiers entiers, en conservant leur structure et les informations de chaque élément.

4. Compression/décompression d’un répertoire, filtres avec PathMatcher

Compresser un répertoire en ZIP

Pour empaqueter un dossier avec des fichiers et sous-dossiers, parcourez récursivement l’arborescence et ajoutez chaque fichier à l’archive avec un chemin relatif correct (les séparateurs dans ZIP sont toujours "/").

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

Path sourceDir = Paths.get("myfolder");
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("archive.zip"))) {
    Files.walk(sourceDir)
        .filter(Files::isRegularFile)
        .forEach(path -> {
            String entryName = sourceDir.relativize(path).toString().replace("\\", "/");
            try (InputStream is = Files.newInputStream(path)) {
                zos.putNextEntry(new ZipEntry(entryName));
                is.transferTo(zos);
                zos.closeEntry();
            } catch (IOException e) { e.printStackTrace(); }
        });
}

Décompresser une archive dans un répertoire

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
        Path outPath = Paths.get("output", entry.getName());
        if (entry.isDirectory()) {
            Files.createDirectories(outPath);
        } else {
            Files.createDirectories(outPath.getParent());
            try (OutputStream os = Files.newOutputStream(outPath)) {
                zis.transferTo(os);
            }
        }
        zis.closeEntry();
    }
}

Filtrer des fichiers par motif (PathMatcher)

Vous pouvez filtrer les fichiers à empaqueter/dépaqueter par motif, par exemple uniquement "*.txt" :

import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.PathMatcher;

PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.txt");
Files.walk(sourceDir)
    .filter(matcher::matches)
    .forEach(/* ... */);

5. Sécurité : Zip Slip, zip bomb, vérification de la normalisation du chemin

Zip Slip (attaque via le chemin)

Problème : un attaquant peut créer une archive avec un fichier dont le nom est "../../../../etc/passwd". Lors d’une décompression sans vérification, un tel fichier peut écraser des fichiers système !

Solution : avant d’écrire un fichier, normalisez le chemin et assurez-vous qu’il ne sort pas du répertoire cible.

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

Path targetDir = Paths.get("output").toAbsolutePath();
try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
        Path outPath = targetDir.resolve(entry.getName()).normalize();
        if (!outPath.startsWith(targetDir)) {
            throw new IOException("Zip Slip: tentative d'écriture en dehors du dossier cible!");
        }
        if (entry.isDirectory()) {
            Files.createDirectories(outPath);
        } else {
            Files.createDirectories(outPath.getParent());
            try (OutputStream os = Files.newOutputStream(outPath)) {
                zis.transferTo(os);
            }
        }
        zis.closeEntry();
    }
}

Zip bomb (bombe ZIP)

Problème : une archive peut contenir un fichier qui, après décompression, occupe des gigaoctets, alors que l’archive elle-même ne pèse que quelques kilo-octets. Cela peut « tuer » un serveur ou remplir un disque.

Solution : limitez la taille maximale des fichiers décompressés et le volume total de décompression, en interrompant le processus lorsqu’un seuil est dépassé.

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

long maxSize = 100 * 1024 * 1024; // 100 Mo
long totalUnzipped = 0;

try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
        Path outPath = Paths.get("output", entry.getName()).normalize();
        Files.createDirectories(outPath.getParent());

        long written = 0;
        try (OutputStream os = Files.newOutputStream(outPath)) {
            byte[] buf = new byte[8192];
            int len;
            while ((len = zis.read(buf)) > 0) {
                os.write(buf, 0, len);
                written += len;
                totalUnzipped += len;
                if (written > maxSize || totalUnzipped > maxSize) {
                    throw new IOException("Zip bomb detected!");
                }
            }
        }
        zis.closeEntry();
    }
}

6. Pratique : utilitaire CLI « zip/unzip » avec masques

Écrivons un petit utilitaire en ligne de commande pour empaqueter et dépaqueter des fichiers avec prise en charge des masques.

Exemple : empaquetage

// java ZipUtil zip myfolder archive.zip *.txt
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public static void zip(String sourceDir, String zipFile, String glob) throws IOException {
    Path src = Paths.get(sourceDir);
    PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + glob);
    try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
        Files.walk(src)
            .filter(Files::isRegularFile)
            .filter(matcher::matches)
            .forEach(path -> {
                String entryName = src.relativize(path).toString().replace("\\", "/");
                try (InputStream is = Files.newInputStream(path)) {
                    zos.putNextEntry(new ZipEntry(entryName));
                    is.transferTo(zos);
                    zos.closeEntry();
                } catch (IOException e) { e.printStackTrace(); }
            });
    }
}

Exemple : dépaquetage avec protection contre Zip Slip

// java ZipUtil unzip archive.zip output
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public static void unzip(String zipFile, String outDir) throws IOException {
    Path targetDir = Paths.get(outDir).toAbsolutePath();
    try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
        ZipEntry entry;
        while ((entry = zis.getNextEntry()) != null) {
            Path outPath = targetDir.resolve(entry.getName()).normalize();
            if (!outPath.startsWith(targetDir)) {
                throw new IOException("Zip Slip: tentative d'écriture en dehors du dossier cible!");
            }
            if (entry.isDirectory()) {
                Files.createDirectories(outPath);
            } else {
                Files.createDirectories(outPath.getParent());
                try (OutputStream os = Files.newOutputStream(outPath)) {
                    zis.transferTo(os);
                }
            }
            zis.closeEntry();
        }
    }
}

Exemple d’exécution :

java ZipUtil zip myfolder archive.zip "*.txt"
java ZipUtil unzip archive.zip output

Dans le premier exemple, la commande java ZipUtil zip myfolder archive.zip "*.txt" empaquette tous les fichiers .txt du dossier myfolder dans l’archive archive.zip. Dans le second exemple, java ZipUtil unzip archive.zip output dépaquette l’archive dans le dossier output, tout en vérifiant qu’aucun fichier n’est écrit en dehors du répertoire cible — c’est la protection contre le Zip Slip.

1
Étude/Quiz
Optimisation IO, niveau 41, leçon 4
Indisponible
Optimisation IO
Optimisation IO
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION