From 370e539e7ba11aee94fe11f1eda49713c77b70e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Tue, 3 Dec 2024 23:52:42 +0100 Subject: [PATCH] #799: fix zip extraction to preserve file attributes --- .../com/devonfw/tools/ide/io/FileAccess.java | 2 +- .../devonfw/tools/ide/io/FileAccessImpl.java | 115 +++++++----------- 2 files changed, 48 insertions(+), 69 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java index a4ae5d3d0..0b51742b0 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java +++ b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java @@ -90,7 +90,7 @@ default void symlink(Path source, Path targetLink) { /** * @param source the source {@link Path file or folder} to copy. - * @param target the {@link Path} to copy {@code source} to. See {@link #copy(Path, Path, FileCopyMode)} for details. will always ensure that in the end + * @param target the {@link Path} to copy {@code source} to. See {@link #copy(Path, Path, FileCopyMode)} for details. Will always ensure that in the end * you will find the same content of {@code source} in {@code target}. */ default void copy(Path source, Path target) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java index 2ed74428e..4535b8606 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java @@ -11,7 +11,9 @@ import java.net.http.HttpClient.Redirect; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.nio.file.FileSystem; import java.nio.file.FileSystemException; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; @@ -27,15 +29,12 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; import java.util.stream.Stream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.ArchiveInputStream; @@ -63,6 +62,8 @@ public class FileAccessImpl implements FileAccess { "On Windows, file operations could fail due to file locks. Please ensure the files in the moved directory are not in use. For further details, see: \n" + WINDOWS_FILE_LOCK_DOCUMENTATION_PAGE; + private static final Map FS_ENV = Map.of("encoding", "UTF-8"); + private final IdeContext context; /** @@ -614,7 +615,28 @@ private Path getProperInstallationSubDirOf(Path path, Path archiveFile) { @Override public void extractZip(Path file, Path targetDir) { - extractZipArchive(file, targetDir); + this.context.trace("Unpacking archive {} to {}", file, targetDir); + + URI uri = URI.create("jar:" + file.toUri()); + try (FileSystem fs = FileSystems.newFileSystem(uri, FS_ENV); + //FileInputStream fis = new FileInputStream(file.toFile()); ZipInputStream zis = new ZipInputStream(fis); IdeProgressBar pb = getProgressbarForUnpacking(getFileSize(file)) + ) { + for (Path root : fs.getRootDirectories()) { + Path filename = root.getFileName(); + if (filename == null) { + Iterator iterator = Files.list(root).iterator(); + while (iterator.hasNext()) { + Path child = iterator.next(); + String fileName = child.getFileName().toString(); + copy(child, targetDir.resolve(fileName), FileCopyMode.COPY_TREE_CONTENT); + } + } else { + copy(root, targetDir.resolve(filename), FileCopyMode.COPY_TREE_CONTENT); + } + } + } catch (IOException e) { + throw new IllegalStateException("Failed to extract " + file + " to " + targetDir, e); + } } @Override @@ -626,29 +648,7 @@ public void extractTar(Path file, Path targetDir, TarCompression compression) { @Override public void extractJar(Path file, Path targetDir) { - this.context.trace("Unpacking JAR {} to {}", file, targetDir); - try (JarInputStream jis = new JarInputStream(Files.newInputStream(file)); IdeProgressBar pb = getProgressbarForUnpacking( - getFileSize(file))) { - JarEntry entry; - while ((entry = jis.getNextJarEntry()) != null) { - Path entryPath = targetDir.resolve(entry.getName()).toAbsolutePath(); - - if (!entryPath.startsWith(targetDir)) { - throw new IOException("Preventing path traversal attack from " + entry.getName() + " to " + entryPath); - } - - if (entry.isDirectory()) { - Files.createDirectories(entryPath); - } else { - Files.createDirectories(entryPath.getParent()); - Files.copy(jis, entryPath); - } - pb.stepBy(entry.getCompressedSize()); - jis.closeEntry(); - } - } catch (IOException e) { - throw new IllegalStateException("Failed to extract JAR " + file + " to " + targetDir, e); - } + extractZip(file, targetDir); } /** @@ -708,34 +708,6 @@ private void extractArchive(Path file, Path targetDir, Function searchDirs) { @Override public void makeExecutable(Path filePath) { - if (SystemInfoImpl.INSTANCE.isWindows()) { - return; - } if (Files.exists(filePath)) { + if (SystemInfoImpl.INSTANCE.isWindows()) { + this.context.trace("Windows does not have executable flags hence omitting for file {}", filePath); + return; + } // Read the current file permissions Set perms; try { @@ -936,15 +909,21 @@ public void makeExecutable(Path filePath) { if (perms != null) { // Add execute permission for all users - perms.add(PosixFilePermission.OWNER_EXECUTE); - perms.add(PosixFilePermission.GROUP_EXECUTE); - perms.add(PosixFilePermission.OTHERS_EXECUTE); - - // Set the new permissions - try { - Files.setPosixFilePermissions(filePath, perms); - } catch (IOException e) { - throw new RuntimeException(e); + boolean update = false; + update |= perms.add(PosixFilePermission.OWNER_EXECUTE); + update |= perms.add(PosixFilePermission.GROUP_EXECUTE); + update |= perms.add(PosixFilePermission.OTHERS_EXECUTE); + + if (update) { + this.context.debug("Setting executable flags for file {}", filePath); + // Set the new permissions + try { + Files.setPosixFilePermissions(filePath, perms); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + this.context.trace("Executable flags already present so no need to set them for file {}", filePath); } } } else {