Java ZipFile教程
JavaZipFile
类(java.util.zip.ZipFile
)可用于从ZIP文件读取文件。实际上,ZipFile
类非常易于使用。本教程将向我们展示如何使用ZipFile
类。
创建一个ZipFile实例
为了使用JavaZipFile
类,我们必须首先创建一个ZipFile
实例。这是创建JavaZipFile
实例的示例:
ZipFile zipFile = new ZipFile("d:\data\myzipfile.zip");
如我们所见,ZipFile
类在其构造函数中采用单个参数。此参数是要打开的ZIP文件的路径。
获取一个ZipEntry
ZIP文件中的每个文件都由一个ZipEntry
(java.util.zip.ZipEntry
)表示。要从ZIP文件中提取文件,我们可以在ZipFile
类上调用getEntry()
方法。这是调用getEntry()
的示例:
ZipEntry zipEntry = zipFile.getEntry("file1.txt");
本示例获取一个ZIP条目,该ZIP条目表示包含在ZIP文件中的file1.txt文件。
如果要提取的文件位于ZIP文件中的一个或者多个目录中,请在路径中包括这些目录,如下所示:
ZipEntry zipEntry = zipFile.getEntry("dir/subdir/file1.txt");
读取文件
要读取由ZipEntry
表示的文件,我们可以像这样从ZipFile
获得InputStream
:
ZipEntry zipEntry = zipFile.getEntry("dir/subdir/file1.txt"); InputStream inputStream = this.zipFile.getInputStream(zipEntry);
从ZipFile类的getInputStream()获得的InputStream可以像其他Java InputStream一样读取。
列出ZipFile中的所有条目
我们可以使用entries()
方法列出ZipFile
中包含的所有条目。这是调用ZipFile``entries()
的示例:
Enumeration<? extends ZipEntry> entries = zipFile.entries();
我们可以像这样迭代由entries()方法返回的"枚举":
Enumeration<? extends ZipEntry> entries = zipFile.entries(); while(entries.hasMoreElements()){ ZipEntry entry = entries.nextElement(); if(entry.isDirectory()){ System.out.println("dir : " + entry.getName()); } else { System.out.println("file : " + entry.getName()); } }
解压缩ZipFile中的所有条目
没有简单的方法可以解压缩" ZipFile"的所有条目。我们将必须自己做。为了使我们更轻松,我将向我们展示解压缩Java ZipFile中所有条目所需的代码示例。这是代码:
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public class FileUnzipper { private String zipFileDir = null; private String zipFileName = null; private String unzipDir = null; public FileUnzipper(String zipFileDir, String zipFileName, String unzipDir) { this.zipFileDir = zipFileDir; this.zipFileName = zipFileName; this.unzipDir = unzipDir; } public void unzip() { String zipFilePath = this.zipFileDir + File.separator + this.zipFileName; try{ System.out.println("zipFilePath = " + zipFilePath); ZipFile zipFile = new ZipFile(zipFilePath); Enumeration<? extends ZipEntry> entries = zipFile.entries(); while(entries.hasMoreElements()){ ZipEntry entry = entries.nextElement(); if(entry.isDirectory()){ System.out.print("dir : " + entry.getName()); String destPath = this.unzipDir + File.separator + entry.getName(); System.out.println(" => " + destPath); //todo check destPath for Zip Slip problem - see further down this page. File file = new File(destPath); file.mkdirs(); } else { String destPath = this.unzipDir + File.separator + entry.getName(); //todo check destPath for Zip Slip problem - see further down this page. try(InputStream inputStream = zipFile.getInputStream(entry); FileOutputStream outputStream = new FileOutputStream(destPath); ){ int data = inputStream.read(); while(data != -1){ outputStream.write(data); data = inputStream.read(); } } System.out.println("file : " + entry.getName() + " => " + destPath); } } } catch(IOException e){ throw new RuntimeException("Error unzipping file " + zipFilePath, e); } } private boolean isValidDestPath(String destPath) { // validate the destination path of a ZipFile entry, // and return true or false telling if it's valid or not. } }
zip slip问题
将ZipFile的所有条目解压缩到目录中的示例很容易受到Zip Slip攻击。 Zip Slip攻击包括将条目添加到ZipFile中,该条目包含相对文件路径,该相对文件路径在路径中包含一个或者多个/ ..
节。这样,文件的最终路径可能最终位于将ZipFile解压缩到的目录之外。让我们看一个例子:
我们请求将ZipFile解压缩到目录/ apps / myapp / data / unzipped-file
。 ZipFile中的条目具有相对路径../../../../ etc / hosts
。该条目的最终路径为:/ apps / myapp / data / unzipped-file /../../../../ etc / hosts
,它等效于/ etc / hosts
。
解压缩该文件可能会覆盖小时主机文件(在Linux操作系统上),从而使攻击者可以指向www.facebook.com自行选择IP地址。下次我们尝试从该计算机访问Facebook时,它将不是我们正在访问的真实Facebook,而是攻击者的欺骗版本。登录后,攻击者现在拥有用户名和密码,并且Facebook帐户可以被黑客入侵。
避免Zip Slip攻击的方法是检查最终输出路径,以查看其是否在目标目录之外。这是我们可以执行的操作:
Path destPath = Paths.get(destPath); Path destPathNormalized = destPath.normalize(); //remove ../../ etc. boolean isWithinTargetDir = destPathNormalized.toString().startsWith(targetDir + File.separator);
如果ZipFile条目目标路径在目标目录之外,该怎么办取决于我们自己决定。我们可能会引发异常,或者只是忽略该文件,并且可能写出到控制台或者记录该文件由于无效的输入路径而被忽略了。
这是一个ZipFile解压缩示例,如果条目的目标路径无效,则会引发异常:
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public class FileUnzipper { private String zipFileDir = null; private String zipFileName = null; private String unzipDir = null; public FileUnzipper(String zipFileDir, String zipFileName, String unzipDir) { this.zipFileDir = zipFileDir; this.zipFileName = zipFileName; this.unzipDir = unzipDir; } public void unzip() { String zipFilePath = this.zipFileDir + File.separator + this.zipFileName; try{ System.out.println("zipFilePath = " + zipFilePath); ZipFile zipFile = new ZipFile(zipFilePath); Enumeration<? extends ZipEntry> entries = zipFile.entries(); while(entries.hasMoreElements()){ ZipEntry entry = entries.nextElement(); if(entry.isDirectory()){ System.out.print("dir : " + entry.getName()); String destPath = this.unzipDir + File.separator + entry.getName(); System.out.println(" => " + destPath); if(! isValidDestPath(zipFileDir, destPath)){ throw new IOException("Final directory output path is invalid: " + destPath); } File file = new File(destPath); file.mkdirs(); } else { String destPath = this.unzipDir + File.separator + entry.getName(); if(! isValidDestPath(zipFileDir, destPath)){ throw new IOException("Final file output path is invalid: " + destPath); } try(InputStream inputStream = zipFile.getInputStream(entry); FileOutputStream outputStream = new FileOutputStream(destPath); ){ int data = inputStream.read(); while(data != -1){ outputStream.write(data); data = inputStream.read(); } } System.out.println("file : " + entry.getName() + " => " + destPath); } } } catch(IOException e){ throw new RuntimeException("Error unzipping file " + zipFilePath, e); } } private boolean isValidDestPath(String targetDir, String destPathStr) { // validate the destination path of a ZipFile entry, // and return true or false telling if it's valid or not. Path destPath = Paths.get(destPathStr); Path destPathNormalized = destPath.normalize(); //remove ../../ etc. return destPathNormalized.toString().startsWith(targetDir + File.separator); } }