package eu.openaire.urls_controller.util; import org.springframework.stereotype.Component; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @Component public class FileUnZipper { public void unzipFolder(Path source, Path target) throws Exception { try ( ZipInputStream zis = new ZipInputStream(Files.newInputStream(source.toFile().toPath())) ) { // Iterate over the files in zip and unzip them. ZipEntry zipEntry = zis.getNextEntry(); while ( zipEntry != null ) { String zipEntryName = zipEntry.getName(); Path targetPath = zipSlipProtect(zipEntryName, target); if ( zipEntryName.endsWith(File.separator) ) // If we have a directory. Files.createDirectories(targetPath); else { // Some zip-files store only the file-paths and not separate directories. We need to create parent directories, e.g data/folder/file.txt Path parentPath = targetPath.getParent(); if ( (parentPath != null) && Files.notExists(parentPath) ) { Files.createDirectories(parentPath); } Files.copy(zis, targetPath, StandardCopyOption.REPLACE_EXISTING); // Copy an individual entry. } zis.closeEntry(); zipEntry = zis.getNextEntry(); } // Here the "zipEntry" will always be "null", so no "current ZIP entry" will be open, (so the "closeEntry()" is not needed). } } // Protect from a Zip Slip attack: https://snyk.io/research/zip-slip-vulnerability public Path zipSlipProtect(String zipEntryName, Path targetDir) throws IOException { Path targetDirResolved = targetDir.resolve(zipEntryName); // Make sure normalized file still has targetDir as its prefix, else throw an exception. Path normalizePath = targetDirResolved.normalize(); if ( !normalizePath.startsWith(targetDir) ) { throw new IOException("Bad zip entry: " + zipEntryName); } return normalizePath; } }