summaryrefslogtreecommitdiffstats
path: root/common-app-api
diff options
context:
space:
mode:
Diffstat (limited to 'common-app-api')
-rw-r--r--common-app-api/pom.xml14
-rw-r--r--common-app-api/src/main/java/org/openecomp/sdc/common/util/ZipUtil.java135
-rw-r--r--common-app-api/src/main/java/org/openecomp/sdc/common/zip/ZipUtils.java354
-rw-r--r--common-app-api/src/main/java/org/openecomp/sdc/common/zip/exception/ZipException.java31
-rw-r--r--common-app-api/src/main/java/org/openecomp/sdc/common/zip/exception/ZipSlipException.java27
-rw-r--r--common-app-api/src/test/java/org/openecomp/sdc/common/util/ZipUtilTest.java67
-rw-r--r--common-app-api/src/test/java/org/openecomp/sdc/common/zip/ZipUtilsTest.java172
-rw-r--r--common-app-api/src/test/resources/zip-slip/zip-slip-linux.zipbin0 -> 545 bytes
-rw-r--r--common-app-api/src/test/resources/zip-slip/zip-slip-windows.zipbin0 -> 547 bytes
-rw-r--r--common-app-api/src/test/resources/zip/extract-test.zipbin0 -> 2588 bytes
10 files changed, 598 insertions, 202 deletions
diff --git a/common-app-api/pom.xml b/common-app-api/pom.xml
index 38d3bbec87..62bfb24b28 100644
--- a/common-app-api/pom.xml
+++ b/common-app-api/pom.xml
@@ -159,6 +159,20 @@
</dependency>
<dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest</artifactId>
+ <version>${hamcrest.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
+ <version>${hamcrest.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
diff --git a/common-app-api/src/main/java/org/openecomp/sdc/common/util/ZipUtil.java b/common-app-api/src/main/java/org/openecomp/sdc/common/util/ZipUtil.java
deleted file mode 100644
index ec3c31cffe..0000000000
--- a/common-app-api/src/main/java/org/openecomp/sdc/common/util/ZipUtil.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- * SDC
- * ================================================================================
- * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END=========================================================
- */
-
-package org.openecomp.sdc.common.util;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.io.output.ByteArrayOutputStream;
-import org.openecomp.sdc.common.log.wrappers.Logger;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-import java.util.zip.ZipOutputStream;
-
-public class ZipUtil {
-
- private static final int KB = 1024;
- private static Logger log = Logger.getLogger(ZipUtil.class.getName());
-
- private ZipUtil() {
- }
-
- public static Map<String, byte[]> readZip(File file) {
- try (InputStream fileInputStream = new FileInputStream(file)) {
- return readZip(IOUtils.toByteArray(fileInputStream));
- } catch (IOException e) {
- log.info("close File stream failed - {}", e);
- return null;
- }
- }
-
- public static Map<String, byte[]> readZip(byte[] zipAsBytes) {
- Map<String, byte[]> fileNameToByteArray = new HashMap<>();
- byte[] buffer = new byte[KB];
- try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(zipAsBytes);
- ZipInputStream zis = new ZipInputStream(byteArrayInputStream)) {
- // get the zipped file list entry
- ZipEntry ze = zis.getNextEntry();
-
- while (ze != null) {
-
- String fileName = ze.getName();
-
- if (!ze.isDirectory()) {
-
- try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
- int len;
- while ((len = zis.read(buffer)) > 0) {
- os.write(buffer, 0, len);
- }
-
- fileNameToByteArray.put(fileName, os.toByteArray());
-
- }
- }
- ze = zis.getNextEntry();
- }
- } catch (IOException ex) {
- log.info("close Byte stream failed", ex);
- return null;
- }
-
- return fileNameToByteArray;
-
- }
-
- public static void main(String[] args) {
- String zipFileName = "/src/test/resources/config/config.zip";
- zipFileName = "C:\\Git_work\\D2-SDnC\\catalog-be\\src\\test\\resources\\config\\config.zip";
- Path path = Paths.get(zipFileName);
-
- try {
- byte[] zipAsBytes = Files.readAllBytes(path);
- // encode to base
-
- ZipUtil.readZip(zipAsBytes);
-
- } catch (IOException e) {
- log.info("close Byte stream failed", e);
- }
- }
-
- public static byte[] zipBytes(byte[] input) throws IOException {
- try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
- ZipOutputStream zos = new ZipOutputStream(baos)) {
- ZipEntry entry = new ZipEntry("zip");
- entry.setSize(input.length);
- zos.putNextEntry(entry);
- zos.write(input);
- zos.closeEntry();
- return baos.toByteArray();
- }
- }
-
- public static byte[] unzip(byte[] zipped) {
- try (ZipInputStream zipinputstream = new ZipInputStream(new ByteArrayInputStream(zipped));
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
- byte[] buf = new byte[KB];
- ZipEntry zipentry = zipinputstream.getNextEntry();
- int n;
- while ((n = zipinputstream.read(buf, 0, KB)) > -1) {
- outputStream.write(buf, 0, n);
- }
- return outputStream.toByteArray();
- } catch (Exception e) {
- throw new IllegalStateException("Can't unzip input stream", e);
- }
- }
-}
diff --git a/common-app-api/src/main/java/org/openecomp/sdc/common/zip/ZipUtils.java b/common-app-api/src/main/java/org/openecomp/sdc/common/zip/ZipUtils.java
new file mode 100644
index 0000000000..d90377fc88
--- /dev/null
+++ b/common-app-api/src/main/java/org/openecomp/sdc/common/zip/ZipUtils.java
@@ -0,0 +1,354 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.openecomp.sdc.common.zip;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+import org.apache.commons.io.IOUtils;
+import org.openecomp.sdc.common.zip.exception.ZipException;
+import org.openecomp.sdc.common.zip.exception.ZipSlipException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handles zip operations.
+ */
+public class ZipUtils {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ZipUtils.class);
+
+ private ZipUtils() {
+ }
+
+ /**
+ * Checks if the path is a zip slip attempt calling the {@link #checkForZipSlipInRead(Path)} method.
+ * @param zipEntry the zip entry
+ * @throws ZipSlipException when a zip slip attempt is detected
+ */
+ public static void checkForZipSlipInRead(final ZipEntry zipEntry) throws ZipSlipException {
+ final Path filePath = Paths.get(zipEntry.getName());
+ checkForZipSlipInRead(filePath);
+ }
+
+ /**
+ * Checks if the path is a zip slip attempt when you don't have a destination folder eg in memory reading or zip
+ * creation.
+ *
+ * @param filePath the file path
+ * @throws ZipSlipException when a zip slip attempt is detected
+ */
+ public static void checkForZipSlipInRead(final Path filePath) throws ZipSlipException {
+ final File file = filePath.toFile();
+ String canonicalPath = null;
+ try {
+ canonicalPath = file.getCanonicalPath();
+ } catch (final IOException ignored) {
+ //ignored
+ }
+ if (canonicalPath != null && !canonicalPath.equals(file.getAbsolutePath())) {
+ throw new ZipSlipException(filePath.toString());
+ }
+
+ if (filePath.toString().contains("../") || filePath.toString().contains("..\\")) {
+ throw new ZipSlipException(filePath.toString());
+ }
+ }
+
+ /**
+ * Checks if the zip entry is a zip slip attempt based on the destination directory.
+ *
+ * @param zipEntry the zip entry
+ * @param targetDirectoryPath the target extraction folder
+ * @throws ZipException when the zip slip was detected as a {@link ZipSlipException}. Also when there was a problem
+ * getting the canonical paths from the zip entry or target directory.
+ */
+ public static void checkForZipSlipInExtraction(final ZipEntry zipEntry,
+ final Path targetDirectoryPath) throws ZipException {
+ final File targetDirectoryAsFile = targetDirectoryPath.toFile();
+ final File targetFile = new File(targetDirectoryAsFile, zipEntry.getName());
+ final String targetDirectoryCanonicalPath;
+ try {
+ targetDirectoryCanonicalPath = targetDirectoryAsFile.getCanonicalPath();
+ } catch (final IOException e) {
+ throw new ZipException(
+ String.format("Could not obtain canonical path of: '%s'", targetDirectoryAsFile.getAbsolutePath()), e);
+ }
+ final String targetFileCanonicalPath;
+ try {
+ targetFileCanonicalPath = targetFile.getCanonicalPath();
+ } catch (final IOException e) {
+ throw new ZipException(
+ String.format("Could not obtain canonical path of: '%s'", targetFile.getAbsolutePath()), e);
+ }
+
+ if (!targetFileCanonicalPath.startsWith(targetDirectoryCanonicalPath + File.separator)) {
+ throw new ZipSlipException(zipEntry.getName());
+ }
+ }
+
+ /**
+ * Creates a ZipInputStream from a byte array.
+ *
+ * @param zipFileBytes the zip byte array
+ * @return the created ZipInputStream.
+ */
+ private static ZipInputStream getInputStreamFromBytes(final byte[] zipFileBytes) {
+ return new ZipInputStream(new ByteArrayInputStream(zipFileBytes));
+ }
+
+ /**
+ * Reads a zip file into memory. Parses the zipFile in byte array and calls {@link #readZip(byte[], boolean)}.
+ *
+ * @param zipFile the zip file to read
+ * @param hasToIncludeDirectories includes or not the directories found during the zip reading
+ * @return a Map representing a pair of file path and file byte array
+ * @throws ZipException when there was a problem during the reading process
+ */
+ public static Map<String, byte[]> readZip(final File zipFile,
+ final boolean hasToIncludeDirectories) throws ZipException {
+ try {
+ return readZip(Files.readAllBytes(zipFile.toPath()), hasToIncludeDirectories);
+ } catch (final IOException e) {
+ throw new ZipException(String.format("Could not read the zip file '%s'", zipFile.getName()), e);
+ }
+ }
+
+ /**
+ * Reads a zip file to a in memory structure formed by the file path and its bytes. The structure can contains only
+ * files or files and directories. If configured to include directories, only empty directories and directories that
+ * contains files will be included. The full directory tree will not be generated, eg:
+ * <pre>
+ * \
+ * \..\Directory
+ * \..\..\ChildDirectory
+ * \..\..\..\aFile.txt
+ * \..\..\EmptyChildDirectory
+ * </pre>
+ * The return will include "Directory\ChildDirectory\aFile.txt" and "Directory\EmptyChildDirectory" but not
+ * "Directory" or the root.
+ *
+ * @param zipFileBytes the zip file byte array to read
+ * @param hasToIncludeDirectories includes or not the directories found during the zip reading.
+ * @return a Map representing a pair of file path and file byte array
+ * @throws ZipException when there was a problem during the reading process
+ */
+ public static Map<String, byte[]> readZip(final byte[] zipFileBytes,
+ final boolean hasToIncludeDirectories) throws ZipException {
+ final Map<String, byte[]> filePathAndByteMap = new HashMap<>();
+
+ try (final ZipInputStream inputZipStream = ZipUtils.getInputStreamFromBytes(zipFileBytes)) {
+ byte[] fileByteContent;
+ String currentEntryName;
+ ZipEntry zipEntry;
+ while ((zipEntry = inputZipStream.getNextEntry()) != null) {
+ checkForZipSlipInRead(zipEntry);
+ currentEntryName = zipEntry.getName();
+ fileByteContent = getBytes(inputZipStream);
+ if (zipEntry.isDirectory()) {
+ if (hasToIncludeDirectories) {
+ filePathAndByteMap.put(normalizeFolder(currentEntryName), null);
+ }
+ } else {
+ if (hasToIncludeDirectories) {
+ final Path parentFolderPath = Paths.get(zipEntry.getName()).getParent();
+ if (parentFolderPath != null) {
+ filePathAndByteMap.putIfAbsent(normalizeFolder(parentFolderPath.toString()), null);
+ }
+ }
+ filePathAndByteMap.put(currentEntryName, fileByteContent);
+ }
+ }
+ } catch (final IOException e) {
+ LOGGER.warn("Could not close the zip input stream", e);
+ }
+
+ return filePathAndByteMap;
+ }
+
+ /**
+ * Adds a {@link File#separator} at the end of the folder path if not present.
+ *
+ * @param folderPath the folder to normalize
+ * @return the normalized folder
+ */
+ private static String normalizeFolder(final String folderPath) {
+ final StringBuilder normalizedFolderBuilder = new StringBuilder(folderPath);
+ if(!folderPath.endsWith(File.separator)) {
+ normalizedFolderBuilder.append(File.separator);
+ }
+ return normalizedFolderBuilder.toString();
+ }
+
+ /**
+ * Converts a ZipInputStream in byte array.
+ *
+ * @param inputZipStream the zip input stream
+ * @return the byte array representing the input stream
+ * @throws ZipException when there was a problem parsing the input zip stream
+ */
+ private static byte[] getBytes(final ZipInputStream inputZipStream) throws ZipException {
+ final byte[] fileByteContent;
+ try {
+ fileByteContent = IOUtils.toByteArray(inputZipStream);
+ } catch (final IOException e) {
+ throw new ZipException("Could not read bytes from file", e);
+ }
+ return fileByteContent;
+ }
+
+ /**
+ * Unzips a zip file into an output folder.
+ *
+ * @param zipFilePath the zip file path
+ * @param outputFolder the output folder path
+ * @throws ZipException when there was a problem during the unzip process
+ */
+ public static void unzip(final Path zipFilePath, final Path outputFolder) throws ZipException {
+ if (zipFilePath == null || outputFolder == null) {
+ return;
+ }
+ createDirectoryIfNotExists(outputFolder);
+
+ final File zipFile = zipFilePath.toFile();
+ try (final FileInputStream fileInputStream = new FileInputStream(zipFile);
+ final ZipInputStream stream = new ZipInputStream(fileInputStream)) {
+
+ ZipEntry zipEntry;
+ while ((zipEntry = stream.getNextEntry()) != null) {
+ checkForZipSlipInExtraction(zipEntry, outputFolder);
+ final String fileName = zipEntry.getName();
+ final Path fileToWritePath = Paths.get(outputFolder.toString(), fileName);
+ if (zipEntry.isDirectory()) {
+ createDirectoryIfNotExists(fileToWritePath);
+ } else {
+ writeFile(stream, fileToWritePath);
+ }
+ }
+ } catch (final FileNotFoundException e) {
+ throw new ZipException(String.format("Could not find file: '%s'", zipFile.getAbsolutePath()), e);
+ } catch (final IOException e) {
+ throw new ZipException(
+ String.format("An unexpected error occurred trying to unzip '%s'", zipFile.getAbsolutePath()), e);
+ }
+ }
+
+ /**
+ * Writes a file from a zipInputStream to a path. Creates the file parent directories if they don't exist.
+ * @param zipInputStream the zip input stream
+ * @param fileToWritePath the file path to write
+ * @throws ZipException when there was a problem during the file creation
+ */
+ private static void writeFile(final ZipInputStream zipInputStream, final Path fileToWritePath) throws ZipException {
+ final Path parentFolderPath = fileToWritePath.getParent();
+ if (parentFolderPath != null) {
+ try {
+ Files.createDirectories(parentFolderPath);
+ } catch (final IOException e) {
+ throw new ZipException(
+ String.format("Could not create parent directories of '%s'", fileToWritePath.toString()), e);
+ }
+ }
+ try (final FileOutputStream outputStream = new FileOutputStream(fileToWritePath.toFile())) {
+ IOUtils.copy(zipInputStream, outputStream);
+ } catch (final FileNotFoundException e) {
+ throw new ZipException(String.format("Could not find file '%s'", fileToWritePath.toString()), e);
+ } catch (final IOException e) {
+ throw new ZipException(
+ String.format("An unexpected error has occurred while writing file '%s'", fileToWritePath.toString())
+ , e);
+ }
+ }
+
+ /**
+ * Creates the path directories if the provided path does not exists.
+ *
+ * @param path the path to create directories
+ * @throws ZipException when there was a problem to create the directories
+ */
+ private static void createDirectoryIfNotExists(final Path path) throws ZipException {
+ if(path.toFile().exists()) {
+ return;
+ }
+ try {
+ Files.createDirectories(path);
+ } catch (final IOException e) {
+ throw new ZipException(String.format("Could not create directories for path '%s'", path.toString()), e);
+ }
+ }
+
+ /**
+ * Zips a directory and its children content.
+ *
+ * @param fromPath the directory path to zip
+ * @param toZipFilePath the path to the zip file that will be created
+ * @throws ZipException when there was a problem during the zip process
+ */
+ public static void createZipFromPath(final Path fromPath, final Path toZipFilePath) throws ZipException {
+ final Path createdZipFilePath;
+ try {
+ createdZipFilePath = Files.createFile(toZipFilePath);
+ } catch (final IOException e) {
+ throw new ZipException(String.format("Could not create file '%s'", toZipFilePath.toString()), e);
+ }
+
+ try(final FileOutputStream fileOutputStream = new FileOutputStream(createdZipFilePath.toFile());
+ final BufferedOutputStream bos = new BufferedOutputStream(fileOutputStream);
+ final ZipOutputStream zipOut = new ZipOutputStream(bos);
+ final Stream<Path> walkStream = Files.walk(fromPath)) {
+ final Set<Path> allFilesSet = walkStream.collect(Collectors.toSet());
+ for (final Path path : allFilesSet) {
+ checkForZipSlipInRead(path);
+ if (path.equals(fromPath)) {
+ continue;
+ }
+ final Path relativePath = fromPath.relativize(path);
+ final File file = path.toFile();
+ if (file.isDirectory()) {
+ zipOut.putNextEntry(new ZipEntry(relativePath.toString() + File.separator));
+ } else {
+ zipOut.putNextEntry(new ZipEntry(relativePath.toString()));
+ zipOut.write(Files.readAllBytes(path));
+ }
+ zipOut.closeEntry();
+ }
+ } catch (final FileNotFoundException e) {
+ throw new ZipException(String.format("Could not create file '%s'", toZipFilePath.toString()), e);
+ } catch (final IOException e) {
+ throw new ZipException("An error has occurred while creating the zip package", e);
+ }
+ }
+
+}
diff --git a/common-app-api/src/main/java/org/openecomp/sdc/common/zip/exception/ZipException.java b/common-app-api/src/main/java/org/openecomp/sdc/common/zip/exception/ZipException.java
new file mode 100644
index 0000000000..414b4823ad
--- /dev/null
+++ b/common-app-api/src/main/java/org/openecomp/sdc/common/zip/exception/ZipException.java
@@ -0,0 +1,31 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.openecomp.sdc.common.zip.exception;
+
+public class ZipException extends Exception {
+
+ public ZipException(String s) {
+ super(s);
+ }
+
+ public ZipException(String s, Throwable throwable) {
+ super(s, throwable);
+ }
+}
diff --git a/common-app-api/src/main/java/org/openecomp/sdc/common/zip/exception/ZipSlipException.java b/common-app-api/src/main/java/org/openecomp/sdc/common/zip/exception/ZipSlipException.java
new file mode 100644
index 0000000000..203e357f8f
--- /dev/null
+++ b/common-app-api/src/main/java/org/openecomp/sdc/common/zip/exception/ZipSlipException.java
@@ -0,0 +1,27 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.openecomp.sdc.common.zip.exception;
+
+public class ZipSlipException extends ZipException {
+
+ public ZipSlipException(final String filePath) {
+ super(String.format("Zip slip attempt detected in file: %s", filePath));
+ }
+}
diff --git a/common-app-api/src/test/java/org/openecomp/sdc/common/util/ZipUtilTest.java b/common-app-api/src/test/java/org/openecomp/sdc/common/util/ZipUtilTest.java
deleted file mode 100644
index 71e8d1509a..0000000000
--- a/common-app-api/src/test/java/org/openecomp/sdc/common/util/ZipUtilTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- * SDC
- * ================================================================================
- * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END=========================================================
- */
-
-package org.openecomp.sdc.common.util;
-
-import org.junit.Test;
-
-import java.util.Map;
-
-public class ZipUtilTest {
-
- // private ZipUtil createTestSubject() {
- // return new ZipUtil();
- // }
-
- @Test
- public void testReadZip() throws Exception {
- byte[] zipAsBytes = new byte[] { ' ' };
- Map<String, byte[]> result;
-
- // default test
- result = ZipUtil.readZip(zipAsBytes);
- }
-
- @Test
- public void testMain() throws Exception {
- String[] args = new String[] { "" };
-
- // default test
- ZipUtil.main(args);
- }
-
- @Test
- public void testZipBytes() throws Exception {
- byte[] input = new byte[] { ' ' };
- byte[] result;
-
- // default test
- result = ZipUtil.zipBytes(input);
- }
-
- @Test
- public void testUnzip() throws Exception {
- byte[] zipped = new byte[] { ' ' };
- byte[] result;
-
- // default test
- result = ZipUtil.unzip(zipped);
- }
-}
diff --git a/common-app-api/src/test/java/org/openecomp/sdc/common/zip/ZipUtilsTest.java b/common-app-api/src/test/java/org/openecomp/sdc/common/zip/ZipUtilsTest.java
new file mode 100644
index 0000000000..d5fb3dcc8b
--- /dev/null
+++ b/common-app-api/src/test/java/org/openecomp/sdc/common/zip/ZipUtilsTest.java
@@ -0,0 +1,172 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.openecomp.sdc.common.zip;
+
+import static org.hamcrest.Matchers.aMapWithSize;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.isIn;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.junit.Test;
+import org.openecomp.sdc.common.zip.exception.ZipException;
+import org.openecomp.sdc.common.zip.exception.ZipSlipException;
+
+public class ZipUtilsTest {
+
+ @Test
+ public void testZipSlipInRead() {
+ final byte[] windowsZipBytes;
+ final byte[] linuxZipBytes;
+ try {
+ final InputStream linuxZipAsStream = ZipUtilsTest.class.getClassLoader().getResourceAsStream("zip-slip/zip-slip-linux.zip");
+ final InputStream windowsZipAsStream = ZipUtilsTest.class.getClassLoader().getResourceAsStream("zip-slip/zip-slip-windows.zip");
+ if(linuxZipAsStream == null || windowsZipAsStream == null) {
+ fail("Could not load the zip slip files");
+ }
+ linuxZipBytes = IOUtils.toByteArray(linuxZipAsStream);
+ windowsZipBytes = IOUtils.toByteArray(windowsZipAsStream);
+ } catch (final IOException e) {
+ e.printStackTrace();
+ fail("Could not load the required zip slip files");
+ return;
+ }
+
+ try {
+ ZipUtils.readZip(linuxZipBytes, true);
+ fail("Zip slip should be detected");
+ } catch (final ZipException ex) {
+ assertThat("Expected ZipSlipException", ex, is(instanceOf(ZipSlipException.class)));
+ }
+
+ try {
+ ZipUtils.readZip(windowsZipBytes, true);
+ fail("Zip slip should be detected");
+ } catch (final ZipException ex) {
+ assertThat("Expected ZipSlipException", ex, is(instanceOf(ZipSlipException.class)));
+ }
+ }
+
+ @Test
+ public void testZipSlipInUnzip() throws IOException {
+ final Path tempDirectoryWindows = Files.createTempDirectory("zipSlipWindows" + System.currentTimeMillis());
+ final Path tempDirectoryLinux = Files.createTempDirectory("zipSlipLinux" + System.currentTimeMillis());
+ try {
+ final Path linuxZipPath;
+ final Path windowsZipPath;
+ try {
+ linuxZipPath = Paths
+ .get(ZipUtilsTest.class.getClassLoader().getResource("zip-slip/zip-slip-linux.zip").toURI());
+ windowsZipPath = Paths
+ .get(ZipUtilsTest.class.getClassLoader().getResource("zip-slip/zip-slip-windows.zip").toURI());
+ } catch (final URISyntaxException e) {
+ fail("Could not load the required zip slip files");
+ return;
+ }
+
+ try {
+ ZipUtils.unzip(windowsZipPath, tempDirectoryWindows);
+ ZipUtils.unzip(linuxZipPath, tempDirectoryLinux);
+ fail("Zip slip should be detected");
+ } catch (final ZipException ex) {
+ assertThat("At least one of the zip files should throw ZipSlipException",
+ ex, is(instanceOf(ZipSlipException.class)));
+ }
+ } finally {
+ org.apache.commons.io.FileUtils.deleteDirectory(tempDirectoryLinux.toFile());
+ org.apache.commons.io.FileUtils.deleteDirectory(tempDirectoryWindows.toFile());
+ }
+ }
+
+ @Test
+ public void testUnzipAndZip() throws IOException, ZipException {
+ final Path unzipTempPath = Files.createTempDirectory("testUnzip");
+ final Path zipTempPath = Files.createTempDirectory("testZip");
+ final Path testZipPath;
+ try {
+ try {
+ testZipPath = Paths
+ .get(ZipUtilsTest.class.getClassLoader().getResource("zip/extract-test.zip").toURI());
+ ZipUtils.unzip(testZipPath, unzipTempPath);
+ } catch (final URISyntaxException e) {
+ fail("Could not load the required zip file");
+ return;
+ }
+
+ final Set<Path> expectedPaths = new HashSet<>();
+ expectedPaths.add(Paths.get(unzipTempPath.toString(),"rootFile1.txt"));
+ expectedPaths.add(Paths.get(unzipTempPath.toString(),"rootFileNoExtension"));
+ expectedPaths.add(Paths.get(unzipTempPath.toString(),"EmptyFolder"));
+ expectedPaths.add(Paths.get(unzipTempPath.toString(), "SingleLvlFolder"));
+ expectedPaths.add(Paths.get(unzipTempPath.toString(), "SingleLvlFolder", "singleLvlFolderFile.txt"));
+ expectedPaths.add(Paths.get(unzipTempPath.toString(), "SingleLvlFolder", "singleLvlFolderFileNoExtension"));
+ expectedPaths.add(Paths.get(unzipTempPath.toString(), "TwoLvlFolder"));
+ expectedPaths.add(Paths.get(unzipTempPath.toString(), "TwoLvlFolder", "twoLvlFolderFile.txt"));
+ expectedPaths.add(Paths.get(unzipTempPath.toString(), "TwoLvlFolder", "twoLvlFolderFileNoExtension"));
+ expectedPaths.add(Paths.get(unzipTempPath.toString(), "TwoLvlFolder", "SingleLvlFolder"));
+ expectedPaths.add(Paths.get(unzipTempPath.toString(), "TwoLvlFolder", "SingleLvlFolder", "singleLvlFolderFile.txt"));
+ expectedPaths.add(Paths.get(unzipTempPath.toString(), "TwoLvlFolder", "SingleLvlFolder", "singleLvlFolderFileNoExtension"));
+
+ final AtomicLong actualPathCount = new AtomicLong(0);
+ try (Stream<Path> stream = Files.walk(unzipTempPath)) {
+ stream.filter(path -> !unzipTempPath.equals(path)).forEach(actualPath -> {
+ actualPathCount.getAndIncrement();
+ assertThat("Unzipped file should be in the expected list", actualPath, isIn(expectedPaths));
+ });
+ }
+ assertThat("The number of unzipped files should be as expected", actualPathCount.get(), is((long) expectedPaths.size()));
+ final Path zipFilePath = zipTempPath.resolve("testzip.zip");
+ ZipUtils.createZipFromPath(unzipTempPath, zipFilePath);
+ final Map<String, byte[]> fileMap = ZipUtils.readZip(zipFilePath.toFile(), true);
+ //matching the folder pattern of the readZip
+ final Set<String> expectedPathStringSet = expectedPaths.stream()
+ .map(path -> {
+ final Path relativePath = unzipTempPath.relativize(path);
+ return path.toFile().isDirectory() ? relativePath.toString() + File.separator : relativePath.toString();
+ }).collect(Collectors.toSet());
+ assertThat("The number of zipped files should be as expected", fileMap, aMapWithSize(expectedPathStringSet.size()));
+ fileMap.keySet().forEach(s -> {
+ assertThat("File in zip package should be in the expected list", s, isIn(expectedPathStringSet));
+ });
+ } finally {
+ FileUtils.deleteDirectory(unzipTempPath.toFile());
+ FileUtils.deleteDirectory(zipTempPath.toFile());
+ }
+ }
+
+
+
+} \ No newline at end of file
diff --git a/common-app-api/src/test/resources/zip-slip/zip-slip-linux.zip b/common-app-api/src/test/resources/zip-slip/zip-slip-linux.zip
new file mode 100644
index 0000000000..38b3f499de
--- /dev/null
+++ b/common-app-api/src/test/resources/zip-slip/zip-slip-linux.zip
Binary files differ
diff --git a/common-app-api/src/test/resources/zip-slip/zip-slip-windows.zip b/common-app-api/src/test/resources/zip-slip/zip-slip-windows.zip
new file mode 100644
index 0000000000..3474c88bec
--- /dev/null
+++ b/common-app-api/src/test/resources/zip-slip/zip-slip-windows.zip
Binary files differ
diff --git a/common-app-api/src/test/resources/zip/extract-test.zip b/common-app-api/src/test/resources/zip/extract-test.zip
new file mode 100644
index 0000000000..880452fdc7
--- /dev/null
+++ b/common-app-api/src/test/resources/zip/extract-test.zip
Binary files differ