CodeGym /Cursos /JAVA 25 SELF /Arquivamento/compactação: java.util.zip

Arquivamento/compactação: java.util.zip

JAVA 25 SELF
Nível 41 , Lição 4
Disponível

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.

1
Pesquisa/teste
Otimização de IO, nível 41, lição 4
Indisponível
Otimização de IO
Otimização de IO
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION