Skip to content

Commit

Permalink
#347 Extract contents of sub-directory even when sub-directory entry …
Browse files Browse the repository at this point in the history
…does not exist
  • Loading branch information
srikanth-lingala committed Sep 11, 2021
1 parent 14aebe0 commit 9ae5253
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 69 deletions.
66 changes: 29 additions & 37 deletions src/main/java/net/lingala/zip4j/ZipFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,34 @@ public void extractFile(FileHeader fileHeader, String destinationPath, UnzipPara
extractFile(fileHeader, destinationPath, null, unzipParameters);
}

/**
* Extracts a specific file from the zip file to the destination path.
* If destination path is invalid, then this method throws an exception.
* <br><br>
* If newFileName is not null or empty, newly created file name will be replaced by
* the value in newFileName. If this value is null, then the file name will be the
* value in FileHeader.getFileName. If file being extract is a directory, the directory name
* will be replaced with the newFileName
* <br><br>
* If fileHeader is a directory, this method extracts all files under this directory.
* <br/><br/>
* Any parameters that have to be considered during extraction can be passed in through unzipParameters
*
* @param fileHeader file header corresponding to the entry which has to be extracted
* @param destinationPath path to which the entries of the zip are to be extracted
* @param newFileName if not null, this will be the name given to the file upon extraction
* @param unzipParameters any parameters that have to be considered during extraction
* @throws ZipException when an issue occurs during extraction
*/
public void extractFile(FileHeader fileHeader, String destinationPath, String newFileName,
UnzipParameters unzipParameters) throws ZipException {
if (fileHeader == null) {
throw new ZipException("input file header is null, cannot extract file");
}

extractFile(fileHeader.getFileName(), destinationPath, newFileName, unzipParameters);
}

/**
* Extracts a specific file from the zip file to the destination path.
* This method first finds the necessary file header from the input file name.
Expand Down Expand Up @@ -631,42 +659,6 @@ public void extractFile(String fileName, String destinationPath, String newFileN
throw new ZipException("file to extract is null or empty, cannot extract file");
}

readZipInfo();

FileHeader fileHeader = HeaderUtil.getFileHeader(zipModel, fileName);

if (fileHeader == null) {
throw new ZipException("No file found with name " + fileName + " in zip file", ZipException.Type.FILE_NOT_FOUND);
}

extractFile(fileHeader, destinationPath, newFileName, unzipParameters);
}

/**
* Extracts a specific file from the zip file to the destination path.
* If destination path is invalid, then this method throws an exception.
* <br><br>
* If newFileName is not null or empty, newly created file name will be replaced by
* the value in newFileName. If this value is null, then the file name will be the
* value in FileHeader.getFileName. If file being extract is a directory, the directory name
* will be replaced with the newFileName
* <br><br>
* If fileHeader is a directory, this method extracts all files under this directory.
* <br/><br/>
* Any parameters that have to be considered during extraction can be passed in through unzipParameters
*
* @param fileHeader file header corresponding to the entry which has to be extracted
* @param destinationPath path to which the entries of the zip are to be extracted
* @param newFileName if not null, this will be the name given to the file upon extraction
* @param unzipParameters any parameters that have to be considered during extraction
* @throws ZipException when an issue occurs during extraction
*/
public void extractFile(FileHeader fileHeader, String destinationPath, String newFileName,
UnzipParameters unzipParameters) throws ZipException {
if (fileHeader == null) {
throw new ZipException("input file header is null, cannot extract file");
}

if (!isStringNotNullAndNotEmpty(destinationPath)) {
throw new ZipException("destination path is empty or null, cannot extract file");
}
Expand All @@ -678,7 +670,7 @@ public void extractFile(FileHeader fileHeader, String destinationPath, String ne
readZipInfo();

new ExtractFileTask(zipModel, password, unzipParameters, buildAsyncParameters()).execute(
new ExtractFileTaskParameters(destinationPath, fileHeader, newFileName, buildConfig()));
new ExtractFileTaskParameters(destinationPath, fileName, newFileName, buildConfig()));
}

/**
Expand Down
9 changes: 2 additions & 7 deletions src/main/java/net/lingala/zip4j/headers/HeaderUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static net.lingala.zip4j.util.InternalZipConstants.ZIP4J_DEFAULT_CHARSET;
Expand Down Expand Up @@ -65,14 +64,10 @@ public static long getOffsetStartOfCentralDirectory(ZipModel zipModel) {
return zipModel.getEndOfCentralDirectoryRecord().getOffsetOfStartOfCentralDirectory();
}

public static List<FileHeader> getFileHeadersUnderDirectory(List<FileHeader> allFileHeaders, FileHeader rootFileHeader) {
if (!rootFileHeader.isDirectory()) {
return Collections.emptyList();
}

public static List<FileHeader> getFileHeadersUnderDirectory(List<FileHeader> allFileHeaders, String fileName) {
List<FileHeader> fileHeadersUnderDirectory = new ArrayList<>();
for (FileHeader fileHeader : allFileHeaders) {
if (fileHeader.getFileName().startsWith(rootFileHeader.getFileName())) {
if (fileHeader.getFileName().startsWith(fileName)) {
fileHeadersUnderDirectory.add(fileHeader);
}
}
Expand Down
42 changes: 23 additions & 19 deletions src/main/java/net/lingala/zip4j/tasks/ExtractFileTask.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package net.lingala.zip4j.tasks;

import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.headers.HeaderUtil;
import net.lingala.zip4j.io.inputstream.SplitInputStream;
import net.lingala.zip4j.io.inputstream.ZipInputStream;
import net.lingala.zip4j.model.FileHeader;
Expand All @@ -8,6 +10,7 @@
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.progress.ProgressMonitor;
import net.lingala.zip4j.tasks.ExtractFileTask.ExtractFileTaskParameters;
import net.lingala.zip4j.util.FileUtils;
import net.lingala.zip4j.util.InternalZipConstants;
import net.lingala.zip4j.util.UnzipUtil;
import net.lingala.zip4j.util.Zip4jUtil;
Expand All @@ -34,13 +37,12 @@ public ExtractFileTask(ZipModel zipModel, char[] password, UnzipParameters unzip
protected void executeTask(ExtractFileTaskParameters taskParameters, ProgressMonitor progressMonitor)
throws IOException {

try(ZipInputStream zipInputStream =
createZipInputStream(taskParameters.fileHeader, taskParameters.zip4jConfig)) {
List<FileHeader> fileHeadersUnderDirectory = getFileHeadersToExtract(taskParameters.fileHeader);
try(ZipInputStream zipInputStream = createZipInputStream(taskParameters.zip4jConfig)) {
List<FileHeader> fileHeadersUnderDirectory = getFileHeadersToExtract(taskParameters.fileToExtract);
byte[] readBuff = new byte[taskParameters.zip4jConfig.getBufferSize()];
for (FileHeader fileHeader : fileHeadersUnderDirectory) {
splitInputStream.prepareExtractionForFileHeader(fileHeader);
String newFileName = determineNewFileName(taskParameters.newFileName, taskParameters.fileHeader, fileHeader);
String newFileName = determineNewFileName(taskParameters.newFileName, taskParameters.fileToExtract, fileHeader);
extractFile(zipInputStream, fileHeader, taskParameters.outputPath, newFileName, progressMonitor, readBuff);
}
} finally {
Expand All @@ -51,32 +53,35 @@ protected void executeTask(ExtractFileTaskParameters taskParameters, ProgressMon
}

@Override
protected long calculateTotalWork(ExtractFileTaskParameters taskParameters) {
List<FileHeader> fileHeadersUnderDirectory = getFileHeadersToExtract(taskParameters.fileHeader);
protected long calculateTotalWork(ExtractFileTaskParameters taskParameters) throws ZipException {
List<FileHeader> fileHeadersUnderDirectory = getFileHeadersToExtract(taskParameters.fileToExtract);
return getTotalUncompressedSizeOfAllFileHeaders(fileHeadersUnderDirectory);
}

private List<FileHeader> getFileHeadersToExtract(FileHeader rootFileHeader) {
if (!rootFileHeader.isDirectory()) {
return Collections.singletonList(rootFileHeader);
private List<FileHeader> getFileHeadersToExtract(String fileNameToExtract) throws ZipException {
if (!FileUtils.isZipEntryDirectory(fileNameToExtract)) {
FileHeader fileHeader = HeaderUtil.getFileHeader(getZipModel(), fileNameToExtract);
if (fileHeader == null) {
throw new ZipException("No file found with name " + fileNameToExtract + " in zip file");
}
return Collections.singletonList(fileHeader);
}

return getFileHeadersUnderDirectory(
getZipModel().getCentralDirectory().getFileHeaders(), rootFileHeader);
return getFileHeadersUnderDirectory(getZipModel().getCentralDirectory().getFileHeaders(), fileNameToExtract);
}

private ZipInputStream createZipInputStream(FileHeader fileHeader, Zip4jConfig zip4jConfig) throws IOException {
private ZipInputStream createZipInputStream(Zip4jConfig zip4jConfig) throws IOException {
splitInputStream = UnzipUtil.createSplitInputStream(getZipModel());
return new ZipInputStream(splitInputStream, password, zip4jConfig);
}

private String determineNewFileName(String newFileName, FileHeader fileHeaderToExtract,
private String determineNewFileName(String newFileName, String fileNameToExtract,
FileHeader fileHeaderBeingExtracted) {
if (!Zip4jUtil.isStringNotNullAndNotEmpty(newFileName)) {
return newFileName;
}

if (!fileHeaderToExtract.isDirectory()) {
if (!FileUtils.isZipEntryDirectory(fileNameToExtract)) {
return newFileName;
}

Expand All @@ -85,20 +90,19 @@ private String determineNewFileName(String newFileName, FileHeader fileHeaderToE
fileSeparator = "";
}

return fileHeaderBeingExtracted.getFileName().replaceFirst(fileHeaderToExtract.getFileName(),
newFileName + fileSeparator);
return fileHeaderBeingExtracted.getFileName().replaceFirst(fileNameToExtract, newFileName + fileSeparator);
}

public static class ExtractFileTaskParameters extends AbstractZipTaskParameters {
private String outputPath;
private FileHeader fileHeader;
private String fileToExtract;
private String newFileName;

public ExtractFileTaskParameters(String outputPath, FileHeader fileHeader, String newFileName,
public ExtractFileTaskParameters(String outputPath, String fileToExtract, String newFileName,
Zip4jConfig zip4jConfig) {
super(zip4jConfig);
this.outputPath = outputPath;
this.fileHeader = fileHeader;
this.fileToExtract = fileToExtract;
this.newFileName = newFileName;
}
}
Expand Down
16 changes: 16 additions & 0 deletions src/test/java/net/lingala/zip4j/ExtractZipFileIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,22 @@ public void testExtractFileHeaderExtractAllFilesIfFileHeaderIsDirectoryAndRename
ZipFileVerifier.verifyFileContent(TestUtils.getTestFileFromResources("öüäöäö/asöäööl"), outputFile);
}

@Test
public void testExtractFileWhichIsAFolderExtractsContentsEvenWhenFolderEntryIsNotInZip() throws IOException {
ZipFile zipFile = new ZipFile(getTestArchiveFromResources("archive-with-no-dir-entries.zip"));
String outputFolderPath = outputFolder.getPath();

zipFile.extractFile("items/", outputFolderPath);

List<File> extractedFiles = FileUtils.getFilesInDirectoryRecursive(outputFolder, false, false);
assertThat(extractedFiles).isNotEmpty();
assertThat(extractedFiles).hasSize(3);
assertThat(extractedFiles).contains(
Paths.get(outputFolderPath, "items").toFile(),
Paths.get(outputFolderPath, "items/subitems").toFile(),
Paths.get(outputFolderPath, "items/subitems/beta.txt").toFile());
}

@Test
public void testExtractJarFileWithFileHeaderCompressedSize2() throws IOException {
extractFile(TestUtils.getTestArchiveFromResources("jar-dir-fh-entry-size-2.jar"));
Expand Down
8 changes: 2 additions & 6 deletions src/test/java/net/lingala/zip4j/headers/HeaderUtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -194,21 +194,17 @@ public void testDecodeStringWithCharsetWithNullCharsetAndEnglishChars() {
@Test
public void testGetFileHeadersUnderDirectoryWhenNotDirectoryReturnsEmptyList() {
List<FileHeader> allFileHeaders = generateFileHeaderWithFileNames("header", 5);
FileHeader rootFileHeader = generateFileHeader("some_name");
rootFileHeader.setDirectory(false);

assertThat(HeaderUtil.getFileHeadersUnderDirectory(allFileHeaders, rootFileHeader)).isEmpty();
assertThat(HeaderUtil.getFileHeadersUnderDirectory(allFileHeaders, "some_name")).isEmpty();
}

@Test
public void testGetFileHeadersUnderDirectoryReturnsFileHeadersUnderDirectory() {
List<FileHeader> allFileHeaders = generateFileHeaderWithFileNames("some_name/header", 5);
allFileHeaders.add(generateFileHeader("some_name/"));
allFileHeaders.add(generateFileHeader("some_other_name.txt"));
FileHeader rootFileHeader = generateFileHeader("some_name/");
rootFileHeader.setDirectory(true);

List<FileHeader> filHeadersUnderDirectory = HeaderUtil.getFileHeadersUnderDirectory(allFileHeaders, rootFileHeader);
List<FileHeader> filHeadersUnderDirectory = HeaderUtil.getFileHeadersUnderDirectory(allFileHeaders, "some_name/");
assertThat(filHeadersUnderDirectory).hasSize(6);
for (FileHeader fileHeader : filHeadersUnderDirectory) {
assertThat(fileHeader)
Expand Down
Binary file not shown.

0 comments on commit 9ae5253

Please sign in to comment.