-
Notifications
You must be signed in to change notification settings - Fork 519
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
review: archive-decompressor supports multiple archive and compressio…
…n formats - Generic support for all commons-compress archive and compression formats - Improved support for nested directories on Windows - Added validations and tests for non-happy-path scenarios Signed-off-by: Marc Nuri <marc@marcnuri.com>
- Loading branch information
Showing
11 changed files
with
240 additions
and
201 deletions.
There are no files selected for viewing
129 changes: 129 additions & 0 deletions
129
jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/archive/ArchiveDecompressor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
/* | ||
* Copyright (c) 2019 Red Hat, Inc. | ||
* This program and the accompanying materials are made | ||
* available under the terms of the Eclipse Public License 2.0 | ||
* which is available at: | ||
* | ||
* https://www.eclipse.org/legal/epl-2.0/ | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
* | ||
* Contributors: | ||
* Red Hat, Inc. - initial API and implementation | ||
*/ | ||
package org.eclipse.jkube.kit.common.archive; | ||
|
||
import org.apache.commons.compress.archivers.ArchiveEntry; | ||
import org.apache.commons.compress.archivers.ArchiveException; | ||
import org.apache.commons.compress.archivers.ArchiveInputStream; | ||
import org.apache.commons.compress.archivers.ArchiveStreamFactory; | ||
import org.apache.commons.compress.compressors.CompressorException; | ||
import org.apache.commons.compress.compressors.CompressorInputStream; | ||
import org.apache.commons.compress.compressors.CompressorStreamFactory; | ||
import org.eclipse.jkube.kit.common.util.FileUtil; | ||
|
||
import java.io.BufferedInputStream; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.nio.file.Files; | ||
|
||
public class ArchiveDecompressor { | ||
|
||
private static final String ERROR_MESSAGE = "Unsupported archive file provided"; | ||
|
||
private ArchiveDecompressor() { } | ||
|
||
/** | ||
* Extracts a given compressed or archive {@link File} to specified target directory. | ||
* | ||
* @param inputFile compressed or archive input file. | ||
* @param targetDirectory target directory to extract the archive to. | ||
* @throws IOException in case a failure occurs while trying to extract the file. | ||
*/ | ||
public static void extractArchive(File inputFile, File targetDirectory) throws IOException { | ||
try (InputStream fis = Files.newInputStream(inputFile.toPath())) { | ||
extractArchive(fis, targetDirectory); | ||
} | ||
} | ||
|
||
/** | ||
* Extracts a given compressed or archive {@link InputStream} to specified target directory. | ||
* | ||
* @param archiveInputStream compressed or archive input stream. | ||
* @param targetDirectory target directory to extract the archive to. | ||
* @throws IOException in case a failure occurs while trying to extract the stream. | ||
*/ | ||
public static void extractArchive(InputStream archiveInputStream, File targetDirectory) throws IOException { | ||
try (BufferedInputStream bis = new BufferedInputStream(archiveInputStream)) { | ||
if (isCompressedFile(bis)) { | ||
extractCompressedFile(bis, targetDirectory); | ||
} else if (isArchive(bis)) { | ||
extractArchiveContents(bis, targetDirectory); | ||
} else { | ||
throw new IllegalArgumentException(ERROR_MESSAGE); | ||
} | ||
} | ||
} | ||
|
||
private static void extractCompressedFile(InputStream is, File targetDirectory) throws IOException { | ||
try ( | ||
CompressorInputStream cis = new CompressorStreamFactory().createCompressorInputStream(is); | ||
BufferedInputStream bis = new BufferedInputStream(cis) | ||
) { | ||
if (isArchive(bis)) { | ||
extractArchiveContents(bis, targetDirectory); | ||
} else { | ||
throw new IllegalArgumentException(ERROR_MESSAGE); | ||
} | ||
} catch (CompressorException ex) { | ||
throw new IllegalArgumentException(ERROR_MESSAGE, ex); | ||
} | ||
} | ||
|
||
private static void extractArchiveContents(InputStream is, File targetDirectory) throws IOException { | ||
if (targetDirectory.exists() && !targetDirectory.isDirectory()) { | ||
throw new IllegalArgumentException("Target directory is not a directory"); | ||
} else if (targetDirectory.exists()) { | ||
FileUtil.cleanDirectory(targetDirectory); | ||
} | ||
FileUtil.createDirectory(targetDirectory); | ||
try (ArchiveInputStream ais = new ArchiveStreamFactory().createArchiveInputStream(is)) { | ||
ArchiveEntry entry; | ||
while ((entry = ais.getNextEntry()) != null) { | ||
final File extractTo = new File(targetDirectory, fileName(entry.getName())); | ||
if (extractTo.getCanonicalPath().startsWith(targetDirectory.getCanonicalPath())) { | ||
if (entry.isDirectory()) { | ||
FileUtil.createDirectory(extractTo); | ||
} else { | ||
Files.copy(ais, extractTo.toPath()); | ||
} | ||
} | ||
} | ||
} catch (ArchiveException ex) { | ||
throw new IllegalArgumentException(ERROR_MESSAGE, ex); | ||
} | ||
} | ||
|
||
private static boolean isCompressedFile(InputStream inputStream) { | ||
try { | ||
CompressorStreamFactory.detect(inputStream); | ||
return true; | ||
} catch(CompressorException ex) { | ||
return false; | ||
} | ||
} | ||
|
||
private static boolean isArchive(InputStream inputStream) { | ||
try { | ||
ArchiveStreamFactory.detect(inputStream); | ||
return true; | ||
} catch (ArchiveException ex) { | ||
return false; | ||
} | ||
} | ||
|
||
private static String fileName(String originalName) { | ||
return originalName.replace('/', File.separatorChar); | ||
} | ||
} |
122 changes: 0 additions & 122 deletions
122
...t/common/src/main/java/org/eclipse/jkube/kit/common/archive/JKubeArchiveDecompressor.java
This file was deleted.
Oops, something went wrong.
110 changes: 110 additions & 0 deletions
110
...it/common/src/test/java/org/eclipse/jkube/kit/common/archive/ArchiveDecompressorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/* | ||
* Copyright (c) 2019 Red Hat, Inc. | ||
* This program and the accompanying materials are made | ||
* available under the terms of the Eclipse Public License 2.0 | ||
* which is available at: | ||
* | ||
* https://www.eclipse.org/legal/epl-2.0/ | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
* | ||
* Contributors: | ||
* Red Hat, Inc. - initial API and implementation | ||
*/ | ||
package org.eclipse.jkube.kit.common.archive; | ||
|
||
import org.eclipse.jkube.kit.common.assertj.FileAssertions; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.io.TempDir; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.CsvSource; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.nio.file.Files; | ||
|
||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; | ||
|
||
class ArchiveDecompressorTest { | ||
|
||
@TempDir | ||
private File tempDir; | ||
|
||
@ParameterizedTest | ||
@CsvSource({ | ||
"/archive/archive-decompressor/pack-v0.31.0-linux.tgz,pack", | ||
"/archive/archive-decompressor/pack-v0.31.0-windows.zip,pack.exe" | ||
}) | ||
void extractArchive_whenArchiveWithSingleFileProvided_thenExtractToSpecifiedDir(String filePath, String expectedFileInExtractedArchiveName) throws IOException { | ||
// Given | ||
File input = new File(ArchiveDecompressorTest.class.getResource(filePath).getFile()); | ||
|
||
// When | ||
ArchiveDecompressor.extractArchive(input, tempDir); | ||
|
||
// Then | ||
FileAssertions.assertThat(tempDir) | ||
.exists() | ||
.fileTree() | ||
.containsExactlyInAnyOrder(expectedFileInExtractedArchiveName); | ||
} | ||
|
||
@ParameterizedTest | ||
@CsvSource({ | ||
"/archive/archive-decompressor/nested-archive.tgz,nested,nested/folder,nested/folder/artifact", | ||
"/archive/archive-decompressor/nested-archive.zip,nested,nested/folder,nested/folder/artifact.exe" | ||
}) | ||
void extractArchive_whenArchiveWithNestedDir_thenExtractToSpecifiedDir(String filePath, String parentDir, String artifactParentDir, String artifact) throws IOException { | ||
// Given | ||
File input = new File(ArchiveDecompressorTest.class.getResource(filePath).getFile()); | ||
|
||
// When | ||
ArchiveDecompressor.extractArchive(input, tempDir); | ||
|
||
// Then | ||
FileAssertions.assertThat(tempDir) | ||
.exists() | ||
.fileTree() | ||
.containsExactlyInAnyOrder(parentDir, artifactParentDir, artifact); | ||
} | ||
|
||
@Test | ||
void extractArchive_whenUnsupportedArchiveProvided_thenThrowException() { | ||
// Given | ||
File input = new File(ArchiveDecompressorTest.class.getResource("/archive/archive-decompressor/foo.xz").getFile()); | ||
|
||
// When | ||
assertThatIllegalArgumentException() | ||
.isThrownBy(() -> ArchiveDecompressor.extractArchive(input, tempDir)) | ||
.withMessage("Unsupported archive file provided"); | ||
} | ||
|
||
@Test | ||
void extractArchive_whenInvalidArchiveProvided_throwsException() throws IOException { | ||
try (final InputStream input = ArchiveDecompressorTest.class.getResourceAsStream("/archive/archive-decompressor/invalid-archive.txt")) { | ||
assertThatIllegalArgumentException() | ||
.isThrownBy(() -> ArchiveDecompressor.extractArchive(input, tempDir)) | ||
.withMessage("Unsupported archive file provided"); | ||
} | ||
} | ||
|
||
@Test | ||
void extractArchive_whenInvalidCompressedArchiveProvided_throwsException() throws IOException { | ||
try (final InputStream input = ArchiveDecompressorTest.class.getResourceAsStream("/archive/archive-decompressor/invalid-archive.txt.gz")) { | ||
assertThatIllegalArgumentException() | ||
.isThrownBy(() -> ArchiveDecompressor.extractArchive(input, tempDir)) | ||
.withMessage("Unsupported archive file provided"); | ||
} | ||
} | ||
|
||
@Test | ||
void extractArchive_whenTargetDirectoryExistsAsFile_throwsException() throws IOException { | ||
try (final InputStream input = ArchiveDecompressorTest.class.getResourceAsStream("/archive/archive-decompressor/nested-archive.tgz")) { | ||
final File targetDirectory = Files.createFile(tempDir.toPath().resolve("target-as-file")).toFile(); | ||
assertThatIllegalArgumentException() | ||
.isThrownBy(() -> ArchiveDecompressor.extractArchive(input, targetDirectory)) | ||
.withMessage("Target directory is not a directory"); | ||
} | ||
} | ||
} |
Oops, something went wrong.