CodeGym /Kurse /JAVA 25 SELF /Archive/Komprimierung: java.util.zip

Archive/Komprimierung: java.util.zip

JAVA 25 SELF
Level 41, Lektion 4
Verfügbar

1. Einführung: Wozu Archive und Komprimierung in Java

In der heutigen Welt ist der Umgang mit Archiven und komprimierten Dateien eine alltägliche Aufgabe: Backups, Dateiaustausch, Logging, Speicherung großer Datenmengen. Java stellt Standardwerkzeuge für die Arbeit mit ZIP-Archiven und GZIP-komprimierten Dateien über das Paket java.util.zip bereit.

Was Java kann:

  • ZIP-Archive lesen und erstellen (Mehrdatei-Container).
  • GZIP-Dateien lesen und erstellen (Komprimierung einer einzelnen Datei).
  • Archivinhalt verwalten, Dateien nach Muster filtern.
  • Den Komprimierungsgrad steuern.
  • Sicherheit beim Entpacken prüfen (Schutz vor Zip Slip und Zip-Bomb).

2. Grundlegende Klassen

ZipInputStream und ZipOutputStream

Das sind Stream-Klassen für sequentielles Lesen/Schreiben von ZIP-Archiven. Wann verwenden: wenn ein Archiv „on the fly“ gelesen oder erstellt werden soll, ohne Random-Access zu einzelnen Dateien.

Beispiel: Archiv lesen

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("Datei: " + entry.getName());
        // Der Inhalt des Eintrags kann über zis.read(...) gelesen werden
        zis.closeEntry();
    }
}

Beispiel: Archiv erstellen

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

ZipFile

Klasse für Random-Access zum Inhalt eines ZIP-Archivs: Man kann schnell die Dateiliste erhalten, jede Datei per Namen öffnen und ihren Inhalt lesen.

Beispiel:

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)) {
    // Dateiinhalt lesen
}
zipFile.close();

Wann ZipFile verwenden?

  • Wenn man schnell die Dateiliste, Metadaten, Größe und Datum erhalten möchte.
  • Wenn man einzelne Dateien lesen will, ohne das gesamte Archiv sequentiell zu durchlaufen.

ZipEntry

Ein Objekt, das eine einzelne Datei oder einen Ordner innerhalb des Archivs repräsentiert. Enthält Name, Größe, Datum, Flags, Komprimierungsstufe usw.

import java.util.zip.ZipEntry;

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

Komprimierungsstufen (Deflater)

Beim Erstellen eines Archivs kann man den Komprimierungsgrad steuern (von 0 – ohne Komprimierung, bis 9 – maximale Komprimierung):

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

Faustregel: je höher die Stufe, desto langsamer, aber desto stärker die Komprimierung.

3. Komprimierung einer einzelnen Datei

GZIP ist ein Format zur Komprimierung einer einzigen Datei (kein Archiv!). Wird für Logs, temporäre Dateien und die Übertragung über das Netzwerk verwendet.

Beispiel: Datei komprimieren

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

Beispiel: Datei entpacken

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

Merken Sie: GZIP arbeitet nur mit einer Datei – es speichert weder Ordnerstruktur noch zusätzliche Metadaten. Im Gegensatz dazu ermöglicht ZIP das Packen mehrerer Dateien und ganzer Ordner und bewahrt deren Struktur sowie Informationen zu jedem Element.

4. Packen/Entpacken eines Verzeichnisses, Filter mit PathMatcher

Ein Verzeichnis in ein ZIP packen

Um einen Ordner mit Dateien und Unterordnern zu packen, durchlaufen wir den Dateibaum rekursiv und fügen jede Datei mit dem richtigen relativen Pfad hinzu (Pfadtrenner im ZIP sind immer "/").

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

Ein Archiv in ein Verzeichnis entpacken

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

Dateien nach Muster filtern (PathMatcher)

Dateien lassen sich für das Packen/Entpacken nach einem Muster filtern, z. B. nur "*.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. Sicherheit: Zip Slip, Zip-Bomb, Prüfung der Pfadnormalisierung

Zip Slip (Angriff über Pfadangaben)

Problem: Ein Angreifer kann ein Archiv mit einer Datei erstellen, deren Name — "../../../../etc/passwd" — lautet. Beim Entpacken ohne Prüfung kann eine solche Datei Systemdateien überschreiben!

Lösung: Vor dem Schreiben den Pfad normalisieren und sicherstellen, dass er das Zielverzeichnis nicht verlässt.

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: Versuch, außerhalb des Zielordners zu schreiben!");
        }
        if (entry.isDirectory()) {
            Files.createDirectories(outPath);
        } else {
            Files.createDirectories(outPath.getParent());
            try (OutputStream os = Files.newOutputStream(outPath)) {
                zis.transferTo(os);
            }
        }
        zis.closeEntry();
    }
}

Zip-Bomb (Archivbombe)

Problem: Ein Archiv kann eine Datei enthalten, die nach dem Entpacken Gigabytes belegt, obwohl das Archiv selbst nur wenige Kilobyte groß ist. Das kann Server oder Festplatte lahmlegen.

Lösung: Beschränken Sie die maximale Größe entpackter Dateien und das Gesamtvolumen der Entpackung und brechen Sie den Prozess bei Überschreitung ab.

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. Praxis: CLI-Tool „zip/unzip“ mit Mustern

Wir schreiben ein einfaches Konsolen-Tool zum Packen und Entpacken von Dateien mit Musterunterstützung.

Beispiel: Packen

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

Beispiel: Entpacken mit Schutz vor 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: Versuch, außerhalb des Zielordners zu schreiben!");
            }
            if (entry.isDirectory()) {
                Files.createDirectories(outPath);
            } else {
                Files.createDirectories(outPath.getParent());
                try (OutputStream os = Files.newOutputStream(outPath)) {
                    zis.transferTo(os);
                }
            }
            zis.closeEntry();
        }
    }
}

Beispielaufruf:

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

Im ersten Beispiel packt der Befehl java ZipUtil zip myfolder archive.zip "*.txt" alle .txt-Dateien aus dem Ordner myfolder in das Archiv archive.zip. Im zweiten Beispiel entpackt java ZipUtil unzip archive.zip output das Archiv in den Ordner output, wobei sichergestellt wird, dass keine Datei außerhalb des Zielverzeichnisses geschrieben wird – genau das ist der Schutz vor Zip Slip.

1
Aufgabe
JAVA 25 SELF, Level 41, Lektion 4
Gesperrt
Versand des geheimen Berichts ins All: Operation "Kompression" 🚀
Versand des geheimen Berichts ins All: Operation "Kompression" 🚀
1
Aufgabe
JAVA 25 SELF, Level 41, Lektion 4
Gesperrt
Die große Archivierung: Sammlung der Textrollen der Digitalbibliothek 📚
Die große Archivierung: Sammlung der Textrollen der Digitalbibliothek 📚
1
Umfrage/Quiz
IO-Optimierung, Level 41, Lektion 4
Nicht verfügbar
IO-Optimierung
IO-Optimierung
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION