CodeGym /Kurslar /JAVA 25 SELF /Arxivlər/sıxılma: java.util.zip

Arxivlər/sıxılma: java.util.zip

JAVA 25 SELF
Səviyyə , Dərs
Mövcuddur

1. Giriş: Java-da arxivlər və sıxılma nə üçün lazımdır

Müasir dünyada arxivlərlə və sıxılmış fayllarla iş — adi bir vəzifədir: ehtiyat nüsxələr, fayl mübadiləsi, loqların aparılması, böyük verilənlərin saxlanması. Java ZIP formatlı arxivlərlə və GZIP sıxılmış fayllarla işləmək üçün java.util.zip paketi vasitəsilə standart vasitələr təqdim edir.

Java nə edə bilir:

  • ZIP arxivlərini oxumaq və yaratmaq (çoxfayllı konteynerlər).
  • GZIP fayllarını oxumaq və yaratmaq (tək faylın sıxılması).
  • Arxivlərin məzmununu idarə etmək, faylları maskaya görə filtrdən keçirmək.
  • Sıxılma səviyyəsini idarə etmək.
  • Arxivdən çıxarma zamanı təhlükəsizliyi yoxlamaq ( zip slipzip bomb ilə mübarizə).

2. Əsas siniflər

ZipInputStream və ZipOutputStream

Bunlar ZIP arxivlərini ardıcıl oxumaq/yazmaq üçün axın sinifləridir. Nə vaxt istifadə etməli: ayrı-ayrı fayllara təsadüfi giriş olmadan arxivi “uçuş zamanı” oxumaq və ya yaratmaq lazım olanda.

Nümunə: arxivin oxunması

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("Fayl: " + entry.getName());
        // Entry məzmununu zis.read(...) vasitəsilə oxumaq olar
        zis.closeEntry();
    }
}

Nümunə: arxivin yaradılması

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

ZipFile

ZIP arxivinin məzmununa təsadüfi giriş üçün sinif: fayl siyahısını tez əldə etmək, istənilən faylı ada görə açmaq, onun məzmununu oxumaq olar.

Nümunə:

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)) {
    // Faylın məzmununu oxuyuruq
}
zipFile.close();

ZipFile nə vaxt istifadə olunmalıdır?

  • Faylların siyahısını, metadatanı, ölçünü, tarixi tez əldə etmək lazım olduqda.
  • Bütün arxivi ardıcıl gəzmeden ayrı-ayrı faylları oxumaq lazım olduqda.

ZipEntry

Arxiv daxilində ayrı bir faylı və ya qovluğu təmsil edən obyekt. Ad, ölçü, tarix, flaqlar, sıxılma səviyyəsi və s. saxlayır.

import java.util.zip.ZipEntry;

ZipEntry entry = new ZipEntry("docs/readme.txt");
entry.setComment("Faylın təsviri");
entry.setTime(System.currentTimeMillis());

Sıxılma səviyyələri (Deflater)

Arxivi yaradarkən sıxılma dərəcəsini idarə etmək olar ( 0 — sıxılma yoxdur, 9 — maksimal sıxılma):

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

Qayda: səviyyə nə qədər yüksəkdirsə — bir o qədər yavaşdır, amma daha güclü sıxır.

3. Tək faylın sıxılması

GZIP — tək bir faylın sıxılması üçün formatdır (arxiv deyil!). Loqlar, müvəqqəti fayllar və şəbəkə üzərindən ötürmə üçün tətbiq olunur.

Nümunə: faylı sıxmaq

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

Nümunə: faylı çıxarmaq

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

Yadda saxlayın: GZIP yalnız bir faylla işləyir — qovluq strukturunu və əlavə metadatanı saxlamır. Ondan fərqli olaraq, ZIP bir neçə faylı və bütöv qovluqları paketləməyə, onların strukturunu və hər element haqqında məlumatı saxlamağa imkan verir.

4. Qovluğun paketlənməsi/çıxarılması, PathMatcher üzrə filtrlər

Qovluğun ZIP-ə paketlənməsi

Fayl və alt qovluqlardan ibarət qovluğu paketləmək üçün fayl ağacını rekursiv gəzib hər faylı düzgün nisbi yol ilə arxivə əlavə edirik (ZIP-də ayırıcı həmişə "/"-dür).

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

Arxivin qovluğa çıxarılması

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

Faylların maskaya görə filtrasiyası (PathMatcher)

Paketləmə/çıxarma üçün faylları maskaya görə filtrdən keçirmək olar, məsələn yalnız "*.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. Təhlükəsizlik: Zip Slip, zip bomb, yolun normallaşdırılmasının yoxlanması

Zip Slip (yol üzərindən hücum)

Problem: Qəsdən zərərli şəxs adı — "../../../../etc/passwd" olan faylla arxiv yarada bilər. Yoxlama olmadan çıxarılma zamanı belə fayl sistem fayllarını üstələyə bilər!

Həll: faylı yazmazdan əvvəl yolu normallaşdırın və onun təyinat qovluğunun hüdudlarından kənara çıxmadığına əmin olun.

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: faylın təyinat qovluğundan kənara yazılmasına cəhd!");
        }
        if (entry.isDirectory()) {
            Files.createDirectories(outPath);
        } else {
            Files.createDirectories(outPath.getParent());
            try (OutputStream os = Files.newOutputStream(outPath)) {
                zis.transferTo(os);
            }
        }
        zis.closeEntry();
    }
}

Zip bomb (arxiv-bomba)

Problem: arxiv elə bir fayl saxlaya bilər ki, çıxarıldıqdan sonra gigabaytlarla yer tutsun, halbuki arxivin özü cəmi bir neçə kilobaytdır. Bu, serveri və ya diski “öldürə” bilər.

Həll: çıxarılan faylların maksimal ölçüsünü və ümumi çıxarma həcmini məhdudlaşdırın, limit aşılarsa prosesi dayandırın.

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. Praktika: maskalarla “zip/unzip” CLI-utiliti

Maskaları dəstəkləyən faylları paketləmək və çıxarmaq üçün sadə konsol utilitisi yazacağıq.

Nümunə: paketləmə

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

Nümunə: Zip Slip-dən qorunma ilə çıxarma

// 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: faylın təyinat qovluğundan kənara yazılmasına cəhd!");
            }
            if (entry.isDirectory()) {
                Files.createDirectories(outPath);
            } else {
                Files.createDirectories(outPath.getParent());
                try (OutputStream os = Files.newOutputStream(outPath)) {
                    zis.transferTo(os);
                }
            }
            zis.closeEntry();
        }
    }
}

İşə salma nümunəsi:

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

Birinci nümunədə java ZipUtil zip myfolder archive.zip "*.txt" əmri myfolder qovluğundakı bütün .txt faylları archive.zip arxivinə paketləyir. İkinci nümunədə java ZipUtil unzip archive.zip output arxivi output qovluğuna çıxarır və heç bir faylın təyinat qovluğundan kənara yazılmaması yoxlanılır — bu, Zip Slip-dən qorunmadır.

1
Sorğu/viktorina
, səviyyə, dərs
Əlçatan deyil
IO optimizasiyası
IO optimizasiyası
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION