Tags:

Nén và giải ném file định dạng ZIP trong Java

Trong hướng dẫn nhanh này, chúng ta sẽ thảo luận về cách nén và giải nén file trong Java theo định dạng zip sử dụng java.util.zip package. Đây là một package được cung cấp sẵn trong bộ thư viện lõi của Java.

Nén một file định dạng ZIP trong Java

Đầu tiên, chúng ta sẽ bắt đầu với công việc đơn giản nhất, giải nén một file trong Java. Ví dụ chúng ta sẽ nén một tệp có tên test1.txt thành file nén định dạng zip có tên compressed.zip

public class ZipFile {
    public static void main(String[] args) throws IOException {
        String sourceFile = "test1.txt";
        FileOutputStream fos = new FileOutputStream("compressed.zip");
        ZipOutputStream zipOut = new ZipOutputStream(fos);
        File fileToZip = new File(sourceFile);
        FileInputStream fis = new FileInputStream(fileToZip);
        ZipEntry zipEntry = new ZipEntry(fileToZip.getName());
        zipOut.putNextEntry(zipEntry);
        byte[] bytes = new byte[1024];
        int length;
        while((length = fis.read(bytes)) >= 0) {
            zipOut.write(bytes, 0, length);
        }
        zipOut.close();
        fis.close();
        fos.close();
    }

Nén nhiều file trong Java

Sau khi đã biết cách giải nén một file sử dụng java.util.zip package. Phần này chúng ta sẽ tìm hiểu cách giải nén nhiều file thành một file nén duy nhất.

 

public class ZipMultipleFiles {
    public static void main(String[] args) throws IOException {
        List<String> srcFiles = Arrays.asList("test1.txt", "test2.txt");
        FileOutputStream fos = new FileOutputStream("multiCompressed.zip");
        ZipOutputStream zipOut = new ZipOutputStream(fos);
        for (String srcFile : srcFiles) {
            File fileToZip = new File(srcFile);
            FileInputStream fis = new FileInputStream(fileToZip);
            ZipEntry zipEntry = new ZipEntry(fileToZip.getName());
            zipOut.putNextEntry(zipEntry);

            byte[] bytes = new byte[1024];
            int length;
            while((length = fis.read(bytes)) >= 0) {
                zipOut.write(bytes, 0, length);
            }
            fis.close();
        }
        zipOut.close();
        fos.close();
    }
}

Nén thư mục trong Java

Bây giờ, chúng ta hãy thảo luận về cách nén toàn bộ thư mục. Ví dụ chúng ta sẽ nén thư mục zipTest thành dirCompressed.zip.

public class ZipDirectory {
    public static void main(String[] args) throws IOException {
        String sourceFile = "zipTest";
        FileOutputStream fos = new FileOutputStream("dirCompressed.zip");
        ZipOutputStream zipOut = new ZipOutputStream(fos);
        File fileToZip = new File(sourceFile);

        zipFile(fileToZip, fileToZip.getName(), zipOut);
        zipOut.close();
        fos.close();
    }

    private static void zipFile(File fileToZip, String fileName, ZipOutputStream zipOut) throws IOException {
        if (fileToZip.isHidden()) {
            return;
        }
        if (fileToZip.isDirectory()) {
            if (fileName.endsWith("/")) {
                zipOut.putNextEntry(new ZipEntry(fileName));
                zipOut.closeEntry();
            } else {
                zipOut.putNextEntry(new ZipEntry(fileName + "/"));
                zipOut.closeEntry();
            }
            File[] children = fileToZip.listFiles();
            for (File childFile : children) {
                zipFile(childFile, fileName + "/" + childFile.getName(), zipOut);
            }
            return;
        }
        FileInputStream fis = new FileInputStream(fileToZip);
        ZipEntry zipEntry = new ZipEntry(fileName);
        zipOut.putNextEntry(zipEntry);
        byte[] bytes = new byte[1024];
        int length;
        while ((length = fis.read(bytes)) >= 0) {
            zipOut.write(bytes, 0, length);
        }
        fis.close();
    }
}

Trong đó

  • Để nén các thư mục con bên trong thư mục zipTest, chúng ta cần sử dụng đệ quy để duyệt hết chúng.
  • Mỗi khi tìm thấy một thư mục, chúng ta sẽ nối tên của nó với các thành phần con bên trong để tạo ra một cấu trúc phân cấp.
  • Ngoài ra chúng ta cũng tạo một thư mục cho các thư mục trống.

Giải nén file ZIP

Đối với ví dụ này, chúng tôi sẽ giải nén compressed.zip vào một thư mục mới có tên là unzipTest.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class UnzipFile {
    public static void main(final String[] args) throws IOException {
        final String fileZip = "src/main/resources/unzipTest/compressed.zip";
        final File destDir = new File("src/main/resources/unzipTest");
        final byte[] buffer = new byte[1024];
        final ZipInputStream zis = new ZipInputStream(new FileInputStream(fileZip));
        ZipEntry zipEntry = zis.getNextEntry();
        while (zipEntry != null) {
            final File newFile = newFile(destDir, zipEntry);
            if (zipEntry.isDirectory()) {
                if (!newFile.isDirectory() && !newFile.mkdirs()) {
                    throw new IOException("Failed to create directory " + newFile);
                }
            } else {
                File parent = newFile.getParentFile();
                if (!parent.isDirectory() && !parent.mkdirs()) {
                    throw new IOException("Failed to create directory " + parent);
                }

                final FileOutputStream fos = new FileOutputStream(newFile);
                int len;
                while ((len = zis.read(buffer)) > 0) {
                    fos.write(buffer, 0, len);
                }
                fos.close();
            }
            zipEntry = zis.getNextEntry();
        }
        zis.closeEntry();
        zis.close();
    }

    /**
     * @see https://snyk.io/research/zip-slip-vulnerability
     */
    public static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException {
        File destFile = new File(destinationDir, zipEntry.getName());

        String destDirPath = destinationDir.getCanonicalPath();
        String destFilePath = destFile.getCanonicalPath();

        if (!destFilePath.startsWith(destDirPath + File.separator)) {
            throw new IOException("Entry is outside of the target dir: " + zipEntry.getName());
        }

        return destFile;
    }
}

Trong đó, đoạn mã sau được dùng để duyệt qua từng ZipEntry và kiểm tra nếu đó là một thư mục, chúng ta sẽ tạo mới một thư mục tương ứng sử dụng mkdirs() method. Ngược lại thì tiếp tục tạo file.

while (zipEntry != null) {
     File newFile = newFile(destDir, zipEntry);
     if (zipEntry.isDirectory()) {
         if (!newFile.isDirectory() && !newFile.mkdirs()) {
             throw new IOException("Failed to create directory " + newFile);
         }
     } else {
         // fix for Windows-created archives
         File parent = newFile.getParentFile();
         if (!parent.isDirectory() && !parent.mkdirs()) {
             throw new IOException("Failed to create directory " + parent);
         }
         
         // write file content
         FileOutputStream fos = new FileOutputStream(newFile);
         int len;
         while ((len = zis.read(buffer)) > 0) {
             fos.write(buffer, 0, len);
         }
         fos.close();
     }
 zipEntry = zis.getNextEntry();
}

Một lưu ý ở đây là trên nhánh else, trước tiên chúng ta cần kiểm tra xem thư mục mẹ của tệp có tồn tại hay không. Điều này là cần thiết cho các tệp lưu trữ được tạo trên Windows, nơi các thư mục gốc không có zip entry trong zip file.

Một điểm quan trọng khác có thể thấy trong phương thức newFile ():

public static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException {
    File destFile = new File(destinationDir, zipEntry.getName());

    String destDirPath = destinationDir.getCanonicalPath();
    String destFilePath = destFile.getCanonicalPath();

    if (!destFilePath.startsWith(destDirPath + File.separator)) {
        throw new IOException("Entry is outside of the target dir: " + zipEntry.getName());
    }

    return destFile;
}

Hàm này dùng để bảo vệ việc ghi tệp vào hệ thống tệp bên ngoài target folder. Lỗ hổng này được gọi là Zip Slip và bạn có thể đọc thêm về nó tại đây.

Nguồn

https://www.baeldung.com/java-compress-and-uncompress

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x