CodeGym /Corsi /JAVA 25 SELF /Archivi/compressione: java.util.zip

Archivi/compressione: java.util.zip

JAVA 25 SELF
Livello 41, Lezione 4
Disponibile

1. Introduzione: perché servono archivi e compressione in Java

Nel mondo moderno lavorare con archivi e file compressi — un compito comune: backup, scambio di file, logging, archiviazione di grandi quantità di dati. Java fornisce strumenti standard per lavorare con archivi in formato ZIP e file compressi GZIP tramite il pacchetto java.util.zip.

Cosa sa fare Java:

  • Leggere e creare archivi ZIP (contenitori multi-file).
  • Leggere e creare file GZIP (compressione di un singolo file).
  • Gestire il contenuto degli archivi, filtrare i file per maschera.
  • Controllare il livello di compressione.
  • Verificare la sicurezza durante l’estrazione (protezione da zip slip e zip bomb).

2. Classi principali

ZipInputStream e ZipOutputStream

Sono classi di stream per la lettura/scrittura sequenziale di archivi ZIP. Quando usare: se è necessario leggere o creare un archivio «al volo», senza accesso casuale ai singoli file.

Esempio: lettura di un archivio

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("File: " + entry.getName());
        // Si può leggere il contenuto dell'entry tramite zis.read(...)
        zis.closeEntry();
    }
}

Esempio: creazione di un archivio

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("Ciao, archivio!".getBytes());
    zos.closeEntry();
}

ZipFile

Classe per l’accesso casuale al contenuto di un archivio ZIP: consente di ottenere rapidamente l’elenco dei file, aprire qualsiasi file per nome e leggerne il contenuto.

Esempio:

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)) {
    // Leggiamo il contenuto del file
}
zipFile.close();

Quando usare ZipFile?

  • Se serve ottenere rapidamente l’elenco dei file, i metadati, le dimensioni, la data.
  • Se bisogna leggere singoli file senza scorrere sequenzialmente l’intero archivio.

ZipEntry

Oggetto che rappresenta un singolo file o una cartella all’interno dell’archivio. Contiene nome, dimensione, data, flag, livello di compressione, ecc.

import java.util.zip.ZipEntry;

ZipEntry entry = new ZipEntry("docs/readme.txt");
entry.setComment("Descrizione del file");
entry.setTime(System.currentTimeMillis());

Livelli di compressione (Deflater)

Durante la creazione di un archivio è possibile controllare il grado di compressione (da 0 — nessuna compressione, a 9 — compressione massima):

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); // oppure 0..9
    // ...
}
  • Deflater.NO_COMPRESSION (0)
  • Deflater.BEST_SPEED (1)
  • Deflater.BEST_COMPRESSION (9)
  • Deflater.DEFAULT_COMPRESSION (-1)

Regola: più alto è il livello — più lenta è l’operazione, ma migliore è la compressione.

3. Compressione di un singolo file

GZIP — è un formato per la compressione di un singolo file (non un archivio!). Si usa per log, file temporanei, trasferimento in rete.

Esempio: comprimere un file

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);
}

Esempio: decomprimere un file

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);
}

Ricorda: GZIP funziona solo con un file — non conserva la struttura delle cartelle né metadati aggiuntivi. Al contrario, ZIP consente di impacchettare più file e intere cartelle, preservandone la struttura e le informazioni di ciascun elemento.

4. Compressione/estrazione di directory, filtri con PathMatcher

Impacchettare una directory in ZIP

Per impacchettare una cartella con file e sottocartelle, si percorre ricorsivamente l’albero dei file e si aggiunge ogni file all’archivio con il percorso relativo corretto (i separatori in ZIP sono sempre "/").

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(); }
        });
}

Estrazione dell’archivio in una directory

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();
    }
}

Filtraggio dei file per maschera (PathMatcher)

È possibile filtrare i file per l’impacchettamento/l’estrazione in base a una maschera, ad esempio solo "*.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. Sicurezza: Zip Slip, zip bomb, verifica della normalizzazione del percorso

Zip Slip (attacco tramite percorso)

Problema: un attaccante può creare un archivio con un file il cui nome è "../../../../etc/passwd". Se estratto senza controlli, un tale file può sovrascrivere file di sistema!

Soluzione: prima di scrivere il file, normalizza il percorso e assicurati che non esca dalla directory di destinazione.

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: tentativo di scrittura fuori dalla cartella di destinazione!");
        }
        if (entry.isDirectory()) {
            Files.createDirectories(outPath);
        } else {
            Files.createDirectories(outPath.getParent());
            try (OutputStream os = Files.newOutputStream(outPath)) {
                zis.transferTo(os);
            }
        }
        zis.closeEntry();
    }
}

Zip bomb (archivio-bomba)

Problema: l’archivio può contenere un file che, una volta estratto, occupa gigabyte, mentre l’archivio stesso pesa pochi kilobyte. Questo può “mandare in crisi” il server o il disco.

Soluzione: limita la dimensione massima dei file estratti e il volume totale dell’estrazione, interrompendo il processo al superamento del limite.

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 MB
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. Pratica: utility CLI «zip/unzip» con maschere

Scriviamo una semplice utility da riga di comando per comprimere e decomprimere file con supporto delle maschere.

Esempio: compressione

// 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(); }
            });
    }
}

Esempio: estrazione con protezione da 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: tentativo di scrittura fuori dalla cartella di destinazione!");
            }
            if (entry.isDirectory()) {
                Files.createDirectories(outPath);
            } else {
                Files.createDirectories(outPath.getParent());
                try (OutputStream os = Files.newOutputStream(outPath)) {
                    zis.transferTo(os);
                }
            }
            zis.closeEntry();
        }
    }
}

Esempio di esecuzione:

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

Nel primo esempio il comando java ZipUtil zip myfolder archive.zip "*.txt" comprime tutti i file .txt dalla cartella myfolder nell’archivio archive.zip. Nel secondo esempio java ZipUtil unzip archive.zip output estrae l’archivio nella cartella output, verificando che nessun file venga scritto al di fuori della directory di destinazione — questa è la protezione da Zip Slip.

1
Compito
JAVA 25 SELF, livello 41, lezione 4
Bloccato
Invio del rapporto segreto nello spazio: Operazione "Compressione" 🚀
Invio del rapporto segreto nello spazio: Operazione "Compressione" 🚀
1
Compito
JAVA 25 SELF, livello 41, lezione 4
Bloccato
La grande archiviazione: Raccolta dei rotoli di testo della Biblioteca Digitale 📚
La grande archiviazione: Raccolta dei rotoli di testo della Biblioteca Digitale 📚
1
Sondaggio/quiz
Ottimizzazione IO, livello 41, lezione 4
Non disponibile
Ottimizzazione IO
Ottimizzazione IO
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION