1. Introdução: por que precisamos de arquivamento e compactação em Java
No mundo atual, trabalhar com arquivos compactados é uma tarefa comum: backups, compartilhamento de arquivos, registros (logs), armazenamento de grandes volumes de dados. O Java fornece ferramentas padrão para trabalhar com arquivos ZIP e arquivos compactados GZIP por meio do pacote java.util.zip.
O que o Java pode fazer:
- Ler e criar arquivos ZIP (contêineres com múltiplos arquivos).
- Ler e criar arquivos GZIP (compactação de um único arquivo).
- Gerenciar o conteúdo de arquivos compactados, filtrando arquivos por máscara.
- Controlar o nível de compactação.
- Verificar a segurança durante a descompactação (combate a zip slip e zip bomb).
2. Classes principais
ZipInputStream e ZipOutputStream
São classes de streams para leitura/gravação sequencial de arquivos ZIP. Quando usar: quando você precisa ler ou criar um arquivo compactado “on-the-fly”, sem acesso aleatório a arquivos individuais.
Exemplo: leitura de um arquivo ZIP
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("Arquivo: " + entry.getName());
// Você pode ler o conteúdo da entry via zis.read(...)
zis.closeEntry();
}
}
Exemplo: criação de um arquivo ZIP
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("Olá, arquivo!".getBytes());
zos.closeEntry();
}
ZipFile
Classe para acesso aleatório ao conteúdo de um arquivo ZIP: é possível obter rapidamente a lista de arquivos, abrir qualquer arquivo pelo nome e ler seu conteúdo.
Exemplo:
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)) {
// Ler o conteúdo do arquivo
}
zipFile.close();
Quando usar ZipFile?
- Quando você precisa rapidamente da lista de arquivos, metadados, tamanho, data.
- Quando você precisa ler arquivos específicos sem percorrer sequencialmente todo o arquivo compactado.
ZipEntry
Objeto que representa um arquivo ou pasta dentro do arquivo compactado. Contém nome, tamanho, data, flags, nível de compactação etc.
import java.util.zip.ZipEntry;
ZipEntry entry = new ZipEntry("docs/readme.txt");
entry.setComment("Descrição do arquivo");
entry.setTime(System.currentTimeMillis());
Níveis de compactação (Deflater)
Ao criar um arquivo compactado, é possível controlar o nível de compactação (de 0 — sem compactação — até 9 — compactação máxima):
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)
Regra: quanto maior o nível — mais lento, porém melhor a compactação.
3. Compactação de um único arquivo
GZIP é um formato para compactar um único arquivo (não é um arquivo compactado com múltiplos arquivos!). Usado para logs, arquivos temporários e transmissão em rede.
Exemplo: compactar arquivo
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);
}
Exemplo: descompactar arquivo
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);
}
Lembre-se: GZIP trabalha apenas com um único arquivo — ele não preserva a estrutura de pastas e metadados adicionais. Diferentemente, ZIP permite empacotar vários arquivos e diretórios inteiros, preservando sua estrutura e as informações de cada elemento.
4. Empacotamento/descompactação de diretórios, filtros com PathMatcher
Empacotar um diretório em ZIP
Para empacotar uma pasta com arquivos e subpastas, percorremos recursivamente a árvore de arquivos e adicionamos cada arquivo ao ZIP com o caminho relativo correto (os separadores em ZIP são 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(); }
});
}
Descompactar um arquivo ZIP em um diretório
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();
}
}
Filtragem de arquivos por máscara (PathMatcher)
É possível filtrar os arquivos para empacotar/descompactar por máscara, por exemplo apenas "*.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. Segurança: Zip Slip, zip bomb, verificação de normalização de caminho
Zip Slip (ataque via caminho)
Problema: um invasor pode criar um arquivo compactado contendo um arquivo cujo nome é "../../../../etc/passwd". Ao descompactar sem validação, esse arquivo pode sobrescrever arquivos do sistema!
Solução: antes de gravar o arquivo, normalize o caminho e garanta que ele não saia do diretório de destino.
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: tentativa de gravação fora da pasta de destino!");
}
if (entry.isDirectory()) {
Files.createDirectories(outPath);
} else {
Files.createDirectories(outPath.getParent());
try (OutputStream os = Files.newOutputStream(outPath)) {
zis.transferTo(os);
}
}
zis.closeEntry();
}
}
Zip bomb (bomba de arquivo)
Problema: um arquivo compactado pode conter um arquivo que, após a descompactação, ocupa gigabytes, embora o arquivo ZIP tenha apenas alguns kilobytes. Isso pode “matar” o servidor ou o disco.
Solução: limite o tamanho máximo dos arquivos descompactados e o volume total da descompactação, interrompendo o processo ao exceder o 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. Prática: utilitário de CLI “zip/unzip” com máscaras
Vamos escrever um utilitário de console simples para empacotar e descompactar arquivos com suporte a máscaras.
Exemplo: empacotamento
// 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(); }
});
}
}
Exemplo: descompactação com proteção contra 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: tentativa de gravação fora da pasta de destino!");
}
if (entry.isDirectory()) {
Files.createDirectories(outPath);
} else {
Files.createDirectories(outPath.getParent());
try (OutputStream os = Files.newOutputStream(outPath)) {
zis.transferTo(os);
}
}
zis.closeEntry();
}
}
}
Exemplo de execução:
java ZipUtil zip myfolder archive.zip "*.txt"
java ZipUtil unzip archive.zip output
No primeiro exemplo, o comando java ZipUtil zip myfolder archive.zip "*.txt" empacota todos os arquivos .txt da pasta myfolder no arquivo archive.zip. No segundo exemplo, java ZipUtil unzip archive.zip output descompacta o arquivo para a pasta output, verificando que nenhum arquivo seja gravado fora do diretório de destino — essa é a proteção contra Zip Slip.
GO TO FULL VERSION